2.9
[glibc/nacl-glibc.git] / sysdeps / unix / sysv / linux / getdents.c
blobb33d1789adff11a04cbb1f6f5616bc8eed59418f
1 /* Copyright (C) 1993, 1995-2003, 2004, 2006, 2007
2 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
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.
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.
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. */
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>
31 #include <sysdep.h>
32 #include <sys/syscall.h>
33 #include <bp-checks.h>
35 #include <linux/posix_types.h>
37 #include <kernel-features.h>
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
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.
59 Here is the kernel definition of `struct dirent' as of 2.1.20: */
61 struct kernel_dirent
63 long int d_ino;
64 __kernel_off_t d_off;
65 unsigned short int d_reclen;
66 char d_name[256];
69 struct kernel_dirent64
71 uint64_t d_ino;
72 int64_t d_off;
73 unsigned short int d_reclen;
74 unsigned char d_type;
75 char d_name[256];
78 #ifndef __GETDENTS
79 # define __GETDENTS __getdents
80 #endif
81 #ifndef DIRENT_TYPE
82 # define DIRENT_TYPE struct dirent
83 #endif
84 #ifndef DIRENT_SET_DP_INO
85 # define DIRENT_SET_DP_INO(dp, value) (dp)->d_ino = (value)
86 #endif
88 /* The problem here is that we cannot simply read the next NBYTES
89 bytes. We need to take the additional field into account. We use
90 some heuristic. Assuming the directory contains names with 14
91 characters on average we can compute an estimated number of entries
92 which fit in the buffer. Taking this number allows us to specify a
93 reasonable number of bytes to read. If we should be wrong, we can
94 reset the file descriptor. In practice the kernel is limiting the
95 amount of data returned much more then the reduced buffer size. */
96 ssize_t
97 internal_function
98 __GETDENTS (int fd, char *buf, size_t nbytes)
100 ssize_t retval;
102 #ifdef __ASSUME_GETDENTS32_D_TYPE
103 if (sizeof (DIRENT_TYPE) == sizeof (struct dirent))
105 retval = INLINE_SYSCALL (getdents, 3, fd, CHECK_N(buf, nbytes), nbytes);
107 /* The kernel added the d_type value after the name. Change
108 this now. */
109 if (retval != -1)
111 union
113 struct kernel_dirent k;
114 struct dirent u;
115 } *kbuf = (void *) buf;
117 while ((char *) kbuf < buf + retval)
119 char d_type = *((char *) kbuf + kbuf->k.d_reclen - 1);
120 memmove (kbuf->u.d_name, kbuf->k.d_name,
121 strlen (kbuf->k.d_name) + 1);
122 kbuf->u.d_type = d_type;
124 kbuf = (void *) ((char *) kbuf + kbuf->k.d_reclen);
128 return retval;
130 #endif
132 off64_t last_offset = -1;
134 #ifdef __NR_getdents64
135 if (!__have_no_getdents64)
137 # ifndef __ASSUME_GETDENTS64_SYSCALL
138 int saved_errno = errno;
139 # endif
140 union
142 struct kernel_dirent64 k;
143 DIRENT_TYPE u;
144 char b[1];
145 } *kbuf = (void *) buf, *outp, *inp;
146 size_t kbytes = nbytes;
147 if (offsetof (DIRENT_TYPE, d_name)
148 < offsetof (struct kernel_dirent64, d_name)
149 && nbytes <= sizeof (DIRENT_TYPE))
151 kbytes = nbytes + offsetof (struct kernel_dirent64, d_name)
152 - offsetof (DIRENT_TYPE, d_name);
153 kbuf = __alloca(kbytes);
155 retval = INLINE_SYSCALL (getdents64, 3, fd, CHECK_N(kbuf, kbytes),
156 kbytes);
157 # ifndef __ASSUME_GETDENTS64_SYSCALL
158 if (retval != -1 || (errno != EINVAL && errno != ENOSYS))
159 # endif
161 const size_t size_diff = (offsetof (struct kernel_dirent64, d_name)
162 - offsetof (DIRENT_TYPE, d_name));
164 /* Return the error if encountered. */
165 if (retval == -1)
166 return -1;
168 /* If the structure returned by the kernel is identical to what we
169 need, don't do any conversions. */
170 if (offsetof (DIRENT_TYPE, d_name)
171 == offsetof (struct kernel_dirent64, d_name)
172 && sizeof (outp->u.d_ino) == sizeof (inp->k.d_ino)
173 && sizeof (outp->u.d_off) == sizeof (inp->k.d_off))
174 return retval;
176 /* These two pointers might alias the same memory buffer.
177 Standard C requires that we always use the same type for them,
178 so we must use the union type. */
179 inp = kbuf;
180 outp = (void *) buf;
182 while (&inp->b < &kbuf->b + retval)
184 const size_t alignment = __alignof__ (DIRENT_TYPE);
185 /* Since inp->k.d_reclen is already aligned for the kernel
186 structure this may compute a value that is bigger
187 than necessary. */
188 size_t old_reclen = inp->k.d_reclen;
189 size_t new_reclen = ((old_reclen - size_diff + alignment - 1)
190 & ~(alignment - 1));
192 /* Copy the data out of the old structure into temporary space.
193 Then copy the name, which may overlap if BUF == KBUF. */
194 const uint64_t d_ino = inp->k.d_ino;
195 const int64_t d_off = inp->k.d_off;
196 const uint8_t d_type = inp->k.d_type;
198 memmove (outp->u.d_name, inp->k.d_name,
199 old_reclen - offsetof (struct kernel_dirent64, d_name));
201 /* Now we have copied the data from INP and access only OUTP. */
203 DIRENT_SET_DP_INO (&outp->u, d_ino);
204 outp->u.d_off = d_off;
205 if ((sizeof (outp->u.d_ino) != sizeof (inp->k.d_ino)
206 && outp->u.d_ino != d_ino)
207 || (sizeof (outp->u.d_off) != sizeof (inp->k.d_off)
208 && outp->u.d_off != d_off))
210 /* Overflow. If there was at least one entry
211 before this one, return them without error,
212 otherwise signal overflow. */
213 if (last_offset != -1)
215 __lseek64 (fd, last_offset, SEEK_SET);
216 return outp->b - buf;
218 __set_errno (EOVERFLOW);
219 return -1;
222 last_offset = d_off;
223 outp->u.d_reclen = new_reclen;
224 outp->u.d_type = d_type;
226 inp = (void *) inp + old_reclen;
227 outp = (void *) outp + new_reclen;
230 return outp->b - buf;
233 # ifndef __ASSUME_GETDENTS64_SYSCALL
234 __set_errno (saved_errno);
235 __have_no_getdents64 = 1;
236 # endif
238 #endif
240 size_t red_nbytes;
241 struct kernel_dirent *skdp, *kdp;
242 const size_t size_diff = (offsetof (DIRENT_TYPE, d_name)
243 - offsetof (struct kernel_dirent, d_name));
245 red_nbytes = MIN (nbytes
246 - ((nbytes / (offsetof (DIRENT_TYPE, d_name) + 14))
247 * size_diff),
248 nbytes - size_diff);
250 skdp = kdp = __alloca (red_nbytes);
252 retval = INLINE_SYSCALL (getdents, 3, fd,
253 CHECK_N ((char *) kdp, red_nbytes), red_nbytes);
255 if (retval == -1)
256 return -1;
258 DIRENT_TYPE *dp = (DIRENT_TYPE *) buf;
259 while ((char *) kdp < (char *) skdp + retval)
261 const size_t alignment = __alignof__ (DIRENT_TYPE);
262 /* Since kdp->d_reclen is already aligned for the kernel structure
263 this may compute a value that is bigger than necessary. */
264 size_t new_reclen = ((kdp->d_reclen + size_diff + alignment - 1)
265 & ~(alignment - 1));
266 if ((char *) dp + new_reclen > buf + nbytes)
268 /* Our heuristic failed. We read too many entries. Reset
269 the stream. */
270 assert (last_offset != -1);
271 __lseek64 (fd, last_offset, SEEK_SET);
273 if ((char *) dp == buf)
275 /* The buffer the user passed in is too small to hold even
276 one entry. */
277 __set_errno (EINVAL);
278 return -1;
281 break;
284 last_offset = kdp->d_off;
285 DIRENT_SET_DP_INO(dp, kdp->d_ino);
286 dp->d_off = kdp->d_off;
287 dp->d_reclen = new_reclen;
288 dp->d_type = DT_UNKNOWN;
289 memcpy (dp->d_name, kdp->d_name,
290 kdp->d_reclen - offsetof (struct kernel_dirent, d_name));
292 dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
293 kdp = (struct kernel_dirent *) (((char *) kdp) + kdp->d_reclen);
296 return (char *) dp - buf;