1/* Copyright (C) 1993-2016 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
32#include <linux/posix_types.h>
33
34#include <kernel-features.h>
35
36#ifdef __NR_getdents64
37# ifndef __ASSUME_GETDENTS64_SYSCALL
38# ifndef __GETDENTS
39/* The variable is shared between all *getdents* calls. */
40int __have_no_getdents64 attribute_hidden;
41# else
42extern int __have_no_getdents64 attribute_hidden;
43# endif
44# define have_no_getdents64_defined 1
45# endif
46#endif
47#ifndef have_no_getdents64_defined
48# define __have_no_getdents64 0
49#endif
50
51/* For Linux we need a special version of this file since the
52 definition of `struct dirent' is not the same for the kernel and
53 the libc. There is one additional field which might be introduced
54 in the kernel structure in the future.
55
56 Here is the kernel definition of `struct dirent' as of 2.1.20: */
57
58struct kernel_dirent
59 {
60 long int d_ino;
61 __kernel_off_t d_off;
62 unsigned short int d_reclen;
63 char d_name[256];
64 };
65
66struct kernel_dirent64
67 {
68 uint64_t d_ino;
69 int64_t d_off;
70 unsigned short int d_reclen;
71 unsigned char d_type;
72 char d_name[256];
73 };
74
75#ifndef __GETDENTS
76# define __GETDENTS __getdents
77#endif
78#ifndef DIRENT_TYPE
79# define DIRENT_TYPE struct dirent
80#endif
81#ifndef DIRENT_SET_DP_INO
82# define DIRENT_SET_DP_INO(dp, value) (dp)->d_ino = (value)
83#endif
84
85/* The problem here is that we cannot simply read the next NBYTES
86 bytes. We need to take the additional field into account. We use
87 some heuristic. Assuming the directory contains names with 14
88 characters on average we can compute an estimated number of entries
89 which fit in the buffer. Taking this number allows us to specify a
90 reasonable number of bytes to read. If we should be wrong, we can
91 reset the file descriptor. In practice the kernel is limiting the
92 amount of data returned much more then the reduced buffer size. */
93ssize_t
94internal_function
95__GETDENTS (int fd, char *buf, size_t nbytes)
96{
97 ssize_t retval;
98
99 /* The d_ino and d_off fields in kernel_dirent and dirent must have
100 the same sizes and alignments. */
101 if (sizeof (DIRENT_TYPE) == sizeof (struct dirent)
102 && (sizeof (((struct kernel_dirent *) 0)->d_ino)
103 == sizeof (((struct dirent *) 0)->d_ino))
104 && (sizeof (((struct kernel_dirent *) 0)->d_off)
105 == sizeof (((struct dirent *) 0)->d_off))
106 && (offsetof (struct kernel_dirent, d_off)
107 == offsetof (struct dirent, d_off))
108 && (offsetof (struct kernel_dirent, d_reclen)
109 == offsetof (struct dirent, d_reclen)))
110 {
111 retval = INLINE_SYSCALL (getdents, 3, fd, buf, nbytes);
112
113 /* The kernel added the d_type value after the name. Change
114 this now. */
115 if (retval != -1)
116 {
117 union
118 {
119 struct kernel_dirent k;
120 struct dirent u;
121 } *kbuf = (void *) buf;
122
123 while ((char *) kbuf < buf + retval)
124 {
125 char d_type = *((char *) kbuf + kbuf->k.d_reclen - 1);
126 memmove (kbuf->u.d_name, kbuf->k.d_name,
127 strlen (kbuf->k.d_name) + 1);
128 kbuf->u.d_type = d_type;
129
130 kbuf = (void *) ((char *) kbuf + kbuf->k.d_reclen);
131 }
132 }
133
134 return retval;
135 }
136
137 off64_t last_offset = -1;
138
139#ifdef __NR_getdents64
140 if (!__have_no_getdents64)
141 {
142# ifndef __ASSUME_GETDENTS64_SYSCALL
143 int saved_errno = errno;
144# endif
145 union
146 {
147 struct kernel_dirent64 k;
148 DIRENT_TYPE u;
149 char b[1];
150 } *kbuf = (void *) buf, *outp, *inp;
151 size_t kbytes = nbytes;
152 if (offsetof (DIRENT_TYPE, d_name)
153 < offsetof (struct kernel_dirent64, d_name)
154 && nbytes <= sizeof (DIRENT_TYPE))
155 {
156 kbytes = nbytes + offsetof (struct kernel_dirent64, d_name)
157 - offsetof (DIRENT_TYPE, d_name);
158 kbuf = __alloca(kbytes);
159 }
160 retval = INLINE_SYSCALL (getdents64, 3, fd, kbuf, 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, (char *) kdp, red_nbytes);
257
258 if (retval == -1)
259 return -1;
260
261 DIRENT_TYPE *dp = (DIRENT_TYPE *) buf;
262 while ((char *) kdp < (char *) skdp + retval)
263 {
264 const size_t alignment = __alignof__ (DIRENT_TYPE);
265 /* Since kdp->d_reclen is already aligned for the kernel structure
266 this may compute a value that is bigger than necessary. */
267 size_t new_reclen = ((kdp->d_reclen + size_diff + alignment - 1)
268 & ~(alignment - 1));
269 if ((char *) dp + new_reclen > buf + nbytes)
270 {
271 /* Our heuristic failed. We read too many entries. Reset
272 the stream. */
273 assert (last_offset != -1);
274 __lseek64 (fd, last_offset, SEEK_SET);
275
276 if ((char *) dp == buf)
277 {
278 /* The buffer the user passed in is too small to hold even
279 one entry. */
280 __set_errno (EINVAL);
281 return -1;
282 }
283
284 break;
285 }
286
287 last_offset = kdp->d_off;
288 DIRENT_SET_DP_INO(dp, kdp->d_ino);
289 dp->d_off = kdp->d_off;
290 dp->d_reclen = new_reclen;
291 dp->d_type = *((char *) kdp + kdp->d_reclen - 1);
292 memcpy (dp->d_name, kdp->d_name,
293 kdp->d_reclen - offsetof (struct kernel_dirent, d_name));
294
295 dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
296 kdp = (struct kernel_dirent *) (((char *) kdp) + kdp->d_reclen);
297 }
298
299 return (char *) dp - buf;
300 }
301}
302