-#include "config.h"
-
-#if HAVE_FTS
-
-int dummy;
-
-#else
-
-/* $Id: compat_fts.c,v 1.4 2014/08/17 20:45:59 schwarze Exp $ */
-/* $OpenBSD: fts.c,v 1.46 2014/05/25 17:47:04 tedu Exp $ */
+/* $Id: compat_fts.c,v 1.17 2020/06/15 01:37:14 schwarze Exp $ */
+/* $OpenBSD: fts.c,v 1.59 2019/06/28 13:32:41 deraadt Exp $ */
/*-
* Copyright (c) 1990, 1993, 1994
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
+#include "config.h"
-#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "compat_fts.h"
+#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
+
static FTSENT *fts_alloc(FTS *, const char *, size_t);
static FTSENT *fts_build(FTS *);
static void fts_lfree(FTSENT *);
static size_t fts_maxarglen(char * const *);
static void fts_padjust(FTS *, FTSENT *);
static int fts_palloc(FTS *, size_t);
+static FTSENT *fts_sort(FTS *, FTSENT *, int);
static unsigned short fts_stat(FTS *, FTSENT *);
-static int fts_safe_changedir(FTS *, FTSENT *, int, const char *);
+
+typedef int (*qsort_compar_proto)(const void *, const void *);
#define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2])))
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
#define CLR(opt) (sp->fts_options &= ~(opt))
#define ISSET(opt) (sp->fts_options & (opt))
#define SET(opt) (sp->fts_options |= (opt))
-#define FCHDIR(sp, fd) (!ISSET(FTS_NOCHDIR) && fchdir(fd))
-
FTS *
-fts_open(char * const *argv, int options, void *dummy)
+fts_open(char * const *argv, int options,
+ int (*compar)(const FTSENT **, const FTSENT **))
{
FTS *sp;
FTSENT *p, *root;
int nitems;
- FTSENT *parent, *tmp;
- size_t len;
+ FTSENT *parent, *prev;
/* Options check. */
if (options & ~FTS_OPTIONMASK) {
return (NULL);
}
+ /* At least one path must be specified. */
+ if (*argv == NULL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
/* Allocate/initialize the stream */
if ((sp = calloc(1, sizeof(FTS))) == NULL)
return (NULL);
+ sp->fts_compar = compar;
sp->fts_options = options;
/*
* Start out with 1K of path space, and enough, in any case,
* to hold the user's paths.
*/
- if (fts_palloc(sp, MAX(fts_maxarglen(argv), PATH_MAX)))
+ if (fts_palloc(sp, MAXIMUM(fts_maxarglen(argv), PATH_MAX)))
goto mem1;
/* Allocate/initialize root's parent. */
parent->fts_level = FTS_ROOTPARENTLEVEL;
/* Allocate/initialize root(s). */
- for (root = NULL, nitems = 0; *argv; ++argv, ++nitems) {
- /* Don't allow zero-length paths. */
- if ((len = strlen(*argv)) == 0) {
- errno = ENOENT;
- goto mem3;
- }
-
- if ((p = fts_alloc(sp, *argv, len)) == NULL)
+ for (root = prev = NULL, nitems = 0; *argv; ++argv, ++nitems) {
+ if ((p = fts_alloc(sp, *argv, strlen(*argv))) == NULL)
goto mem3;
p->fts_level = FTS_ROOTLEVEL;
p->fts_parent = parent;
if (p->fts_info == FTS_DOT)
p->fts_info = FTS_D;
- p->fts_link = NULL;
- if (root == NULL)
- tmp = root = p;
- else {
- tmp->fts_link = p;
- tmp = p;
+ /*
+ * If comparison routine supplied, traverse in sorted
+ * order; otherwise traverse in the order specified.
+ */
+ if (compar) {
+ p->fts_link = root;
+ root = p;
+ } else {
+ p->fts_link = NULL;
+ if (root == NULL)
+ root = p;
+ else
+ prev->fts_link = p;
+ prev = p;
}
}
+ if (compar && nitems > 1)
+ root = fts_sort(sp, root, nitems);
/*
* Allocate a dummy pointer and make fts_read think that we've just
sp->fts_cur->fts_link = root;
sp->fts_cur->fts_info = FTS_INIT;
- /*
- * 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 paths,
- * 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 = open(".", O_RDONLY, 0)) < 0)
- SET(FTS_NOCHDIR);
-
if (nitems == 0)
free(parent);
fts_close(FTS *sp)
{
FTSENT *freep, *p;
- int rfd, error = 0;
/*
* This still works if we haven't read anything -- the dummy structure
free(p);
}
- /* Stash the original directory fd if needed. */
- rfd = ISSET(FTS_NOCHDIR) ? -1 : sp->fts_rfd;
-
/* Free up child linked list, sort array, path buffer, stream ptr.*/
if (sp->fts_child)
fts_lfree(sp->fts_child);
+ free(sp->fts_array);
free(sp->fts_path);
free(sp);
- /* Return to original directory, checking for error. */
- if (rfd != -1) {
- int saved_errno;
- error = fchdir(rfd);
- saved_errno = errno;
- (void)close(rfd);
- errno = saved_errno;
- }
-
- return (error);
+ return (0);
}
/*
}
/*
- * Cd to the subdirectory.
- *
- * If have already read and now fail to chdir, whack the list
- * to make the names come out right, and set the parent errno
- * so the application will eventually get an error condition.
- * Set the FTS_DONTCHDIR flag so that when we logically change
- * directories back to the parent we don't do a chdir.
- *
* If haven't read do so. If the read fails, fts_build sets
* FTS_STOP or the fts_info field of the node.
*/
if (sp->fts_child) {
- if (fts_safe_changedir(sp, p, -1, p->fts_accpath)) {
- p->fts_errno = errno;
- p->fts_flags |= FTS_DONTCHDIR;
- for (p = sp->fts_child; p; p = p->fts_link)
- p->fts_accpath =
- p->fts_parent->fts_accpath;
- }
+ /* nothing */
} else if ((sp->fts_child = fts_build(sp)) == NULL) {
if (ISSET(FTS_STOP))
return (NULL);
* the root of the tree), and load the paths for the next root.
*/
if (p->fts_level == FTS_ROOTLEVEL) {
- if (FCHDIR(sp, sp->fts_rfd)) {
- SET(FTS_STOP);
- return (NULL);
- }
fts_load(sp, p);
return (sp->fts_cur = p);
}
/* NUL terminate the pathname. */
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.
- */
- if (p->fts_level == FTS_ROOTLEVEL) {
- if (FCHDIR(sp, sp->fts_rfd)) {
- SET(FTS_STOP);
- sp->fts_cur = p;
- return (NULL);
- }
- } else if (!(p->fts_flags & FTS_DONTCHDIR) &&
- fts_safe_changedir(sp, p->fts_parent, -1, "..")) {
- SET(FTS_STOP);
- sp->fts_cur = p;
- return (NULL);
- }
p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP;
return (sp->fts_cur = p);
}
* semantics to fts using fts_set. An error return is allowed for similar
* reasons.
*/
-/* ARGSUSED */
int
fts_set(FTS *sp, FTSENT *p, int instr)
{
DIR *dirp;
void *oldaddr;
size_t dlen, len, maxlen;
- int nitems, cderrno, descend, level, nlinks, nostat, doadjust;
+ int nitems, level, doadjust;
int saved_errno;
char *cp;
return (NULL);
}
- /*
- * Nlinks is the number of possible entries of type directory in the
- * directory if we're cheating on stat calls, 0 if we're not doing
- * any stat calls at all, -1 if we're doing stats on everything.
- */
- nlinks = -1;
- nostat = 0;
-
- /*
- * If we're going to need to stat anything or we want to descend
- * and stay in the directory, chdir. If this fails we keep going,
- * but set a flag so we don't chdir after the post-order visit.
- * We won't be able to stat anything, but we can still return the
- * names themselves. Note, that since fts_read won't be able to
- * chdir into the directory, it will have to return different path
- * names than before, i.e. "a/b" instead of "b". Since the node
- * has already been visited in pre-order, have to wait until the
- * post-order visit to return the error. There is a special case
- * here, if there was nothing to stat then it's not an error to
- * not be able to stat. This is all fairly nasty. If a program
- * needed sorted entries or stat information, they had better be
- * checking FTS_NS on the returned nodes.
- */
- cderrno = 0;
- if (fts_safe_changedir(sp, cur, dirfd(dirp), NULL)) {
- if (nlinks)
- cur->fts_errno = errno;
- cur->fts_flags |= FTS_DONTCHDIR;
- descend = 0;
- cderrno = errno;
- (void)closedir(dirp);
- dirp = NULL;
- } else
- descend = 1;
-
/*
* Figure out the max file name length that can be stored in the
* current path -- the inner loop allocates more path as necessary.
* each new name into the path.
*/
len = NAPPEND(cur);
- if (ISSET(FTS_NOCHDIR)) {
- cp = sp->fts_path + len;
- *cp++ = '/';
- }
+ cp = sp->fts_path + len;
+ *cp++ = '/';
len++;
maxlen = sp->fts_pathlen - len;
* structures already allocated.
*/
mem1: saved_errno = errno;
- if (p)
- free(p);
+ free(p);
fts_lfree(head);
(void)closedir(dirp);
cur->fts_info = FTS_ERR;
/* Did realloc() change the pointer? */
if (oldaddr != sp->fts_path) {
doadjust = 1;
- if (ISSET(FTS_NOCHDIR))
- cp = sp->fts_path + len;
+ cp = sp->fts_path + len;
}
maxlen = sp->fts_pathlen - len;
}
return (NULL);
}
- if (cderrno) {
- if (nlinks) {
- p->fts_info = FTS_NS;
- p->fts_errno = cderrno;
- } else
- p->fts_info = FTS_NSOK;
- p->fts_accpath = cur->fts_accpath;
- } else if (nlinks == 0
-#ifdef DT_DIR
- || (nostat &&
- dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN)
-#endif
- ) {
- p->fts_accpath =
- ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name;
- p->fts_info = FTS_NSOK;
- } else {
- /* Build a file name for fts_stat to stat. */
- if (ISSET(FTS_NOCHDIR)) {
- p->fts_accpath = p->fts_path;
- memmove(cp, p->fts_name, p->fts_namelen + 1);
- } else
- p->fts_accpath = p->fts_name;
- /* Stat it. */
- p->fts_info = fts_stat(sp, p);
-
- /* Decrement link count if applicable. */
- if (nlinks > 0 && (p->fts_info == FTS_D ||
- p->fts_info == FTS_DC || p->fts_info == FTS_DOT))
- --nlinks;
- }
+ /* Build a file name for fts_stat to stat. */
+ p->fts_accpath = p->fts_path;
+ memmove(cp, p->fts_name, p->fts_namelen + 1);
+ /* Stat it. */
+ p->fts_info = fts_stat(sp, p);
/* We walk in directory order so "ls -f" doesn't get upset. */
p->fts_link = NULL;
* If not changing directories, reset the path back to original
* state.
*/
- if (ISSET(FTS_NOCHDIR)) {
- if (len == sp->fts_pathlen || nitems == 0)
- --cp;
- *cp = '\0';
- }
-
- /*
- * If descended after called from fts_children or after called from
- * fts_read and nothing found, get back. At the root level we use
- * the saved fd; if one of fts_open()'s arguments is a relative path
- * to an empty directory, we wind up here with no other way back. If
- * can't get back, we're done.
- */
- if (descend && !nitems &&
- (cur->fts_level == FTS_ROOTLEVEL ? FCHDIR(sp, sp->fts_rfd) :
- fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) {
- cur->fts_info = FTS_ERR;
- SET(FTS_STOP);
- return (NULL);
- }
+ if (len == sp->fts_pathlen || nitems == 0)
+ --cp;
+ *cp = '\0';
/* If didn't find anything, return NULL. */
if (!nitems) {
cur->fts_info = FTS_DP;
return (NULL);
}
+
+ /* Sort the entries. */
+ if (sp->fts_compar && nitems > 1)
+ head = fts_sort(sp, head, nitems);
return (head);
}
return (FTS_DEFAULT);
}
+static FTSENT *
+fts_sort(FTS *sp, FTSENT *head, int nitems)
+{
+ FTSENT **ap, *p;
+
+ /*
+ * Construct an array of pointers to the structures and call qsort(3).
+ * Reassemble the array in the order returned by qsort. If unable to
+ * sort for memory reasons, return the directory entries in their
+ * current order. Allocate enough space for the current needs plus
+ * 40 so don't realloc one entry at a time.
+ */
+ if (nitems > sp->fts_nitems) {
+ struct _ftsent **a;
+
+ if ((a = reallocarray(sp->fts_array,
+ nitems + 40, sizeof(FTSENT *))) == NULL) {
+ free(sp->fts_array);
+ sp->fts_array = NULL;
+ sp->fts_nitems = 0;
+ return (head);
+ }
+ sp->fts_nitems = nitems + 40;
+ sp->fts_array = a;
+ }
+ for (ap = sp->fts_array, p = head; p; p = p->fts_link)
+ *ap++ = p;
+ qsort(sp->fts_array, nitems, sizeof(FTSENT *),
+ (qsort_compar_proto)sp->fts_compar);
+ for (head = *(ap = sp->fts_array); --nitems; ++ap)
+ ap[0]->fts_link = ap[1];
+ ap[0]->fts_link = NULL;
+ return (head);
+}
+
static FTSENT *
fts_alloc(FTS *sp, const char *name, size_t namelen)
{
*/
more += 256;
if (sp->fts_pathlen + more < sp->fts_pathlen) {
- if (sp->fts_path)
- free(sp->fts_path);
+ free(sp->fts_path);
sp->fts_path = NULL;
errno = ENAMETOOLONG;
return (1);
}
- sp->fts_pathlen += more;
- p = realloc(sp->fts_path, sp->fts_pathlen);
+ p = recallocarray(sp->fts_path, sp->fts_pathlen,
+ sp->fts_pathlen + more, 1);
if (p == NULL) {
- if (sp->fts_path)
- free(sp->fts_path);
+ free(sp->fts_path);
sp->fts_path = NULL;
return (1);
}
+ sp->fts_pathlen += more;
sp->fts_path = p;
return (0);
}
max = len;
return (max + 1);
}
-
-/*
- * Change to dir specified by fd or p->fts_accpath without getting
- * tricked by someone changing the world out from underneath us.
- * Assumes p->fts_dev and p->fts_ino are filled in.
- */
-static int
-fts_safe_changedir(FTS *sp, FTSENT *p, int fd, const char *path)
-{
- int ret, oerrno, newfd;
- struct stat sb;
-
- newfd = fd;
- if (ISSET(FTS_NOCHDIR))
- return (0);
- if (fd < 0 && (newfd = open(path, O_RDONLY, 0)) < 0)
- return (-1);
- if (fstat(newfd, &sb)) {
- ret = -1;
- goto bail;
- }
- if (p->fts_dev != sb.st_dev || p->fts_ino != sb.st_ino) {
- errno = ENOENT; /* disinformation */
- ret = -1;
- goto bail;
- }
- ret = fchdir(newfd);
-bail:
- oerrno = errno;
- if (fd < 0)
- (void)close(newfd);
- errno = oerrno;
- return (ret);
-}
-
-#endif