mirror of
https://github.com/coreutils/coreutils.git
synced 2024-12-03 06:53:31 +08:00
Rewrite fts.c not to change the current working directory,
by using openat, fstatat, fdopendir, etc.. [! _LIBC]: Include "openat.h" and "unistd--.h". (HAVE_OPENAT_SUPPORT): Define. [_LIBC] (fchdir): Don't undef or define; no longer used. (FCHDIR): Define in terms of cwd_advance_fd rather than fchdir. Now, this `function' always succeeds, and consumes its file descriptor parameter -- so callers must not close such FDs. Update callers. (diropen_fd, opendirat, cwd_advance_fd): New functions. (diropen): Add parameter, SP. Adjust all callers. Implement using diropen_fd, rather than open. (fts_open): Initialize new member, fts_cwd_fd. Remove fts_rft-setting code. (fts_close): Close fts_cwd_fd, if necessary. (__opendir2): Define in terms of opendir or opendirat, depending on whether the FST_NOCHDIR flag is set. (fts_build): Since fts_safe_changedir consumes its FD, and since this code must do `closedir(dirp)', dup the dirfd(dirp) argument, and close the dup'd file descriptor upon failure. (fts_stat): Use fstatat(...AT_SYMLINK_NOFOLLOW) in place of lstat. (fts_safe_changedir): Tweak semantics to reflect that this function now calls cwd_advance_fd and hence consumes its FD argument.
This commit is contained in:
parent
739de8914f
commit
c1994c16c2
217
lib/fts.c
217
lib/fts.c
@ -72,8 +72,10 @@ static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94";
|
||||
#include <unistd.h>
|
||||
|
||||
#if ! _LIBC
|
||||
# include "lstat.h"
|
||||
# include "fcntl--.h"
|
||||
# include "lstat.h"
|
||||
# include "openat.h"
|
||||
# include "unistd--.h"
|
||||
#endif
|
||||
|
||||
#if defined _LIBC
|
||||
@ -103,8 +105,6 @@ static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94";
|
||||
# define close __close
|
||||
# undef closedir
|
||||
# define closedir __closedir
|
||||
# undef fchdir
|
||||
# define fchdir __fchdir
|
||||
# undef open
|
||||
# define open __open
|
||||
# undef opendir
|
||||
@ -130,6 +130,13 @@ static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94";
|
||||
# define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
|
||||
#endif
|
||||
|
||||
/* If this host provides the openat function, then we can avoid
|
||||
attempting to open "." in some initialization code below. */
|
||||
#ifdef HAVE_OPENAT
|
||||
# define HAVE_OPENAT_SUPPORT 1
|
||||
#else
|
||||
# define HAVE_OPENAT_SUPPORT 0
|
||||
#endif
|
||||
|
||||
static FTSENT *fts_alloc (FTS *, const char *, size_t) internal_function;
|
||||
static FTSENT *fts_build (FTS *, int) internal_function;
|
||||
@ -171,7 +178,9 @@ static void free_dir (FTS *fts) {}
|
||||
#define ISSET(opt) (sp->fts_options & (opt))
|
||||
#define SET(opt) (sp->fts_options |= (opt))
|
||||
|
||||
#define FCHDIR(sp, fd) (!ISSET(FTS_NOCHDIR) && fchdir(fd))
|
||||
#define FCHDIR(sp, fd) (!ISSET(FTS_NOCHDIR) \
|
||||
&& (cwd_advance_fd (sp, fd), 0))
|
||||
|
||||
|
||||
/* fts_build flags */
|
||||
#define BCHILD 1 /* fts_children */
|
||||
@ -196,15 +205,64 @@ bool fts_debug = false;
|
||||
} \
|
||||
while (false)
|
||||
|
||||
/* Open the directory DIR if possible, and return a file descriptor.
|
||||
As with openat-like functions, if DIR is a relative name,
|
||||
interpret it relative to the directory open on file descriptor FD.
|
||||
Return -1 and set errno on failure. */
|
||||
static int
|
||||
internal_function
|
||||
diropen_fd (int cwd_fd, char const *dir)
|
||||
{
|
||||
int fd = openat (cwd_fd, dir,
|
||||
O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK);
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* file-descriptor-relative opendir. */
|
||||
/* FIXME: if others need this function, move it into lib/openat.c */
|
||||
static inline DIR *
|
||||
internal_function
|
||||
opendirat (int fd, char const *dir)
|
||||
{
|
||||
int new_fd = openat (fd, dir, O_RDONLY);
|
||||
DIR *dirp;
|
||||
|
||||
if (new_fd < 0)
|
||||
return NULL;
|
||||
dirp = fdopendir (new_fd);
|
||||
if (dirp == NULL)
|
||||
{
|
||||
int saved_errno = errno;
|
||||
close (new_fd);
|
||||
errno = saved_errno;
|
||||
}
|
||||
return dirp;
|
||||
}
|
||||
|
||||
/* Virtual fchdir. Advance SP's working directory
|
||||
file descriptor, SP->fts_cwd_fd, to FD, and close
|
||||
the previous one, ignoring any error. */
|
||||
static void
|
||||
internal_function
|
||||
cwd_advance_fd (FTS *sp, int fd)
|
||||
{
|
||||
int old = sp->fts_cwd_fd;
|
||||
if (old == fd && old != AT_FDCWD)
|
||||
abort ();
|
||||
sp->fts_cwd_fd = fd;
|
||||
if (0 <= old)
|
||||
close (old); /* ignore any close failure */
|
||||
}
|
||||
|
||||
/* Open the directory DIR if possible, and return a file
|
||||
descriptor. Return -1 and set errno on failure. It doesn't matter
|
||||
whether the file descriptor has read or write access. */
|
||||
|
||||
static int
|
||||
static inline int
|
||||
internal_function
|
||||
diropen (char const *dir)
|
||||
diropen (FTS const *sp, char const *dir)
|
||||
{
|
||||
return open (dir, O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK);
|
||||
return diropen_fd (sp->fts_cwd_fd, dir);
|
||||
}
|
||||
|
||||
FTS *
|
||||
@ -235,6 +293,40 @@ fts_open (char * const *argv,
|
||||
if (ISSET(FTS_LOGICAL))
|
||||
SET(FTS_NOCHDIR);
|
||||
|
||||
/* Initialize fts_cwd_fd. */
|
||||
sp->fts_cwd_fd = AT_FDCWD;
|
||||
if ( ! ISSET(FTS_NOCHDIR) && ! HAVE_OPENAT_SUPPORT)
|
||||
{
|
||||
/* While it isn't technically necessary to open "." this
|
||||
early, doing it here saves us the trouble of ensuring
|
||||
later (where it'd be messier) that "." can in fact
|
||||
be opened. If not, revert to FTS_NOCHDIR mode. */
|
||||
int fd = open (".", O_RDONLY);
|
||||
if (fd < 0)
|
||||
{
|
||||
/* Even if `.' is unreadable, don't revert to FTS_NOCHDIR mode
|
||||
on systems like Linux+PROC_FS, where our openat emulation
|
||||
is good enough. Note: on a system that emulates
|
||||
openat via /proc, this technique can still fail, but
|
||||
only in extreme conditions, e.g., when the working
|
||||
directory cannot be saved (i.e. save_cwd fails) --
|
||||
and that happens only on Linux only when "." is unreadable
|
||||
and the CWD would be longer than PATH_MAX.
|
||||
FIXME: once Linux kernel openat support is well established,
|
||||
replace the above open call and this entire if/else block
|
||||
with the body of the if-block below. */
|
||||
if ( openat_needs_fchdir ())
|
||||
{
|
||||
SET(FTS_NOCHDIR);
|
||||
sp->fts_cwd_fd = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
close (fd);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Start out with 1K of file name space, and enough, in any case,
|
||||
* to hold the user's file names.
|
||||
@ -304,17 +396,6 @@ fts_open (char * const *argv,
|
||||
if (! setup_dir (sp))
|
||||
goto mem3;
|
||||
|
||||
/*
|
||||
* If using chdir(2), grab a file descriptor pointing to dot to ensure
|
||||
* that we can get back here; this could be avoided for some file names,
|
||||
* but almost certainly not worth the effort. Slashes, symbolic links,
|
||||
* and ".." are all fairly nasty problems. Note, if we can't get the
|
||||
* descriptor we run anyway, just more slowly.
|
||||
*/
|
||||
if (!ISSET(FTS_NOCHDIR)
|
||||
&& (sp->fts_rfd = diropen (".")) < 0)
|
||||
SET(FTS_NOCHDIR);
|
||||
|
||||
return (sp);
|
||||
|
||||
mem3: fts_lfree(root);
|
||||
@ -376,12 +457,8 @@ fts_close (FTS *sp)
|
||||
free(sp->fts_array);
|
||||
free(sp->fts_path);
|
||||
|
||||
/* Return to original directory, save errno if necessary. */
|
||||
if (!ISSET(FTS_NOCHDIR)) {
|
||||
if (fchdir(sp->fts_rfd))
|
||||
saved_errno = errno;
|
||||
(void)close(sp->fts_rfd);
|
||||
}
|
||||
if (0 <= sp->fts_cwd_fd)
|
||||
close (sp->fts_cwd_fd);
|
||||
|
||||
free_dir (sp);
|
||||
|
||||
@ -411,7 +488,6 @@ fts_read (register FTS *sp)
|
||||
register FTSENT *p, *tmp;
|
||||
register unsigned short int instr;
|
||||
register char *t;
|
||||
int saved_errno;
|
||||
|
||||
/* If finished or unrecoverable error, return NULL. */
|
||||
if (sp->fts_cur == NULL || ISSET(FTS_STOP))
|
||||
@ -442,7 +518,7 @@ fts_read (register FTS *sp)
|
||||
(p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) {
|
||||
p->fts_info = fts_stat(sp, p, true);
|
||||
if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
|
||||
if ((p->fts_symfd = diropen (".")) < 0) {
|
||||
if ((p->fts_symfd = diropen (sp, ".")) < 0) {
|
||||
p->fts_errno = errno;
|
||||
p->fts_info = FTS_ERR;
|
||||
} else
|
||||
@ -503,7 +579,6 @@ fts_read (register FTS *sp)
|
||||
subdirectory, tell the caller. */
|
||||
if (p->fts_errno)
|
||||
p->fts_info = FTS_ERR;
|
||||
/* FIXME: see if this should be in an else block */
|
||||
LEAVE_DIR (sp, p, "2");
|
||||
return (p);
|
||||
}
|
||||
@ -523,11 +598,7 @@ next: tmp = p;
|
||||
* root.
|
||||
*/
|
||||
if (p->fts_level == FTS_ROOTLEVEL) {
|
||||
if (FCHDIR(sp, sp->fts_rfd)) {
|
||||
SET(FTS_STOP);
|
||||
sp->fts_cur = p;
|
||||
return (NULL);
|
||||
}
|
||||
FCHDIR(sp, AT_FDCWD);
|
||||
fts_load(sp, p);
|
||||
goto check_for_dir;
|
||||
}
|
||||
@ -542,7 +613,7 @@ next: tmp = p;
|
||||
if (p->fts_instr == FTS_FOLLOW) {
|
||||
p->fts_info = fts_stat(sp, p, true);
|
||||
if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
|
||||
if ((p->fts_symfd = diropen (".")) < 0) {
|
||||
if ((p->fts_symfd = diropen (sp, ".")) < 0) {
|
||||
p->fts_errno = errno;
|
||||
p->fts_info = FTS_ERR;
|
||||
} else
|
||||
@ -586,23 +657,15 @@ check_for_dir:
|
||||
sp->fts_path[p->fts_pathlen] = '\0';
|
||||
|
||||
/*
|
||||
* Return to the parent directory. If at a root node or came through
|
||||
* a symlink, go back through the file descriptor. Otherwise, cd up
|
||||
* one directory.
|
||||
* Return to the parent directory. If at a root node, just set
|
||||
* sp->fts_cwd_fd to AT_FDCWD. If we came through a symlink,
|
||||
* go back through the file descriptor. Otherwise, move up
|
||||
* one level, via "..".
|
||||
*/
|
||||
if (p->fts_level == FTS_ROOTLEVEL) {
|
||||
if (FCHDIR(sp, sp->fts_rfd)) {
|
||||
p->fts_errno = errno;
|
||||
SET(FTS_STOP);
|
||||
}
|
||||
FCHDIR(sp, AT_FDCWD);
|
||||
} else if (p->fts_flags & FTS_SYMFOLLOW) {
|
||||
if (FCHDIR(sp, p->fts_symfd)) {
|
||||
saved_errno = errno;
|
||||
(void)close(p->fts_symfd);
|
||||
__set_errno (saved_errno);
|
||||
p->fts_errno = errno;
|
||||
SET(FTS_STOP);
|
||||
}
|
||||
FCHDIR(sp, p->fts_symfd);
|
||||
(void)close(p->fts_symfd);
|
||||
} else if (!(p->fts_flags & FTS_DONTCHDIR) &&
|
||||
fts_safe_changedir(sp, p->fts_parent, -1, "..")) {
|
||||
@ -692,14 +755,10 @@ fts_children (register FTS *sp, int instr)
|
||||
ISSET(FTS_NOCHDIR))
|
||||
return (sp->fts_child = fts_build(sp, instr));
|
||||
|
||||
if ((fd = diropen (".")) < 0)
|
||||
if ((fd = diropen (sp, ".")) < 0)
|
||||
return (sp->fts_child = NULL);
|
||||
sp->fts_child = fts_build(sp, instr);
|
||||
if (fchdir(fd)) {
|
||||
(void)close(fd);
|
||||
return (NULL);
|
||||
}
|
||||
(void)close(fd);
|
||||
cwd_advance_fd (sp, fd);
|
||||
return (sp->fts_child);
|
||||
}
|
||||
|
||||
@ -750,7 +809,10 @@ fts_build (register FTS *sp, int type)
|
||||
else
|
||||
oflag = DTF_HIDEW|DTF_NODUP|DTF_REWIND;
|
||||
#else
|
||||
# define __opendir2(file, flag) opendir(file)
|
||||
# define __opendir2(file, flag) \
|
||||
(ISSET(FTS_NOCHDIR) \
|
||||
? opendir(file) \
|
||||
: opendirat(sp->fts_cwd_fd, file))
|
||||
#endif
|
||||
if ((dirp = __opendir2(cur->fts_accpath, oflag)) == NULL) {
|
||||
if (type == BREAD) {
|
||||
@ -795,13 +857,16 @@ fts_build (register FTS *sp, int type)
|
||||
*/
|
||||
cderrno = 0;
|
||||
if (nlinks || type == BREAD) {
|
||||
if (fts_safe_changedir(sp, cur, dirfd(dirp), NULL)) {
|
||||
int dir_fd = dup (dirfd(dirp));
|
||||
if (dir_fd < 0 || fts_safe_changedir(sp, cur, dir_fd, NULL)) {
|
||||
if (nlinks && type == BREAD)
|
||||
cur->fts_errno = errno;
|
||||
cur->fts_flags |= FTS_DONTCHDIR;
|
||||
descend = false;
|
||||
cderrno = errno;
|
||||
closedir(dirp);
|
||||
if (0 <= dir_fd)
|
||||
close (dir_fd);
|
||||
dirp = NULL;
|
||||
} else
|
||||
descend = true;
|
||||
@ -962,7 +1027,7 @@ mem1: saved_errno = errno;
|
||||
*/
|
||||
if (descend && (type == BCHILD || !nitems) &&
|
||||
(cur->fts_level == FTS_ROOTLEVEL ?
|
||||
FCHDIR(sp, sp->fts_rfd) :
|
||||
FCHDIR(sp, AT_FDCWD) :
|
||||
fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) {
|
||||
cur->fts_info = FTS_ERR;
|
||||
SET(FTS_STOP);
|
||||
@ -1077,7 +1142,8 @@ fts_stat(FTS *sp, register FTSENT *p, bool follow)
|
||||
p->fts_errno = saved_errno;
|
||||
goto err;
|
||||
}
|
||||
} else if (lstat(p->fts_accpath, sbp)) {
|
||||
} else if (fstatat(sp->fts_cwd_fd, p->fts_accpath, sbp,
|
||||
AT_SYMLINK_NOFOLLOW)) {
|
||||
p->fts_errno = errno;
|
||||
err: memset(sbp, 0, sizeof(struct stat));
|
||||
return (FTS_NS);
|
||||
@ -1305,34 +1371,39 @@ fts_maxarglen (char * const *argv)
|
||||
* Change to dir specified by fd or file name without getting
|
||||
* tricked by someone changing the world out from underneath us.
|
||||
* Assumes p->fts_statp->st_dev and p->fts_statp->st_ino are filled in.
|
||||
* If FD is non-negative, expect it to be used after this function returns,
|
||||
* and to be closed eventually. So don't pass e.g., `dirfd(dirp)' and then
|
||||
* do closedir(dirp), because that would invalidate the saved FD.
|
||||
* Upon failure, close FD immediately and return nonzero.
|
||||
*/
|
||||
static int
|
||||
internal_function
|
||||
fts_safe_changedir (FTS *sp, FTSENT *p, int fd, char const *dir)
|
||||
{
|
||||
int ret, oerrno, newfd;
|
||||
struct stat sb;
|
||||
|
||||
newfd = fd;
|
||||
if (ISSET(FTS_NOCHDIR))
|
||||
int newfd = fd;
|
||||
if (ISSET(FTS_NOCHDIR)) {
|
||||
if (0 <= fd)
|
||||
close (fd);
|
||||
return (0);
|
||||
if (fd < 0 && (newfd = diropen (dir)) < 0)
|
||||
}
|
||||
if (fd < 0 && (newfd = diropen (sp, dir)) < 0)
|
||||
return (-1);
|
||||
if (fstat(newfd, &sb)) {
|
||||
ret = -1;
|
||||
goto bail;
|
||||
if (0 <= fd) {
|
||||
int saved_errno = errno;
|
||||
close (fd);
|
||||
errno = saved_errno;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (p->fts_statp->st_dev != sb.st_dev
|
||||
|| p->fts_statp->st_ino != sb.st_ino) {
|
||||
if (0 <= fd)
|
||||
close (fd);
|
||||
__set_errno (ENOENT); /* disinformation */
|
||||
ret = -1;
|
||||
goto bail;
|
||||
return -1;
|
||||
}
|
||||
ret = fchdir(newfd);
|
||||
bail:
|
||||
oerrno = errno;
|
||||
if (fd < 0)
|
||||
(void)close(newfd);
|
||||
__set_errno (oerrno);
|
||||
return (ret);
|
||||
cwd_advance_fd (sp, newfd);
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user