112657Skvn/*-
212657Skvn * Copyright (c) 1988, 1993, 1994
312657Skvn *	The Regents of the University of California.  All rights reserved.
412657Skvn *
512657Skvn * This code is derived from software contributed to Berkeley by
612657Skvn * David Hitz of Auspex Systems Inc.
712657Skvn *
812657Skvn * Redistribution and use in source and binary forms, with or without
912657Skvn * modification, are permitted provided that the following conditions
1012657Skvn * are met:
1112657Skvn * 1. Redistributions of source code must retain the above copyright
1212657Skvn *    notice, this list of conditions and the following disclaimer.
1312657Skvn * 2. Redistributions in binary form must reproduce the above copyright
1412657Skvn *    notice, this list of conditions and the following disclaimer in the
1512657Skvn *    documentation and/or other materials provided with the distribution.
1612657Skvn * 4. Neither the name of the University nor the names of its contributors
1712657Skvn *    may be used to endorse or promote products derived from this software
1812657Skvn *    without specific prior written permission.
1912657Skvn *
2012657Skvn * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2112657Skvn * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2212657Skvn * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2312657Skvn * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2412657Skvn * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2512968Siveresov * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2612968Siveresov * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2712657Skvn * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2812657Skvn * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2912657Skvn * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3012657Skvn * SUCH DAMAGE.
3112657Skvn */
3212657Skvn
3312657Skvn#if 0
3412657Skvn#ifndef lint
3512657Skvnstatic char const copyright[] =
3612657Skvn"@(#) Copyright (c) 1988, 1993, 1994\n\
3712657Skvn	The Regents of the University of California.  All rights reserved.\n";
3812657Skvn#endif /* not lint */
3912657Skvn
4012657Skvn#ifndef lint
4112657Skvnstatic char sccsid[] = "@(#)cp.c	8.2 (Berkeley) 4/1/94";
4212657Skvn#endif /* not lint */
4312657Skvn#endif
4412657Skvn#include <sys/cdefs.h>
4512657Skvn__FBSDID("$FreeBSD$");
4612657Skvn
4712657Skvn/*
4812657Skvn * Cp copies source files to target files.
4912657Skvn *
5012657Skvn * The global PATH_T structure "to" always contains the path to the
5112657Skvn * current target file.  Since fts(3) does not change directories,
5212657Skvn * this path can be either absolute or dot-relative.
5312657Skvn *
5412657Skvn * The basic algorithm is to initialize "to" and use fts(3) to traverse
5512657Skvn * the file hierarchy rooted in the argument list.  A trivial case is the
5612657Skvn * case of 'cp file1 file2'.  The more interesting case is the case of
5712968Siveresov * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the
5812968Siveresov * path (relative to the root of the traversal) is appended to dir (stored
5912995Siveresov * in "to") to form the final target path.
6012968Siveresov */
6112657Skvn
6212657Skvn#include <sys/types.h>
6312657Skvn#include <sys/stat.h>
6412657Skvn
6512657Skvn#include <err.h>
6612657Skvn#include <errno.h>
6712657Skvn#include <fts.h>
6812657Skvn#include <limits.h>
6912657Skvn#include <signal.h>
7012657Skvn#include <stdio.h>
7112657Skvn#include <stdlib.h>
7212657Skvn#include <string.h>
7312657Skvn#include <unistd.h>
7412657Skvn
7512657Skvn#include "extern.h"
7612657Skvn
7713264Siveresov#define	STRIP_TRAILING_SLASH(p) {					\
7812657Skvn        while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/')	\
7912657Skvn                *--(p).p_end = 0;					\
8012657Skvn}
8112657Skvn
8212657Skvnstatic char emptystring[] = "";
8312657Skvn
8412657SkvnPATH_T to = { to.p_path, emptystring, "" };
8512657Skvn
8612657Skvnint fflag, iflag, lflag, nflag, pflag, vflag;
8712657Skvnstatic int Rflag, rflag;
8812657Skvnvolatile sig_atomic_t info;
8912657Skvn
9012657Skvnenum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE };
9112657Skvn
9212968Siveresovstatic int copy(char *[], enum op, int);
9312657Skvnstatic int mastercmp(const FTSENT * const *, const FTSENT * const *);
9412968Siveresovstatic void siginfo(int __unused);
9512657Skvn
9612657Skvnint
9712657Skvnmain(int argc, char *argv[])
9812657Skvn{
9912657Skvn	struct stat to_stat, tmp_stat;
10012657Skvn	enum op type;
10112657Skvn	int Hflag, Lflag, ch, fts_options, r, have_trailing_slash;
10212657Skvn	char *target;
10312657Skvn
10412657Skvn	fts_options = FTS_NOCHDIR | FTS_PHYSICAL;
10512657Skvn	Hflag = Lflag = 0;
10612657Skvn	while ((ch = getopt(argc, argv, "HLPRafilnprvx")) != -1)
10712657Skvn		switch (ch) {
10812657Skvn		case 'H':
10912968Siveresov			Hflag = 1;
11012657Skvn			Lflag = 0;
11112657Skvn			break;
11212968Siveresov		case 'L':
11312657Skvn			Lflag = 1;
11412657Skvn			Hflag = 0;
11512657Skvn			break;
11612657Skvn		case 'P':
11712657Skvn			Hflag = Lflag = 0;
11812657Skvn			break;
11912657Skvn		case 'R':
12013175Siveresov			Rflag = 1;
12113175Siveresov			break;
12212657Skvn		case 'a':
12312657Skvn			pflag = 1;
12412657Skvn			Rflag = 1;
12512657Skvn			Hflag = Lflag = 0;
12612657Skvn			break;
12712657Skvn		case 'f':
12812657Skvn			fflag = 1;
12912657Skvn			iflag = nflag = 0;
13012657Skvn			break;
13112657Skvn		case 'i':
13212657Skvn			iflag = 1;
13312657Skvn			fflag = nflag = 0;
13412657Skvn			break;
13512657Skvn		case 'l':
13612657Skvn			lflag = 1;
13712657Skvn			break;
13812657Skvn		case 'n':
13912657Skvn			nflag = 1;
14012657Skvn			fflag = iflag = 0;
14112657Skvn			break;
14212657Skvn		case 'p':
14312657Skvn			pflag = 1;
14412657Skvn			break;
14512657Skvn		case 'r':
14612657Skvn			rflag = Lflag = 1;
14712657Skvn			Hflag = 0;
14812968Siveresov			break;
14912968Siveresov		case 'v':
15012657Skvn			vflag = 1;
15112657Skvn			break;
15212657Skvn		case 'x':
15312657Skvn			fts_options |= FTS_XDEV;
15412657Skvn			break;
15512657Skvn		default:
15612657Skvn			usage();
15712657Skvn			break;
15812657Skvn		}
15912657Skvn	argc -= optind;
16012657Skvn	argv += optind;
16112657Skvn
16212657Skvn	if (argc < 2)
16312657Skvn		usage();
16412657Skvn
16512968Siveresov	if (Rflag && rflag)
16612968Siveresov		errx(1, "the -R and -r options may not be specified together");
16712657Skvn	if (rflag)
16812657Skvn		Rflag = 1;
16912657Skvn	if (Rflag) {
17012968Siveresov		if (Hflag)
17112968Siveresov			fts_options |= FTS_COMFOLLOW;
17212657Skvn		if (Lflag) {
17312657Skvn			fts_options &= ~FTS_PHYSICAL;
17412657Skvn			fts_options |= FTS_LOGICAL;
17512968Siveresov		}
17612968Siveresov	} else {
17712968Siveresov		fts_options &= ~FTS_PHYSICAL;
17812657Skvn		fts_options |= FTS_LOGICAL | FTS_COMFOLLOW;
17912968Siveresov	}
18012657Skvn	(void)signal(SIGINFO, siginfo);
18112657Skvn
18212657Skvn	/* Save the target base in "to". */
18312657Skvn	target = argv[--argc];
18412657Skvn	if (strlcpy(to.p_path, target, sizeof(to.p_path)) >= sizeof(to.p_path))
18512657Skvn		errx(1, "%s: name too long", target);
18612657Skvn	to.p_end = to.p_path + strlen(to.p_path);
18712657Skvn        if (to.p_path == to.p_end) {
18812657Skvn		*to.p_end++ = '.';
18912657Skvn		*to.p_end = 0;
19012657Skvn	}
19112657Skvn	have_trailing_slash = (to.p_end[-1] == '/');
19212657Skvn	if (have_trailing_slash)
19312657Skvn		STRIP_TRAILING_SLASH(to);
19412657Skvn	to.target_end = to.p_end;
19512657Skvn
19612657Skvn	/* Set end of argument list for fts(3). */
19712657Skvn	argv[argc] = NULL;
19812657Skvn
19912657Skvn	/*
20012657Skvn	 * Cp has two distinct cases:
20112657Skvn	 *
20212968Siveresov	 * cp [-R] source target
20312968Siveresov	 * cp [-R] source1 ... sourceN directory
20412968Siveresov	 *
20512968Siveresov	 * In both cases, source can be either a file or a directory.
20612968Siveresov	 *
20712968Siveresov	 * In (1), the target becomes a copy of the source. That is, if the
20812968Siveresov	 * source is a file, the target will be a file, and likewise for
20912968Siveresov	 * directories.
21012657Skvn	 *
21112657Skvn	 * In (2), the real target is not directory, but "directory/source".
21212657Skvn	 */
21312657Skvn	r = stat(to.p_path, &to_stat);
21412657Skvn	if (r == -1 && errno != ENOENT)
21512657Skvn		err(1, "%s", to.p_path);
21612657Skvn	if (r == -1 || !S_ISDIR(to_stat.st_mode)) {
21712657Skvn		/*
21812968Siveresov		 * Case (1).  Target is not a directory.
21912657Skvn		 */
22012968Siveresov		if (argc > 1)
22112968Siveresov			errx(1, "%s is not a directory", to.p_path);
22212968Siveresov
22312657Skvn		/*
22412657Skvn		 * Need to detect the case:
22512968Siveresov		 *	cp -R dir foo
22612968Siveresov		 * Where dir is a directory and foo does not exist, where
22712657Skvn		 * we want pathname concatenations turned on but not for
22812657Skvn		 * the initial mkdir().
22912995Siveresov		 */
23012657Skvn		if (r == -1) {
23112657Skvn			if (Rflag && (Lflag || Hflag))
23212657Skvn				stat(*argv, &tmp_stat);
23312657Skvn			else
23412968Siveresov				lstat(*argv, &tmp_stat);
23512968Siveresov
23612657Skvn			if (S_ISDIR(tmp_stat.st_mode) && Rflag)
23712657Skvn				type = DIR_TO_DNE;
23812657Skvn			else
23912657Skvn				type = FILE_TO_FILE;
24012968Siveresov		} else
24112657Skvn			type = FILE_TO_FILE;
24212657Skvn
24312657Skvn		if (have_trailing_slash && type == FILE_TO_FILE) {
24412968Siveresov			if (r == -1)
24512968Siveresov				errx(1, "directory %s does not exist",
24612968Siveresov				     to.p_path);
24712968Siveresov			else
24812968Siveresov				errx(1, "%s is not a directory", to.p_path);
24912968Siveresov		}
25012968Siveresov	} else
25112968Siveresov		/*
25212657Skvn		 * Case (2).  Target is a directory.
25312657Skvn		 */
25412657Skvn		type = FILE_TO_DIR;
25512968Siveresov
25612968Siveresov	exit (copy(argv, type, fts_options));
25712968Siveresov}
25812968Siveresov
25912968Siveresovstatic int
26012657Skvncopy(char *argv[], enum op type, int fts_options)
26112968Siveresov{
26212968Siveresov	struct stat to_stat;
26312968Siveresov	FTS *ftsp;
26412968Siveresov	FTSENT *curr;
26512657Skvn	int base = 0, dne, badcp, rval;
26612657Skvn	size_t nlen;
26712657Skvn	char *p, *target_mid;
26812657Skvn	mode_t mask, mode;
26912657Skvn
27012657Skvn	/*
27112657Skvn	 * Keep an inverted copy of the umask, for use in correcting
27212968Siveresov	 * permissions on created directories when not using -p.
27312657Skvn	 */
27412657Skvn	mask = ~umask(0777);
27512657Skvn	umask(~mask);
27612657Skvn
27712968Siveresov	if ((ftsp = fts_open(argv, fts_options, mastercmp)) == NULL)
27812657Skvn		err(1, "fts_open");
27912657Skvn	for (badcp = rval = 0; (curr = fts_read(ftsp)) != NULL; badcp = 0) {
28012657Skvn		switch (curr->fts_info) {
28112657Skvn		case FTS_NS:
28212657Skvn		case FTS_DNR:
28312657Skvn		case FTS_ERR:
28412657Skvn			warnx("%s: %s",
28512657Skvn			    curr->fts_path, strerror(curr->fts_errno));
28612657Skvn			badcp = rval = 1;
28712657Skvn			continue;
28812657Skvn		case FTS_DC:			/* Warn, continue. */
28912657Skvn			warnx("%s: directory causes a cycle", curr->fts_path);
29012657Skvn			badcp = rval = 1;
29112968Siveresov			continue;
29212968Siveresov		default:
29313175Siveresov			;
29412657Skvn		}
29512968Siveresov
29612657Skvn		/*
29712657Skvn		 * If we are in case (2) or (3) above, we need to append the
29812657Skvn                 * source name to the target name.
29912657Skvn                 */
30012968Siveresov		if (type != FILE_TO_FILE) {
30112968Siveresov			/*
30213190Siveresov			 * Need to remember the roots of traversals to create
30313190Siveresov			 * correct pathnames.  If there's a directory being
30412968Siveresov			 * copied to a non-existent directory, e.g.
30512657Skvn			 *	cp -R a/dir noexist
30612657Skvn			 * the resulting path name should be noexist/foo, not
30712657Skvn			 * noexist/dir/foo (where foo is a file in dir), which
30812657Skvn			 * is the case where the target exists.
30912968Siveresov			 *
31012968Siveresov			 * Also, check for "..".  This is for correct path
31112968Siveresov			 * concatenation for paths ending in "..", e.g.
31212995Siveresov			 *	cp -R .. /tmp
31312657Skvn			 * Paths ending in ".." are changed to ".".  This is
31412657Skvn			 * tricky, but seems the easiest way to fix the problem.
31512968Siveresov			 *
31612968Siveresov			 * XXX
31712657Skvn			 * Since the first level MUST be FTS_ROOTLEVEL, base
31812657Skvn			 * is always initialized.
31912657Skvn			 */
32012657Skvn			if (curr->fts_level == FTS_ROOTLEVEL) {
32112657Skvn				if (type != DIR_TO_DNE) {
32212657Skvn					p = strrchr(curr->fts_path, '/');
32312657Skvn					base = (p == NULL) ? 0 :
32412657Skvn					    (int)(p - curr->fts_path + 1);
32512657Skvn
32612657Skvn					if (!strcmp(&curr->fts_path[base],
32712657Skvn					    ".."))
32812968Siveresov						base += 1;
32912968Siveresov				} else
33012968Siveresov					base = curr->fts_pathlen;
33112968Siveresov			}
33212657Skvn
33312657Skvn			p = &curr->fts_path[base];
33412657Skvn			nlen = curr->fts_pathlen - base;
33512968Siveresov			target_mid = to.target_end;
33612657Skvn			if (*p != '/' && target_mid[-1] != '/')
33712657Skvn				*target_mid++ = '/';
33812657Skvn			*target_mid = 0;
33912995Siveresov			if (target_mid - to.p_path + nlen >= PATH_MAX) {
34012657Skvn				warnx("%s%s: name too long (not copied)",
34112657Skvn				    to.p_path, p);
34212657Skvn				badcp = rval = 1;
34312657Skvn				continue;
34412657Skvn			}
34512995Siveresov			(void)strncat(target_mid, p, nlen);
34612657Skvn			to.p_end = target_mid + nlen;
34712657Skvn			*to.p_end = 0;
34812657Skvn			STRIP_TRAILING_SLASH(to);
34912657Skvn		}
35012657Skvn
35112657Skvn		if (curr->fts_info == FTS_DP) {
35212995Siveresov			/*
35312657Skvn			 * We are nearly finished with this directory.  If we
35412657Skvn			 * didn't actually copy it, or otherwise don't need to
35512657Skvn			 * change its attributes, then we are done.
35612657Skvn			 */
35712968Siveresov			if (!curr->fts_number)
35812968Siveresov				continue;
35912968Siveresov			/*
36012657Skvn			 * If -p is in effect, set all the attributes.
36112657Skvn			 * Otherwise, set the correct permissions, limited
36212657Skvn			 * by the umask.  Optimise by avoiding a chmod()
36312657Skvn			 * if possible (which is usually the case if we
36412968Siveresov			 * made the directory).  Note that mkdir() does not
36512657Skvn			 * honour setuid, setgid and sticky bits, but we
36612657Skvn			 * normally want to preserve them on directories.
36712657Skvn			 */
36812657Skvn			if (pflag) {
36912657Skvn				if (setfile(curr->fts_statp, -1))
37012657Skvn					rval = 1;
37112657Skvn				if (preserve_dir_acls(curr->fts_statp,
37212657Skvn				    curr->fts_accpath, to.p_path) != 0)
37312657Skvn					rval = 1;
37412657Skvn			} else {
37512968Siveresov				mode = curr->fts_statp->st_mode;
37612657Skvn				if ((mode & (S_ISUID | S_ISGID | S_ISTXT)) ||
37712657Skvn				    ((mode | S_IRWXU) & mask) != (mode & mask))
37812968Siveresov					if (chmod(to.p_path, mode & mask) != 0){
37912968Siveresov						warn("chmod: %s", to.p_path);
38012968Siveresov						rval = 1;
38112968Siveresov					}
38212968Siveresov			}
38312968Siveresov			continue;
38412657Skvn		}
38512657Skvn
38612657Skvn		/* Not an error but need to remember it happened */
38712657Skvn		if (stat(to.p_path, &to_stat) == -1)
38812657Skvn			dne = 1;
38912657Skvn		else {
39012657Skvn			if (to_stat.st_dev == curr->fts_statp->st_dev &&
39112657Skvn			    to_stat.st_ino == curr->fts_statp->st_ino) {
39212657Skvn				warnx("%s and %s are identical (not copied).",
39312657Skvn				    to.p_path, curr->fts_path);
39412657Skvn				badcp = rval = 1;
39512657Skvn				if (S_ISDIR(curr->fts_statp->st_mode))
39612657Skvn					(void)fts_set(ftsp, curr, FTS_SKIP);
39712657Skvn				continue;
39812657Skvn			}
39912657Skvn			if (!S_ISDIR(curr->fts_statp->st_mode) &&
40012968Siveresov			    S_ISDIR(to_stat.st_mode)) {
40112995Siveresov				warnx("cannot overwrite directory %s with "
40212657Skvn				    "non-directory %s",
40312657Skvn				    to.p_path, curr->fts_path);
40412657Skvn				badcp = rval = 1;
40512657Skvn				continue;
40612657Skvn			}
40712657Skvn			dne = 0;
40812657Skvn		}
40912657Skvn
41012657Skvn		switch (curr->fts_statp->st_mode & S_IFMT) {
41112657Skvn		case S_IFLNK:
41212968Siveresov			/* Catch special case of a non-dangling symlink */
41312657Skvn			if ((fts_options & FTS_LOGICAL) ||
41412657Skvn			    ((fts_options & FTS_COMFOLLOW) &&
41512657Skvn			    curr->fts_level == 0)) {
41612657Skvn				if (copy_file(curr, dne))
41712657Skvn					badcp = rval = 1;
41812657Skvn			} else {
41913190Siveresov				if (copy_link(curr, !dne))
42013133Siveresov					badcp = rval = 1;
42113133Siveresov			}
42212657Skvn			break;
42312657Skvn		case S_IFDIR:
42412995Siveresov			if (!Rflag) {
42512657Skvn				warnx("%s is a directory (not copied).",
42612657Skvn				    curr->fts_path);
42712657Skvn				(void)fts_set(ftsp, curr, FTS_SKIP);
42812657Skvn				badcp = rval = 1;
42912657Skvn				break;
43012657Skvn			}
43112657Skvn			/*
43212657Skvn			 * If the directory doesn't exist, create the new
43312657Skvn			 * one with the from file mode plus owner RWX bits,
43412657Skvn			 * modified by the umask.  Trade-off between being
43512657Skvn			 * able to write the directory (if from directory is
43612657Skvn			 * 555) and not causing a permissions race.  If the
43712657Skvn			 * umask blocks owner writes, we fail..
43812657Skvn			 */
43912657Skvn			if (dne) {
44012657Skvn				if (mkdir(to.p_path,
44112657Skvn				    curr->fts_statp->st_mode | S_IRWXU) < 0)
44212657Skvn					err(1, "%s", to.p_path);
44312968Siveresov			} else if (!S_ISDIR(to_stat.st_mode)) {
44412968Siveresov				errno = ENOTDIR;
44512657Skvn				err(1, "%s", to.p_path);
44612657Skvn			}
44712968Siveresov			/*
44812657Skvn			 * Arrange to correct directory attributes later
44912657Skvn			 * (in the post-order phase) if this is a new
45012657Skvn			 * directory, or if the -p flag is in effect.
45112968Siveresov			 */
45212657Skvn			curr->fts_number = pflag || dne;
45312657Skvn			break;
45412657Skvn		case S_IFBLK:
45512657Skvn		case S_IFCHR:
45612657Skvn			if (Rflag) {
45712657Skvn				if (copy_special(curr->fts_statp, !dne))
45812657Skvn					badcp = rval = 1;
45912657Skvn			} else {
46012968Siveresov				if (copy_file(curr, dne))
46112968Siveresov					badcp = rval = 1;
46212657Skvn			}
46312968Siveresov			break;
46412657Skvn		case S_IFSOCK:
46512657Skvn			warnx("%s is a socket (not copied).",
46612657Skvn				    curr->fts_path);
46712657Skvn			break;
46812657Skvn		case S_IFIFO:
46912968Siveresov			if (Rflag) {
47012657Skvn				if (copy_fifo(curr->fts_statp, !dne))
47112657Skvn					badcp = rval = 1;
47212657Skvn			} else {
47313083Skvn				if (copy_file(curr, dne))
47412657Skvn					badcp = rval = 1;
47512968Siveresov			}
47612968Siveresov			break;
47712657Skvn		default:
47812657Skvn			if (copy_file(curr, dne))
47912657Skvn				badcp = rval = 1;
48012968Siveresov			break;
48112968Siveresov		}
48212657Skvn		if (vflag && !badcp)
48312657Skvn			(void)printf("%s -> %s\n", curr->fts_path, to.p_path);
48412657Skvn	}
48512968Siveresov	if (errno)
48612657Skvn		err(1, "fts_read");
48712657Skvn	fts_close(ftsp);
48812657Skvn	return (rval);
48912657Skvn}
49012657Skvn
49112657Skvn/*
49212657Skvn * mastercmp --
49312657Skvn *	The comparison function for the copy order.  The order is to copy
49412657Skvn *	non-directory files before directory files.  The reason for this
49512657Skvn *	is because files tend to be in the same cylinder group as their
49612657Skvn *	parent directory, whereas directories tend not to be.  Copying the
49712657Skvn *	files first reduces seeking.
49812657Skvn */
49912657Skvnstatic int
50012968Siveresovmastercmp(const FTSENT * const *a, const FTSENT * const *b)
50112968Siveresov{
50212657Skvn	int a_info, b_info;
50312657Skvn
50412657Skvn	a_info = (*a)->fts_info;
50512657Skvn	if (a_info == FTS_ERR || a_info == FTS_NS || a_info == FTS_DNR)
50612657Skvn		return (0);
50712657Skvn	b_info = (*b)->fts_info;
50812657Skvn	if (b_info == FTS_ERR || b_info == FTS_NS || b_info == FTS_DNR)
50912968Siveresov		return (0);
51012657Skvn	if (a_info == FTS_D)
51112657Skvn		return (-1);
51212657Skvn	if (b_info == FTS_D)
51312657Skvn		return (1);
51412657Skvn	return (0);
51512968Siveresov}
51612657Skvn
51712657Skvnstatic void
51812657Skvnsiginfo(int sig __unused)
51912968Siveresov{
52012657Skvn
52112968Siveresov	info = 1;
52212657Skvn}
52312968Siveresov