xref: /netbsd-src/external/bsd/mdocml/dist/compat_fts.c (revision 9508192e445c6bc8463a56d16765781892ab889e)
1fec65c98Schristos #include "config.h"
2fec65c98Schristos 
3fec65c98Schristos #if HAVE_FTS
4fec65c98Schristos 
5fec65c98Schristos int dummy;
6fec65c98Schristos 
7fec65c98Schristos #else
8fec65c98Schristos 
9*9508192eSchristos /*	Id: compat_fts.c,v 1.14 2017/02/18 12:24:24 schwarze Exp 	*/
10*9508192eSchristos /*	$OpenBSD: fts.c,v 1.56 2016/09/21 04:38:56 guenther Exp $	*/
11fec65c98Schristos 
12fec65c98Schristos /*-
13fec65c98Schristos  * Copyright (c) 1990, 1993, 1994
14fec65c98Schristos  *	The Regents of the University of California.  All rights reserved.
15fec65c98Schristos  *
16fec65c98Schristos  * Redistribution and use in source and binary forms, with or without
17fec65c98Schristos  * modification, are permitted provided that the following conditions
18fec65c98Schristos  * are met:
19fec65c98Schristos  * 1. Redistributions of source code must retain the above copyright
20fec65c98Schristos  *    notice, this list of conditions and the following disclaimer.
21fec65c98Schristos  * 2. Redistributions in binary form must reproduce the above copyright
22fec65c98Schristos  *    notice, this list of conditions and the following disclaimer in the
23fec65c98Schristos  *    documentation and/or other materials provided with the distribution.
24fec65c98Schristos  * 3. Neither the name of the University nor the names of its contributors
25fec65c98Schristos  *    may be used to endorse or promote products derived from this software
26fec65c98Schristos  *    without specific prior written permission.
27fec65c98Schristos  *
28fec65c98Schristos  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29fec65c98Schristos  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30fec65c98Schristos  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31fec65c98Schristos  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32fec65c98Schristos  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33fec65c98Schristos  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34fec65c98Schristos  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35fec65c98Schristos  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36fec65c98Schristos  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37fec65c98Schristos  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38fec65c98Schristos  * SUCH DAMAGE.
39fec65c98Schristos  */
40fec65c98Schristos 
41fec65c98Schristos #include <sys/stat.h>
42fec65c98Schristos #include <sys/types.h>
43fec65c98Schristos 
44fec65c98Schristos #include <dirent.h>
45fec65c98Schristos #include <errno.h>
46fec65c98Schristos #include <fcntl.h>
47fec65c98Schristos #include <limits.h>
48fec65c98Schristos #include <stdlib.h>
49fec65c98Schristos #include <string.h>
50fec65c98Schristos #include <unistd.h>
51fec65c98Schristos #include "compat_fts.h"
52fec65c98Schristos 
53fec65c98Schristos #define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))
54fec65c98Schristos 
55fec65c98Schristos static FTSENT	*fts_alloc(FTS *, const char *, size_t);
56fec65c98Schristos static FTSENT	*fts_build(FTS *);
57fec65c98Schristos static void	 fts_lfree(FTSENT *);
58fec65c98Schristos static void	 fts_load(FTS *, FTSENT *);
59fec65c98Schristos static size_t	 fts_maxarglen(char * const *);
60fec65c98Schristos static void	 fts_padjust(FTS *, FTSENT *);
61fec65c98Schristos static int	 fts_palloc(FTS *, size_t);
62*9508192eSchristos static FTSENT	*fts_sort(FTS *, FTSENT *, int);
63fec65c98Schristos static unsigned short	 fts_stat(FTS *, FTSENT *);
64fec65c98Schristos 
65fec65c98Schristos #define	ISDOT(a)	(a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2])))
66fec65c98Schristos #ifndef	O_CLOEXEC
67fec65c98Schristos #define	O_CLOEXEC	0
68fec65c98Schristos #endif
69fec65c98Schristos 
70fec65c98Schristos #define	CLR(opt)	(sp->fts_options &= ~(opt))
71fec65c98Schristos #define	ISSET(opt)	(sp->fts_options & (opt))
72fec65c98Schristos #define	SET(opt)	(sp->fts_options |= (opt))
73fec65c98Schristos 
74fec65c98Schristos FTS *
fts_open(char * const * argv,int options,int (* compar)(const FTSENT **,const FTSENT **))75*9508192eSchristos fts_open(char * const *argv, int options,
76*9508192eSchristos     int (*compar)(const FTSENT **, const FTSENT **))
77fec65c98Schristos {
78fec65c98Schristos 	FTS *sp;
79fec65c98Schristos 	FTSENT *p, *root;
80fec65c98Schristos 	int nitems;
81*9508192eSchristos 	FTSENT *parent, *prev;
82fec65c98Schristos 
83fec65c98Schristos 	/* Options check. */
84fec65c98Schristos 	if (options & ~FTS_OPTIONMASK) {
85fec65c98Schristos 		errno = EINVAL;
86fec65c98Schristos 		return (NULL);
87fec65c98Schristos 	}
88fec65c98Schristos 
89*9508192eSchristos 	/* At least one path must be specified. */
90*9508192eSchristos 	if (*argv == NULL) {
91*9508192eSchristos 		errno = EINVAL;
92*9508192eSchristos 		return (NULL);
93*9508192eSchristos 	}
94*9508192eSchristos 
95fec65c98Schristos 	/* Allocate/initialize the stream */
96fec65c98Schristos 	if ((sp = calloc(1, sizeof(FTS))) == NULL)
97fec65c98Schristos 		return (NULL);
98*9508192eSchristos 	sp->fts_compar = compar;
99fec65c98Schristos 	sp->fts_options = options;
100fec65c98Schristos 
101fec65c98Schristos 	/*
102fec65c98Schristos 	 * Start out with 1K of path space, and enough, in any case,
103fec65c98Schristos 	 * to hold the user's paths.
104fec65c98Schristos 	 */
105fec65c98Schristos 	if (fts_palloc(sp, MAXIMUM(fts_maxarglen(argv), PATH_MAX)))
106fec65c98Schristos 		goto mem1;
107fec65c98Schristos 
108fec65c98Schristos 	/* Allocate/initialize root's parent. */
109fec65c98Schristos 	if ((parent = fts_alloc(sp, "", 0)) == NULL)
110fec65c98Schristos 		goto mem2;
111fec65c98Schristos 	parent->fts_level = FTS_ROOTPARENTLEVEL;
112fec65c98Schristos 
113fec65c98Schristos 	/* Allocate/initialize root(s). */
114*9508192eSchristos 	for (root = prev = NULL, nitems = 0; *argv; ++argv, ++nitems) {
115*9508192eSchristos 		if ((p = fts_alloc(sp, *argv, strlen(*argv))) == NULL)
116fec65c98Schristos 			goto mem3;
117fec65c98Schristos 		p->fts_level = FTS_ROOTLEVEL;
118fec65c98Schristos 		p->fts_parent = parent;
119fec65c98Schristos 		p->fts_accpath = p->fts_name;
120fec65c98Schristos 		p->fts_info = fts_stat(sp, p);
121fec65c98Schristos 
122fec65c98Schristos 		/* Command-line "." and ".." are real directories. */
123fec65c98Schristos 		if (p->fts_info == FTS_DOT)
124fec65c98Schristos 			p->fts_info = FTS_D;
125fec65c98Schristos 
126*9508192eSchristos 		/*
127*9508192eSchristos 		 * If comparison routine supplied, traverse in sorted
128*9508192eSchristos 		 * order; otherwise traverse in the order specified.
129*9508192eSchristos 		 */
130*9508192eSchristos 		if (compar) {
131*9508192eSchristos 			p->fts_link = root;
132*9508192eSchristos 			root = p;
133*9508192eSchristos 		} else {
134fec65c98Schristos 			p->fts_link = NULL;
135fec65c98Schristos 			if (root == NULL)
136*9508192eSchristos 				root = p;
137*9508192eSchristos 			else
138*9508192eSchristos 				prev->fts_link = p;
139*9508192eSchristos 			prev = p;
140fec65c98Schristos 		}
141fec65c98Schristos 	}
142*9508192eSchristos 	if (compar && nitems > 1)
143*9508192eSchristos 		root = fts_sort(sp, root, nitems);
144fec65c98Schristos 
145fec65c98Schristos 	/*
146fec65c98Schristos 	 * Allocate a dummy pointer and make fts_read think that we've just
147fec65c98Schristos 	 * finished the node before the root(s); set p->fts_info to FTS_INIT
148fec65c98Schristos 	 * so that everything about the "current" node is ignored.
149fec65c98Schristos 	 */
150fec65c98Schristos 	if ((sp->fts_cur = fts_alloc(sp, "", 0)) == NULL)
151fec65c98Schristos 		goto mem3;
152fec65c98Schristos 	sp->fts_cur->fts_link = root;
153fec65c98Schristos 	sp->fts_cur->fts_info = FTS_INIT;
154fec65c98Schristos 
155fec65c98Schristos 	if (nitems == 0)
156fec65c98Schristos 		free(parent);
157fec65c98Schristos 
158fec65c98Schristos 	return (sp);
159fec65c98Schristos 
160fec65c98Schristos mem3:	fts_lfree(root);
161fec65c98Schristos 	free(parent);
162fec65c98Schristos mem2:	free(sp->fts_path);
163fec65c98Schristos mem1:	free(sp);
164fec65c98Schristos 	return (NULL);
165fec65c98Schristos }
166fec65c98Schristos 
167fec65c98Schristos static void
fts_load(FTS * sp,FTSENT * p)168fec65c98Schristos fts_load(FTS *sp, FTSENT *p)
169fec65c98Schristos {
170fec65c98Schristos 	size_t len;
171fec65c98Schristos 	char *cp;
172fec65c98Schristos 
173fec65c98Schristos 	/*
174fec65c98Schristos 	 * Load the stream structure for the next traversal.  Since we don't
175fec65c98Schristos 	 * actually enter the directory until after the preorder visit, set
176fec65c98Schristos 	 * the fts_accpath field specially so the chdir gets done to the right
177fec65c98Schristos 	 * place and the user can access the first node.  From fts_open it's
178fec65c98Schristos 	 * known that the path will fit.
179fec65c98Schristos 	 */
180fec65c98Schristos 	len = p->fts_pathlen = p->fts_namelen;
181fec65c98Schristos 	memmove(sp->fts_path, p->fts_name, len + 1);
182fec65c98Schristos 	if ((cp = strrchr(p->fts_name, '/')) && (cp != p->fts_name || cp[1])) {
183fec65c98Schristos 		len = strlen(++cp);
184fec65c98Schristos 		memmove(p->fts_name, cp, len + 1);
185fec65c98Schristos 		p->fts_namelen = len;
186fec65c98Schristos 	}
187fec65c98Schristos 	p->fts_accpath = p->fts_path = sp->fts_path;
188fec65c98Schristos 	sp->fts_dev = p->fts_dev;
189fec65c98Schristos }
190fec65c98Schristos 
191fec65c98Schristos int
fts_close(FTS * sp)192fec65c98Schristos fts_close(FTS *sp)
193fec65c98Schristos {
194fec65c98Schristos 	FTSENT *freep, *p;
195fec65c98Schristos 
196fec65c98Schristos 	/*
197fec65c98Schristos 	 * This still works if we haven't read anything -- the dummy structure
198fec65c98Schristos 	 * points to the root list, so we step through to the end of the root
199fec65c98Schristos 	 * list which has a valid parent pointer.
200fec65c98Schristos 	 */
201fec65c98Schristos 	if (sp->fts_cur) {
202fec65c98Schristos 		for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) {
203fec65c98Schristos 			freep = p;
204fec65c98Schristos 			p = p->fts_link ? p->fts_link : p->fts_parent;
205fec65c98Schristos 			free(freep);
206fec65c98Schristos 		}
207fec65c98Schristos 		free(p);
208fec65c98Schristos 	}
209fec65c98Schristos 
210fec65c98Schristos 	/* Free up child linked list, sort array, path buffer, stream ptr.*/
211fec65c98Schristos 	if (sp->fts_child)
212fec65c98Schristos 		fts_lfree(sp->fts_child);
213*9508192eSchristos 	free(sp->fts_array);
214fec65c98Schristos 	free(sp->fts_path);
215fec65c98Schristos 	free(sp);
216fec65c98Schristos 
2179ff1f2acSchristos 	return (0);
218fec65c98Schristos }
219fec65c98Schristos 
220fec65c98Schristos /*
221fec65c98Schristos  * Special case of "/" at the end of the path so that slashes aren't
222fec65c98Schristos  * appended which would cause paths to be written as "....//foo".
223fec65c98Schristos  */
224fec65c98Schristos #define	NAPPEND(p)							\
225fec65c98Schristos 	(p->fts_path[p->fts_pathlen - 1] == '/'				\
226fec65c98Schristos 	    ? p->fts_pathlen - 1 : p->fts_pathlen)
227fec65c98Schristos 
228fec65c98Schristos FTSENT *
fts_read(FTS * sp)229fec65c98Schristos fts_read(FTS *sp)
230fec65c98Schristos {
231fec65c98Schristos 	FTSENT *p, *tmp;
232fec65c98Schristos 	int instr;
233fec65c98Schristos 	char *t;
234fec65c98Schristos 
235fec65c98Schristos 	/* If finished or unrecoverable error, return NULL. */
236fec65c98Schristos 	if (sp->fts_cur == NULL || ISSET(FTS_STOP))
237fec65c98Schristos 		return (NULL);
238fec65c98Schristos 
239fec65c98Schristos 	/* Set current node pointer. */
240fec65c98Schristos 	p = sp->fts_cur;
241fec65c98Schristos 
242fec65c98Schristos 	/* Save and zero out user instructions. */
243fec65c98Schristos 	instr = p->fts_instr;
244fec65c98Schristos 	p->fts_instr = FTS_NOINSTR;
245fec65c98Schristos 
246fec65c98Schristos 	/* Directory in pre-order. */
247fec65c98Schristos 	if (p->fts_info == FTS_D) {
248fec65c98Schristos 		/* If skipped or crossed mount point, do post-order visit. */
249fec65c98Schristos 		if (instr == FTS_SKIP ||
250fec65c98Schristos 		    (ISSET(FTS_XDEV) && p->fts_dev != sp->fts_dev)) {
251fec65c98Schristos 			if (sp->fts_child) {
252fec65c98Schristos 				fts_lfree(sp->fts_child);
253fec65c98Schristos 				sp->fts_child = NULL;
254fec65c98Schristos 			}
255fec65c98Schristos 			p->fts_info = FTS_DP;
256fec65c98Schristos 			return (p);
257fec65c98Schristos 		}
258fec65c98Schristos 
259fec65c98Schristos 		/*
260fec65c98Schristos 		 * If haven't read do so.  If the read fails, fts_build sets
261fec65c98Schristos 		 * FTS_STOP or the fts_info field of the node.
262fec65c98Schristos 		 */
263fec65c98Schristos 		if (sp->fts_child) {
2649ff1f2acSchristos 			/* nothing */
265fec65c98Schristos 		} else if ((sp->fts_child = fts_build(sp)) == NULL) {
266fec65c98Schristos 			if (ISSET(FTS_STOP))
267fec65c98Schristos 				return (NULL);
268fec65c98Schristos 			return (p);
269fec65c98Schristos 		}
270fec65c98Schristos 		p = sp->fts_child;
271fec65c98Schristos 		sp->fts_child = NULL;
272fec65c98Schristos 		goto name;
273fec65c98Schristos 	}
274fec65c98Schristos 
275fec65c98Schristos 	/* Move to the next node on this level. */
276fec65c98Schristos next:	tmp = p;
277fec65c98Schristos 	if ((p = p->fts_link)) {
278fec65c98Schristos 		free(tmp);
279fec65c98Schristos 
280fec65c98Schristos 		/*
281fec65c98Schristos 		 * If reached the top, return to the original directory (or
282fec65c98Schristos 		 * the root of the tree), and load the paths for the next root.
283fec65c98Schristos 		 */
284fec65c98Schristos 		if (p->fts_level == FTS_ROOTLEVEL) {
285fec65c98Schristos 			fts_load(sp, p);
286fec65c98Schristos 			return (sp->fts_cur = p);
287fec65c98Schristos 		}
288fec65c98Schristos 
289fec65c98Schristos 		/*
290fec65c98Schristos 		 * User may have called fts_set on the node.  If skipped,
291fec65c98Schristos 		 * ignore.  If followed, get a file descriptor so we can
292fec65c98Schristos 		 * get back if necessary.
293fec65c98Schristos 		 */
294fec65c98Schristos 		if (p->fts_instr == FTS_SKIP)
295fec65c98Schristos 			goto next;
296fec65c98Schristos 
297fec65c98Schristos name:		t = sp->fts_path + NAPPEND(p->fts_parent);
298fec65c98Schristos 		*t++ = '/';
299fec65c98Schristos 		memmove(t, p->fts_name, p->fts_namelen + 1);
300fec65c98Schristos 		return (sp->fts_cur = p);
301fec65c98Schristos 	}
302fec65c98Schristos 
303fec65c98Schristos 	/* Move up to the parent node. */
304fec65c98Schristos 	p = tmp->fts_parent;
305fec65c98Schristos 	free(tmp);
306fec65c98Schristos 
307fec65c98Schristos 	if (p->fts_level == FTS_ROOTPARENTLEVEL) {
308fec65c98Schristos 		/*
309fec65c98Schristos 		 * Done; free everything up and set errno to 0 so the user
310fec65c98Schristos 		 * can distinguish between error and EOF.
311fec65c98Schristos 		 */
312fec65c98Schristos 		free(p);
313fec65c98Schristos 		errno = 0;
314fec65c98Schristos 		return (sp->fts_cur = NULL);
315fec65c98Schristos 	}
316fec65c98Schristos 
317fec65c98Schristos 	/* NUL terminate the pathname. */
318fec65c98Schristos 	sp->fts_path[p->fts_pathlen] = '\0';
319fec65c98Schristos 
320fec65c98Schristos 	p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP;
321fec65c98Schristos 	return (sp->fts_cur = p);
322fec65c98Schristos }
323fec65c98Schristos 
324fec65c98Schristos /*
325fec65c98Schristos  * Fts_set takes the stream as an argument although it's not used in this
326fec65c98Schristos  * implementation; it would be necessary if anyone wanted to add global
327fec65c98Schristos  * semantics to fts using fts_set.  An error return is allowed for similar
328fec65c98Schristos  * reasons.
329fec65c98Schristos  */
330fec65c98Schristos int
fts_set(FTS * sp,FTSENT * p,int instr)331fec65c98Schristos fts_set(FTS *sp, FTSENT *p, int instr)
332fec65c98Schristos {
333fec65c98Schristos 	if (instr && instr != FTS_NOINSTR && instr != FTS_SKIP) {
334fec65c98Schristos 		errno = EINVAL;
335fec65c98Schristos 		return (1);
336fec65c98Schristos 	}
337fec65c98Schristos 	p->fts_instr = instr;
338fec65c98Schristos 	return (0);
339fec65c98Schristos }
340fec65c98Schristos 
341fec65c98Schristos /*
342fec65c98Schristos  * This is the tricky part -- do not casually change *anything* in here.  The
343fec65c98Schristos  * idea is to build the linked list of entries that are used by fts_children
344fec65c98Schristos  * and fts_read.  There are lots of special cases.
345fec65c98Schristos  *
346fec65c98Schristos  * The real slowdown in walking the tree is the stat calls.  If FTS_NOSTAT is
347fec65c98Schristos  * set and it's a physical walk (so that symbolic links can't be directories),
348fec65c98Schristos  * we can do things quickly.  First, if it's a 4.4BSD file system, the type
349fec65c98Schristos  * of the file is in the directory entry.  Otherwise, we assume that the number
350fec65c98Schristos  * of subdirectories in a node is equal to the number of links to the parent.
351fec65c98Schristos  * The former skips all stat calls.  The latter skips stat calls in any leaf
352fec65c98Schristos  * directories and for any files after the subdirectories in the directory have
353fec65c98Schristos  * been found, cutting the stat calls by about 2/3.
354fec65c98Schristos  */
355fec65c98Schristos static FTSENT *
fts_build(FTS * sp)356fec65c98Schristos fts_build(FTS *sp)
357fec65c98Schristos {
358fec65c98Schristos 	struct dirent *dp;
359fec65c98Schristos 	FTSENT *p, *head;
360fec65c98Schristos 	FTSENT *cur, *tail;
361fec65c98Schristos 	DIR *dirp;
362fec65c98Schristos 	void *oldaddr;
363fec65c98Schristos 	size_t dlen, len, maxlen;
3649ff1f2acSchristos 	int nitems, level, doadjust;
365fec65c98Schristos 	int saved_errno;
366fec65c98Schristos 	char *cp;
367fec65c98Schristos 
368fec65c98Schristos 	/* Set current node pointer. */
369fec65c98Schristos 	cur = sp->fts_cur;
370fec65c98Schristos 
371fec65c98Schristos 	/*
372fec65c98Schristos 	 * Open the directory for reading.  If this fails, we're done.
373fec65c98Schristos 	 * If being called from fts_read, set the fts_info field.
374fec65c98Schristos 	 */
375fec65c98Schristos 	if ((dirp = opendir(cur->fts_accpath)) == NULL) {
376fec65c98Schristos 		cur->fts_info = FTS_DNR;
377fec65c98Schristos 		cur->fts_errno = errno;
378fec65c98Schristos 		return (NULL);
379fec65c98Schristos 	}
380fec65c98Schristos 
381fec65c98Schristos 	/*
382fec65c98Schristos 	 * Figure out the max file name length that can be stored in the
383fec65c98Schristos 	 * current path -- the inner loop allocates more path as necessary.
384fec65c98Schristos 	 * We really wouldn't have to do the maxlen calculations here, we
385fec65c98Schristos 	 * could do them in fts_read before returning the path, but it's a
386fec65c98Schristos 	 * lot easier here since the length is part of the dirent structure.
387fec65c98Schristos 	 *
388fec65c98Schristos 	 * If not changing directories set a pointer so that can just append
389fec65c98Schristos 	 * each new name into the path.
390fec65c98Schristos 	 */
391fec65c98Schristos 	len = NAPPEND(cur);
392fec65c98Schristos 	cp = sp->fts_path + len;
393fec65c98Schristos 	*cp++ = '/';
394fec65c98Schristos 	len++;
395fec65c98Schristos 	maxlen = sp->fts_pathlen - len;
396fec65c98Schristos 
397fec65c98Schristos 	/*
398fec65c98Schristos 	 * fts_level is signed so we must prevent it from wrapping
399fec65c98Schristos 	 * around to FTS_ROOTLEVEL and FTS_ROOTPARENTLEVEL.
400fec65c98Schristos 	 */
401fec65c98Schristos 	level = cur->fts_level;
402fec65c98Schristos 	if (level < FTS_MAXLEVEL)
403fec65c98Schristos 	    level++;
404fec65c98Schristos 
405fec65c98Schristos 	/* Read the directory, attaching each entry to the `link' pointer. */
406fec65c98Schristos 	doadjust = 0;
407fec65c98Schristos 	for (head = tail = NULL, nitems = 0; dirp && (dp = readdir(dirp));) {
408fec65c98Schristos 		if (ISDOT(dp->d_name))
409fec65c98Schristos 			continue;
410fec65c98Schristos 
411fec65c98Schristos #if HAVE_DIRENT_NAMLEN
412fec65c98Schristos 		dlen = dp->d_namlen;
413fec65c98Schristos #else
414fec65c98Schristos 		dlen = strlen(dp->d_name);
415fec65c98Schristos #endif
416fec65c98Schristos 
417fec65c98Schristos 		if (!(p = fts_alloc(sp, dp->d_name, dlen)))
418fec65c98Schristos 			goto mem1;
419fec65c98Schristos 		if (dlen >= maxlen) {	/* include space for NUL */
420fec65c98Schristos 			oldaddr = sp->fts_path;
421fec65c98Schristos 			if (fts_palloc(sp, dlen + len + 1)) {
422fec65c98Schristos 				/*
423fec65c98Schristos 				 * No more memory for path or structures.  Save
424fec65c98Schristos 				 * errno, free up the current structure and the
425fec65c98Schristos 				 * structures already allocated.
426fec65c98Schristos 				 */
427fec65c98Schristos mem1:				saved_errno = errno;
428fec65c98Schristos 				free(p);
429fec65c98Schristos 				fts_lfree(head);
430fec65c98Schristos 				(void)closedir(dirp);
431fec65c98Schristos 				cur->fts_info = FTS_ERR;
432fec65c98Schristos 				SET(FTS_STOP);
433fec65c98Schristos 				errno = saved_errno;
434fec65c98Schristos 				return (NULL);
435fec65c98Schristos 			}
436fec65c98Schristos 			/* Did realloc() change the pointer? */
437fec65c98Schristos 			if (oldaddr != sp->fts_path) {
438fec65c98Schristos 				doadjust = 1;
439fec65c98Schristos 				cp = sp->fts_path + len;
440fec65c98Schristos 			}
441fec65c98Schristos 			maxlen = sp->fts_pathlen - len;
442fec65c98Schristos 		}
443fec65c98Schristos 
444fec65c98Schristos 		p->fts_level = level;
445fec65c98Schristos 		p->fts_parent = sp->fts_cur;
446fec65c98Schristos 		p->fts_pathlen = len + dlen;
447fec65c98Schristos 		if (p->fts_pathlen < len) {
448fec65c98Schristos 			/*
449fec65c98Schristos 			 * If we wrap, free up the current structure and
450fec65c98Schristos 			 * the structures already allocated, then error
451fec65c98Schristos 			 * out with ENAMETOOLONG.
452fec65c98Schristos 			 */
453fec65c98Schristos 			free(p);
454fec65c98Schristos 			fts_lfree(head);
455fec65c98Schristos 			(void)closedir(dirp);
456fec65c98Schristos 			cur->fts_info = FTS_ERR;
457fec65c98Schristos 			SET(FTS_STOP);
458fec65c98Schristos 			errno = ENAMETOOLONG;
459fec65c98Schristos 			return (NULL);
460fec65c98Schristos 		}
461fec65c98Schristos 
462fec65c98Schristos 		/* Build a file name for fts_stat to stat. */
463fec65c98Schristos 		p->fts_accpath = p->fts_path;
464fec65c98Schristos 		memmove(cp, p->fts_name, p->fts_namelen + 1);
465fec65c98Schristos 		/* Stat it. */
466fec65c98Schristos 		p->fts_info = fts_stat(sp, p);
467fec65c98Schristos 
468fec65c98Schristos 		/* We walk in directory order so "ls -f" doesn't get upset. */
469fec65c98Schristos 		p->fts_link = NULL;
470fec65c98Schristos 		if (head == NULL)
471fec65c98Schristos 			head = tail = p;
472fec65c98Schristos 		else {
473fec65c98Schristos 			tail->fts_link = p;
474fec65c98Schristos 			tail = p;
475fec65c98Schristos 		}
476fec65c98Schristos 		++nitems;
477fec65c98Schristos 	}
478fec65c98Schristos 	if (dirp)
479fec65c98Schristos 		(void)closedir(dirp);
480fec65c98Schristos 
481fec65c98Schristos 	/*
482fec65c98Schristos 	 * If realloc() changed the address of the path, adjust the
483fec65c98Schristos 	 * addresses for the rest of the tree and the dir list.
484fec65c98Schristos 	 */
485fec65c98Schristos 	if (doadjust)
486fec65c98Schristos 		fts_padjust(sp, head);
487fec65c98Schristos 
488fec65c98Schristos 	/*
489fec65c98Schristos 	 * If not changing directories, reset the path back to original
490fec65c98Schristos 	 * state.
491fec65c98Schristos 	 */
492fec65c98Schristos 	if (len == sp->fts_pathlen || nitems == 0)
493fec65c98Schristos 		--cp;
494fec65c98Schristos 	*cp = '\0';
495fec65c98Schristos 
496fec65c98Schristos 	/* If didn't find anything, return NULL. */
497fec65c98Schristos 	if (!nitems) {
498fec65c98Schristos 		cur->fts_info = FTS_DP;
499fec65c98Schristos 		return (NULL);
500fec65c98Schristos 	}
501*9508192eSchristos 
502*9508192eSchristos 	/* Sort the entries. */
503*9508192eSchristos 	if (sp->fts_compar && nitems > 1)
504*9508192eSchristos 		head = fts_sort(sp, head, nitems);
505fec65c98Schristos 	return (head);
506fec65c98Schristos }
507fec65c98Schristos 
508fec65c98Schristos static unsigned short
fts_stat(FTS * sp,FTSENT * p)509fec65c98Schristos fts_stat(FTS *sp, FTSENT *p)
510fec65c98Schristos {
511fec65c98Schristos 	FTSENT *t;
512fec65c98Schristos 	dev_t dev;
513fec65c98Schristos 	ino_t ino;
514fec65c98Schristos 	struct stat *sbp;
515fec65c98Schristos 
516fec65c98Schristos 	/* If user needs stat info, stat buffer already allocated. */
517fec65c98Schristos 	sbp = p->fts_statp;
518fec65c98Schristos 
519fec65c98Schristos 	if (lstat(p->fts_accpath, sbp)) {
520fec65c98Schristos 		p->fts_errno = errno;
521fec65c98Schristos 		memset(sbp, 0, sizeof(struct stat));
522fec65c98Schristos 		return (FTS_NS);
523fec65c98Schristos 	}
524fec65c98Schristos 
525fec65c98Schristos 	if (S_ISDIR(sbp->st_mode)) {
526fec65c98Schristos 		/*
527fec65c98Schristos 		 * Set the device/inode.  Used to find cycles and check for
528fec65c98Schristos 		 * crossing mount points.  Also remember the link count, used
529fec65c98Schristos 		 * in fts_build to limit the number of stat calls.  It is
530fec65c98Schristos 		 * understood that these fields are only referenced if fts_info
531fec65c98Schristos 		 * is set to FTS_D.
532fec65c98Schristos 		 */
533fec65c98Schristos 		dev = p->fts_dev = sbp->st_dev;
534fec65c98Schristos 		ino = p->fts_ino = sbp->st_ino;
535fec65c98Schristos 		p->fts_nlink = sbp->st_nlink;
536fec65c98Schristos 
537fec65c98Schristos 		if (ISDOT(p->fts_name))
538fec65c98Schristos 			return (FTS_DOT);
539fec65c98Schristos 
540fec65c98Schristos 		/*
541fec65c98Schristos 		 * Cycle detection is done by brute force when the directory
542fec65c98Schristos 		 * is first encountered.  If the tree gets deep enough or the
543fec65c98Schristos 		 * number of symbolic links to directories is high enough,
544fec65c98Schristos 		 * something faster might be worthwhile.
545fec65c98Schristos 		 */
546fec65c98Schristos 		for (t = p->fts_parent;
547fec65c98Schristos 		    t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent)
548fec65c98Schristos 			if (ino == t->fts_ino && dev == t->fts_dev) {
549fec65c98Schristos 				p->fts_cycle = t;
550fec65c98Schristos 				return (FTS_DC);
551fec65c98Schristos 			}
552fec65c98Schristos 		return (FTS_D);
553fec65c98Schristos 	}
554fec65c98Schristos 	if (S_ISLNK(sbp->st_mode))
555fec65c98Schristos 		return (FTS_SL);
556fec65c98Schristos 	if (S_ISREG(sbp->st_mode))
557fec65c98Schristos 		return (FTS_F);
558fec65c98Schristos 	return (FTS_DEFAULT);
559fec65c98Schristos }
560fec65c98Schristos 
561fec65c98Schristos static FTSENT *
fts_sort(FTS * sp,FTSENT * head,int nitems)562*9508192eSchristos fts_sort(FTS *sp, FTSENT *head, int nitems)
563*9508192eSchristos {
564*9508192eSchristos 	FTSENT **ap, *p;
565*9508192eSchristos 
566*9508192eSchristos 	/*
567*9508192eSchristos 	 * Construct an array of pointers to the structures and call qsort(3).
568*9508192eSchristos 	 * Reassemble the array in the order returned by qsort.  If unable to
569*9508192eSchristos 	 * sort for memory reasons, return the directory entries in their
570*9508192eSchristos 	 * current order.  Allocate enough space for the current needs plus
571*9508192eSchristos 	 * 40 so don't realloc one entry at a time.
572*9508192eSchristos 	 */
573*9508192eSchristos 	if (nitems > sp->fts_nitems) {
574*9508192eSchristos 		struct _ftsent **a;
575*9508192eSchristos 
576*9508192eSchristos 		sp->fts_nitems = nitems + 40;
577*9508192eSchristos 		if ((a = reallocarray(sp->fts_array,
578*9508192eSchristos 		    sp->fts_nitems, sizeof(FTSENT *))) == NULL) {
579*9508192eSchristos 			free(sp->fts_array);
580*9508192eSchristos 			sp->fts_array = NULL;
581*9508192eSchristos 			sp->fts_nitems = 0;
582*9508192eSchristos 			return (head);
583*9508192eSchristos 		}
584*9508192eSchristos 		sp->fts_array = a;
585*9508192eSchristos 	}
586*9508192eSchristos 	for (ap = sp->fts_array, p = head; p; p = p->fts_link)
587*9508192eSchristos 		*ap++ = p;
588*9508192eSchristos 	qsort(sp->fts_array, nitems, sizeof(FTSENT *), sp->fts_compar);
589*9508192eSchristos 	for (head = *(ap = sp->fts_array); --nitems; ++ap)
590*9508192eSchristos 		ap[0]->fts_link = ap[1];
591*9508192eSchristos 	ap[0]->fts_link = NULL;
592*9508192eSchristos 	return (head);
593*9508192eSchristos }
594*9508192eSchristos 
595*9508192eSchristos static FTSENT *
fts_alloc(FTS * sp,const char * name,size_t namelen)596fec65c98Schristos fts_alloc(FTS *sp, const char *name, size_t namelen)
597fec65c98Schristos {
598fec65c98Schristos 	FTSENT *p;
599fec65c98Schristos 	size_t len;
600fec65c98Schristos 
601fec65c98Schristos 	len = sizeof(FTSENT) + namelen;
602fec65c98Schristos 	if ((p = calloc(1, len)) == NULL)
603fec65c98Schristos 		return (NULL);
604fec65c98Schristos 
605fec65c98Schristos 	p->fts_path = sp->fts_path;
606fec65c98Schristos 	p->fts_namelen = namelen;
607fec65c98Schristos 	p->fts_instr = FTS_NOINSTR;
608fec65c98Schristos 	p->fts_statp = malloc(sizeof(struct stat));
609fec65c98Schristos 	if (p->fts_statp == NULL) {
610fec65c98Schristos 		free(p);
611fec65c98Schristos 		return (NULL);
612fec65c98Schristos 	}
613fec65c98Schristos 	memcpy(p->fts_name, name, namelen);
614fec65c98Schristos 
615fec65c98Schristos 	return (p);
616fec65c98Schristos }
617fec65c98Schristos 
618fec65c98Schristos static void
fts_lfree(FTSENT * head)619fec65c98Schristos fts_lfree(FTSENT *head)
620fec65c98Schristos {
621fec65c98Schristos 	FTSENT *p;
622fec65c98Schristos 
623fec65c98Schristos 	/* Free a linked list of structures. */
624fec65c98Schristos 	while ((p = head)) {
625fec65c98Schristos 		head = head->fts_link;
626fec65c98Schristos 		free(p);
627fec65c98Schristos 	}
628fec65c98Schristos }
629fec65c98Schristos 
630fec65c98Schristos /*
631fec65c98Schristos  * Allow essentially unlimited paths; find, rm, ls should all work on any tree.
632fec65c98Schristos  * Most systems will allow creation of paths much longer than PATH_MAX, even
633fec65c98Schristos  * though the kernel won't resolve them.  Add the size (not just what's needed)
634fec65c98Schristos  * plus 256 bytes so don't realloc the path 2 bytes at a time.
635fec65c98Schristos  */
636fec65c98Schristos static int
fts_palloc(FTS * sp,size_t more)637fec65c98Schristos fts_palloc(FTS *sp, size_t more)
638fec65c98Schristos {
639fec65c98Schristos 	char *p;
640fec65c98Schristos 
641fec65c98Schristos 	/*
642fec65c98Schristos 	 * Check for possible wraparound.
643fec65c98Schristos 	 */
644fec65c98Schristos 	more += 256;
645fec65c98Schristos 	if (sp->fts_pathlen + more < sp->fts_pathlen) {
646fec65c98Schristos 		free(sp->fts_path);
647fec65c98Schristos 		sp->fts_path = NULL;
648fec65c98Schristos 		errno = ENAMETOOLONG;
649fec65c98Schristos 		return (1);
650fec65c98Schristos 	}
651fec65c98Schristos 	sp->fts_pathlen += more;
652fec65c98Schristos 	p = realloc(sp->fts_path, sp->fts_pathlen);
653fec65c98Schristos 	if (p == NULL) {
654fec65c98Schristos 		free(sp->fts_path);
655fec65c98Schristos 		sp->fts_path = NULL;
656fec65c98Schristos 		return (1);
657fec65c98Schristos 	}
658fec65c98Schristos 	sp->fts_path = p;
659fec65c98Schristos 	return (0);
660fec65c98Schristos }
661fec65c98Schristos 
662fec65c98Schristos /*
663fec65c98Schristos  * When the path is realloc'd, have to fix all of the pointers in structures
664fec65c98Schristos  * already returned.
665fec65c98Schristos  */
666fec65c98Schristos static void
fts_padjust(FTS * sp,FTSENT * head)667fec65c98Schristos fts_padjust(FTS *sp, FTSENT *head)
668fec65c98Schristos {
669fec65c98Schristos 	FTSENT *p;
670fec65c98Schristos 	char *addr = sp->fts_path;
671fec65c98Schristos 
672fec65c98Schristos #define	ADJUST(p) {							\
673fec65c98Schristos 	if ((p)->fts_accpath != (p)->fts_name) {			\
674fec65c98Schristos 		(p)->fts_accpath =					\
675fec65c98Schristos 		    (char *)addr + ((p)->fts_accpath - (p)->fts_path);	\
676fec65c98Schristos 	}								\
677fec65c98Schristos 	(p)->fts_path = addr;						\
678fec65c98Schristos }
679fec65c98Schristos 	/* Adjust the current set of children. */
680fec65c98Schristos 	for (p = sp->fts_child; p; p = p->fts_link)
681fec65c98Schristos 		ADJUST(p);
682fec65c98Schristos 
683fec65c98Schristos 	/* Adjust the rest of the tree, including the current level. */
684fec65c98Schristos 	for (p = head; p->fts_level >= FTS_ROOTLEVEL;) {
685fec65c98Schristos 		ADJUST(p);
686fec65c98Schristos 		p = p->fts_link ? p->fts_link : p->fts_parent;
687fec65c98Schristos 	}
688fec65c98Schristos }
689fec65c98Schristos 
690fec65c98Schristos static size_t
fts_maxarglen(char * const * argv)691fec65c98Schristos fts_maxarglen(char * const *argv)
692fec65c98Schristos {
693fec65c98Schristos 	size_t len, max;
694fec65c98Schristos 
695fec65c98Schristos 	for (max = 0; *argv; ++argv)
696fec65c98Schristos 		if ((len = strlen(*argv)) > max)
697fec65c98Schristos 			max = len;
698fec65c98Schristos 	return (max + 1);
699fec65c98Schristos }
700fec65c98Schristos 
701fec65c98Schristos #endif
702