xref: /netbsd-src/bin/ls/ls.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
1 /*	$NetBSD: ls.c,v 1.70 2012/11/20 12:37:29 abs Exp $	*/
2 
3 /*
4  * Copyright (c) 1989, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Michael Fischbein.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 #ifndef lint
37 __COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
38  The Regents of the University of California.  All rights reserved.");
39 #endif /* not lint */
40 
41 #ifndef lint
42 #if 0
43 static char sccsid[] = "@(#)ls.c	8.7 (Berkeley) 8/5/94";
44 #else
45 __RCSID("$NetBSD: ls.c,v 1.70 2012/11/20 12:37:29 abs Exp $");
46 #endif
47 #endif /* not lint */
48 
49 #include <sys/param.h>
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <sys/ioctl.h>
53 
54 #include <dirent.h>
55 #include <err.h>
56 #include <errno.h>
57 #include <fts.h>
58 #include <locale.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <unistd.h>
63 #include <termios.h>
64 #include <pwd.h>
65 #include <grp.h>
66 #include <util.h>
67 
68 #include "ls.h"
69 #include "extern.h"
70 
71 static void	 display(FTSENT *, FTSENT *);
72 static int	 mastercmp(const FTSENT **, const FTSENT **);
73 static void	 traverse(int, char **, int);
74 
75 static void (*printfcn)(DISPLAY *);
76 static int (*sortfcn)(const FTSENT *, const FTSENT *);
77 
78 #define	BY_NAME 0
79 #define	BY_SIZE 1
80 #define	BY_TIME	2
81 
82 long blocksize;			/* block size units */
83 int termwidth = 80;		/* default terminal width */
84 int sortkey = BY_NAME;
85 int rval = EXIT_SUCCESS;	/* exit value - set if error encountered */
86 
87 /* flags */
88 int f_accesstime;		/* use time of last access */
89 int f_column;			/* columnated format */
90 int f_columnacross;		/* columnated format, sorted across */
91 int f_flags;			/* show flags associated with a file */
92 int f_grouponly;		/* long listing without owner */
93 int f_humanize;			/* humanize the size field */
94 int f_commas;           /* separate size field with comma */
95 int f_inode;			/* print inode */
96 int f_listdir;			/* list actual directory, not contents */
97 int f_listdot;			/* list files beginning with . */
98 int f_longform;			/* long listing format */
99 int f_nonprint;			/* show unprintables as ? */
100 int f_nosort;			/* don't sort output */
101 int f_numericonly;		/* don't convert uid/gid to name */
102 int f_octal;			/* print octal escapes for nongraphic characters */
103 int f_octal_escape;		/* like f_octal but use C escapes if possible */
104 int f_recursive;		/* ls subdirectories also */
105 int f_reversesort;		/* reverse whatever sort is used */
106 int f_sectime;			/* print the real time for all files */
107 int f_singlecol;		/* use single column output */
108 int f_size;			/* list size in short listing */
109 int f_statustime;		/* use time of last mode change */
110 int f_stream;			/* stream format */
111 int f_type;			/* add type character for non-regular files */
112 int f_typedir;			/* add type character for directories */
113 int f_whiteout;			/* show whiteout entries */
114 
115 __dead static void
116 usage(void)
117 {
118 
119 	(void)fprintf(stderr,
120 	    "usage: %s [-1AaBbCcdFfghikLlMmnopqRrSsTtuWwx] [file ...]\n",
121 	    getprogname());
122 	exit(EXIT_FAILURE);
123 	/* NOTREACHED */
124 }
125 
126 int
127 ls_main(int argc, char *argv[])
128 {
129 	static char dot[] = ".", *dotav[] = { dot, NULL };
130 	struct winsize win;
131 	int ch, fts_options;
132 	int kflag = 0;
133 	const char *p;
134 
135 	setprogname(argv[0]);
136 	(void)setlocale(LC_ALL, "");
137 
138 	/* Terminal defaults to -Cq, non-terminal defaults to -1. */
139 	if (isatty(STDOUT_FILENO)) {
140 		if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 &&
141 		    win.ws_col > 0)
142 			termwidth = win.ws_col;
143 		f_column = f_nonprint = 1;
144 	} else
145 		f_singlecol = 1;
146 
147 	/* Root is -A automatically. */
148 	if (!getuid())
149 		f_listdot = 1;
150 
151 	fts_options = FTS_PHYSICAL;
152 	while ((ch = getopt(argc, argv, "1ABCFLMRSTWabcdfghiklmnopqrstuwx")) != -1) {
153 		switch (ch) {
154 		/*
155 		 * The -1, -C, -l, -m and -x options all override each other so
156 		 * shell aliasing works correctly.
157 		 */
158 		case '1':
159 			f_singlecol = 1;
160 			f_column = f_columnacross = f_longform = f_stream = 0;
161 			break;
162 		case 'C':
163 			f_column = 1;
164 			f_columnacross = f_longform = f_singlecol = f_stream =
165 			    0;
166 			break;
167 		case 'g':
168 			if (f_grouponly != -1)
169 				f_grouponly = 1;
170 			f_longform = 1;
171 			f_column = f_columnacross = f_singlecol = f_stream = 0;
172 			break;
173 		case 'l':
174 			f_longform = 1;
175 			f_column = f_columnacross = f_singlecol = f_stream = 0;
176 			/* Never let -g take precedence over -l. */
177 			f_grouponly = -1;
178 			break;
179 		case 'm':
180 			f_stream = 1;
181 			f_column = f_columnacross = f_longform = f_singlecol =
182 			    0;
183 			break;
184 		case 'x':
185 			f_columnacross = 1;
186 			f_column = f_longform = f_singlecol = f_stream = 0;
187 			break;
188 		/* The -c and -u options override each other. */
189 		case 'c':
190 			f_statustime = 1;
191 			f_accesstime = 0;
192 			break;
193 		case 'u':
194 			f_accesstime = 1;
195 			f_statustime = 0;
196 			break;
197 		case 'F':
198 			f_type = 1;
199 			break;
200 		case 'L':
201 			fts_options &= ~FTS_PHYSICAL;
202 			fts_options |= FTS_LOGICAL;
203 			break;
204 		case 'R':
205 			f_recursive = 1;
206 			break;
207 		case 'a':
208 			fts_options |= FTS_SEEDOT;
209 			/* FALLTHROUGH */
210 		case 'A':
211 			f_listdot = 1;
212 			break;
213 		/* The -B option turns off the -b, -q and -w options. */
214 		case 'B':
215 			f_nonprint = 0;
216 			f_octal = 1;
217 			f_octal_escape = 0;
218 			break;
219 		/* The -b option turns off the -B, -q and -w options. */
220 		case 'b':
221 			f_nonprint = 0;
222 			f_octal = 0;
223 			f_octal_escape = 1;
224 			break;
225 		/* The -d option turns off the -R option. */
226 		case 'd':
227 			f_listdir = 1;
228 			f_recursive = 0;
229 			break;
230 		case 'f':
231 			f_nosort = 1;
232 			break;
233 		case 'i':
234 			f_inode = 1;
235 			break;
236 		case 'k':
237 			blocksize = 1024;
238 			kflag = 1;
239 			f_humanize = 0;
240 			break;
241 		/* The -h option forces all sizes to be measured in bytes. */
242 		case 'h':
243 			f_humanize = 1;
244 			kflag = 0;
245 			f_commas = 0;
246 			break;
247 		case 'M':
248 			f_humanize = 0;
249 			f_commas = 1;
250 			break;
251 		case 'n':
252 			f_numericonly = 1;
253 			f_longform = 1;
254 			f_column = f_columnacross = f_singlecol = f_stream = 0;
255 			break;
256 		case 'o':
257 			f_flags = 1;
258 			break;
259 		case 'p':
260 			f_typedir = 1;
261 			break;
262 		/* The -q option turns off the -B, -b and -w options. */
263 		case 'q':
264 			f_nonprint = 1;
265 			f_octal = 0;
266 			f_octal_escape = 0;
267 			break;
268 		case 'r':
269 			f_reversesort = 1;
270 			break;
271 		case 'S':
272 			sortkey = BY_SIZE;
273 			break;
274 		case 's':
275 			f_size = 1;
276 			break;
277 		case 'T':
278 			f_sectime = 1;
279 			break;
280 		case 't':
281 			sortkey = BY_TIME;
282 			break;
283 		case 'W':
284 			f_whiteout = 1;
285 			break;
286 		/* The -w option turns off the -B, -b and -q options. */
287 		case 'w':
288 			f_nonprint = 0;
289 			f_octal = 0;
290 			f_octal_escape = 0;
291 			break;
292 		default:
293 		case '?':
294 			usage();
295 		}
296 	}
297 	argc -= optind;
298 	argv += optind;
299 
300 	if (f_column || f_columnacross || f_stream) {
301 		if ((p = getenv("COLUMNS")) != NULL)
302 			termwidth = atoi(p);
303 	}
304 
305 	/*
306 	 * If both -g and -l options, let -l take precedence.
307 	 */
308 	if (f_grouponly == -1)
309 		f_grouponly = 0;
310 
311 	/*
312 	 * If not -F, -i, -l, -p, -S, -s or -t options, don't require stat
313 	 * information.
314 	 */
315 	if (!f_inode && !f_longform && !f_size && !f_type && !f_typedir &&
316 	    sortkey == BY_NAME)
317 		fts_options |= FTS_NOSTAT;
318 
319 	/*
320 	 * If not -F, -d or -l options, follow any symbolic links listed on
321 	 * the command line.
322 	 */
323 	if (!f_longform && !f_listdir && !f_type)
324 		fts_options |= FTS_COMFOLLOW;
325 
326 	/*
327 	 * If -W, show whiteout entries
328 	 */
329 #ifdef FTS_WHITEOUT
330 	if (f_whiteout)
331 		fts_options |= FTS_WHITEOUT;
332 #endif
333 
334 	/* If -i, -l, or -s, figure out block size. */
335 	if (f_inode || f_longform || f_size) {
336 		if (!kflag)
337 			(void)getbsize(NULL, &blocksize);
338 		blocksize /= 512;
339 	}
340 
341 	/* Select a sort function. */
342 	if (f_reversesort) {
343 		switch (sortkey) {
344 		case BY_NAME:
345 			sortfcn = revnamecmp;
346 			break;
347 		case BY_SIZE:
348 			sortfcn = revsizecmp;
349 			break;
350 		case BY_TIME:
351 			if (f_accesstime)
352 				sortfcn = revacccmp;
353 			else if (f_statustime)
354 				sortfcn = revstatcmp;
355 			else /* Use modification time. */
356 				sortfcn = revmodcmp;
357 			break;
358 		}
359 	} else {
360 		switch (sortkey) {
361 		case BY_NAME:
362 			sortfcn = namecmp;
363 			break;
364 		case BY_SIZE:
365 			sortfcn = sizecmp;
366 			break;
367 		case BY_TIME:
368 			if (f_accesstime)
369 				sortfcn = acccmp;
370 			else if (f_statustime)
371 				sortfcn = statcmp;
372 			else /* Use modification time. */
373 				sortfcn = modcmp;
374 			break;
375 		}
376 	}
377 
378 	/* Select a print function. */
379 	if (f_singlecol)
380 		printfcn = printscol;
381 	else if (f_columnacross)
382 		printfcn = printacol;
383 	else if (f_longform)
384 		printfcn = printlong;
385 	else if (f_stream)
386 		printfcn = printstream;
387 	else
388 		printfcn = printcol;
389 
390 	if (argc)
391 		traverse(argc, argv, fts_options);
392 	else
393 		traverse(1, dotav, fts_options);
394 	return rval;
395 	/* NOTREACHED */
396 }
397 
398 static int output;			/* If anything output. */
399 
400 /*
401  * Traverse() walks the logical directory structure specified by the argv list
402  * in the order specified by the mastercmp() comparison function.  During the
403  * traversal it passes linked lists of structures to display() which represent
404  * a superset (may be exact set) of the files to be displayed.
405  */
406 static void
407 traverse(int argc, char *argv[], int options)
408 {
409 	FTS *ftsp;
410 	FTSENT *p, *chp;
411 	int ch_options, error;
412 
413 	if ((ftsp =
414 	    fts_open(argv, options, f_nosort ? NULL : mastercmp)) == NULL)
415 		err(EXIT_FAILURE, NULL);
416 
417 	display(NULL, fts_children(ftsp, 0));
418 	if (f_listdir) {
419 		(void)fts_close(ftsp);
420 		return;
421 	}
422 
423 	/*
424 	 * If not recursing down this tree and don't need stat info, just get
425 	 * the names.
426 	 */
427 	ch_options = !f_recursive && options & FTS_NOSTAT ? FTS_NAMEONLY : 0;
428 
429 	while ((p = fts_read(ftsp)) != NULL)
430 		switch (p->fts_info) {
431 		case FTS_DC:
432 			warnx("%s: directory causes a cycle", p->fts_name);
433 			break;
434 		case FTS_DNR:
435 		case FTS_ERR:
436 			warnx("%s: %s", p->fts_name, strerror(p->fts_errno));
437 			rval = EXIT_FAILURE;
438 			break;
439 		case FTS_D:
440 			if (p->fts_level != FTS_ROOTLEVEL &&
441 			    p->fts_name[0] == '.' && !f_listdot)
442 				break;
443 
444 			/*
445 			 * If already output something, put out a newline as
446 			 * a separator.  If multiple arguments, precede each
447 			 * directory with its name.
448 			 */
449 			if (output)
450 				(void)printf("\n%s:\n", p->fts_path);
451 			else if (argc > 1) {
452 				(void)printf("%s:\n", p->fts_path);
453 				output = 1;
454 			}
455 
456 			chp = fts_children(ftsp, ch_options);
457 			display(p, chp);
458 
459 			if (!f_recursive && chp != NULL)
460 				(void)fts_set(ftsp, p, FTS_SKIP);
461 			break;
462 		}
463 	error = errno;
464 	(void)fts_close(ftsp);
465 	errno = error;
466 	if (errno)
467 		err(EXIT_FAILURE, "fts_read");
468 }
469 
470 /*
471  * Display() takes a linked list of FTSENT structures and passes the list
472  * along with any other necessary information to the print function.  P
473  * points to the parent directory of the display list.
474  */
475 static void
476 display(FTSENT *p, FTSENT *list)
477 {
478 	struct stat *sp;
479 	DISPLAY d;
480 	FTSENT *cur;
481 	NAMES *np;
482 	u_int64_t btotal, stotal;
483 	off_t maxsize;
484 	blkcnt_t maxblock;
485 	ino_t maxinode;
486 	int maxmajor, maxminor;
487 	uint32_t maxnlink;
488 	int bcfile, entries, flen, glen, ulen, maxflags, maxgroup;
489 	unsigned int maxlen;
490 	int maxuser, needstats;
491 	const char *user, *group;
492 	char buf[21];		/* 64 bits == 20 digits, +1 for NUL */
493 	char nuser[12], ngroup[12];
494 	char *flags = NULL;
495 
496 	/*
497 	 * If list is NULL there are two possibilities: that the parent
498 	 * directory p has no children, or that fts_children() returned an
499 	 * error.  We ignore the error case since it will be replicated
500 	 * on the next call to fts_read() on the post-order visit to the
501 	 * directory p, and will be signalled in traverse().
502 	 */
503 	if (list == NULL)
504 		return;
505 
506 	needstats = f_inode || f_longform || f_size;
507 	flen = 0;
508 	maxinode = maxnlink = 0;
509 	bcfile = 0;
510 	maxuser = maxgroup = maxflags = maxlen = 0;
511 	btotal = stotal = maxblock = maxsize = 0;
512 	maxmajor = maxminor = 0;
513 	for (cur = list, entries = 0; cur; cur = cur->fts_link) {
514 		if (cur->fts_info == FTS_ERR || cur->fts_info == FTS_NS) {
515 			warnx("%s: %s",
516 			    cur->fts_name, strerror(cur->fts_errno));
517 			cur->fts_number = NO_PRINT;
518 			rval = EXIT_FAILURE;
519 			continue;
520 		}
521 
522 		/*
523 		 * P is NULL if list is the argv list, to which different rules
524 		 * apply.
525 		 */
526 		if (p == NULL) {
527 			/* Directories will be displayed later. */
528 			if (cur->fts_info == FTS_D && !f_listdir) {
529 				cur->fts_number = NO_PRINT;
530 				continue;
531 			}
532 		} else {
533 			/* Only display dot file if -a/-A set. */
534 			if (cur->fts_name[0] == '.' && !f_listdot) {
535 				cur->fts_number = NO_PRINT;
536 				continue;
537 			}
538 		}
539 		if (cur->fts_namelen > maxlen)
540 			maxlen = cur->fts_namelen;
541 		if (needstats) {
542 			sp = cur->fts_statp;
543 			if (sp->st_blocks > maxblock)
544 				maxblock = sp->st_blocks;
545 			if (sp->st_ino > maxinode)
546 				maxinode = sp->st_ino;
547 			if (sp->st_nlink > maxnlink)
548 				maxnlink = sp->st_nlink;
549 			if (sp->st_size > maxsize)
550 				maxsize = sp->st_size;
551 			if (S_ISCHR(sp->st_mode) || S_ISBLK(sp->st_mode)) {
552 				bcfile = 1;
553 				if (major(sp->st_rdev) > maxmajor)
554 					maxmajor = major(sp->st_rdev);
555 				if (minor(sp->st_rdev) > maxminor)
556 					maxminor = minor(sp->st_rdev);
557 			}
558 
559 			btotal += sp->st_blocks;
560 			stotal += sp->st_size;
561 			if (f_longform) {
562 				if (f_numericonly ||
563 				    (user = user_from_uid(sp->st_uid, 0)) ==
564 				    NULL) {
565 					(void)snprintf(nuser, sizeof(nuser),
566 					    "%u", sp->st_uid);
567 					user = nuser;
568 				}
569 				if (f_numericonly ||
570 				    (group = group_from_gid(sp->st_gid, 0)) ==
571 				    NULL) {
572 					(void)snprintf(ngroup, sizeof(ngroup),
573 					    "%u", sp->st_gid);
574 					group = ngroup;
575 				}
576 				if ((ulen = strlen(user)) > maxuser)
577 					maxuser = ulen;
578 				if ((glen = strlen(group)) > maxgroup)
579 					maxgroup = glen;
580 				if (f_flags) {
581 					flags =
582 					    flags_to_string((u_long)sp->st_flags, "-");
583 					if ((flen = strlen(flags)) > maxflags)
584 						maxflags = flen;
585 				} else
586 					flen = 0;
587 
588 				if ((np = malloc(sizeof(NAMES) +
589 				    ulen + glen + flen + 2)) == NULL)
590 					err(EXIT_FAILURE, NULL);
591 
592 				np->user = &np->data[0];
593 				(void)strcpy(np->user, user);
594 				np->group = &np->data[ulen + 1];
595 				(void)strcpy(np->group, group);
596 
597 				if (f_flags) {
598 					np->flags = &np->data[ulen + glen + 2];
599 				  	(void)strcpy(np->flags, flags);
600 					free(flags);
601 				}
602 				cur->fts_pointer = np;
603 			}
604 		}
605 		++entries;
606 	}
607 
608 	if (!entries)
609 		return;
610 
611 	d.list = list;
612 	d.entries = entries;
613 	d.maxlen = maxlen;
614 	if (needstats) {
615 		d.btotal = btotal;
616 		d.stotal = stotal;
617 		if (f_humanize) {
618 			d.s_block = 4; /* min buf length for humanize_number */
619 		} else {
620 			(void)snprintf(buf, sizeof(buf), "%llu",
621 			    (long long)howmany(maxblock, blocksize));
622 			d.s_block = strlen(buf);
623 			if (f_commas) /* allow for commas before every third digit */
624 				d.s_block += (d.s_block - 1) / 3;
625 		}
626 		d.s_flags = maxflags;
627 		d.s_group = maxgroup;
628 		(void)snprintf(buf, sizeof(buf), "%llu",
629 		    (unsigned long long)maxinode);
630 		d.s_inode = strlen(buf);
631 		(void)snprintf(buf, sizeof(buf), "%u", maxnlink);
632 		d.s_nlink = strlen(buf);
633 		if (f_humanize) {
634 			d.s_size = 4; /* min buf length for humanize_number */
635 		} else {
636 			(void)snprintf(buf, sizeof(buf), "%llu",
637 			    (long long)maxsize);
638 			d.s_size = strlen(buf);
639 			if (f_commas) /* allow for commas before every third digit */
640 				d.s_size += (d.s_size - 1) / 3;
641 		}
642 		d.s_user = maxuser;
643 		if (bcfile) {
644 			(void)snprintf(buf, sizeof(buf), "%u", maxmajor);
645 			d.s_major = strlen(buf);
646 			(void)snprintf(buf, sizeof(buf), "%u", maxminor);
647 			d.s_minor = strlen(buf);
648 			if (d.s_major + d.s_minor + 2 > d.s_size)
649 				d.s_size = d.s_major + d.s_minor + 2;
650 			else if (d.s_size - d.s_minor - 2 > d.s_major)
651 				d.s_major = d.s_size - d.s_minor - 2;
652 		} else {
653 			d.s_major = 0;
654 			d.s_minor = 0;
655 		}
656 	}
657 
658 	printfcn(&d);
659 	output = 1;
660 
661 	if (f_longform)
662 		for (cur = list; cur; cur = cur->fts_link)
663 			free(cur->fts_pointer);
664 }
665 
666 /*
667  * Ordering for mastercmp:
668  * If ordering the argv (fts_level = FTS_ROOTLEVEL) return non-directories
669  * as larger than directories.  Within either group, use the sort function.
670  * All other levels use the sort function.  Error entries remain unsorted.
671  */
672 static int
673 mastercmp(const FTSENT **a, const FTSENT **b)
674 {
675 	int a_info, b_info;
676 
677 	a_info = (*a)->fts_info;
678 	if (a_info == FTS_ERR)
679 		return (0);
680 	b_info = (*b)->fts_info;
681 	if (b_info == FTS_ERR)
682 		return (0);
683 
684 	if (a_info == FTS_NS || b_info == FTS_NS) {
685 		if (b_info != FTS_NS)
686 			return (1);
687 		else if (a_info != FTS_NS)
688 			return (-1);
689 		else
690 			return (namecmp(*a, *b));
691 	}
692 
693 	if (a_info != b_info && !f_listdir &&
694 	    (*a)->fts_level == FTS_ROOTLEVEL) {
695 		if (a_info == FTS_D)
696 			return (1);
697 		else if (b_info == FTS_D)
698 			return (-1);
699 	}
700 	return (sortfcn(*a, *b));
701 }
702