xref: /freebsd/bin/ls/print.c (revision 0fdf7fa8)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
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 #if 0
36 #ifndef lint
37 static char sccsid[] = "@(#)print.c	8.4 (Berkeley) 4/17/94";
38 #endif /* not lint */
39 #endif
40 #include <sys/cdefs.h>
41 __FBSDID("$FreeBSD$");
42 
43 #include <sys/param.h>
44 #include <sys/stat.h>
45 #include <sys/acl.h>
46 
47 #include <err.h>
48 #include <errno.h>
49 #include <fts.h>
50 #include <langinfo.h>
51 #include <libutil.h>
52 #include <limits.h>
53 #include <stdio.h>
54 #include <stdint.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <time.h>
58 #include <unistd.h>
59 #include <wchar.h>
60 #ifdef COLORLS
61 #include <ctype.h>
62 #include <termcap.h>
63 #include <signal.h>
64 #endif
65 
66 #include "ls.h"
67 #include "extern.h"
68 
69 static int	printaname(const FTSENT *, u_long, u_long);
70 static void	printdev(size_t, dev_t);
71 static void	printlink(const FTSENT *);
72 static void	printtime(time_t);
73 static int	printtype(u_int);
74 static void	printsize(size_t, off_t);
75 #ifdef COLORLS
76 static void	endcolor(int);
77 static int	colortype(mode_t);
78 #endif
79 static void	aclmode(char *, const FTSENT *);
80 
81 #define	IS_NOPRINT(p)	((p)->fts_number == NO_PRINT)
82 
83 #ifdef COLORLS
84 /* Most of these are taken from <sys/stat.h> */
85 typedef enum Colors {
86 	C_DIR,			/* directory */
87 	C_LNK,			/* symbolic link */
88 	C_SOCK,			/* socket */
89 	C_FIFO,			/* pipe */
90 	C_EXEC,			/* executable */
91 	C_BLK,			/* block special */
92 	C_CHR,			/* character special */
93 	C_SUID,			/* setuid executable */
94 	C_SGID,			/* setgid executable */
95 	C_WSDIR,		/* directory writeble to others, with sticky
96 				 * bit */
97 	C_WDIR,			/* directory writeble to others, without
98 				 * sticky bit */
99 	C_NUMCOLORS		/* just a place-holder */
100 } Colors;
101 
102 static const char *defcolors = "exfxcxdxbxegedabagacad";
103 
104 /* colors for file types */
105 static struct {
106 	int	num[2];
107 	int	bold;
108 } colors[C_NUMCOLORS];
109 #endif
110 
111 static size_t padding_for_month[12];
112 static size_t month_max_size = 0;
113 
114 void
115 printscol(const DISPLAY *dp)
116 {
117 	FTSENT *p;
118 
119 	for (p = dp->list; p; p = p->fts_link) {
120 		if (IS_NOPRINT(p))
121 			continue;
122 		(void)printaname(p, dp->s_inode, dp->s_block);
123 		(void)putchar('\n');
124 	}
125 }
126 
127 /*
128  * print name in current style
129  */
130 int
131 printname(const char *name)
132 {
133 	if (f_octal || f_octal_escape)
134 		return prn_octal(name);
135 	else if (f_nonprint)
136 		return prn_printable(name);
137 	else
138 		return prn_normal(name);
139 }
140 
141 static const char *
142 get_abmon(int mon)
143 {
144 
145 	switch (mon) {
146 	case 0: return (nl_langinfo(ABMON_1));
147 	case 1: return (nl_langinfo(ABMON_2));
148 	case 2: return (nl_langinfo(ABMON_3));
149 	case 3: return (nl_langinfo(ABMON_4));
150 	case 4: return (nl_langinfo(ABMON_5));
151 	case 5: return (nl_langinfo(ABMON_6));
152 	case 6: return (nl_langinfo(ABMON_7));
153 	case 7: return (nl_langinfo(ABMON_8));
154 	case 8: return (nl_langinfo(ABMON_9));
155 	case 9: return (nl_langinfo(ABMON_10));
156 	case 10: return (nl_langinfo(ABMON_11));
157 	case 11: return (nl_langinfo(ABMON_12));
158 	}
159 
160 	/* should never happen */
161 	abort();
162 }
163 
164 static size_t
165 mbswidth(const char *month)
166 {
167 	wchar_t wc;
168 	size_t width, donelen, clen, w;
169 
170 	width = donelen = 0;
171 	while ((clen = mbrtowc(&wc, month + donelen, MB_LEN_MAX, NULL)) != 0) {
172 		if (clen == (size_t)-1 || clen == (size_t)-2)
173 			return (-1);
174 		donelen += clen;
175 		if ((w = wcwidth(wc)) == (size_t)-1)
176 			return (-1);
177 		width += w;
178 	}
179 
180 	return (width);
181 }
182 
183 static void
184 compute_abbreviated_month_size(void)
185 {
186 	int i;
187 	size_t width;
188 	size_t months_width[12];
189 
190 	for (i = 0; i < 12; i++) {
191 		width = mbswidth(get_abmon(i));
192 		if (width == (size_t)-1) {
193 			month_max_size = -1;
194 			return;
195 		}
196 		months_width[i] = width;
197 		if (width > month_max_size)
198 			month_max_size = width;
199 	}
200 
201 	for (i = 0; i < 12; i++)
202 		padding_for_month[i] = month_max_size - months_width[i];
203 }
204 
205 void
206 printlong(const DISPLAY *dp)
207 {
208 	struct stat *sp;
209 	FTSENT *p;
210 	NAMES *np;
211 	char buf[20];
212 #ifdef COLORLS
213 	int color_printed = 0;
214 #endif
215 
216 	if ((dp->list == NULL || dp->list->fts_level != FTS_ROOTLEVEL) &&
217 	    (f_longform || f_size)) {
218 		(void)printf("total %lu\n", howmany(dp->btotal, blocksize));
219 	}
220 
221 	for (p = dp->list; p; p = p->fts_link) {
222 		if (IS_NOPRINT(p))
223 			continue;
224 		sp = p->fts_statp;
225 		if (f_inode)
226 			(void)printf("%*ju ",
227 			    dp->s_inode, (uintmax_t)sp->st_ino);
228 		if (f_size)
229 			(void)printf("%*jd ",
230 			    dp->s_block, howmany(sp->st_blocks, blocksize));
231 		strmode(sp->st_mode, buf);
232 		aclmode(buf, p);
233 		np = p->fts_pointer;
234 		(void)printf("%s %*ju %-*s  %-*s  ", buf, dp->s_nlink,
235 		    (uintmax_t)sp->st_nlink, dp->s_user, np->user, dp->s_group,
236 		    np->group);
237 		if (f_flags)
238 			(void)printf("%-*s ", dp->s_flags, np->flags);
239 		if (f_label)
240 			(void)printf("%-*s ", dp->s_label, np->label);
241 		if (S_ISCHR(sp->st_mode) || S_ISBLK(sp->st_mode))
242 			printdev(dp->s_size, sp->st_rdev);
243 		else
244 			printsize(dp->s_size, sp->st_size);
245 		if (f_accesstime)
246 			printtime(sp->st_atime);
247 		else if (f_birthtime)
248 			printtime(sp->st_birthtime);
249 		else if (f_statustime)
250 			printtime(sp->st_ctime);
251 		else
252 			printtime(sp->st_mtime);
253 #ifdef COLORLS
254 		if (f_color)
255 			color_printed = colortype(sp->st_mode);
256 #endif
257 		(void)printname(p->fts_name);
258 #ifdef COLORLS
259 		if (f_color && color_printed)
260 			endcolor(0);
261 #endif
262 		if (f_type)
263 			(void)printtype(sp->st_mode);
264 		if (S_ISLNK(sp->st_mode))
265 			printlink(p);
266 		(void)putchar('\n');
267 	}
268 }
269 
270 void
271 printstream(const DISPLAY *dp)
272 {
273 	FTSENT *p;
274 	int chcnt;
275 
276 	for (p = dp->list, chcnt = 0; p; p = p->fts_link) {
277 		if (p->fts_number == NO_PRINT)
278 			continue;
279 		/* XXX strlen does not take octal escapes into account. */
280 		if (strlen(p->fts_name) + chcnt +
281 		    (p->fts_link ? 2 : 0) >= (unsigned)termwidth) {
282 			putchar('\n');
283 			chcnt = 0;
284 		}
285 		chcnt += printaname(p, dp->s_inode, dp->s_block);
286 		if (p->fts_link) {
287 			printf(", ");
288 			chcnt += 2;
289 		}
290 	}
291 	if (chcnt)
292 		putchar('\n');
293 }
294 
295 void
296 printcol(const DISPLAY *dp)
297 {
298 	static FTSENT **array;
299 	static int lastentries = -1;
300 	FTSENT *p;
301 	FTSENT **narray;
302 	int base;
303 	int chcnt;
304 	int cnt;
305 	int col;
306 	int colwidth;
307 	int endcol;
308 	int num;
309 	int numcols;
310 	int numrows;
311 	int row;
312 	int tabwidth;
313 
314 	if (f_notabs)
315 		tabwidth = 1;
316 	else
317 		tabwidth = 8;
318 
319 	/*
320 	 * Have to do random access in the linked list -- build a table
321 	 * of pointers.
322 	 */
323 	if (dp->entries > lastentries) {
324 		if ((narray =
325 		    realloc(array, dp->entries * sizeof(FTSENT *))) == NULL) {
326 			warn(NULL);
327 			printscol(dp);
328 			return;
329 		}
330 		lastentries = dp->entries;
331 		array = narray;
332 	}
333 	for (p = dp->list, num = 0; p; p = p->fts_link)
334 		if (p->fts_number != NO_PRINT)
335 			array[num++] = p;
336 
337 	colwidth = dp->maxlen;
338 	if (f_inode)
339 		colwidth += dp->s_inode + 1;
340 	if (f_size)
341 		colwidth += dp->s_block + 1;
342 	if (f_type)
343 		colwidth += 1;
344 
345 	colwidth = (colwidth + tabwidth) & ~(tabwidth - 1);
346 	if (termwidth < 2 * colwidth) {
347 		printscol(dp);
348 		return;
349 	}
350 	numcols = termwidth / colwidth;
351 	numrows = num / numcols;
352 	if (num % numcols)
353 		++numrows;
354 
355 	if ((dp->list == NULL || dp->list->fts_level != FTS_ROOTLEVEL) &&
356 	    (f_longform || f_size)) {
357 		(void)printf("total %lu\n", howmany(dp->btotal, blocksize));
358 	}
359 
360 	base = 0;
361 	for (row = 0; row < numrows; ++row) {
362 		endcol = colwidth;
363 		if (!f_sortacross)
364 			base = row;
365 		for (col = 0, chcnt = 0; col < numcols; ++col) {
366 			chcnt += printaname(array[base], dp->s_inode,
367 			    dp->s_block);
368 			if (f_sortacross)
369 				base++;
370 			else
371 				base += numrows;
372 			if (base >= num)
373 				break;
374 			while ((cnt = ((chcnt + tabwidth) & ~(tabwidth - 1)))
375 			    <= endcol) {
376 				if (f_sortacross && col + 1 >= numcols)
377 					break;
378 				(void)putchar(f_notabs ? ' ' : '\t');
379 				chcnt = cnt;
380 			}
381 			endcol += colwidth;
382 		}
383 		(void)putchar('\n');
384 	}
385 }
386 
387 /*
388  * print [inode] [size] name
389  * return # of characters printed, no trailing characters.
390  */
391 static int
392 printaname(const FTSENT *p, u_long inodefield, u_long sizefield)
393 {
394 	struct stat *sp;
395 	int chcnt;
396 #ifdef COLORLS
397 	int color_printed = 0;
398 #endif
399 
400 	sp = p->fts_statp;
401 	chcnt = 0;
402 	if (f_inode)
403 		chcnt += printf("%*ju ",
404 		    (int)inodefield, (uintmax_t)sp->st_ino);
405 	if (f_size)
406 		chcnt += printf("%*jd ",
407 		    (int)sizefield, howmany(sp->st_blocks, blocksize));
408 #ifdef COLORLS
409 	if (f_color)
410 		color_printed = colortype(sp->st_mode);
411 #endif
412 	chcnt += printname(p->fts_name);
413 #ifdef COLORLS
414 	if (f_color && color_printed)
415 		endcolor(0);
416 #endif
417 	if (f_type)
418 		chcnt += printtype(sp->st_mode);
419 	return (chcnt);
420 }
421 
422 /*
423  * Print device special file major and minor numbers.
424  */
425 static void
426 printdev(size_t width, dev_t dev)
427 {
428 
429 	(void)printf("%#*jx ", (u_int)width, (uintmax_t)dev);
430 }
431 
432 static size_t
433 ls_strftime(char *str, size_t len, const char *fmt, const struct tm *tm)
434 {
435 	char *posb, nfmt[BUFSIZ];
436 	const char *format = fmt;
437 	size_t ret;
438 
439 	if ((posb = strstr(fmt, "%b")) != NULL) {
440 		if (month_max_size == 0) {
441 			compute_abbreviated_month_size();
442 		}
443 		if (month_max_size > 0) {
444 			snprintf(nfmt, sizeof(nfmt),  "%.*s%s%*s%s",
445 			    (int)(posb - fmt), fmt,
446 			    get_abmon(tm->tm_mon),
447 			    (int)padding_for_month[tm->tm_mon],
448 			    "",
449 			    posb + 2);
450 			format = nfmt;
451 		}
452 	}
453 	ret = strftime(str, len, format, tm);
454 	return (ret);
455 }
456 
457 static void
458 printtime(time_t ftime)
459 {
460 	char longstring[80];
461 	static time_t now = 0;
462 	const char *format;
463 	static int d_first = -1;
464 
465 	if (d_first < 0)
466 		d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
467 	if (now == 0)
468 		now = time(NULL);
469 
470 #define	SIXMONTHS	((365 / 2) * 86400)
471 	if (f_timeformat)  /* user specified format */
472 		format = f_timeformat;
473 	else if (f_sectime)
474 		/* mmm dd hh:mm:ss yyyy || dd mmm hh:mm:ss yyyy */
475 		format = d_first ? "%e %b %T %Y" : "%b %e %T %Y";
476 	else if (ftime + SIXMONTHS > now && ftime < now + SIXMONTHS)
477 		/* mmm dd hh:mm || dd mmm hh:mm */
478 		format = d_first ? "%e %b %R" : "%b %e %R";
479 	else
480 		/* mmm dd  yyyy || dd mmm  yyyy */
481 		format = d_first ? "%e %b  %Y" : "%b %e  %Y";
482 	ls_strftime(longstring, sizeof(longstring), format, localtime(&ftime));
483 	fputs(longstring, stdout);
484 	fputc(' ', stdout);
485 }
486 
487 static int
488 printtype(u_int mode)
489 {
490 
491 	if (f_slash) {
492 		if ((mode & S_IFMT) == S_IFDIR) {
493 			(void)putchar('/');
494 			return (1);
495 		}
496 		return (0);
497 	}
498 
499 	switch (mode & S_IFMT) {
500 	case S_IFDIR:
501 		(void)putchar('/');
502 		return (1);
503 	case S_IFIFO:
504 		(void)putchar('|');
505 		return (1);
506 	case S_IFLNK:
507 		(void)putchar('@');
508 		return (1);
509 	case S_IFSOCK:
510 		(void)putchar('=');
511 		return (1);
512 	case S_IFWHT:
513 		(void)putchar('%');
514 		return (1);
515 	default:
516 		break;
517 	}
518 	if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
519 		(void)putchar('*');
520 		return (1);
521 	}
522 	return (0);
523 }
524 
525 #ifdef COLORLS
526 static int
527 putch(int c)
528 {
529 	(void)putchar(c);
530 	return 0;
531 }
532 
533 static int
534 writech(int c)
535 {
536 	char tmp = (char)c;
537 
538 	(void)write(STDOUT_FILENO, &tmp, 1);
539 	return 0;
540 }
541 
542 static void
543 printcolor(Colors c)
544 {
545 	char *ansiseq;
546 
547 	if (colors[c].bold)
548 		tputs(enter_bold, 1, putch);
549 
550 	if (colors[c].num[0] != -1) {
551 		ansiseq = tgoto(ansi_fgcol, 0, colors[c].num[0]);
552 		if (ansiseq)
553 			tputs(ansiseq, 1, putch);
554 	}
555 	if (colors[c].num[1] != -1) {
556 		ansiseq = tgoto(ansi_bgcol, 0, colors[c].num[1]);
557 		if (ansiseq)
558 			tputs(ansiseq, 1, putch);
559 	}
560 }
561 
562 static void
563 endcolor(int sig)
564 {
565 	tputs(ansi_coloff, 1, sig ? writech : putch);
566 	tputs(attrs_off, 1, sig ? writech : putch);
567 }
568 
569 static int
570 colortype(mode_t mode)
571 {
572 	switch (mode & S_IFMT) {
573 	case S_IFDIR:
574 		if (mode & S_IWOTH)
575 			if (mode & S_ISTXT)
576 				printcolor(C_WSDIR);
577 			else
578 				printcolor(C_WDIR);
579 		else
580 			printcolor(C_DIR);
581 		return (1);
582 	case S_IFLNK:
583 		printcolor(C_LNK);
584 		return (1);
585 	case S_IFSOCK:
586 		printcolor(C_SOCK);
587 		return (1);
588 	case S_IFIFO:
589 		printcolor(C_FIFO);
590 		return (1);
591 	case S_IFBLK:
592 		printcolor(C_BLK);
593 		return (1);
594 	case S_IFCHR:
595 		printcolor(C_CHR);
596 		return (1);
597 	default:;
598 	}
599 	if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
600 		if (mode & S_ISUID)
601 			printcolor(C_SUID);
602 		else if (mode & S_ISGID)
603 			printcolor(C_SGID);
604 		else
605 			printcolor(C_EXEC);
606 		return (1);
607 	}
608 	return (0);
609 }
610 
611 void
612 parsecolors(const char *cs)
613 {
614 	int i;
615 	int j;
616 	size_t len;
617 	char c[2];
618 	short legacy_warn = 0;
619 
620 	if (cs == NULL)
621 		cs = "";	/* LSCOLORS not set */
622 	len = strlen(cs);
623 	for (i = 0; i < (int)C_NUMCOLORS; i++) {
624 		colors[i].bold = 0;
625 
626 		if (len <= 2 * (size_t)i) {
627 			c[0] = defcolors[2 * i];
628 			c[1] = defcolors[2 * i + 1];
629 		} else {
630 			c[0] = cs[2 * i];
631 			c[1] = cs[2 * i + 1];
632 		}
633 		for (j = 0; j < 2; j++) {
634 			/* Legacy colours used 0-7 */
635 			if (c[j] >= '0' && c[j] <= '7') {
636 				colors[i].num[j] = c[j] - '0';
637 				if (!legacy_warn) {
638 					warnx("LSCOLORS should use "
639 					    "characters a-h instead of 0-9 ("
640 					    "see the manual page)");
641 				}
642 				legacy_warn = 1;
643 			} else if (c[j] >= 'a' && c[j] <= 'h')
644 				colors[i].num[j] = c[j] - 'a';
645 			else if (c[j] >= 'A' && c[j] <= 'H') {
646 				colors[i].num[j] = c[j] - 'A';
647 				colors[i].bold = 1;
648 			} else if (tolower((unsigned char)c[j]) == 'x')
649 				colors[i].num[j] = -1;
650 			else {
651 				warnx("invalid character '%c' in LSCOLORS"
652 				    " env var", c[j]);
653 				colors[i].num[j] = -1;
654 			}
655 		}
656 	}
657 }
658 
659 void
660 colorquit(int sig)
661 {
662 	endcolor(sig);
663 
664 	(void)signal(sig, SIG_DFL);
665 	(void)kill(getpid(), sig);
666 }
667 
668 #endif /* COLORLS */
669 
670 static void
671 printlink(const FTSENT *p)
672 {
673 	int lnklen;
674 	char name[MAXPATHLEN + 1];
675 	char path[MAXPATHLEN + 1];
676 
677 	if (p->fts_level == FTS_ROOTLEVEL)
678 		(void)snprintf(name, sizeof(name), "%s", p->fts_name);
679 	else
680 		(void)snprintf(name, sizeof(name),
681 		    "%s/%s", p->fts_parent->fts_accpath, p->fts_name);
682 	if ((lnklen = readlink(name, path, sizeof(path) - 1)) == -1) {
683 		(void)fprintf(stderr, "\nls: %s: %s\n", name, strerror(errno));
684 		return;
685 	}
686 	path[lnklen] = '\0';
687 	(void)printf(" -> ");
688 	(void)printname(path);
689 }
690 
691 static void
692 printsize(size_t width, off_t bytes)
693 {
694 
695 	if (f_humanval) {
696 		/*
697 		 * Reserve one space before the size and allocate room for
698 		 * the trailing '\0'.
699 		 */
700 		char buf[HUMANVALSTR_LEN - 1 + 1];
701 
702 		humanize_number(buf, sizeof(buf), (int64_t)bytes, "",
703 		    HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
704 		(void)printf("%*s ", (u_int)width, buf);
705 	} else if (f_thousands) {		/* with commas */
706 		/* This format assignment needed to work round gcc bug. */
707 		const char *format = "%*j'd ";
708 		(void)printf(format, (u_int)width, bytes);
709 	} else
710 		(void)printf("%*jd ", (u_int)width, bytes);
711 }
712 
713 /*
714  * Add a + after the standard rwxrwxrwx mode if the file has an
715  * ACL. strmode() reserves space at the end of the string.
716  */
717 static void
718 aclmode(char *buf, const FTSENT *p)
719 {
720 	char name[MAXPATHLEN + 1];
721 	int ret, trivial;
722 	static dev_t previous_dev = NODEV;
723 	static int supports_acls = -1;
724 	static int type = ACL_TYPE_ACCESS;
725 	acl_t facl;
726 
727 	/*
728 	 * XXX: ACLs are not supported on whiteouts and device files
729 	 * residing on UFS.
730 	 */
731 	if (S_ISCHR(p->fts_statp->st_mode) || S_ISBLK(p->fts_statp->st_mode) ||
732 	    S_ISWHT(p->fts_statp->st_mode))
733 		return;
734 
735 	if (previous_dev == p->fts_statp->st_dev && supports_acls == 0)
736 		return;
737 
738 	if (p->fts_level == FTS_ROOTLEVEL)
739 		snprintf(name, sizeof(name), "%s", p->fts_name);
740 	else
741 		snprintf(name, sizeof(name), "%s/%s",
742 		    p->fts_parent->fts_accpath, p->fts_name);
743 
744 	if (previous_dev != p->fts_statp->st_dev) {
745 		previous_dev = p->fts_statp->st_dev;
746 		supports_acls = 0;
747 
748 		ret = lpathconf(name, _PC_ACL_NFS4);
749 		if (ret > 0) {
750 			type = ACL_TYPE_NFS4;
751 			supports_acls = 1;
752 		} else if (ret < 0 && errno != EINVAL) {
753 			warn("%s", name);
754 			return;
755 		}
756 		if (supports_acls == 0) {
757 			ret = lpathconf(name, _PC_ACL_EXTENDED);
758 			if (ret > 0) {
759 				type = ACL_TYPE_ACCESS;
760 				supports_acls = 1;
761 			} else if (ret < 0 && errno != EINVAL) {
762 				warn("%s", name);
763 				return;
764 			}
765 		}
766 	}
767 	if (supports_acls == 0)
768 		return;
769 	facl = acl_get_link_np(name, type);
770 	if (facl == NULL) {
771 		warn("%s", name);
772 		return;
773 	}
774 	if (acl_is_trivial_np(facl, &trivial)) {
775 		acl_free(facl);
776 		warn("%s", name);
777 		return;
778 	}
779 	if (!trivial)
780 		buf[10] = '+';
781 	acl_free(facl);
782 }
783