diff options
author | jilles <jilles@FreeBSD.org> | 2013-02-24 20:53:32 +0000 |
---|---|---|
committer | jilles <jilles@FreeBSD.org> | 2013-02-24 20:53:32 +0000 |
commit | b4ce52f66cd37c76a07d049fbc0d62864c8c54a2 (patch) | |
tree | 950cbdd6a0d369f97bad1e978ed1573b7b32eff9 | |
parent | 60edaad227012ae225dd29f164d2612342298cfe (diff) | |
download | FreeBSD-src-b4ce52f66cd37c76a07d049fbc0d62864c8c54a2.zip FreeBSD-src-b4ce52f66cd37c76a07d049fbc0d62864c8c54a2.tar.gz |
libc/opendir: Improve behaviour of union uniquifier:
* Reopen the directory using openat(fd, ".", ...) instead of opening the
pathname again. This fixes a race condition where the meaning of the
pathname changes and allows a reopen with fdopendir().
* Always reopen the directory for union stacks, not only when DTF_REWIND
is passed. Applications should be able to fchdir(dirfd(dir)) and
*at(dirfd(dir), ...). DTF_REWIND now does nothing.
-rw-r--r-- | lib/libc/gen/opendir.c | 51 |
1 files changed, 26 insertions, 25 deletions
diff --git a/lib/libc/gen/opendir.c b/lib/libc/gen/opendir.c index 2d505fc..a9eb0af 100644 --- a/lib/libc/gen/opendir.c +++ b/lib/libc/gen/opendir.c @@ -49,7 +49,7 @@ __FBSDID("$FreeBSD$"); #include "gen-private.h" #include "telldir.h" -static DIR * __opendir_common(int, const char *, int); +static DIR * __opendir_common(int, int); /* * Open a directory. @@ -78,7 +78,7 @@ fdopendir(int fd) } if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) return (NULL); - return (__opendir_common(fd, NULL, DTF_HIDEW|DTF_NODUP)); + return (__opendir_common(fd, DTF_HIDEW|DTF_NODUP)); } DIR * @@ -92,7 +92,7 @@ __opendir2(const char *name, int flags) O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1) return (NULL); - dir = __opendir_common(fd, name, flags); + dir = __opendir_common(fd, flags); if (dir == NULL) { saved_errno = errno; _close(fd); @@ -113,7 +113,7 @@ opendir_compar(const void *p1, const void *p2) * Common routine for opendir(3), __opendir2(3) and fdopendir(3). */ static DIR * -__opendir_common(int fd, const char *name, int flags) +__opendir_common(int fd, int flags) { DIR *dirp; int incr; @@ -121,6 +121,8 @@ __opendir_common(int fd, const char *name, int flags) int unionstack; int fd2; + fd2 = -1; + if ((dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL) return (NULL); @@ -165,7 +167,22 @@ __opendir_common(int fd, const char *name, int flags) * 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 ((fd2 = _openat(fd, ".", O_RDONLY | O_CLOEXEC)) == -1) { + saved_errno = errno; + free(buf); + free(dirp); + errno = saved_errno; + return (NULL); + } do { /* @@ -181,7 +198,7 @@ __opendir_common(int fd, const char *name, int flags) ddptr = buf + (len - space); } - n = _getdirentries(fd, ddptr, space, &dirp->dd_seek); + n = _getdirentries(fd2, ddptr, space, &dirp->dd_seek); if (n > 0) { ddptr += n; space -= n; @@ -191,25 +208,8 @@ __opendir_common(int fd, const char *name, int flags) ddeptr = ddptr; flags |= __DTF_READALL; - /* - * 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) { - if ((fd2 = _open(name, O_RDONLY | O_DIRECTORY | - O_CLOEXEC)) == -1) { - saved_errno = errno; - free(buf); - free(dirp); - errno = saved_errno; - return (NULL); - } - (void)_dup2(fd2, fd); - _close(fd2); - } + _close(fd2); + fd2 = -1; /* * There is now a buffer full of (possibly) duplicate @@ -293,7 +293,6 @@ __opendir_common(int fd, const char *name, int flags) if (dirp->dd_buf == NULL) goto fail; dirp->dd_seek = 0; - flags &= ~DTF_REWIND; } dirp->dd_loc = 0; @@ -310,6 +309,8 @@ __opendir_common(int fd, const char *name, int flags) fail: saved_errno = errno; + if (fd2 != -1) + _close(fd2); free(dirp); errno = saved_errno; return (NULL); |