=================================================================== RCS file: /cvs/mandoc/compat_fts.c,v retrieving revision 1.2 retrieving revision 1.16 diff -u -p -r1.2 -r1.16 --- mandoc/compat_fts.c 2014/08/11 03:19:39 1.2 +++ mandoc/compat_fts.c 2020/06/14 23:08:35 1.16 @@ -1,12 +1,13 @@ #include "config.h" -#ifdef HAVE_FTS +#if HAVE_FTS int dummy; #else -/* $OpenBSD: compat_fts.c,v 1.2 2014/08/11 03:19:39 schwarze Exp $ */ +/* $Id: compat_fts.c,v 1.16 2020/06/14 23:08:35 schwarze Exp $ */ +/* $OpenBSD: compat_fts.c,v 1.16 2020/06/14 23:08:35 schwarze Exp $ */ /*- * Copyright (c) 1990, 1993, 1994 @@ -37,7 +38,6 @@ int dummy; * SUCH DAMAGE. */ -#include #include #include @@ -50,6 +50,8 @@ int dummy; #include #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 *); @@ -57,25 +59,28 @@ static void fts_load(FTS *, 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) { @@ -83,16 +88,23 @@ fts_open(char * const *argv, int options, void *dummy) 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. */ @@ -101,15 +113,9 @@ fts_open(char * const *argv, int options, void *dummy) 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; + for (root = prev = NULL, nitems = 0; *argv; ++argv, ++nitems) { + if ((p = fts_alloc(sp, *argv, strlen(*argv))) == NULL) goto mem3; - } - - if ((p = fts_alloc(sp, *argv, len)) == NULL) - goto mem3; p->fts_level = FTS_ROOTLEVEL; p->fts_parent = parent; p->fts_accpath = p->fts_name; @@ -119,14 +125,24 @@ fts_open(char * const *argv, int options, void *dummy) 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 @@ -138,16 +154,6 @@ fts_open(char * const *argv, int options, void *dummy) 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); @@ -188,7 +194,6 @@ int fts_close(FTS *sp) { FTSENT *freep, *p; - int rfd, error = 0; /* * This still works if we haven't read anything -- the dummy structure @@ -204,25 +209,14 @@ fts_close(FTS *sp) 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); } /* @@ -265,25 +259,11 @@ fts_read(FTS *sp) } /* - * 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); @@ -304,10 +284,6 @@ next: tmp = p; * 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); } @@ -343,23 +319,6 @@ name: t = sp->fts_path + NAPPEND(p->fts_parent); /* 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); } @@ -370,7 +329,6 @@ name: t = sp->fts_path + NAPPEND(p->fts_parent); * semantics to fts using fts_set. An error return is allowed for similar * reasons. */ -/* ARGSUSED */ int fts_set(FTS *sp, FTSENT *p, int instr) { @@ -405,7 +363,7 @@ fts_build(FTS *sp) 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; @@ -423,41 +381,6 @@ fts_build(FTS *sp) } /* - * 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. * We really wouldn't have to do the maxlen calculations here, we @@ -468,10 +391,8 @@ fts_build(FTS *sp) * 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; @@ -489,7 +410,7 @@ fts_build(FTS *sp) if (ISDOT(dp->d_name)) continue; -#ifdef HAVE_DIRENT_NAMLEN +#if HAVE_DIRENT_NAMLEN dlen = dp->d_namlen; #else dlen = strlen(dp->d_name); @@ -506,8 +427,7 @@ fts_build(FTS *sp) * structures already allocated. */ mem1: saved_errno = errno; - if (p) - free(p); + free(p); fts_lfree(head); (void)closedir(dirp); cur->fts_info = FTS_ERR; @@ -518,8 +438,7 @@ mem1: saved_errno = errno; /* 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; } @@ -542,38 +461,12 @@ mem1: saved_errno = errno; 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); + /* 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); - /* Decrement link count if applicable. */ - if (nlinks > 0 && (p->fts_info == FTS_D || - p->fts_info == FTS_DC || p->fts_info == FTS_DOT)) - --nlinks; - } - /* We walk in directory order so "ls -f" doesn't get upset. */ p->fts_link = NULL; if (head == NULL) @@ -598,32 +491,19 @@ mem1: saved_errno = errno; * 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 (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 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); } @@ -681,6 +561,41 @@ fts_stat(FTS *sp, FTSENT *p) } 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) { FTSENT *p; @@ -731,20 +646,19 @@ fts_palloc(FTS *sp, size_t more) */ 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); } @@ -786,40 +700,6 @@ fts_maxarglen(char * const *argv) if ((len = strlen(*argv)) > max) 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