(__GETDENTS): Use __lseek64 instead of __lseek.
[glibc.git] / sysdeps / unix / sysv / linux / getdents.c
blob1fb007744cbe6023064c66de9c8f23a7f2a756b4
1 /* Copyright (C) 1993,95,96,97,98,99,2000,2001 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 <stdint.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <sys/param.h>
28 #include <sys/types.h>
30 #include <sysdep.h>
31 #include <sys/syscall.h>
32 #include <bp-checks.h>
34 #include <linux/posix_types.h>
36 #include "kernel-features.h"
38 #ifdef __NR_getdents64
39 #ifndef __ASSUME_GETDENTS64_SYSCALL
40 #ifndef __GETDENTS
41 /* The variable is shared between all *getdents* calls. */
42 int __have_no_getdents64;
43 #else
44 extern int __have_no_getdents64;
45 #endif
46 #endif
47 #endif
49 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
51 extern int __syscall_getdents (int fd, char *__unbounded buf, unsigned int nbytes);
52 extern int __syscall_getdents64 (int fd, char *__unbounded buf, unsigned int nbytes);
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 DIRENT_TYPE *dp;
101 off64_t last_offset = -1;
102 ssize_t retval;
104 #ifdef __NR_getdents64
105 # ifndef __ASSUME_GETDENTS64_SYSCALL
106 if (!__have_no_getdents64)
107 # endif
109 # ifndef __ASSUME_GETDENTS64_SYSCALL
110 int saved_errno = errno;
111 # endif
112 char *kbuf = buf;
113 size_t kbytes = nbytes;
114 if (offsetof (DIRENT_TYPE, d_name)
115 < offsetof (struct kernel_dirent64, d_name)
116 && nbytes <= sizeof (DIRENT_TYPE))
118 kbytes = nbytes + offsetof (struct kernel_dirent64, d_name)
119 - offsetof (DIRENT_TYPE, d_name);
120 kbuf = __alloca(kbytes);
122 retval = INLINE_SYSCALL (getdents64, 3, fd, CHECK_N(kbuf, kbytes),
123 kbytes);
124 # ifndef __ASSUME_GETDENTS64_SYSCALL
125 if (retval != -1 && errno != -EINVAL)
126 # endif
128 struct kernel_dirent64 *kdp;
129 const size_t size_diff = (offsetof (struct kernel_dirent64, d_name)
130 - offsetof (DIRENT_TYPE, d_name));
132 /* If the structure returned by the kernel is identical to what we
133 need, don't do any conversions. */
134 if (offsetof (DIRENT_TYPE, d_name)
135 == offsetof (struct kernel_dirent64, d_name)
136 && sizeof (dp->d_ino) == sizeof (kdp->d_ino)
137 && sizeof (dp->d_off) == sizeof (kdp->d_off))
138 return retval;
140 dp = (DIRENT_TYPE *)buf;
141 kdp = (struct kernel_dirent64 *) kbuf;
142 while ((char *) kdp < kbuf + retval)
144 const size_t alignment = __alignof__ (DIRENT_TYPE);
145 /* Since kdp->d_reclen is already aligned for the kernel
146 structure this may compute a value that is bigger
147 than necessary. */
148 size_t old_reclen = kdp->d_reclen;
149 size_t new_reclen = ((old_reclen - size_diff + alignment - 1)
150 & ~(alignment - 1));
151 uint64_t d_ino = kdp->d_ino;
152 int64_t d_off = kdp->d_off;
153 unsigned char d_type = kdp->d_type;
155 DIRENT_SET_DP_INO (dp, d_ino);
156 dp->d_off = d_off;
157 if ((sizeof (dp->d_ino) != sizeof (kdp->d_ino)
158 && dp->d_ino != d_ino)
159 || (sizeof (dp->d_off) != sizeof (kdp->d_off)
160 && dp->d_off != d_off))
162 /* Overflow. If there was at least one entry
163 before this one, return them without error,
164 otherwise signal overflow. */
165 if (last_offset != -1)
167 __lseek64 (fd, last_offset, SEEK_SET);
168 return (char *) dp - buf;
170 __set_errno (EOVERFLOW);
171 return -1;
174 last_offset = d_off;
175 dp->d_reclen = new_reclen;
176 dp->d_type = d_type;
177 memmove (dp->d_name, kdp->d_name,
178 old_reclen - offsetof (struct kernel_dirent64, d_name));
180 dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
181 kdp = (struct kernel_dirent64 *) ((char *) kdp + old_reclen);
184 return (char *) dp - buf;
187 # ifndef __ASSUME_GETDENTS64_SYSCALL
188 __set_errno (saved_errno);
189 __have_no_getdents64 = 1;
190 # endif
192 #endif
194 size_t red_nbytes;
195 struct kernel_dirent *skdp, *kdp;
196 const size_t size_diff = (offsetof (DIRENT_TYPE, d_name)
197 - offsetof (struct kernel_dirent, d_name));
199 red_nbytes = MIN (nbytes
200 - ((nbytes / (offsetof (DIRENT_TYPE, d_name) + 14))
201 * size_diff),
202 nbytes - size_diff);
204 dp = (DIRENT_TYPE *) buf;
205 skdp = kdp = __alloca (red_nbytes);
207 retval = INLINE_SYSCALL (getdents, 3, fd,
208 CHECK_N ((char *) kdp, red_nbytes), red_nbytes);
210 if (retval == -1)
211 return -1;
213 while ((char *) kdp < (char *) skdp + retval)
215 const size_t alignment = __alignof__ (DIRENT_TYPE);
216 /* Since kdp->d_reclen is already aligned for the kernel structure
217 this may compute a value that is bigger than necessary. */
218 size_t new_reclen = ((kdp->d_reclen + size_diff + alignment - 1)
219 & ~(alignment - 1));
220 if ((char *) dp + new_reclen > buf + nbytes)
222 /* Our heuristic failed. We read too many entries. Reset
223 the stream. */
224 assert (last_offset != -1);
225 __lseek64 (fd, last_offset, SEEK_SET);
227 if ((char *) dp == buf)
229 /* The buffer the user passed in is too small to hold even
230 one entry. */
231 __set_errno (EINVAL);
232 return -1;
235 break;
238 last_offset = kdp->d_off;
239 DIRENT_SET_DP_INO(dp, kdp->d_ino);
240 dp->d_off = kdp->d_off;
241 dp->d_reclen = new_reclen;
242 dp->d_type = DT_UNKNOWN;
243 memcpy (dp->d_name, kdp->d_name,
244 kdp->d_reclen - offsetof (struct kernel_dirent, d_name));
246 dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
247 kdp = (struct kernel_dirent *) (((char *) kdp) + kdp->d_reclen);
251 return (char *) dp - buf;