]> git.ipfire.org Git - thirdparty/glibc.git/blame - sysdeps/unix/sysv/linux/getdents.c
Add script to update copyright notices and reformat some to facilitate its use.
[thirdparty/glibc.git] / sysdeps / unix / sysv / linux / getdents.c
CommitLineData
f4cf5f2d 1/* Copyright (C) 1993-2012 Free Software Foundation, Inc.
df4ef2ab
UD
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
41bdb6e2
AJ
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.
df4ef2ab
UD
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
41bdb6e2 12 Lesser General Public License for more details.
df4ef2ab 13
41bdb6e2 14 You should have received a copy of the GNU Lesser General Public
59ba27a6
PE
15 License along with the GNU C Library; if not, see
16 <http://www.gnu.org/licenses/>. */
df4ef2ab 17
8d57beea 18#include <alloca.h>
4186c9f4 19#include <assert.h>
0dee6738 20#include <errno.h>
df4ef2ab
UD
21#include <dirent.h>
22#include <stddef.h>
da0fdef0 23#include <stdint.h>
df4ef2ab
UD
24#include <string.h>
25#include <unistd.h>
8d57beea 26#include <sys/param.h>
df4ef2ab
UD
27#include <sys/types.h>
28
0dee6738
UD
29#include <sysdep.h>
30#include <sys/syscall.h>
4bbb61e4 31#include <bp-checks.h>
0dee6738 32
df4ef2ab
UD
33#include <linux/posix_types.h>
34
6ddd37a4 35#include <kernel-features.h>
14860991
UD
36
37#ifdef __NR_getdents64
afedc4af
UD
38# ifndef __ASSUME_GETDENTS64_SYSCALL
39# ifndef __GETDENTS
14860991 40/* The variable is shared between all *getdents* calls. */
8263ed62 41int __have_no_getdents64 attribute_hidden;
afedc4af 42# else
8263ed62 43extern int __have_no_getdents64 attribute_hidden;
afedc4af
UD
44# endif
45# define have_no_getdents64_defined 1
46# endif
14860991 47#endif
afedc4af
UD
48#ifndef have_no_getdents64_defined
49# define __have_no_getdents64 0
14860991
UD
50#endif
51
df4ef2ab
UD
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
0dee6738 57 Here is the kernel definition of `struct dirent' as of 2.1.20: */
df4ef2ab
UD
58
59struct 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
14860991
UD
67struct kernel_dirent64
68 {
da0fdef0 69 uint64_t d_ino;
14860991
UD
70 int64_t d_off;
71 unsigned short int d_reclen;
72 unsigned char d_type;
73 char d_name[256];
74 };
75
56ddf355
UD
76#ifndef __GETDENTS
77# define __GETDENTS __getdents
14860991
UD
78#endif
79#ifndef DIRENT_TYPE
56ddf355
UD
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)
9756dfe1 84#endif
df4ef2ab
UD
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
831372e7 88 some heuristic. Assuming the directory contains names with 14
980e5832 89 characters on average we can compute an estimated number of entries
831372e7 90 which fit in the buffer. Taking this number allows us to specify a
980e5832
UD
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. */
8d57beea 94ssize_t
980e5832 95internal_function
56ddf355 96__GETDENTS (int fd, char *buf, size_t nbytes)
df4ef2ab 97{
14860991 98 ssize_t retval;
980e5832 99
7cd195df
L
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)))
afedc4af
UD
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 }
afedc4af
UD
137
138 off64_t last_offset = -1;
139
14860991 140#ifdef __NR_getdents64
14860991 141 if (!__have_no_getdents64)
df4ef2ab 142 {
da0fdef0 143# ifndef __ASSUME_GETDENTS64_SYSCALL
14860991 144 int saved_errno = errno;
da0fdef0 145# endif
c213fe9c
RM
146 union
147 {
148 struct kernel_dirent64 k;
149 DIRENT_TYPE u;
150 char b[1];
151 } *kbuf = (void *) buf, *outp, *inp;
14860991
UD
152 size_t kbytes = nbytes;
153 if (offsetof (DIRENT_TYPE, d_name)
154 < offsetof (struct kernel_dirent64, d_name)
155 && nbytes <= sizeof (DIRENT_TYPE))
831372e7 156 {
14860991
UD
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);
da0fdef0 163# ifndef __ASSUME_GETDENTS64_SYSCALL
31df0ab0 164 if (retval != -1 || (errno != EINVAL && errno != ENOSYS))
da0fdef0 165# endif
14860991 166 {
14860991 167 const size_t size_diff = (offsetof (struct kernel_dirent64, d_name)
f9ef3e72 168 - offsetof (DIRENT_TYPE, d_name));
14860991 169
05ae4d6a
UD
170 /* Return the error if encountered. */
171 if (retval == -1)
172 return -1;
173
14860991
UD
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)
c213fe9c
RM
178 && sizeof (outp->u.d_ino) == sizeof (inp->k.d_ino)
179 && sizeof (outp->u.d_off) == sizeof (inp->k.d_off))
14860991
UD
180 return retval;
181
c213fe9c
RM
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)
f11b9da6 189 {
14860991 190 const size_t alignment = __alignof__ (DIRENT_TYPE);
c213fe9c 191 /* Since inp->k.d_reclen is already aligned for the kernel
14860991
UD
192 structure this may compute a value that is bigger
193 than necessary. */
c213fe9c 194 size_t old_reclen = inp->k.d_reclen;
14860991
UD
195 size_t new_reclen = ((old_reclen - size_diff + alignment - 1)
196 & ~(alignment - 1));
c213fe9c
RM
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))
14860991
UD
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 {
f9ef3e72 221 __lseek64 (fd, last_offset, SEEK_SET);
c213fe9c 222 return outp->b - buf;
14860991
UD
223 }
224 __set_errno (EOVERFLOW);
225 return -1;
226 }
227
228 last_offset = d_off;
c213fe9c
RM
229 outp->u.d_reclen = new_reclen;
230 outp->u.d_type = d_type;
14860991 231
c213fe9c
RM
232 inp = (void *) inp + old_reclen;
233 outp = (void *) outp + new_reclen;
f11b9da6
UD
234 }
235
c213fe9c 236 return outp->b - buf;
831372e7
UD
237 }
238
da0fdef0 239# ifndef __ASSUME_GETDENTS64_SYSCALL
14860991
UD
240 __set_errno (saved_errno);
241 __have_no_getdents64 = 1;
da0fdef0 242# endif
14860991
UD
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
14860991
UD
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
c213fe9c 264 DIRENT_TYPE *dp = (DIRENT_TYPE *) buf;
14860991
UD
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);
f9ef3e72 277 __lseek64 (fd, last_offset, SEEK_SET);
14860991
UD
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;
1ac7a2c7 294 dp->d_type = *((char *) kdp + kdp->d_reclen - 1);
14860991
UD
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 }
df4ef2ab 301
c213fe9c
RM
302 return (char *) dp - buf;
303 }
df4ef2ab 304}