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