about summary refs log tree commit diff
path: root/sysdeps
diff options
context:
space:
mode:
Diffstat (limited to 'sysdeps')
-rw-r--r--sysdeps/unix/sysv/linux/alpha/getdents.c1
-rw-r--r--sysdeps/unix/sysv/linux/arm/Versions15
-rw-r--r--sysdeps/unix/sysv/linux/getdents.c212
-rw-r--r--sysdeps/unix/sysv/linux/i386/Versions2
-rw-r--r--sysdeps/unix/sysv/linux/i386/getdents64.c1
-rw-r--r--sysdeps/unix/sysv/linux/kernel-features.h6
-rw-r--r--sysdeps/unix/sysv/linux/powerpc/Versions8
-rw-r--r--sysdeps/unix/sysv/linux/sparc/sparc32/Versions9
8 files changed, 187 insertions, 67 deletions
diff --git a/sysdeps/unix/sysv/linux/alpha/getdents.c b/sysdeps/unix/sysv/linux/alpha/getdents.c
index 6deb87e2e4..175be9df85 100644
--- a/sysdeps/unix/sysv/linux/alpha/getdents.c
+++ b/sysdeps/unix/sysv/linux/alpha/getdents.c
@@ -1,3 +1,4 @@
+#define DIRENT_TYPE struct dirent64
 #define DIRENT_SET_DP_INO(dp, value) \
   do { (dp)->d_ino = (value); (dp)->__pad = 0; } while (0)
 #define __getdents64 __no___getdents64_decl
diff --git a/sysdeps/unix/sysv/linux/arm/Versions b/sysdeps/unix/sysv/linux/arm/Versions
index 4ac5b58a9b..5498086253 100644
--- a/sysdeps/unix/sysv/linux/arm/Versions
+++ b/sysdeps/unix/sysv/linux/arm/Versions
@@ -11,7 +11,22 @@ libc {
     outb; outw; outl;
   }
   GLIBC_2.2 {
+    # functions used in other libraries
+    __xstat64; __fxstat64; __lxstat64;
+
+    # a*
+    alphasort64;
+
     # New rlimit interface
     getrlimit; setrlimit; getrlimit64;
+
+    # r*
+    readdir64; readdir64_r;
+
+    # s*
+    scandir64;
+
+    # v*
+    versionsort64;
   }
 }
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;
diff --git a/sysdeps/unix/sysv/linux/i386/Versions b/sysdeps/unix/sysv/linux/i386/Versions
index 58c7b3d9b1..b7af24b1b3 100644
--- a/sysdeps/unix/sysv/linux/i386/Versions
+++ b/sysdeps/unix/sysv/linux/i386/Versions
@@ -19,8 +19,6 @@ libc {
     # a*
     alphasort64;
 
-    # g*
-    getdirentries64;
     # New rlimit interface
     getrlimit; setrlimit; getrlimit64;
 
diff --git a/sysdeps/unix/sysv/linux/i386/getdents64.c b/sysdeps/unix/sysv/linux/i386/getdents64.c
index dac046fa0c..bbfff20cf0 100644
--- a/sysdeps/unix/sysv/linux/i386/getdents64.c
+++ b/sysdeps/unix/sysv/linux/i386/getdents64.c
@@ -36,6 +36,7 @@ versioned_symbol (libc, __getdents64, getdents64, GLIBC_2_2);
 #define __GETDENTS __old_getdents64
 #define DIRENT_TYPE struct __old_dirent64
 #define kernel_dirent old_kernel_dirent
+#define kernel_dirent64 old_kernel_dirent64
 
 #include <sysdeps/unix/sysv/linux/getdents.c>
 
diff --git a/sysdeps/unix/sysv/linux/kernel-features.h b/sysdeps/unix/sysv/linux/kernel-features.h
index e7699a62f8..2f9d12bb28 100644
--- a/sysdeps/unix/sysv/linux/kernel-features.h
+++ b/sysdeps/unix/sysv/linux/kernel-features.h
@@ -136,3 +136,9 @@
 #if __LINUX_KERNEL_VERSION >= 132097 && (defined __i386__ || defined __sparc__)
 # define __ASSUME_FCNTL64		1
 #endif
+
+/* The getdents64 syscall was introduced in 2.4.0-test7.  We test for
+   2.4.1 for the earliest version we know the syscall is available.  */
+#if __LINUX_KERNEL_VERSION >= 132097
+# define __ASSUME_GETDENTS64_SYSCALL	1
+#endif
diff --git a/sysdeps/unix/sysv/linux/powerpc/Versions b/sysdeps/unix/sysv/linux/powerpc/Versions
index 6466be2cc5..1ea93d74f3 100644
--- a/sysdeps/unix/sysv/linux/powerpc/Versions
+++ b/sysdeps/unix/sysv/linux/powerpc/Versions
@@ -9,11 +9,6 @@ libc {
     # functions used in other libraries
     __xstat64; __fxstat64; __lxstat64;
 
-    # a*
-    alphasort64;
-
-    # g*
-    getdirentries64;
     # New rlimit interface
     getrlimit; setrlimit; getrlimit64; setrlimit64;
 
@@ -22,8 +17,5 @@ libc {
 
     # s*
     scandir64;
-
-    # v*
-    versionsort64;
   }
 }
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/Versions b/sysdeps/unix/sysv/linux/sparc/sparc32/Versions
index fcb9df31ec..2448fa2d37 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc32/Versions
+++ b/sysdeps/unix/sysv/linux/sparc/sparc32/Versions
@@ -9,19 +9,10 @@ libc {
     # functions used in other libraries
     __xstat64; __fxstat64; __lxstat64;
 
-    # a*
-    alphasort64;
-
-    # g*
-    getdirentries64;
-
     # r*
     readdir64; readdir64_r;
 
     # s*
     scandir64;
-
-    # v*
-    versionsort64;
   }
 }