Back to index

glibc  2.9
getdents.c
Go to the documentation of this file.
00001 /* Copyright (C) 1993, 1995-2003, 2004, 2006, 2007
00002    Free Software Foundation, Inc.
00003    This file is part of the GNU C Library.
00004 
00005    The GNU C Library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Lesser General Public
00007    License as published by the Free Software Foundation; either
00008    version 2.1 of the License, or (at your option) any later version.
00009 
00010    The GNU C Library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Lesser General Public License for more details.
00014 
00015    You should have received a copy of the GNU Lesser General Public
00016    License along with the GNU C Library; if not, write to the Free
00017    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
00018    02111-1307 USA.  */
00019 
00020 #include <alloca.h>
00021 #include <assert.h>
00022 #include <errno.h>
00023 #include <dirent.h>
00024 #include <stddef.h>
00025 #include <stdint.h>
00026 #include <string.h>
00027 #include <unistd.h>
00028 #include <sys/param.h>
00029 #include <sys/types.h>
00030 
00031 #include <sysdep.h>
00032 #include <sys/syscall.h>
00033 #include <bp-checks.h>
00034 
00035 #include <linux/posix_types.h>
00036 
00037 #include <kernel-features.h>
00038 
00039 #ifdef __NR_getdents64
00040 # ifndef __ASSUME_GETDENTS64_SYSCALL
00041 #  ifndef __GETDENTS
00042 /* The variable is shared between all *getdents* calls.  */
00043 int __have_no_getdents64 attribute_hidden;
00044 #  else
00045 extern int __have_no_getdents64 attribute_hidden;
00046 #  endif
00047 #  define have_no_getdents64_defined 1
00048 # endif
00049 #endif
00050 #ifndef have_no_getdents64_defined
00051 # define __have_no_getdents64 0
00052 #endif
00053 
00054 /* For Linux we need a special version of this file since the
00055    definition of `struct dirent' is not the same for the kernel and
00056    the libc.  There is one additional field which might be introduced
00057    in the kernel structure in the future.
00058 
00059    Here is the kernel definition of `struct dirent' as of 2.1.20:  */
00060 
00061 struct kernel_dirent
00062   {
00063     long int d_ino;
00064     __kernel_off_t d_off;
00065     unsigned short int d_reclen;
00066     char d_name[256];
00067   };
00068 
00069 struct kernel_dirent64
00070   {
00071     uint64_t         d_ino;
00072     int64_t          d_off;
00073     unsigned short int      d_reclen;
00074     unsigned char    d_type;
00075     char             d_name[256];
00076   };
00077 
00078 #ifndef __GETDENTS
00079 # define __GETDENTS __getdents
00080 #endif
00081 #ifndef DIRENT_TYPE
00082 # define DIRENT_TYPE struct dirent
00083 #endif
00084 #ifndef DIRENT_SET_DP_INO
00085 # define DIRENT_SET_DP_INO(dp, value) (dp)->d_ino = (value)
00086 #endif
00087 
00088 /* The problem here is that we cannot simply read the next NBYTES
00089    bytes.  We need to take the additional field into account.  We use
00090    some heuristic.  Assuming the directory contains names with 14
00091    characters on average we can compute an estimated number of entries
00092    which fit in the buffer.  Taking this number allows us to specify a
00093    reasonable number of bytes to read.  If we should be wrong, we can
00094    reset the file descriptor.  In practice the kernel is limiting the
00095    amount of data returned much more then the reduced buffer size.  */
00096 ssize_t
00097 internal_function
00098 __GETDENTS (int fd, char *buf, size_t nbytes)
00099 {
00100   ssize_t retval;
00101 
00102 #ifdef __ASSUME_GETDENTS32_D_TYPE
00103   if (sizeof (DIRENT_TYPE) == sizeof (struct dirent))
00104     {
00105       retval = INLINE_SYSCALL (getdents, 3, fd, CHECK_N(buf, nbytes), nbytes);
00106 
00107       /* The kernel added the d_type value after the name.  Change
00108         this now.  */
00109       if (retval != -1)
00110        {
00111          union
00112          {
00113            struct kernel_dirent k;
00114            struct dirent u;
00115          } *kbuf = (void *) buf;
00116 
00117          while ((char *) kbuf < buf + retval)
00118            {
00119              char d_type = *((char *) kbuf + kbuf->k.d_reclen - 1);
00120              memmove (kbuf->u.d_name, kbuf->k.d_name,
00121                      strlen (kbuf->k.d_name) + 1);
00122              kbuf->u.d_type = d_type;
00123 
00124              kbuf = (void *) ((char *) kbuf + kbuf->k.d_reclen);
00125            }
00126        }
00127 
00128       return retval;
00129     }
00130 #endif
00131 
00132   off64_t last_offset = -1;
00133 
00134 #ifdef __NR_getdents64
00135   if (!__have_no_getdents64)
00136     {
00137 # ifndef __ASSUME_GETDENTS64_SYSCALL
00138       int saved_errno = errno;
00139 # endif
00140       union
00141       {
00142        struct kernel_dirent64 k;
00143        DIRENT_TYPE u;
00144        char b[1];
00145       } *kbuf = (void *) buf, *outp, *inp;
00146       size_t kbytes = nbytes;
00147       if (offsetof (DIRENT_TYPE, d_name)
00148          < offsetof (struct kernel_dirent64, d_name)
00149          && nbytes <= sizeof (DIRENT_TYPE))
00150        {
00151          kbytes = nbytes + offsetof (struct kernel_dirent64, d_name)
00152                  - offsetof (DIRENT_TYPE, d_name);
00153          kbuf = __alloca(kbytes);
00154        }
00155       retval = INLINE_SYSCALL (getdents64, 3, fd, CHECK_N(kbuf, kbytes),
00156                             kbytes);
00157 # ifndef __ASSUME_GETDENTS64_SYSCALL
00158       if (retval != -1 || (errno != EINVAL && errno != ENOSYS))
00159 # endif
00160        {
00161          const size_t size_diff = (offsetof (struct kernel_dirent64, d_name)
00162                                 - offsetof (DIRENT_TYPE, d_name));
00163 
00164          /* Return the error if encountered.  */
00165          if (retval == -1)
00166            return -1;
00167 
00168          /* If the structure returned by the kernel is identical to what we
00169             need, don't do any conversions.  */
00170          if (offsetof (DIRENT_TYPE, d_name)
00171              == offsetof (struct kernel_dirent64, d_name)
00172              && sizeof (outp->u.d_ino) == sizeof (inp->k.d_ino)
00173              && sizeof (outp->u.d_off) == sizeof (inp->k.d_off))
00174            return retval;
00175 
00176          /* These two pointers might alias the same memory buffer.
00177             Standard C requires that we always use the same type for them,
00178             so we must use the union type.  */
00179          inp = kbuf;
00180          outp = (void *) buf;
00181 
00182          while (&inp->b < &kbuf->b + retval)
00183            {
00184              const size_t alignment = __alignof__ (DIRENT_TYPE);
00185              /* Since inp->k.d_reclen is already aligned for the kernel
00186                structure this may compute a value that is bigger
00187                than necessary.  */
00188              size_t old_reclen = inp->k.d_reclen;
00189              size_t new_reclen = ((old_reclen - size_diff + alignment - 1)
00190                               & ~(alignment - 1));
00191 
00192              /* Copy the data out of the old structure into temporary space.
00193                Then copy the name, which may overlap if BUF == KBUF.  */
00194              const uint64_t d_ino = inp->k.d_ino;
00195              const int64_t d_off = inp->k.d_off;
00196              const uint8_t d_type = inp->k.d_type;
00197 
00198              memmove (outp->u.d_name, inp->k.d_name,
00199                      old_reclen - offsetof (struct kernel_dirent64, d_name));
00200 
00201              /* Now we have copied the data from INP and access only OUTP.  */
00202 
00203              DIRENT_SET_DP_INO (&outp->u, d_ino);
00204              outp->u.d_off = d_off;
00205              if ((sizeof (outp->u.d_ino) != sizeof (inp->k.d_ino)
00206                  && outp->u.d_ino != d_ino)
00207                 || (sizeof (outp->u.d_off) != sizeof (inp->k.d_off)
00208                     && outp->u.d_off != d_off))
00209               {
00210                 /* Overflow.  If there was at least one entry
00211                    before this one, return them without error,
00212                    otherwise signal overflow.  */
00213                 if (last_offset != -1)
00214                   {
00215                     __lseek64 (fd, last_offset, SEEK_SET);
00216                     return outp->b - buf;
00217                   }
00218                 __set_errno (EOVERFLOW);
00219                 return -1;
00220               }
00221 
00222              last_offset = d_off;
00223              outp->u.d_reclen = new_reclen;
00224              outp->u.d_type = d_type;
00225 
00226              inp = (void *) inp + old_reclen;
00227              outp = (void *) outp + new_reclen;
00228            }
00229 
00230          return outp->b - buf;
00231        }
00232 
00233 # ifndef __ASSUME_GETDENTS64_SYSCALL
00234       __set_errno (saved_errno);
00235       __have_no_getdents64 = 1;
00236 # endif
00237     }
00238 #endif
00239   {
00240     size_t red_nbytes;
00241     struct kernel_dirent *skdp, *kdp;
00242     const size_t size_diff = (offsetof (DIRENT_TYPE, d_name)
00243                            - offsetof (struct kernel_dirent, d_name));
00244 
00245     red_nbytes = MIN (nbytes
00246                     - ((nbytes / (offsetof (DIRENT_TYPE, d_name) + 14))
00247                       * size_diff),
00248                     nbytes - size_diff);
00249 
00250     skdp = kdp = __alloca (red_nbytes);
00251 
00252     retval = INLINE_SYSCALL (getdents, 3, fd,
00253                           CHECK_N ((char *) kdp, red_nbytes), red_nbytes);
00254 
00255     if (retval == -1)
00256       return -1;
00257 
00258     DIRENT_TYPE *dp = (DIRENT_TYPE *) buf;
00259     while ((char *) kdp < (char *) skdp + retval)
00260       {
00261        const size_t alignment = __alignof__ (DIRENT_TYPE);
00262        /* Since kdp->d_reclen is already aligned for the kernel structure
00263           this may compute a value that is bigger than necessary.  */
00264        size_t new_reclen = ((kdp->d_reclen + size_diff + alignment - 1)
00265                           & ~(alignment - 1));
00266        if ((char *) dp + new_reclen > buf + nbytes)
00267          {
00268            /* Our heuristic failed.  We read too many entries.  Reset
00269               the stream.  */
00270            assert (last_offset != -1);
00271            __lseek64 (fd, last_offset, SEEK_SET);
00272 
00273            if ((char *) dp == buf)
00274              {
00275               /* The buffer the user passed in is too small to hold even
00276                  one entry.  */
00277               __set_errno (EINVAL);
00278               return -1;
00279              }
00280 
00281            break;
00282          }
00283 
00284        last_offset = kdp->d_off;
00285        DIRENT_SET_DP_INO(dp, kdp->d_ino);
00286        dp->d_off = kdp->d_off;
00287        dp->d_reclen = new_reclen;
00288        dp->d_type = DT_UNKNOWN;
00289        memcpy (dp->d_name, kdp->d_name,
00290               kdp->d_reclen - offsetof (struct kernel_dirent, d_name));
00291 
00292        dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
00293        kdp = (struct kernel_dirent *) (((char *) kdp) + kdp->d_reclen);
00294       }
00295 
00296     return (char *) dp - buf;
00297   }
00298 }