]>
Commit | Line | Data |
---|---|---|
1 | /*- | |
2 | * Copyright (c) 1983, 1993 | |
3 | * The Regents of the University of California. All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * 4. Neither the name of the University nor the names of its contributors | |
14 | * may be used to endorse or promote products derived from this software | |
15 | * without specific prior written permission. | |
16 | * | |
17 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
27 | * SUCH DAMAGE. | |
28 | */ | |
29 | ||
30 | #if defined(LIBC_SCCS) && !defined(lint) | |
31 | static char sccsid[] = "@(#)opendir.c 8.8 (Berkeley) 5/1/95"; | |
32 | #endif /* LIBC_SCCS and not lint */ | |
33 | #include <sys/cdefs.h> | |
34 | __FBSDID("$FreeBSD$"); | |
35 | ||
36 | #include "namespace.h" | |
37 | #include <sys/param.h> | |
38 | #include <sys/mount.h> | |
39 | #include <sys/stat.h> | |
40 | #include <sys/sysctl.h> | |
41 | ||
42 | #include <dirent.h> | |
43 | #include <errno.h> | |
44 | #include <fcntl.h> | |
45 | #include <stdlib.h> | |
46 | #include <string.h> | |
47 | #include <unistd.h> | |
48 | #include <pthread.h> | |
49 | #include "un-namespace.h" | |
50 | ||
51 | #include "telldir.h" | |
52 | ||
53 | static bool | |
54 | __kernel_supports_unionfs(void) | |
55 | { | |
56 | static int8_t kernel_supports_unionfs = -1; | |
57 | if (kernel_supports_unionfs == -1) { | |
58 | int value = 0; | |
59 | size_t len = sizeof(value); | |
60 | sysctlbyname("kern.secure_kernel", &value, &len, NULL, 0); | |
61 | kernel_supports_unionfs = !value; | |
62 | } | |
63 | return kernel_supports_unionfs; | |
64 | } | |
65 | ||
66 | static int | |
67 | __fd_is_on_union_mount(int fd) | |
68 | { | |
69 | struct statfs stbuf; | |
70 | int rc; | |
71 | ||
72 | rc = fstatfs(fd, &stbuf); | |
73 | if (rc < 0) { | |
74 | return rc; | |
75 | } | |
76 | return (stbuf.f_flags & MNT_UNION) != 0; | |
77 | } | |
78 | ||
79 | static DIR * __opendir_common(int, int, bool); | |
80 | ||
81 | /* | |
82 | * Open a directory. | |
83 | */ | |
84 | DIR * | |
85 | opendir(const char *name) | |
86 | { | |
87 | ||
88 | return (__opendir2(name, DTF_HIDEW|DTF_NODUP)); | |
89 | } | |
90 | ||
91 | /* | |
92 | * Open a directory with existing file descriptor. | |
93 | */ | |
94 | DIR * | |
95 | fdopendir(int fd) | |
96 | { | |
97 | struct stat statb; | |
98 | ||
99 | /* Check that fd is associated with a directory. */ | |
100 | if (_fstat(fd, &statb) != 0) | |
101 | return (NULL); | |
102 | if (!S_ISDIR(statb.st_mode)) { | |
103 | errno = ENOTDIR; | |
104 | return (NULL); | |
105 | } | |
106 | /* Make sure CLOEXEC is set on the fd */ | |
107 | if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) | |
108 | return (NULL); | |
109 | return (__opendir_common(fd, DTF_HIDEW|DTF_NODUP, true)); | |
110 | } | |
111 | ||
112 | DIR * | |
113 | __opendir2(const char *name, int flags) | |
114 | { | |
115 | int fd; | |
116 | DIR *dir; | |
117 | int saved_errno; | |
118 | ||
119 | if ((flags & (__DTF_READALL | __DTF_SKIPREAD)) != 0) | |
120 | return (NULL); | |
121 | if ((fd = _open(name, | |
122 | O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1) | |
123 | return (NULL); | |
124 | ||
125 | dir = __opendir_common(fd, flags, false); | |
126 | if (dir == NULL) { | |
127 | saved_errno = errno; | |
128 | _close(fd); | |
129 | errno = saved_errno; | |
130 | } | |
131 | return (dir); | |
132 | } | |
133 | ||
134 | static int | |
135 | opendir_compar(const void *p1, const void *p2) | |
136 | { | |
137 | ||
138 | return (strcmp((*(const struct dirent **)p1)->d_name, | |
139 | (*(const struct dirent **)p2)->d_name)); | |
140 | } | |
141 | ||
142 | /* | |
143 | * For a directory at the top of a unionfs stack, the entire directory's | |
144 | * contents are read and cached locally until the next call to rewinddir(). | |
145 | * For the fdopendir() case, the initial seek position must be preserved. | |
146 | * For rewinddir(), the full directory should always be re-read from the | |
147 | * beginning. | |
148 | * | |
149 | * If an error occurs, the existing buffer and state of 'dirp' is left | |
150 | * unchanged. | |
151 | */ | |
152 | bool | |
153 | _filldir(DIR *dirp, bool use_current_pos) | |
154 | { | |
155 | struct dirent **dpv; | |
156 | char *buf, *ddptr, *ddeptr; | |
157 | off_t pos; | |
158 | int fd2, incr, len, n, saved_errno, space; | |
159 | ||
160 | len = 0; | |
161 | space = 0; | |
162 | buf = NULL; | |
163 | ddptr = NULL; | |
164 | ||
165 | /* | |
166 | * Use the system page size if that is a multiple of DIRBLKSIZ. | |
167 | * Hopefully this can be a big win someday by allowing page | |
168 | * trades to user space to be done by _getdirentries(). | |
169 | */ | |
170 | incr = getpagesize(); | |
171 | if ((incr % DIRBLKSIZ) != 0) | |
172 | incr = DIRBLKSIZ; | |
173 | ||
174 | /* | |
175 | * The strategy here is to read all the directory | |
176 | * entries into a buffer, sort the buffer, and | |
177 | * remove duplicate entries by setting the inode | |
178 | * number to zero. | |
179 | * | |
180 | * We reopen the directory because _getdirentries() | |
181 | * on a MNT_UNION mount modifies the open directory, | |
182 | * making it refer to the lower directory after the | |
183 | * upper directory's entries are exhausted. | |
184 | * This would otherwise break software that uses | |
185 | * the directory descriptor for fchdir or *at | |
186 | * functions, such as fts.c. | |
187 | */ | |
188 | if ((fd2 = openat(dirp->dd_fd, ".", O_RDONLY | O_CLOEXEC)) == -1) | |
189 | return (false); | |
190 | ||
191 | if (use_current_pos) { | |
192 | pos = lseek(dirp->dd_fd, 0, SEEK_CUR); | |
193 | if (pos == -1 || lseek(fd2, pos, SEEK_SET) == -1) { | |
194 | saved_errno = errno; | |
195 | _close(fd2); | |
196 | errno = saved_errno; | |
197 | return (false); | |
198 | } | |
199 | } | |
200 | ||
201 | do { | |
202 | /* | |
203 | * Always make at least DIRBLKSIZ bytes | |
204 | * available to _getdirentries | |
205 | */ | |
206 | if (space < DIRBLKSIZ) { | |
207 | space += incr; | |
208 | len += incr; | |
209 | buf = reallocf(buf, len); | |
210 | if (buf == NULL) { | |
211 | saved_errno = errno; | |
212 | _close(fd2); | |
213 | errno = saved_errno; | |
214 | return (false); | |
215 | } | |
216 | ddptr = buf + (len - space); | |
217 | } | |
218 | ||
219 | #if __DARWIN_64_BIT_INO_T | |
220 | n = (int)__getdirentries64(fd2, ddptr, space, &dirp->dd_td->seekoff); | |
221 | #else /* !__DARWIN_64_BIT_INO_T */ | |
222 | n = _getdirentries(fd2, ddptr, space, &dirp->dd_seek); | |
223 | #endif /* __DARWIN_64_BIT_INO_T */ | |
224 | if (n > 0) { | |
225 | ddptr += n; | |
226 | space -= n; | |
227 | } | |
228 | if (n < 0) { | |
229 | saved_errno = errno; | |
230 | _close(fd2); | |
231 | errno = saved_errno; | |
232 | return (false); | |
233 | } | |
234 | } while (n > 0); | |
235 | _close(fd2); | |
236 | ||
237 | ddeptr = ddptr; | |
238 | ||
239 | /* | |
240 | * There is now a buffer full of (possibly) duplicate | |
241 | * names. | |
242 | */ | |
243 | dirp->dd_buf = buf; | |
244 | ||
245 | /* | |
246 | * Go round this loop twice... | |
247 | * | |
248 | * Scan through the buffer, counting entries. | |
249 | * On the second pass, save pointers to each one. | |
250 | * Then sort the pointers and remove duplicate names. | |
251 | */ | |
252 | for (dpv = NULL;;) { | |
253 | n = 0; | |
254 | ddptr = buf; | |
255 | while (ddptr < ddeptr) { | |
256 | struct dirent *dp; | |
257 | ||
258 | dp = (struct dirent *) ddptr; | |
259 | if ((long)dp & 03L) | |
260 | break; | |
261 | if ((dp->d_reclen <= 0) || | |
262 | (dp->d_reclen > (ddeptr + 1 - ddptr))) | |
263 | break; | |
264 | ddptr += dp->d_reclen; | |
265 | if (dp->d_fileno) { | |
266 | if (dpv) | |
267 | dpv[n] = dp; | |
268 | n++; | |
269 | } | |
270 | } | |
271 | ||
272 | if (dpv) { | |
273 | struct dirent *xp; | |
274 | ||
275 | /* | |
276 | * This sort must be stable. | |
277 | */ | |
278 | mergesort(dpv, n, sizeof(*dpv), opendir_compar); | |
279 | ||
280 | dpv[n] = NULL; | |
281 | xp = NULL; | |
282 | ||
283 | /* | |
284 | * Scan through the buffer in sort order, | |
285 | * zapping the inode number of any | |
286 | * duplicate names. | |
287 | */ | |
288 | for (n = 0; dpv[n]; n++) { | |
289 | struct dirent *dp = dpv[n]; | |
290 | ||
291 | if ((xp == NULL) || | |
292 | strcmp(dp->d_name, xp->d_name)) { | |
293 | xp = dp; | |
294 | } else { | |
295 | dp->d_fileno = 0; | |
296 | } | |
297 | if (dp->d_type == DT_WHT && | |
298 | (dirp->dd_flags & DTF_HIDEW)) | |
299 | dp->d_fileno = 0; | |
300 | } | |
301 | ||
302 | free(dpv); | |
303 | break; | |
304 | } else { | |
305 | dpv = malloc((n+1) * sizeof(struct dirent *)); | |
306 | if (dpv == NULL) | |
307 | break; | |
308 | } | |
309 | } | |
310 | ||
311 | dirp->dd_len = len; | |
312 | dirp->dd_size = ddptr - dirp->dd_buf; | |
313 | return (true); | |
314 | } | |
315 | ||
316 | ||
317 | /* | |
318 | * Common routine for opendir(3), __opendir2(3) and fdopendir(3). | |
319 | */ | |
320 | static DIR * | |
321 | __opendir_common(int fd, int flags, bool use_current_pos) | |
322 | { | |
323 | DIR *dirp; | |
324 | int saved_errno; | |
325 | int unionstack; | |
326 | ||
327 | if ((dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL) | |
328 | return (NULL); | |
329 | ||
330 | dirp->dd_buf = NULL; | |
331 | dirp->dd_fd = fd; | |
332 | dirp->dd_flags = flags; | |
333 | dirp->dd_loc = 0; | |
334 | dirp->dd_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; | |
335 | dirp->dd_td = (struct _telldir *)((char *)dirp + sizeof(DIR)); | |
336 | LIST_INIT(&dirp->dd_td->td_locq); | |
337 | dirp->dd_td->td_loccnt = 0; | |
338 | ||
339 | /* | |
340 | * Determine whether this directory is the top of a union stack. | |
341 | */ | |
342 | if ((flags & DTF_NODUP) && __kernel_supports_unionfs()) { | |
343 | unionstack = __fd_is_on_union_mount(fd); | |
344 | if (unionstack < 0) | |
345 | goto fail; | |
346 | } else { | |
347 | unionstack = 0; | |
348 | } | |
349 | ||
350 | if (unionstack) { | |
351 | if (!_filldir(dirp, use_current_pos)) | |
352 | goto fail; | |
353 | dirp->dd_flags |= __DTF_READALL; | |
354 | } else { | |
355 | /* | |
356 | * Start with a small-ish size to avoid allocating full pages. | |
357 | * readdir() will allocate a larger buffer if it didn't fit | |
358 | * to stay fast for large directories. | |
359 | */ | |
360 | _Static_assert(GETDIRENTRIES64_EXTENDED_BUFSIZE <= READDIR_INITIAL_SIZE, | |
361 | "Make sure we'll get extended metadata"); | |
362 | dirp->dd_len = READDIR_INITIAL_SIZE; | |
363 | dirp->dd_buf = malloc(dirp->dd_len); | |
364 | if (dirp->dd_buf == NULL) | |
365 | goto fail; | |
366 | if (use_current_pos) { | |
367 | /* | |
368 | * Read the first batch of directory entries | |
369 | * to prime dd_seek. This also checks if the | |
370 | * fd passed to fdopendir() is a directory. | |
371 | */ | |
372 | #if __DARWIN_64_BIT_INO_T | |
373 | /* | |
374 | * sufficiently recent kernels when the buffer is large enough, | |
375 | * will use the last bytes of the buffer to return status. | |
376 | * | |
377 | * To support older kernels: | |
378 | * - make sure it's 0 initialized | |
379 | * - make sure it's past `dd_size` before reading it | |
380 | */ | |
381 | getdirentries64_flags_t *gdeflags = | |
382 | (getdirentries64_flags_t *)(dirp->dd_buf + dirp->dd_len - | |
383 | sizeof(getdirentries64_flags_t)); | |
384 | *gdeflags = 0; | |
385 | dirp->dd_size = (long)__getdirentries64(dirp->dd_fd, | |
386 | dirp->dd_buf, dirp->dd_len, &dirp->dd_td->seekoff); | |
387 | if (dirp->dd_size >= 0 && | |
388 | dirp->dd_size <= dirp->dd_len - sizeof(getdirentries64_flags_t)) { | |
389 | if (*gdeflags & GETDIRENTRIES64_EOF) { | |
390 | dirp->dd_flags |= __DTF_ATEND; | |
391 | } | |
392 | } | |
393 | #else /* !__DARWIN_64_BIT_INO_T */ | |
394 | dirp->dd_size = _getdirentries(dirp->dd_fd, | |
395 | dirp->dd_buf, dirp->dd_len, &dirp->dd_seek); | |
396 | #endif /* __DARWIN_64_BIT_INO_T */ | |
397 | if (dirp->dd_size < 0) { | |
398 | if (errno == EINVAL) | |
399 | errno = ENOTDIR; | |
400 | goto fail; | |
401 | } | |
402 | dirp->dd_flags |= __DTF_SKIPREAD; | |
403 | } else { | |
404 | dirp->dd_size = 0; | |
405 | #if __DARWIN_64_BIT_INO_T | |
406 | dirp->dd_td->seekoff = 0; | |
407 | #else /* !__DARWIN_64_BIT_INO_T */ | |
408 | dirp->dd_seek = 0; | |
409 | #endif /* __DARWIN_64_BIT_INO_T */ | |
410 | } | |
411 | } | |
412 | ||
413 | return (dirp); | |
414 | ||
415 | fail: | |
416 | saved_errno = errno; | |
417 | free(dirp->dd_buf); | |
418 | free(dirp); | |
419 | errno = saved_errno; | |
420 | return (NULL); | |
421 | } |