about summary refs log tree commit diff
path: root/sysdeps/unix/sysv/linux/getdents.c
diff options
context:
space:
mode:
authorUlrich Drepper <drepper@redhat.com>2000-08-14 17:41:59 +0000
committerUlrich Drepper <drepper@redhat.com>2000-08-14 17:41:59 +0000
commit14860991fcb0bc8ccb3bbb62a12df07cd222af4d (patch)
treee9f53b98c96490d66ae6d037856f5f6a997144db /sysdeps/unix/sysv/linux/getdents.c
parent8c2f6130c331614a8b14522c519b31b8d12ca2eb (diff)
downloadglibc-14860991fcb0bc8ccb3bbb62a12df07cd222af4d.tar.gz
glibc-14860991fcb0bc8ccb3bbb62a12df07cd222af4d.tar.xz
glibc-14860991fcb0bc8ccb3bbb62a12df07cd222af4d.zip
Update.
2000-08-14  Jakub Jelinek  <jakub@redhat.com>

	* dirent/Versions (getdirentries64): Export at GLIBC_2.2.
	* sysdeps/unix/sysv/linux/kernel-features.h
	(__ASSUME_GETDENTS64_SYSCALL): Define.
	* sysdeps/unix/sysv/linux/getdents.c (__getdents): Use getdents64
	syscall if available to get d_type fields.
	* sysdeps/unix/sysv/linux/alpha/getdents.c (DIRENT_TYPE): Define.
	* sysdeps/unix/sysv/linux/arm/Versions (__xstat64, __fxstat64,
	__lxstat64): Export at GLIBC_2.2.
	(alphasort64, readdir64, readdir64_r, scandir64, versionsort64):
	Likewise.
	* sysdeps/unix/sysv/linux/i386/Versions (getdirentries64): Remove.
	* sysdeps/unix/sysv/linux/i386/getdents64.c (kernel_dirent64): Define.
	* sysdeps/unix/sysv/linux/powerpc/Versions (alphasort64,
	getdirentries64, versionsort64): Remove.
	* sysdeps/unix/sysv/linux/sparc/sparc32/Versions (alphasort64,
	getdirentries64, versionsort64): Remove.
Diffstat (limited to 'sysdeps/unix/sysv/linux/getdents.c')
-rw-r--r--sysdeps/unix/sysv/linux/getdents.c212
1 files changed, 164 insertions, 48 deletions
diff --git a/sysdeps/unix/sysv/linux/getdents.c b/sysdeps/unix/sysv/linux/getdents.c
index 474bf1989b..19ab9238fe 100644
--- a/sysdeps/unix/sysv/linux/getdents.c
+++ b/sysdeps/unix/sysv/linux/getdents.c
@@ -32,6 +32,19 @@
 
 #include <linux/posix_types.h>
 
+#include "kernel-features.h"
+
+#ifdef __NR_getdents64
+#ifndef __ASSUME_GETDENTS64_SYSCALL
+#ifndef __GETDENTS
+/* The variable is shared between all *getdents* calls.  */
+int __have_no_getdents64;
+#else
+extern int __have_no_getdents64;
+#endif
+#endif
+#endif
+
 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
 
 extern int __syscall_getdents (int fd, char *__unbounded buf, size_t nbytes);
@@ -51,8 +64,19 @@ struct kernel_dirent
     char d_name[256];
   };
 
+struct kernel_dirent64
+  {
+    u_int64_t		d_ino;
+    int64_t		d_off;
+    unsigned short int	d_reclen;
+    unsigned char	d_type;
+    char		d_name[256];
+  };
+
 #ifndef __GETDENTS
 # define __GETDENTS __getdents
+#endif
+#ifndef DIRENT_TYPE
 # define DIRENT_TYPE struct dirent
 #endif
 #ifndef DIRENT_SET_DP_INO
@@ -71,63 +95,155 @@ ssize_t
 internal_function
 __GETDENTS (int fd, char *buf, size_t nbytes)
 {
-  off_t last_offset = -1;
-  size_t red_nbytes;
-  struct kernel_dirent *skdp, *kdp;
   DIRENT_TYPE *dp;
-  int retval;
-  const size_t size_diff = (offsetof (DIRENT_TYPE, d_name)
-			    - offsetof (struct kernel_dirent, d_name));
-
-  red_nbytes = MIN (nbytes
-		    - ((nbytes / (offsetof (DIRENT_TYPE, d_name) + 14))
-		       * size_diff),
-		    nbytes - size_diff);
-
-  dp = (DIRENT_TYPE *) buf;
-  skdp = kdp = __alloca (red_nbytes);
-
-  retval = INLINE_SYSCALL (getdents, 3, fd,
-			   CHECK_N ((char *) kdp, red_nbytes), red_nbytes);
-
-  if (retval == -1)
-    return -1;
+  off_t last_offset = -1;
+  ssize_t retval;
 
-  while ((char *) kdp < (char *) skdp + retval)
+#ifdef __NR_getdents64
+#ifndef __ASSUME_GETDENTS64_SYSCALL
+  if (!__have_no_getdents64)
+#endif
     {
-      const size_t alignment = __alignof__ (DIRENT_TYPE);
-      /* Since kdp->d_reclen is already aligned for the kernel structure
-	 this may compute a value that is bigger than necessary.  */
-      size_t new_reclen = ((kdp->d_reclen + size_diff + alignment - 1)
-			   & ~(alignment - 1));
-      if ((char *) dp + new_reclen > buf + nbytes)
+#ifndef __ASSUME_GETDENTS64_SYSCALL
+      int saved_errno = errno;
+#endif
+      char *kbuf = buf;
+      size_t kbytes = nbytes;
+      if (offsetof (DIRENT_TYPE, d_name)
+	  < offsetof (struct kernel_dirent64, d_name)
+	  && nbytes <= sizeof (DIRENT_TYPE))
 	{
-	  /* Our heuristic failed.  We read too many entries.  Reset
-	     the stream.  */
-	  assert (last_offset != -1);
-	  __lseek (fd, last_offset, SEEK_SET);
-
-	  if ((char *) dp == buf)
+	  kbytes = nbytes + offsetof (struct kernel_dirent64, d_name)
+		   - offsetof (DIRENT_TYPE, d_name);
+	  kbuf = __alloca(kbytes);
+	}
+      retval = INLINE_SYSCALL (getdents64, 3, fd, CHECK_N(kbuf, kbytes),
+			       kbytes);
+#ifndef __ASSUME_GETDENTS64_SYSCALL
+      if (retval != -1 && errno != -EINVAL)
+#endif
+	{
+	  struct kernel_dirent64 *kdp;
+	  const size_t size_diff = (offsetof (struct kernel_dirent64, d_name)
+				   - offsetof (DIRENT_TYPE, d_name));
+
+	  /* If the structure returned by the kernel is identical to what we
+	     need, don't do any conversions.  */
+	  if (offsetof (DIRENT_TYPE, d_name)
+	      == offsetof (struct kernel_dirent64, d_name)
+	      && sizeof (dp->d_ino) == sizeof (kdp->d_ino)
+	      && sizeof (dp->d_off) == sizeof (kdp->d_off))
+	    return retval;
+
+	  dp = (DIRENT_TYPE *)buf;
+	  kdp = (struct kernel_dirent64 *)kbuf;
+	  while ((char *) kdp < kbuf + retval)
 	    {
-	      /* The buffer the user passed in is too small to hold even
-		 one entry.  */
-	      __set_errno (EINVAL);
-	      return -1;
+	      const size_t alignment = __alignof__ (DIRENT_TYPE);
+	      /* Since kdp->d_reclen is already aligned for the kernel
+		 structure this may compute a value that is bigger
+		 than necessary.  */
+	      size_t old_reclen = kdp->d_reclen;
+	      size_t new_reclen = ((old_reclen - size_diff + alignment - 1)
+				  & ~(alignment - 1));
+	      u_int64_t d_ino = kdp->d_ino;
+	      int64_t d_off = kdp->d_off;
+	      unsigned char d_type = kdp->d_type;
+
+	      DIRENT_SET_DP_INO(dp, d_ino);
+	      dp->d_off = d_off;
+	      if ((sizeof (dp->d_ino) != sizeof (kdp->d_ino)
+		   && dp->d_ino != d_ino)
+		  || (sizeof (dp->d_off) != sizeof (kdp->d_off)
+		      && dp->d_off != d_off))
+		{
+		  /* Overflow.  If there was at least one entry
+		     before this one, return them without error,
+		     otherwise signal overflow.  */
+		  if (last_offset != -1)
+		    {
+		      __lseek (fd, last_offset, SEEK_SET);
+		      return (char *) dp - buf;
+		    }
+		  __set_errno (EOVERFLOW);
+		  return -1;
+		}
+
+	      last_offset = d_off;
+	      dp->d_reclen = new_reclen;
+	      dp->d_type = d_type;
+	      memmove (dp->d_name, kdp->d_name,
+		       old_reclen - offsetof (struct kernel_dirent64, d_name));
+
+	      dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
+	      kdp = (struct kernel_dirent64 *) ((char *) kdp + old_reclen);
 	    }
 
-	  break;
+	  return (char *) dp - buf;
 	}
 
-      last_offset = kdp->d_off;
-      DIRENT_SET_DP_INO(dp, kdp->d_ino);
-      dp->d_off = kdp->d_off;
-      dp->d_reclen = new_reclen;
-      dp->d_type = DT_UNKNOWN;
-      memcpy (dp->d_name, kdp->d_name,
-	      kdp->d_reclen - offsetof (struct kernel_dirent, d_name));
-
-      dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
-      kdp = (struct kernel_dirent *) (((char *) kdp) + kdp->d_reclen);
+#ifndef __ASSUME_GETDENTS64_SYSCALL
+      __set_errno (saved_errno);
+      __have_no_getdents64 = 1;
+#endif
+    }
+#endif
+  {
+    size_t red_nbytes;
+    struct kernel_dirent *skdp, *kdp;
+    const size_t size_diff = (offsetof (DIRENT_TYPE, d_name)
+			      - offsetof (struct kernel_dirent, d_name));
+
+    red_nbytes = MIN (nbytes
+		      - ((nbytes / (offsetof (DIRENT_TYPE, d_name) + 14))
+			 * size_diff),
+		      nbytes - size_diff);
+
+    dp = (DIRENT_TYPE *) buf;
+    skdp = kdp = __alloca (red_nbytes);
+
+    retval = INLINE_SYSCALL (getdents, 3, fd,
+			     CHECK_N ((char *) kdp, red_nbytes), red_nbytes);
+
+    if (retval == -1)
+      return -1;
+
+    while ((char *) kdp < (char *) skdp + retval)
+      {
+	const size_t alignment = __alignof__ (DIRENT_TYPE);
+	/* Since kdp->d_reclen is already aligned for the kernel structure
+	   this may compute a value that is bigger than necessary.  */
+	size_t new_reclen = ((kdp->d_reclen + size_diff + alignment - 1)
+			     & ~(alignment - 1));
+	if ((char *) dp + new_reclen > buf + nbytes)
+	  {
+	    /* Our heuristic failed.  We read too many entries.  Reset
+	       the stream.  */
+	    assert (last_offset != -1);
+	    __lseek (fd, last_offset, SEEK_SET);
+
+	    if ((char *) dp == buf)
+	      {
+		/* The buffer the user passed in is too small to hold even
+		   one entry.  */
+		__set_errno (EINVAL);
+		return -1;
+	      }
+
+	    break;
+	  }
+
+	last_offset = kdp->d_off;
+	DIRENT_SET_DP_INO(dp, kdp->d_ino);
+	dp->d_off = kdp->d_off;
+	dp->d_reclen = new_reclen;
+	dp->d_type = DT_UNKNOWN;
+	memcpy (dp->d_name, kdp->d_name,
+		kdp->d_reclen - offsetof (struct kernel_dirent, d_name));
+
+	dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
+	kdp = (struct kernel_dirent *) (((char *) kdp) + kdp->d_reclen);
+      }
     }
 
   return (char *) dp - buf;