Add d_type to 'struct dirent'
[kopensolaris-gnu/glibc.git] / sysdeps / unix / sysv / linux / getdents.c
1 /* Copyright (C) 1993, 1995-2003, 2004, 2006, 2007
2    Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, write to the Free
17    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18    02111-1307 USA.  */
19
20 #include <alloca.h>
21 #include <assert.h>
22 #include <errno.h>
23 #include <dirent.h>
24 #include <stddef.h>
25 #include <stdint.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/param.h>
29 #include <sys/types.h>
30
31 #include <sysdep.h>
32 #include <sys/syscall.h>
33 #include <bp-checks.h>
34
35 #include <linux/posix_types.h>
36
37 #include <kernel-features.h>
38
39 #ifdef __NR_getdents64
40 # ifndef __ASSUME_GETDENTS64_SYSCALL
41 #  ifndef __GETDENTS
42 /* The variable is shared between all *getdents* calls.  */
43 int __have_no_getdents64 attribute_hidden;
44 #  else
45 extern int __have_no_getdents64 attribute_hidden;
46 #  endif
47 #  define have_no_getdents64_defined 1
48 # endif
49 #endif
50 #ifndef have_no_getdents64_defined
51 # define __have_no_getdents64 0
52 #endif
53
54 /* For Linux we need a special version of this file since the
55    definition of `struct dirent' is not the same for the kernel and
56    the libc.  There is one additional field which might be introduced
57    in the kernel structure in the future.
58
59    Here is the kernel definition of `struct dirent' as of 2.1.20:  */
60
61 #ifndef __KERNEL_DIRENT
62 struct kernel_dirent
63   {
64     long int d_ino;
65     __kernel_off_t d_off;
66     unsigned short int d_reclen;
67     char d_name[256];
68   };
69
70 # ifdef __NR_getdents64
71 struct kernel_dirent64
72   {
73     uint64_t            d_ino;
74     int64_t             d_off;
75     unsigned short int  d_reclen;
76     unsigned char       d_type;
77     char                d_name[256];
78   };
79 # endif
80 #endif
81
82 #ifndef __GETDENTS
83 # define __GETDENTS __getdents
84 #endif
85 #ifndef DIRENT_TYPE
86 # define DIRENT_TYPE struct dirent
87 #endif
88 #ifndef DIRENT_SET_DP_INO
89 # define DIRENT_SET_DP_INO(dp, value) (dp)->d_ino = (value)
90 #endif
91
92 /* The problem here is that we cannot simply read the next NBYTES
93    bytes.  We need to take the additional field into account.  We use
94    some heuristic.  Assuming the directory contains names with 14
95    characters on average we can compute an estimated number of entries
96    which fit in the buffer.  Taking this number allows us to specify a
97    reasonable number of bytes to read.  If we should be wrong, we can
98    reset the file descriptor.  In practice the kernel is limiting the
99    amount of data returned much more then the reduced buffer size.  */
100 ssize_t
101 internal_function
102 __GETDENTS (int fd, char *buf, size_t nbytes)
103 {
104   ssize_t retval;
105
106 #ifdef __ASSUME_GETDENTS32_D_TYPE
107   if (sizeof (DIRENT_TYPE) == sizeof (struct dirent))
108     {
109       retval = INLINE_SYSCALL (getdents, 3, fd, CHECK_N(buf, nbytes), nbytes);
110
111       /* The kernel added the d_type value after the name.  Change
112          this now.  */
113       if (retval != -1)
114         {
115           union
116           {
117             struct kernel_dirent k;
118             struct dirent u;
119           } *kbuf = (void *) buf;
120
121           while ((char *) kbuf < buf + retval)
122             {
123               char d_type = *((char *) kbuf + kbuf->k.d_reclen - 1);
124               memmove (kbuf->u.d_name, kbuf->k.d_name,
125                        strlen (kbuf->k.d_name) + 1);
126               kbuf->u.d_type = d_type;
127
128               kbuf = (void *) ((char *) kbuf + kbuf->k.d_reclen);
129             }
130         }
131
132       return retval;
133     }
134 #endif
135
136   off64_t last_offset = -1;
137
138 #ifdef __NR_getdents64
139   if (!__have_no_getdents64)
140     {
141 # ifndef __ASSUME_GETDENTS64_SYSCALL
142       int saved_errno = errno;
143 # endif
144       union
145       {
146         struct kernel_dirent64 k;
147         DIRENT_TYPE u;
148         char b[1];
149       } *kbuf = (void *) buf, *outp, *inp;
150       size_t kbytes = nbytes;
151       if (offsetof (DIRENT_TYPE, d_name)
152           < offsetof (struct kernel_dirent64, d_name)
153           && nbytes <= sizeof (DIRENT_TYPE))
154         {
155           kbytes = nbytes + offsetof (struct kernel_dirent64, d_name)
156                    - offsetof (DIRENT_TYPE, d_name);
157           kbuf = __alloca(kbytes);
158         }
159       retval = INLINE_SYSCALL (getdents64, 3, fd, CHECK_N(kbuf, kbytes),
160                                kbytes);
161 # ifndef __ASSUME_GETDENTS64_SYSCALL
162       if (retval != -1 || (errno != EINVAL && errno != ENOSYS))
163 # endif
164         {
165           const size_t size_diff = (offsetof (struct kernel_dirent64, d_name)
166                                     - offsetof (DIRENT_TYPE, d_name));
167
168           /* Return the error if encountered.  */
169           if (retval == -1)
170             return -1;
171
172           /* If the structure returned by the kernel is identical to what we
173              need, don't do any conversions.  */
174           if (offsetof (DIRENT_TYPE, d_name)
175               == offsetof (struct kernel_dirent64, d_name)
176               && sizeof (outp->u.d_ino) == sizeof (inp->k.d_ino)
177               && sizeof (outp->u.d_off) == sizeof (inp->k.d_off))
178             return retval;
179
180           /* These two pointers might alias the same memory buffer.
181              Standard C requires that we always use the same type for them,
182              so we must use the union type.  */
183           inp = kbuf;
184           outp = (void *) buf;
185
186           while (&inp->b < &kbuf->b + retval)
187             {
188               const size_t alignment = __alignof__ (DIRENT_TYPE);
189               /* Since inp->k.d_reclen is already aligned for the kernel
190                  structure this may compute a value that is bigger
191                  than necessary.  */
192               size_t old_reclen = inp->k.d_reclen;
193               size_t new_reclen = ((old_reclen - size_diff + alignment - 1)
194                                   & ~(alignment - 1));
195
196               /* Copy the data out of the old structure into temporary space.
197                  Then copy the name, which may overlap if BUF == KBUF.  */
198               const uint64_t d_ino = inp->k.d_ino;
199               const int64_t d_off = inp->k.d_off;
200               const uint8_t d_type = inp->k.d_type;
201
202               memmove (outp->u.d_name, inp->k.d_name,
203                        old_reclen - offsetof (struct kernel_dirent64, d_name));
204
205               /* Now we have copied the data from INP and access only OUTP.  */
206
207               DIRENT_SET_DP_INO (&outp->u, d_ino);
208               outp->u.d_off = d_off;
209               if ((sizeof (outp->u.d_ino) != sizeof (inp->k.d_ino)
210                    && outp->u.d_ino != d_ino)
211                   || (sizeof (outp->u.d_off) != sizeof (inp->k.d_off)
212                       && outp->u.d_off != d_off))
213                 {
214                   /* Overflow.  If there was at least one entry
215                      before this one, return them without error,
216                      otherwise signal overflow.  */
217                   if (last_offset != -1)
218                     {
219                       __lseek64 (fd, last_offset, SEEK_SET);
220                       return outp->b - buf;
221                     }
222                   __set_errno (EOVERFLOW);
223                   return -1;
224                 }
225
226               last_offset = d_off;
227               outp->u.d_reclen = new_reclen;
228               outp->u.d_type = d_type;
229
230               inp = (void *) inp + old_reclen;
231               outp = (void *) outp + new_reclen;
232             }
233
234           return outp->b - buf;
235         }
236
237 # ifndef __ASSUME_GETDENTS64_SYSCALL
238       __set_errno (saved_errno);
239       __have_no_getdents64 = 1;
240 # endif
241     }
242 #endif
243   {
244     size_t red_nbytes;
245     struct kernel_dirent *skdp, *kdp;
246     const size_t size_diff = (offsetof (DIRENT_TYPE, d_name)
247                               - offsetof (struct kernel_dirent, d_name));
248
249     red_nbytes = MIN (nbytes
250                       - ((nbytes / (offsetof (DIRENT_TYPE, d_name) + 14))
251                          * size_diff),
252                       nbytes - size_diff);
253
254     skdp = kdp = __alloca (red_nbytes);
255
256     retval = INLINE_SYSCALL (getdents, 3, fd,
257                              CHECK_N ((char *) kdp, red_nbytes), red_nbytes);
258
259     if (retval == -1)
260       return -1;
261
262     DIRENT_TYPE *dp = (DIRENT_TYPE *) buf;
263     while ((char *) kdp < (char *) skdp + retval)
264       {
265         const size_t alignment = __alignof__ (DIRENT_TYPE);
266         /* Since kdp->d_reclen is already aligned for the kernel structure
267            this may compute a value that is bigger than necessary.  */
268         size_t new_reclen = ((kdp->d_reclen + size_diff + alignment - 1)
269                              & ~(alignment - 1));
270         if ((char *) dp + new_reclen > buf + nbytes)
271           {
272             /* Our heuristic failed.  We read too many entries.  Reset
273                the stream.  */
274             assert (last_offset != -1);
275             __lseek64 (fd, last_offset, SEEK_SET);
276
277             if ((char *) dp == buf)
278               {
279                 /* The buffer the user passed in is too small to hold even
280                    one entry.  */
281                 __set_errno (EINVAL);
282                 return -1;
283               }
284
285             break;
286           }
287
288         last_offset = kdp->d_off;
289         DIRENT_SET_DP_INO(dp, kdp->d_ino);
290         dp->d_off = kdp->d_off;
291         dp->d_reclen = new_reclen;
292         dp->d_type = DT_UNKNOWN;
293         memcpy (dp->d_name, kdp->d_name,
294                 kdp->d_reclen - offsetof (struct kernel_dirent, d_name));
295
296         dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
297         kdp = (struct kernel_dirent *) (((char *) kdp) + kdp->d_reclen);
298       }
299
300     return (char *) dp - buf;
301   }
302 }