Update.
[glibc.git] / sysdeps / unix / sysv / linux / getdents.c
blob19ab9238fea423d7f8b8437d1a6a8cffc02ffd68
1 /* Copyright (C) 1993, 95, 96, 97, 98, 99, 2000 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public License as
6 published by the Free Software Foundation; either version 2 of the
7 License, or (at your option) any later version.
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 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public
15 License along with the GNU C Library; see the file COPYING.LIB. If not,
16 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 Boston, MA 02111-1307, USA. */
19 #include <alloca.h>
20 #include <assert.h>
21 #include <errno.h>
22 #include <dirent.h>
23 #include <stddef.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <sys/param.h>
27 #include <sys/types.h>
29 #include <sysdep.h>
30 #include <sys/syscall.h>
31 #include <bp-checks.h>
33 #include <linux/posix_types.h>
35 #include "kernel-features.h"
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;
42 #else
43 extern int __have_no_getdents64;
44 #endif
45 #endif
46 #endif
48 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
50 extern int __syscall_getdents (int fd, char *__unbounded buf, size_t nbytes);
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.
57 Here is the kernel definition of `struct dirent' as of 2.1.20: */
59 struct kernel_dirent
61 long int d_ino;
62 __kernel_off_t d_off;
63 unsigned short int d_reclen;
64 char d_name[256];
67 struct kernel_dirent64
69 u_int64_t d_ino;
70 int64_t d_off;
71 unsigned short int d_reclen;
72 unsigned char d_type;
73 char d_name[256];
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
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)
98 DIRENT_TYPE *dp;
99 off_t last_offset = -1;
100 ssize_t retval;
102 #ifdef __NR_getdents64
103 #ifndef __ASSUME_GETDENTS64_SYSCALL
104 if (!__have_no_getdents64)
105 #endif
107 #ifndef __ASSUME_GETDENTS64_SYSCALL
108 int saved_errno = errno;
109 #endif
110 char *kbuf = buf;
111 size_t kbytes = nbytes;
112 if (offsetof (DIRENT_TYPE, d_name)
113 < offsetof (struct kernel_dirent64, d_name)
114 && nbytes <= sizeof (DIRENT_TYPE))
116 kbytes = nbytes + offsetof (struct kernel_dirent64, d_name)
117 - offsetof (DIRENT_TYPE, d_name);
118 kbuf = __alloca(kbytes);
120 retval = INLINE_SYSCALL (getdents64, 3, fd, CHECK_N(kbuf, kbytes),
121 kbytes);
122 #ifndef __ASSUME_GETDENTS64_SYSCALL
123 if (retval != -1 && errno != -EINVAL)
124 #endif
126 struct kernel_dirent64 *kdp;
127 const size_t size_diff = (offsetof (struct kernel_dirent64, d_name)
128 - offsetof (DIRENT_TYPE, d_name));
130 /* If the structure returned by the kernel is identical to what we
131 need, don't do any conversions. */
132 if (offsetof (DIRENT_TYPE, d_name)
133 == offsetof (struct kernel_dirent64, d_name)
134 && sizeof (dp->d_ino) == sizeof (kdp->d_ino)
135 && sizeof (dp->d_off) == sizeof (kdp->d_off))
136 return retval;
138 dp = (DIRENT_TYPE *)buf;
139 kdp = (struct kernel_dirent64 *)kbuf;
140 while ((char *) kdp < kbuf + retval)
142 const size_t alignment = __alignof__ (DIRENT_TYPE);
143 /* Since kdp->d_reclen is already aligned for the kernel
144 structure this may compute a value that is bigger
145 than necessary. */
146 size_t old_reclen = kdp->d_reclen;
147 size_t new_reclen = ((old_reclen - size_diff + alignment - 1)
148 & ~(alignment - 1));
149 u_int64_t d_ino = kdp->d_ino;
150 int64_t d_off = kdp->d_off;
151 unsigned char d_type = kdp->d_type;
153 DIRENT_SET_DP_INO(dp, d_ino);
154 dp->d_off = d_off;
155 if ((sizeof (dp->d_ino) != sizeof (kdp->d_ino)
156 && dp->d_ino != d_ino)
157 || (sizeof (dp->d_off) != sizeof (kdp->d_off)
158 && dp->d_off != d_off))
160 /* Overflow. If there was at least one entry
161 before this one, return them without error,
162 otherwise signal overflow. */
163 if (last_offset != -1)
165 __lseek (fd, last_offset, SEEK_SET);
166 return (char *) dp - buf;
168 __set_errno (EOVERFLOW);
169 return -1;
172 last_offset = d_off;
173 dp->d_reclen = new_reclen;
174 dp->d_type = d_type;
175 memmove (dp->d_name, kdp->d_name,
176 old_reclen - offsetof (struct kernel_dirent64, d_name));
178 dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
179 kdp = (struct kernel_dirent64 *) ((char *) kdp + old_reclen);
182 return (char *) dp - buf;
185 #ifndef __ASSUME_GETDENTS64_SYSCALL
186 __set_errno (saved_errno);
187 __have_no_getdents64 = 1;
188 #endif
190 #endif
192 size_t red_nbytes;
193 struct kernel_dirent *skdp, *kdp;
194 const size_t size_diff = (offsetof (DIRENT_TYPE, d_name)
195 - offsetof (struct kernel_dirent, d_name));
197 red_nbytes = MIN (nbytes
198 - ((nbytes / (offsetof (DIRENT_TYPE, d_name) + 14))
199 * size_diff),
200 nbytes - size_diff);
202 dp = (DIRENT_TYPE *) buf;
203 skdp = kdp = __alloca (red_nbytes);
205 retval = INLINE_SYSCALL (getdents, 3, fd,
206 CHECK_N ((char *) kdp, red_nbytes), red_nbytes);
208 if (retval == -1)
209 return -1;
211 while ((char *) kdp < (char *) skdp + retval)
213 const size_t alignment = __alignof__ (DIRENT_TYPE);
214 /* Since kdp->d_reclen is already aligned for the kernel structure
215 this may compute a value that is bigger than necessary. */
216 size_t new_reclen = ((kdp->d_reclen + size_diff + alignment - 1)
217 & ~(alignment - 1));
218 if ((char *) dp + new_reclen > buf + nbytes)
220 /* Our heuristic failed. We read too many entries. Reset
221 the stream. */
222 assert (last_offset != -1);
223 __lseek (fd, last_offset, SEEK_SET);
225 if ((char *) dp == buf)
227 /* The buffer the user passed in is too small to hold even
228 one entry. */
229 __set_errno (EINVAL);
230 return -1;
233 break;
236 last_offset = kdp->d_off;
237 DIRENT_SET_DP_INO(dp, kdp->d_ino);
238 dp->d_off = kdp->d_off;
239 dp->d_reclen = new_reclen;
240 dp->d_type = DT_UNKNOWN;
241 memcpy (dp->d_name, kdp->d_name,
242 kdp->d_reclen - offsetof (struct kernel_dirent, d_name));
244 dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
245 kdp = (struct kernel_dirent *) (((char *) kdp) + kdp->d_reclen);
249 return (char *) dp - buf;