X-Git-Url: https://git.saurik.com/apple/libc.git/blobdiff_plain/ad3c9f2af814c84582fdd1649e49ec4f68572c5a..507116e319a1470bb0a5040d4d23e5c76955ef97:/gen/FreeBSD/opendir.c diff --git a/gen/FreeBSD/opendir.c b/gen/FreeBSD/opendir.c index b2c49df..844a7e8 100644 --- a/gen/FreeBSD/opendir.c +++ b/gen/FreeBSD/opendir.c @@ -31,12 +31,13 @@ static char sccsid[] = "@(#)opendir.c 8.8 (Berkeley) 5/1/95"; #endif /* LIBC_SCCS and not lint */ #include -__FBSDID("$FreeBSD: src/lib/libc/gen/opendir.c,v 1.24 2008/04/16 18:40:52 delphij Exp $"); +__FBSDID("$FreeBSD$"); #include "namespace.h" #include #include #include +#include #include #include @@ -48,6 +49,35 @@ __FBSDID("$FreeBSD: src/lib/libc/gen/opendir.c,v 1.24 2008/04/16 18:40:52 delphi #include "un-namespace.h" #include "telldir.h" + +static bool +__kernel_supports_unionfs(void) +{ + static int8_t kernel_supports_unionfs = -1; + if (kernel_supports_unionfs == -1) { + int value = 0; + size_t len = sizeof(value); + sysctlbyname("kern.secure_kernel", &value, &len, NULL, 0); + kernel_supports_unionfs = !value; + } + return kernel_supports_unionfs; +} + +static int +__fd_is_on_union_mount(int fd) +{ + struct statfs stbuf; + int rc; + + rc = fstatfs(fd, &stbuf); + if (rc < 0) { + return rc; + } + return (stbuf.f_flags & MNT_UNION) != 0; +} + +static DIR * __opendir_common(int, int, bool); + /* * Open a directory. */ @@ -58,29 +88,79 @@ opendir(const char *name) return (__opendir2(name, DTF_HIDEW|DTF_NODUP)); } +/* + * Open a directory with existing file descriptor. + */ +DIR * +fdopendir(int fd) +{ + struct stat statb; + + /* Check that fd is associated with a directory. */ + if (_fstat(fd, &statb) != 0) + return (NULL); + if (!S_ISDIR(statb.st_mode)) { + errno = ENOTDIR; + return (NULL); + } + /* Make sure CLOEXEC is set on the fd */ + if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + return (NULL); + return (__opendir_common(fd, DTF_HIDEW|DTF_NODUP, true)); +} + DIR * __opendir2(const char *name, int flags) { - DIR *dirp; int fd; - int incr; + DIR *dir; int saved_errno; - int unionstack; - /* - * Use O_DIRECTORY to only open directories (because opening of - * special files may be harmful). errno is set to ENOTDIR if - * not a directory. - */ - if ((fd = _open(name, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1) + if ((flags & (__DTF_READALL | __DTF_SKIPREAD)) != 0) + return (NULL); + if ((fd = _open(name, + O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1) return (NULL); - dirp = malloc(sizeof(DIR) + sizeof(struct _telldir)); - if (dirp == NULL) - goto fail; - dirp->dd_td = (struct _telldir *)((char *)dirp + sizeof(DIR)); - LIST_INIT(&dirp->dd_td->td_locq); - dirp->dd_td->td_loccnt = 0; + dir = __opendir_common(fd, flags, false); + if (dir == NULL) { + saved_errno = errno; + _close(fd); + errno = saved_errno; + } + return (dir); +} + +static int +opendir_compar(const void *p1, const void *p2) +{ + + return (strcmp((*(const struct dirent **)p1)->d_name, + (*(const struct dirent **)p2)->d_name)); +} + +/* + * For a directory at the top of a unionfs stack, the entire directory's + * contents are read and cached locally until the next call to rewinddir(). + * For the fdopendir() case, the initial seek position must be preserved. + * For rewinddir(), the full directory should always be re-read from the + * beginning. + * + * If an error occurs, the existing buffer and state of 'dirp' is left + * unchanged. + */ +bool +_filldir(DIR *dirp, bool use_current_pos) +{ + struct dirent **dpv; + char *buf, *ddptr, *ddeptr; + off_t pos; + int fd2, incr, len, n, saved_errno, space; + + len = 0; + space = 0; + buf = NULL; + ddptr = NULL; /* * Use the system page size if that is a multiple of DIRBLKSIZ. @@ -92,184 +172,250 @@ __opendir2(const char *name, int flags) incr = DIRBLKSIZ; /* - * Determine whether this directory is the top of a union stack. + * The strategy here is to read all the directory + * entries into a buffer, sort the buffer, and + * remove duplicate entries by setting the inode + * number to zero. + * + * We reopen the directory because _getdirentries() + * on a MNT_UNION mount modifies the open directory, + * making it refer to the lower directory after the + * upper directory's entries are exhausted. + * This would otherwise break software that uses + * the directory descriptor for fchdir or *at + * functions, such as fts.c. */ - if (flags & DTF_NODUP) { - struct statfs sfb; + if ((fd2 = openat(dirp->dd_fd, ".", O_RDONLY | O_CLOEXEC)) == -1) + return (false); - if (_fstatfs(fd, &sfb) < 0) - goto fail; - unionstack = (sfb.f_flags & MNT_UNION); - } else { - unionstack = 0; + if (use_current_pos) { + pos = lseek(dirp->dd_fd, 0, SEEK_CUR); + if (pos == -1 || lseek(fd2, pos, SEEK_SET) == -1) { + saved_errno = errno; + _close(fd2); + errno = saved_errno; + return (false); + } } - if (unionstack) { - int len = 0; - int space = 0; - char *buf = 0; - char *ddptr = 0; - char *ddeptr; - int n; - struct dirent **dpv; - + do { /* - * The strategy here is to read all the directory - * entries into a buffer, sort the buffer, and - * remove duplicate entries by setting the inode - * number to zero. + * Always make at least DIRBLKSIZ bytes + * available to _getdirentries */ - - do { - /* - * Always make at least DIRBLKSIZ bytes - * available to _getdirentries - */ - if (space < DIRBLKSIZ) { - space += incr; - len += incr; - buf = reallocf(buf, len); - if (buf == NULL) - goto fail; - ddptr = buf + (len - space); + if (space < DIRBLKSIZ) { + space += incr; + len += incr; + buf = reallocf(buf, len); + if (buf == NULL) { + saved_errno = errno; + _close(fd2); + errno = saved_errno; + return (false); } + ddptr = buf + (len - space); + } #if __DARWIN_64_BIT_INO_T - n = __getdirentries64(fd, ddptr, space, &dirp->dd_td->seekoff); + n = (int)__getdirentries64(fd2, ddptr, space, &dirp->dd_td->seekoff); #else /* !__DARWIN_64_BIT_INO_T */ - n = _getdirentries(fd, ddptr, space, &dirp->dd_seek); + n = _getdirentries(fd2, ddptr, space, &dirp->dd_seek); #endif /* __DARWIN_64_BIT_INO_T */ - if (n > 0) { - ddptr += n; - space -= n; - } - } while (n > 0); + if (n > 0) { + ddptr += n; + space -= n; + } + if (n < 0) { + saved_errno = errno; + _close(fd2); + errno = saved_errno; + return (false); + } + } while (n > 0); + _close(fd2); - ddeptr = ddptr; - flags |= __DTF_READALL; + ddeptr = ddptr; - /* - * Re-open the directory. - * This has the effect of rewinding back to the - * top of the union stack and is needed by - * programs which plan to fchdir to a descriptor - * which has also been read -- see fts.c. - */ - if (flags & DTF_REWIND) { - (void)_close(fd); - if ((fd = _open(name, O_RDONLY)) == -1) { - saved_errno = errno; - free(buf); - free(dirp); - errno = saved_errno; - return (NULL); + /* + * There is now a buffer full of (possibly) duplicate + * names. + */ + dirp->dd_buf = buf; + + /* + * Go round this loop twice... + * + * Scan through the buffer, counting entries. + * On the second pass, save pointers to each one. + * Then sort the pointers and remove duplicate names. + */ + for (dpv = NULL;;) { + n = 0; + ddptr = buf; + while (ddptr < ddeptr) { + struct dirent *dp; + + dp = (struct dirent *) ddptr; + if ((long)dp & 03L) + break; + if ((dp->d_reclen <= 0) || + (dp->d_reclen > (ddeptr + 1 - ddptr))) + break; + ddptr += dp->d_reclen; + if (dp->d_fileno) { + if (dpv) + dpv[n] = dp; + n++; } } - /* - * There is now a buffer full of (possibly) duplicate - * names. - */ - dirp->dd_buf = buf; + if (dpv) { + struct dirent *xp; - /* - * Go round this loop twice... - * - * Scan through the buffer, counting entries. - * On the second pass, save pointers to each one. - * Then sort the pointers and remove duplicate names. - */ - for (dpv = 0;;) { - n = 0; - ddptr = buf; - while (ddptr < ddeptr) { - struct dirent *dp; - - dp = (struct dirent *) ddptr; - if ((long)dp & 03L) - break; - if ((dp->d_reclen <= 0) || - (dp->d_reclen > (ddeptr + 1 - ddptr))) - break; - ddptr += dp->d_reclen; - if (dp->d_fileno) { - if (dpv) - dpv[n] = dp; - n++; - } - } + /* + * This sort must be stable. + */ + mergesort(dpv, n, sizeof(*dpv), opendir_compar); - if (dpv) { - struct dirent *xp; - - /* - * This sort must be stable. - */ - mergesort(dpv, n, sizeof(*dpv), alphasort); - - dpv[n] = NULL; - xp = NULL; - - /* - * Scan through the buffer in sort order, - * zapping the inode number of any - * duplicate names. - */ - for (n = 0; dpv[n]; n++) { - struct dirent *dp = dpv[n]; - - if ((xp == NULL) || - strcmp(dp->d_name, xp->d_name)) { - xp = dp; - } else { - dp->d_fileno = 0; - } - if (dp->d_type == DT_WHT && - (flags & DTF_HIDEW)) - dp->d_fileno = 0; + dpv[n] = NULL; + xp = NULL; + + /* + * Scan through the buffer in sort order, + * zapping the inode number of any + * duplicate names. + */ + for (n = 0; dpv[n]; n++) { + struct dirent *dp = dpv[n]; + + if ((xp == NULL) || + strcmp(dp->d_name, xp->d_name)) { + xp = dp; + } else { + dp->d_fileno = 0; } + if (dp->d_type == DT_WHT && + (dirp->dd_flags & DTF_HIDEW)) + dp->d_fileno = 0; + } - free(dpv); + free(dpv); + break; + } else { + dpv = malloc((n+1) * sizeof(struct dirent *)); + if (dpv == NULL) break; - } else { - dpv = malloc((n+1) * sizeof(struct dirent *)); - if (dpv == NULL) - break; - } } + } + + dirp->dd_len = len; + dirp->dd_size = ddptr - dirp->dd_buf; + return (true); +} + + +/* + * Common routine for opendir(3), __opendir2(3) and fdopendir(3). + */ +static DIR * +__opendir_common(int fd, int flags, bool use_current_pos) +{ + DIR *dirp; + int saved_errno; + int unionstack; - dirp->dd_len = len; - dirp->dd_size = ddptr - dirp->dd_buf; + if ((dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL) + return (NULL); + + dirp->dd_buf = NULL; + dirp->dd_fd = fd; + dirp->dd_flags = flags; + dirp->dd_loc = 0; + dirp->dd_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + dirp->dd_td = (struct _telldir *)((char *)dirp + sizeof(DIR)); + LIST_INIT(&dirp->dd_td->td_locq); + dirp->dd_td->td_loccnt = 0; + + /* + * Determine whether this directory is the top of a union stack. + */ + if ((flags & DTF_NODUP) && __kernel_supports_unionfs()) { + unionstack = __fd_is_on_union_mount(fd); + if (unionstack < 0) + goto fail; } else { - dirp->dd_len = incr; - dirp->dd_size = 0; + unionstack = 0; + } + + if (unionstack) { + if (!_filldir(dirp, use_current_pos)) + goto fail; + dirp->dd_flags |= __DTF_READALL; + } else { + /* + * Start with a small-ish size to avoid allocating full pages. + * readdir() will allocate a larger buffer if it didn't fit + * to stay fast for large directories. + */ + _Static_assert(GETDIRENTRIES64_EXTENDED_BUFSIZE <= READDIR_INITIAL_SIZE, + "Make sure we'll get extended metadata"); + dirp->dd_len = READDIR_INITIAL_SIZE; dirp->dd_buf = malloc(dirp->dd_len); if (dirp->dd_buf == NULL) goto fail; + if (use_current_pos) { + /* + * Read the first batch of directory entries + * to prime dd_seek. This also checks if the + * fd passed to fdopendir() is a directory. + */ #if __DARWIN_64_BIT_INO_T - dirp->dd_td->seekoff = 0; + /* + * sufficiently recent kernels when the buffer is large enough, + * will use the last bytes of the buffer to return status. + * + * To support older kernels: + * - make sure it's 0 initialized + * - make sure it's past `dd_size` before reading it + */ + getdirentries64_flags_t *gdeflags = + (getdirentries64_flags_t *)(dirp->dd_buf + dirp->dd_len - + sizeof(getdirentries64_flags_t)); + *gdeflags = 0; + dirp->dd_size = (long)__getdirentries64(dirp->dd_fd, + dirp->dd_buf, dirp->dd_len, &dirp->dd_td->seekoff); + if (dirp->dd_size >= 0 && + dirp->dd_size <= dirp->dd_len - sizeof(getdirentries64_flags_t)) { + if (*gdeflags & GETDIRENTRIES64_EOF) { + dirp->dd_flags |= __DTF_ATEND; + } + } +#else /* !__DARWIN_64_BIT_INO_T */ + dirp->dd_size = _getdirentries(dirp->dd_fd, + dirp->dd_buf, dirp->dd_len, &dirp->dd_seek); +#endif /* __DARWIN_64_BIT_INO_T */ + if (dirp->dd_size < 0) { + if (errno == EINVAL) + errno = ENOTDIR; + goto fail; + } + dirp->dd_flags |= __DTF_SKIPREAD; + } else { + dirp->dd_size = 0; +#if __DARWIN_64_BIT_INO_T + dirp->dd_td->seekoff = 0; #else /* !__DARWIN_64_BIT_INO_T */ - dirp->dd_seek = 0; + dirp->dd_seek = 0; #endif /* __DARWIN_64_BIT_INO_T */ - flags &= ~DTF_REWIND; + } } - dirp->dd_loc = 0; - dirp->dd_fd = fd; - dirp->dd_flags = flags; - dirp->dd_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; - - /* - * Set up seek point for rewinddir. - */ - dirp->dd_rewind = telldir(dirp); - return (dirp); fail: saved_errno = errno; + free(dirp->dd_buf); free(dirp); - (void)_close(fd); errno = saved_errno; return (NULL); }