diff options
-rw-r--r-- | INSTALL | 6 | ||||
-rw-r--r-- | LICENSE | 3 | ||||
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | Makefile.depend | 21 | ||||
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | compat_fts.c | 824 | ||||
-rw-r--r-- | compat_fts.h | 106 | ||||
-rwxr-xr-x | configure | 1 | ||||
-rw-r--r-- | mandocdb.c | 4 | ||||
-rw-r--r-- | test-fts.c | 42 |
10 files changed, 997 insertions, 22 deletions
@@ -57,10 +57,8 @@ problems, apropos(1) is fully usable with SQLite 3.7.5. Versions older than 3.7.5 may or may not work, they have not been tested. 1.2. The fts(3) directory traversion functions. -A compatibility version will be bundled for 1.13.2 but is not available -yet. If you want apropos(1) and makewhatis(8) but do not have fts(3), -please stay with mandoc 1.12.3 for now and upgrade first to 1.12.4, -then to 1.13.2 when these versionns are released. Be careful: the +If your system does not have them, the bundled compatibility version +will be used, so you need not worry in that case. But be careful: the glibc version of fts(3) is known to be broken on 32bit platforms, see <https://sourceware.org/bugzilla/show_bug.cgi?id=15838>. @@ -37,8 +37,9 @@ The following files included from outside sources are protected by other people's Copyright and are distributed under a 3-clause BSD license; see these individual files for details. +compat_fts.c, compat_fts.h, compat_getsubopt.c, compat_strcasestr.c, compat_strsep.c: -Copyright (c) 1990, 1993 The Regents of the University of California +Copyright (c) 1989,1990,1993,1994 The Regents of the University of California compat_fgetln.c: Copyright (c) 1998 The NetBSD Foundation, Inc. @@ -52,10 +52,8 @@ INSTALL_MAN = $(INSTALL_DATA) # --- user settings related to database support ------------------------ -# Building apropos(1) and makewhatis(8) requires both SQLite3 and fts(3). -# To avoid those dependencies, comment the following line. -# Be careful: the fts(3) implementation in glibc is broken on 32bit -# machines, see: https://sourceware.org/bugzilla/show_bug.cgi?id=15838 +# Building apropos(1) and makewhatis(8) requires SQLite3. +# To avoid that dependency, comment the following line. # BUILD_TARGETS += db-build @@ -135,6 +133,7 @@ SRCS = apropos.c \ cgi.c \ chars.c \ compat_fgetln.c \ + compat_fts.c \ compat_getsubopt.c \ compat_ohash.c \ compat_reallocarray.c \ @@ -201,6 +200,7 @@ DISTFILES = INSTALL \ att.in \ cgi.h.example \ chars.in \ + compat_fts.h \ compat_ohash.h \ config.h.post \ config.h.pre \ @@ -282,6 +282,7 @@ LIBMANDOC_OBJS = $(LIBMAN_OBJS) \ read.o COMPAT_OBJS = compat_fgetln.o \ + compat_fts.o \ compat_getsubopt.o \ compat_ohash.o \ compat_reallocarray.o \ diff --git a/Makefile.depend b/Makefile.depend index dc49310e..5f2e40fa 100644 --- a/Makefile.depend +++ b/Makefile.depend @@ -3,15 +3,16 @@ arch.o: arch.c config.h mdoc.h libmdoc.h arch.in att.o: att.c config.h mdoc.h libmdoc.h att.in cgi.o: cgi.c config.h mandoc.h mandoc_aux.h main.h manpath.h mansearch.h cgi.h chars.o: chars.c config.h mandoc.h mandoc_aux.h libmandoc.h chars.in -compat_fgetln.o: compat_fgetln.c config.h -compat_getsubopt.o: compat_getsubopt.c config.h -compat_ohash.o: compat_ohash.c config.h -compat_reallocarray.o: compat_reallocarray.c config.h -compat_sqlite3_errstr.o: compat_sqlite3_errstr.c config.h -compat_strcasestr.o: compat_strcasestr.c config.h -compat_strlcat.o: compat_strlcat.c config.h -compat_strlcpy.o: compat_strlcpy.c config.h -compat_strsep.o: compat_strsep.c config.h +compat_fgetln.o: compat_fgetln.c config.h +compat_fts.o: compat_fts.c config.h compat_fts.h +compat_getsubopt.o: compat_getsubopt.c config.h +compat_ohash.o: compat_ohash.c config.h +compat_reallocarray.o: compat_reallocarray.c config.h +compat_sqlite3_errstr.o: compat_sqlite3_errstr.c config.h +compat_strcasestr.o: compat_strcasestr.c config.h +compat_strlcat.o: compat_strlcat.c config.h +compat_strlcpy.o: compat_strlcpy.c config.h +compat_strsep.o: compat_strsep.c config.h demandoc.o: demandoc.c config.h man.h mdoc.h mandoc.h eqn.o: eqn.c config.h mandoc.h mandoc_aux.h libmandoc.h libroff.h eqn_html.o: eqn_html.c config.h mandoc.h out.h html.h @@ -27,7 +28,7 @@ man_term.o: man_term.c config.h mandoc.h mandoc_aux.h out.h man.h term.h main.h man_validate.o: man_validate.c config.h man.h mandoc.h mandoc_aux.h libman.h libmandoc.h mandoc.o: mandoc.c config.h mandoc.h mandoc_aux.h libmandoc.h mandoc_aux.o: mandoc_aux.c config.h mandoc.h mandoc_aux.h -mandocdb.o: mandocdb.c config.h mdoc.h man.h mandoc.h mandoc_aux.h manpath.h mansearch.h +mandocdb.o: mandocdb.c config.h compat_fts.h mdoc.h man.h mandoc.h mandoc_aux.h manpath.h mansearch.h manpage.o: manpage.c config.h manpath.h mansearch.h manpath.o: manpath.c config.h mandoc_aux.h manpath.h mansearch.o: mansearch.c config.h mandoc.h mandoc_aux.h manpath.h mansearch.h @@ -7,9 +7,6 @@ Changes in version 1.13.1, released on August 10, 2014 --- MAJOR NEW FEATURES --- * A complete apropos(1)/makewhatis(8)/man.cgi(8) suite based on SQLite3 is now included. - CAVEAT: This also requires a working fts(3) implementation. - If your system lacks that *and* you want apropos(1)/makewhatis(8), - stay with 1.12.3 for now, then go to 1.12.4 and 1.13.2. * The roff(7) parser now provides an almost complete implementation of numerical expressions. * Warning and error messages have been improved in many ways. diff --git a/compat_fts.c b/compat_fts.c new file mode 100644 index 00000000..91e9d60c --- /dev/null +++ b/compat_fts.c @@ -0,0 +1,824 @@ +#include "config.h" + +#ifdef HAVE_FTS + +int dummy; + +#else + +/* $OpenBSD: fts.c,v 1.46 2014/05/25 17:47:04 tedu Exp $ */ + +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "compat_fts.h" + +static FTSENT *fts_alloc(FTS *, const char *, size_t); +static FTSENT *fts_build(FTS *); +static void fts_lfree(FTSENT *); +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 unsigned short fts_stat(FTS *, FTSENT *); +static int fts_safe_changedir(FTS *, FTSENT *, int, const char *); + +#define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2]))) + +#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 *sp; + FTSENT *p, *root; + int nitems; + FTSENT *parent, *tmp; + size_t len; + + /* Options check. */ + if (options & ~FTS_OPTIONMASK) { + errno = EINVAL; + return (NULL); + } + + /* Allocate/initialize the stream */ + if ((sp = calloc(1, sizeof(FTS))) == NULL) + return (NULL); + 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))) + goto mem1; + + /* Allocate/initialize root's parent. */ + if ((parent = fts_alloc(sp, "", 0)) == NULL) + goto mem2; + 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) + goto mem3; + p->fts_level = FTS_ROOTLEVEL; + p->fts_parent = parent; + p->fts_accpath = p->fts_name; + p->fts_info = fts_stat(sp, p); + + /* Command-line "." and ".." are real directories. */ + 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; + } + } + + /* + * Allocate a dummy pointer and make fts_read think that we've just + * finished the node before the root(s); set p->fts_info to FTS_INIT + * so that everything about the "current" node is ignored. + */ + if ((sp->fts_cur = fts_alloc(sp, "", 0)) == NULL) + goto mem3; + 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); + + return (sp); + +mem3: fts_lfree(root); + free(parent); +mem2: free(sp->fts_path); +mem1: free(sp); + return (NULL); +} + +static void +fts_load(FTS *sp, FTSENT *p) +{ + size_t len; + char *cp; + + /* + * Load the stream structure for the next traversal. Since we don't + * actually enter the directory until after the preorder visit, set + * the fts_accpath field specially so the chdir gets done to the right + * place and the user can access the first node. From fts_open it's + * known that the path will fit. + */ + len = p->fts_pathlen = p->fts_namelen; + memmove(sp->fts_path, p->fts_name, len + 1); + if ((cp = strrchr(p->fts_name, '/')) && (cp != p->fts_name || cp[1])) { + len = strlen(++cp); + memmove(p->fts_name, cp, len + 1); + p->fts_namelen = len; + } + p->fts_accpath = p->fts_path = sp->fts_path; + sp->fts_dev = p->fts_dev; +} + +int +fts_close(FTS *sp) +{ + FTSENT *freep, *p; + int rfd, error = 0; + + /* + * This still works if we haven't read anything -- the dummy structure + * points to the root list, so we step through to the end of the root + * list which has a valid parent pointer. + */ + if (sp->fts_cur) { + for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) { + freep = p; + p = p->fts_link ? p->fts_link : p->fts_parent; + free(freep); + } + 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_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); +} + +/* + * Special case of "/" at the end of the path so that slashes aren't + * appended which would cause paths to be written as "....//foo". + */ +#define NAPPEND(p) \ + (p->fts_path[p->fts_pathlen - 1] == '/' \ + ? p->fts_pathlen - 1 : p->fts_pathlen) + +FTSENT * +fts_read(FTS *sp) +{ + FTSENT *p, *tmp; + int instr; + char *t; + + /* If finished or unrecoverable error, return NULL. */ + if (sp->fts_cur == NULL || ISSET(FTS_STOP)) + return (NULL); + + /* Set current node pointer. */ + p = sp->fts_cur; + + /* Save and zero out user instructions. */ + instr = p->fts_instr; + p->fts_instr = FTS_NOINSTR; + + /* Directory in pre-order. */ + if (p->fts_info == FTS_D) { + /* If skipped or crossed mount point, do post-order visit. */ + if (instr == FTS_SKIP || + (ISSET(FTS_XDEV) && p->fts_dev != sp->fts_dev)) { + if (sp->fts_child) { + fts_lfree(sp->fts_child); + sp->fts_child = NULL; + } + p->fts_info = FTS_DP; + return (p); + } + + /* + * 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; + } + } else if ((sp->fts_child = fts_build(sp)) == NULL) { + if (ISSET(FTS_STOP)) + return (NULL); + return (p); + } + p = sp->fts_child; + sp->fts_child = NULL; + goto name; + } + + /* Move to the next node on this level. */ +next: tmp = p; + if ((p = p->fts_link)) { + free(tmp); + + /* + * If reached the top, return to the original directory (or + * 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); + } + + /* + * User may have called fts_set on the node. If skipped, + * ignore. If followed, get a file descriptor so we can + * get back if necessary. + */ + if (p->fts_instr == FTS_SKIP) + goto next; + +name: t = sp->fts_path + NAPPEND(p->fts_parent); + *t++ = '/'; + memmove(t, p->fts_name, p->fts_namelen + 1); + return (sp->fts_cur = p); + } + + /* Move up to the parent node. */ + p = tmp->fts_parent; + free(tmp); + + if (p->fts_level == FTS_ROOTPARENTLEVEL) { + /* + * Done; free everything up and set errno to 0 so the user + * can distinguish between error and EOF. + */ + free(p); + errno = 0; + return (sp->fts_cur = NULL); + } + + /* 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); +} + +/* + * Fts_set takes the stream as an argument although it's not used in this + * implementation; it would be necessary if anyone wanted to add global + * semantics to fts using fts_set. An error return is allowed for similar + * reasons. + */ +/* ARGSUSED */ +int +fts_set(FTS *sp, FTSENT *p, int instr) +{ + if (instr && instr != FTS_NOINSTR && instr != FTS_SKIP) { + errno = EINVAL; + return (1); + } + p->fts_instr = instr; + return (0); +} + +/* + * This is the tricky part -- do not casually change *anything* in here. The + * idea is to build the linked list of entries that are used by fts_children + * and fts_read. There are lots of special cases. + * + * The real slowdown in walking the tree is the stat calls. If FTS_NOSTAT is + * set and it's a physical walk (so that symbolic links can't be directories), + * we can do things quickly. First, if it's a 4.4BSD file system, the type + * of the file is in the directory entry. Otherwise, we assume that the number + * of subdirectories in a node is equal to the number of links to the parent. + * The former skips all stat calls. The latter skips stat calls in any leaf + * directories and for any files after the subdirectories in the directory have + * been found, cutting the stat calls by about 2/3. + */ +static FTSENT * +fts_build(FTS *sp) +{ + struct dirent *dp; + FTSENT *p, *head; + FTSENT *cur, *tail; + DIR *dirp; + void *oldaddr; + size_t len, maxlen; + int nitems, cderrno, descend, level, nlinks, nostat, doadjust; + int saved_errno; + char *cp; + + /* Set current node pointer. */ + cur = sp->fts_cur; + + /* + * Open the directory for reading. If this fails, we're done. + * If being called from fts_read, set the fts_info field. + */ + if ((dirp = opendir(cur->fts_accpath)) == NULL) { + cur->fts_info = FTS_DNR; + cur->fts_errno = errno; + 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. + * We really wouldn't have to do the maxlen calculations here, we + * could do them in fts_read before returning the path, but it's a + * lot easier here since the length is part of the dirent structure. + * + * If not changing directories set a pointer so that can just append + * each new name into the path. + */ + len = NAPPEND(cur); + if (ISSET(FTS_NOCHDIR)) { + cp = sp->fts_path + len; + *cp++ = '/'; + } + len++; + maxlen = sp->fts_pathlen - len; + + /* + * fts_level is signed so we must prevent it from wrapping + * around to FTS_ROOTLEVEL and FTS_ROOTPARENTLEVEL. + */ + level = cur->fts_level; + if (level < FTS_MAXLEVEL) + level++; + + /* Read the directory, attaching each entry to the `link' pointer. */ + doadjust = 0; + for (head = tail = NULL, nitems = 0; dirp && (dp = readdir(dirp));) { + if (ISDOT(dp->d_name)) + continue; + + if (!(p = fts_alloc(sp, dp->d_name, (size_t)dp->d_namlen))) + goto mem1; + if (dp->d_namlen >= maxlen) { /* include space for NUL */ + oldaddr = sp->fts_path; + if (fts_palloc(sp, dp->d_namlen +len + 1)) { + /* + * No more memory for path or structures. Save + * errno, free up the current structure and the + * structures already allocated. + */ +mem1: saved_errno = errno; + if (p) + free(p); + fts_lfree(head); + (void)closedir(dirp); + cur->fts_info = FTS_ERR; + SET(FTS_STOP); + errno = saved_errno; + return (NULL); + } + /* Did realloc() change the pointer? */ + if (oldaddr != sp->fts_path) { + doadjust = 1; + if (ISSET(FTS_NOCHDIR)) + cp = sp->fts_path + len; + } + maxlen = sp->fts_pathlen - len; + } + + p->fts_level = level; + p->fts_parent = sp->fts_cur; + p->fts_pathlen = len + dp->d_namlen; + if (p->fts_pathlen < len) { + /* + * If we wrap, free up the current structure and + * the structures already allocated, then error + * out with ENAMETOOLONG. + */ + free(p); + fts_lfree(head); + (void)closedir(dirp); + cur->fts_info = FTS_ERR; + SET(FTS_STOP); + errno = ENAMETOOLONG; + 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; + } + + /* We walk in directory order so "ls -f" doesn't get upset. */ + p->fts_link = NULL; + if (head == NULL) + head = tail = p; + else { + tail->fts_link = p; + tail = p; + } + ++nitems; + } + if (dirp) + (void)closedir(dirp); + + /* + * If realloc() changed the address of the path, adjust the + * addresses for the rest of the tree and the dir list. + */ + if (doadjust) + fts_padjust(sp, head); + + /* + * 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 didn't find anything, return NULL. */ + if (!nitems) { + cur->fts_info = FTS_DP; + return (NULL); + } + return (head); +} + +static unsigned short +fts_stat(FTS *sp, FTSENT *p) +{ + FTSENT *t; + dev_t dev; + ino_t ino; + struct stat *sbp; + + /* If user needs stat info, stat buffer already allocated. */ + sbp = p->fts_statp; + + if (lstat(p->fts_accpath, sbp)) { + p->fts_errno = errno; + memset(sbp, 0, sizeof(struct stat)); + return (FTS_NS); + } + + if (S_ISDIR(sbp->st_mode)) { + /* + * Set the device/inode. Used to find cycles and check for + * crossing mount points. Also remember the link count, used + * in fts_build to limit the number of stat calls. It is + * understood that these fields are only referenced if fts_info + * is set to FTS_D. + */ + dev = p->fts_dev = sbp->st_dev; + ino = p->fts_ino = sbp->st_ino; + p->fts_nlink = sbp->st_nlink; + + if (ISDOT(p->fts_name)) + return (FTS_DOT); + + /* + * Cycle detection is done by brute force when the directory + * is first encountered. If the tree gets deep enough or the + * number of symbolic links to directories is high enough, + * something faster might be worthwhile. + */ + for (t = p->fts_parent; + t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent) + if (ino == t->fts_ino && dev == t->fts_dev) { + p->fts_cycle = t; + return (FTS_DC); + } + return (FTS_D); + } + if (S_ISLNK(sbp->st_mode)) + return (FTS_SL); + if (S_ISREG(sbp->st_mode)) + return (FTS_F); + return (FTS_DEFAULT); +} + +static FTSENT * +fts_alloc(FTS *sp, const char *name, size_t namelen) +{ + FTSENT *p; + size_t len; + + /* + * The file name is a variable length array and no stat structure is + * necessary if the user has set the nostat bit. Allocate the FTSENT + * structure, the file name and the stat structure in one chunk, but + * be careful that the stat structure is reasonably aligned. Since the + * fts_name field is declared to be of size 1, the fts_name pointer is + * namelen + 2 before the first possible address of the stat structure. + */ + len = sizeof(FTSENT) + namelen; + len += sizeof(struct stat) + ALIGNBYTES; + if ((p = calloc(1, len)) == NULL) + return (NULL); + + p->fts_path = sp->fts_path; + p->fts_namelen = namelen; + p->fts_instr = FTS_NOINSTR; + p->fts_statp = (struct stat *)ALIGN(p->fts_name + namelen + 2); + memcpy(p->fts_name, name, namelen); + + return (p); +} + +static void +fts_lfree(FTSENT *head) +{ + FTSENT *p; + + /* Free a linked list of structures. */ + while ((p = head)) { + head = head->fts_link; + free(p); + } +} + +/* + * Allow essentially unlimited paths; find, rm, ls should all work on any tree. + * Most systems will allow creation of paths much longer than PATH_MAX, even + * though the kernel won't resolve them. Add the size (not just what's needed) + * plus 256 bytes so don't realloc the path 2 bytes at a time. + */ +static int +fts_palloc(FTS *sp, size_t more) +{ + char *p; + + /* + * Check for possible wraparound. + */ + more += 256; + if (sp->fts_pathlen + more < sp->fts_pathlen) { + if (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); + if (p == NULL) { + if (sp->fts_path) + free(sp->fts_path); + sp->fts_path = NULL; + return (1); + } + sp->fts_path = p; + return (0); +} + +/* + * When the path is realloc'd, have to fix all of the pointers in structures + * already returned. + */ +static void +fts_padjust(FTS *sp, FTSENT *head) +{ + FTSENT *p; + char *addr = sp->fts_path; + +#define ADJUST(p) { \ + if ((p)->fts_accpath != (p)->fts_name) { \ + (p)->fts_accpath = \ + (char *)addr + ((p)->fts_accpath - (p)->fts_path); \ + } \ + (p)->fts_path = addr; \ +} + /* Adjust the current set of children. */ + for (p = sp->fts_child; p; p = p->fts_link) + ADJUST(p); + + /* Adjust the rest of the tree, including the current level. */ + for (p = head; p->fts_level >= FTS_ROOTLEVEL;) { + ADJUST(p); + p = p->fts_link ? p->fts_link : p->fts_parent; + } +} + +static size_t +fts_maxarglen(char * const *argv) +{ + size_t len, max; + + for (max = 0; *argv; ++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 diff --git a/compat_fts.h b/compat_fts.h new file mode 100644 index 00000000..ce7ee628 --- /dev/null +++ b/compat_fts.h @@ -0,0 +1,106 @@ +/* $OpenBSD: fts.h,v 1.14 2012/12/05 23:19:57 deraadt Exp $ */ +/* $NetBSD: fts.h,v 1.7 2012/03/01 16:18:51 hans Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)fts.h 8.3 (Berkeley) 8/14/94 + */ + +#ifndef _FTS_H_ +#define _FTS_H_ + +typedef struct { + struct _ftsent *fts_cur; /* current node */ + struct _ftsent *fts_child; /* linked list of children */ + dev_t fts_dev; /* starting device # */ + char *fts_path; /* path for this descent */ + int fts_rfd; /* fd for root */ + size_t fts_pathlen; /* sizeof(path) */ + +#define FTS_NOCHDIR 0x0004 /* don't change directories */ +#define FTS_PHYSICAL 0x0010 /* physical walk */ +#define FTS_XDEV 0x0040 /* don't cross devices */ +#define FTS_OPTIONMASK 0x00ff /* valid user option mask */ + +#define FTS_STOP 0x2000 /* (private) unrecoverable error */ + int fts_options; /* fts_open options, global flags */ +} FTS; + +typedef struct _ftsent { + struct _ftsent *fts_cycle; /* cycle node */ + struct _ftsent *fts_parent; /* parent directory */ + struct _ftsent *fts_link; /* next file in directory */ + char *fts_accpath; /* access path */ + char *fts_path; /* root path */ + int fts_errno; /* errno for this node */ + size_t fts_pathlen; /* strlen(fts_path) */ + size_t fts_namelen; /* strlen(fts_name) */ + + ino_t fts_ino; /* inode */ + dev_t fts_dev; /* device */ + nlink_t fts_nlink; /* link count */ + +#define FTS_ROOTPARENTLEVEL -1 +#define FTS_ROOTLEVEL 0 +#define FTS_MAXLEVEL 0x7fffffff + int fts_level; /* depth (-1 to N) */ + +#define FTS_D 1 /* preorder directory */ +#define FTS_DC 2 /* directory that causes cycles */ +#define FTS_DEFAULT 3 /* none of the above */ +#define FTS_DNR 4 /* unreadable directory */ +#define FTS_DOT 5 /* dot or dot-dot */ +#define FTS_DP 6 /* postorder directory */ +#define FTS_ERR 7 /* error; errno is set */ +#define FTS_F 8 /* regular file */ +#define FTS_INIT 9 /* initialized only */ +#define FTS_NS 10 /* stat(2) failed */ +#define FTS_NSOK 11 /* no stat(2) requested */ +#define FTS_SL 12 /* symbolic link */ + unsigned short fts_info; /* user flags for FTSENT structure */ + +#define FTS_DONTCHDIR 0x01 /* don't chdir .. to the parent */ + unsigned short fts_flags; /* private flags for FTSENT structure */ + +#define FTS_NOINSTR 3 /* no instructions */ +#define FTS_SKIP 4 /* discard node */ + unsigned short fts_instr; /* fts_set() instructions */ + + struct stat *fts_statp; /* stat(2) information */ + char fts_name[1]; /* file name */ +} FTSENT; + +__BEGIN_DECLS +int fts_close(FTS *); +FTS *fts_open(char * const *, int, void *); +FTSENT *fts_read(FTS *); +int fts_set(FTS *, FTSENT *, int); +__END_DECLS + +#endif /* !_FTS_H_ */ @@ -33,6 +33,7 @@ cat config.h.pre echo echo "#define VERSION \"${VERSION}\"" runtest fgetln FGETLN +runtest fts FTS runtest getsubopt GETSUBOPT runtest mmap MMAP runtest ohash OHASH "${DBLIB}" @@ -25,7 +25,11 @@ #include <ctype.h> #include <errno.h> #include <fcntl.h> +#ifdef HAVE_FTS #include <fts.h> +#else +#include "compat_fts.h" +#endif #include <getopt.h> #include <limits.h> #include <stddef.h> diff --git a/test-fts.c b/test-fts.c new file mode 100644 index 00000000..5c6fc2d0 --- /dev/null +++ b/test-fts.c @@ -0,0 +1,42 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <fts.h> +#include <stdio.h> + +int +main(void) +{ + const char *argv[2]; + FTS *ftsp; + FTSENT *entry; + + argv[0] = "."; + argv[1] = (char *)NULL; + + ftsp = fts_open((char * const *)argv, + FTS_PHYSICAL | FTS_NOCHDIR, NULL); + + if (ftsp == NULL) { + perror("fts_open"); + return(1); + } + + entry = fts_read(ftsp); + + if (entry == NULL) { + perror("fts_read"); + return(1); + } + + if (fts_set(ftsp, entry, FTS_SKIP) != 0) { + perror("fts_set"); + return(1); + } + + if (fts_close(NULL) != 0) { + perror("fts_close"); + return(1); + } + + return(0); +} |