xref: /freebsd/bin/ls/print.c (revision bdcbfde31e8e9b343f113a1956384bdf30d1ed62)
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 #endif
37 #include <sys/cdefs.h>
38 #include <sys/param.h>
39 #include <sys/stat.h>
40 #include <sys/acl.h>
41 
42 #include <err.h>
43 #include <errno.h>
44 #include <fts.h>
45 #include <langinfo.h>
46 #include <libutil.h>
47 #include <limits.h>
48 #include <stdio.h>
49 #include <stdint.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <time.h>
53 #include <unistd.h>
54 #include <wchar.h>
55 #ifdef COLORLS
56 #include <ctype.h>
57 #include <termcap.h>
58 #include <signal.h>
59 #endif
60 
61 #include "ls.h"
62 #include "extern.h"
63 
64 static int	printaname(const FTSENT *, u_long, u_long);
65 static void	printdev(size_t, dev_t);
66 static void	printlink(const FTSENT *);
67 static void	printtime(time_t);
68 static int	printtype(u_int);
69 static void	printsize(size_t, off_t);
70 #ifdef COLORLS
71 static void	endcolor_termcap(int);
72 static void	endcolor_ansi(void);
73 static void	endcolor(int);
74 static int	colortype(mode_t);
75 #endif
76 static void	aclmode(char *, const FTSENT *);
77 
78 #define	IS_NOPRINT(p)	((p)->fts_number == NO_PRINT)
79 
80 #ifdef COLORLS
81 /* Most of these are taken from <sys/stat.h> */
82 typedef enum Colors {
83 	C_DIR,			/* directory */
84 	C_LNK,			/* symbolic link */
85 	C_SOCK,			/* socket */
86 	C_FIFO,			/* pipe */
87 	C_EXEC,			/* executable */
88 	C_BLK,			/* block special */
89 	C_CHR,			/* character special */
90 	C_SUID,			/* setuid executable */
91 	C_SGID,			/* setgid executable */
92 	C_WSDIR,		/* directory writeble to others, with sticky
93 				 * bit */
94 	C_WDIR,			/* directory writeble to others, without
95 				 * sticky bit */
96 	C_NUMCOLORS		/* just a place-holder */
97 } Colors;
98 
99 static const char *defcolors = "exfxcxdxbxegedabagacad";
100 
101 /* colors for file types */
102 static struct {
103 	int	num[2];
104 	bool	bold;
105 	bool	underline;
106 } colors[C_NUMCOLORS];
107 #endif
108 
109 static size_t padding_for_month[12];
110 static size_t month_max_size = 0;
111 
112 void
113 printscol(const DISPLAY *dp)
114 {
115 	FTSENT *p;
116 
117 	for (p = dp->list; p; p = p->fts_link) {
118 		if (IS_NOPRINT(p))
119 			continue;
120 		(void)printaname(p, dp->s_inode, dp->s_block);
121 		(void)putchar('\n');
122 	}
123 }
124 
125 /*
126  * print name in current style
127  */
128 int
129 printname(const char *name)
130 {
131 	if (f_octal || f_octal_escape)
132 		return prn_octal(name);
133 	else if (f_nonprint)
134 		return prn_printable(name);
135 	else
136 		return prn_normal(name);
137 }
138 
139 static const char *
140 get_abmon(int mon)
141 {
142 
143 	switch (mon) {
144 	case 0: return (nl_langinfo(ABMON_1));
145 	case 1: return (nl_langinfo(ABMON_2));
146 	case 2: return (nl_langinfo(ABMON_3));
147 	case 3: return (nl_langinfo(ABMON_4));
148 	case 4: return (nl_langinfo(ABMON_5));
149 	case 5: return (nl_langinfo(ABMON_6));
150 	case 6: return (nl_langinfo(ABMON_7));
151 	case 7: return (nl_langinfo(ABMON_8));
152 	case 8: return (nl_langinfo(ABMON_9));
153 	case 9: return (nl_langinfo(ABMON_10));
154 	case 10: return (nl_langinfo(ABMON_11));
155 	case 11: return (nl_langinfo(ABMON_12));
156 	}
157 
158 	/* should never happen */
159 	abort();
160 }
161 
162 static size_t
163 mbswidth(const char *month)
164 {
165 	wchar_t wc;
166 	size_t width, donelen, clen, w;
167 
168 	width = donelen = 0;
169 	while ((clen = mbrtowc(&wc, month + donelen, MB_LEN_MAX, NULL)) != 0) {
170 		if (clen == (size_t)-1 || clen == (size_t)-2)
171 			return (-1);
172 		donelen += clen;
173 		if ((w = wcwidth(wc)) == (size_t)-1)
174 			return (-1);
175 		width += w;
176 	}
177 
178 	return (width);
179 }
180 
181 static void
182 compute_abbreviated_month_size(void)
183 {
184 	int i;
185 	size_t width;
186 	size_t months_width[12];
187 
188 	for (i = 0; i < 12; i++) {
189 		width = mbswidth(get_abmon(i));
190 		if (width == (size_t)-1) {
191 			month_max_size = -1;
192 			return;
193 		}
194 		months_width[i] = width;
195 		if (width > month_max_size)
196 			month_max_size = width;
197 	}
198 
199 	for (i = 0; i < 12; i++)
200 		padding_for_month[i] = month_max_size - months_width[i];
201 }
202 
203 void
204 printlong(const DISPLAY *dp)
205 {
206 	struct stat *sp;
207 	FTSENT *p;
208 	NAMES *np;
209 	char buf[20];
210 #ifdef COLORLS
211 	int color_printed = 0;
212 #endif
213 
214 	if ((dp->list == NULL || dp->list->fts_level != FTS_ROOTLEVEL) &&
215 	    (f_longform || f_size)) {
216 		(void)printf("total %lu\n", howmany(dp->btotal, blocksize));
217 	}
218 
219 	for (p = dp->list; p; p = p->fts_link) {
220 		if (IS_NOPRINT(p))
221 			continue;
222 		sp = p->fts_statp;
223 		if (f_inode)
224 			(void)printf("%*ju ",
225 			    dp->s_inode, (uintmax_t)sp->st_ino);
226 		if (f_size)
227 			(void)printf("%*jd ",
228 			    dp->s_block, howmany(sp->st_blocks, blocksize));
229 		strmode(sp->st_mode, buf);
230 		aclmode(buf, p);
231 		np = p->fts_pointer;
232 		(void)printf("%s %*ju ", buf, dp->s_nlink,
233 		    (uintmax_t)sp->st_nlink);
234 		if (!f_sowner)
235 			(void)printf("%-*s ", dp->s_user, np->user);
236 		(void)printf("%-*s ", dp->s_group, 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 void
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 
438 	if ((posb = strstr(fmt, "%b")) != NULL) {
439 		if (month_max_size == 0) {
440 			compute_abbreviated_month_size();
441 		}
442 		if (month_max_size > 0 && tm != NULL) {
443 			snprintf(nfmt, sizeof(nfmt),  "%.*s%s%*s%s",
444 			    (int)(posb - fmt), fmt,
445 			    get_abmon(tm->tm_mon),
446 			    (int)padding_for_month[tm->tm_mon],
447 			    "",
448 			    posb + 2);
449 			format = nfmt;
450 		}
451 	}
452 	if (tm != NULL)
453 		strftime(str, len, format, tm);
454 	else
455 		strlcpy(str, "bad date val", len);
456 }
457 
458 static void
459 printtime(time_t ftime)
460 {
461 	char longstring[80];
462 	static time_t now = 0;
463 	const char *format;
464 	static int d_first = -1;
465 
466 	if (d_first < 0)
467 		d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
468 	if (now == 0)
469 		now = time(NULL);
470 
471 #define	SIXMONTHS	((365 / 2) * 86400)
472 	if (f_timeformat)  /* user specified format */
473 		format = f_timeformat;
474 	else if (f_sectime)
475 		/* mmm dd hh:mm:ss yyyy || dd mmm hh:mm:ss yyyy */
476 		format = d_first ? "%e %b %T %Y" : "%b %e %T %Y";
477 	else if (ftime + SIXMONTHS > now && ftime < now + SIXMONTHS)
478 		/* mmm dd hh:mm || dd mmm hh:mm */
479 		format = d_first ? "%e %b %R" : "%b %e %R";
480 	else
481 		/* mmm dd  yyyy || dd mmm  yyyy */
482 		format = d_first ? "%e %b  %Y" : "%b %e  %Y";
483 	ls_strftime(longstring, sizeof(longstring), format, localtime(&ftime));
484 	fputs(longstring, stdout);
485 	fputc(' ', stdout);
486 }
487 
488 static int
489 printtype(u_int mode)
490 {
491 
492 	if (f_slash) {
493 		if ((mode & S_IFMT) == S_IFDIR) {
494 			(void)putchar('/');
495 			return (1);
496 		}
497 		return (0);
498 	}
499 
500 	switch (mode & S_IFMT) {
501 	case S_IFDIR:
502 		(void)putchar('/');
503 		return (1);
504 	case S_IFIFO:
505 		(void)putchar('|');
506 		return (1);
507 	case S_IFLNK:
508 		(void)putchar('@');
509 		return (1);
510 	case S_IFSOCK:
511 		(void)putchar('=');
512 		return (1);
513 	case S_IFWHT:
514 		(void)putchar('%');
515 		return (1);
516 	default:
517 		break;
518 	}
519 	if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
520 		(void)putchar('*');
521 		return (1);
522 	}
523 	return (0);
524 }
525 
526 #ifdef COLORLS
527 static int
528 putch(int c)
529 {
530 	(void)putchar(c);
531 	return 0;
532 }
533 
534 static int
535 writech(int c)
536 {
537 	char tmp = (char)c;
538 
539 	(void)write(STDOUT_FILENO, &tmp, 1);
540 	return 0;
541 }
542 
543 static void
544 printcolor_termcap(Colors c)
545 {
546 	char *ansiseq;
547 
548 	if (colors[c].bold)
549 		tputs(enter_bold, 1, putch);
550 	if (colors[c].underline)
551 		tputs(enter_underline, 1, putch);
552 
553 	if (colors[c].num[0] != -1) {
554 		ansiseq = tgoto(ansi_fgcol, 0, colors[c].num[0]);
555 		if (ansiseq)
556 			tputs(ansiseq, 1, putch);
557 	}
558 	if (colors[c].num[1] != -1) {
559 		ansiseq = tgoto(ansi_bgcol, 0, colors[c].num[1]);
560 		if (ansiseq)
561 			tputs(ansiseq, 1, putch);
562 	}
563 }
564 
565 static void
566 printcolor_ansi(Colors c)
567 {
568 
569 	printf("\033[");
570 
571 	if (colors[c].bold)
572 		printf("1");
573 	if (colors[c].underline)
574 		printf(";4");
575 	if (colors[c].num[0] != -1)
576 		printf(";3%d", colors[c].num[0]);
577 	if (colors[c].num[1] != -1)
578 		printf(";4%d", colors[c].num[1]);
579 	printf("m");
580 }
581 
582 static void
583 printcolor(Colors c)
584 {
585 
586 	if (explicitansi)
587 		printcolor_ansi(c);
588 	else
589 		printcolor_termcap(c);
590 }
591 
592 static void
593 endcolor_termcap(int sig)
594 {
595 
596 	tputs(ansi_coloff, 1, sig ? writech : putch);
597 	tputs(attrs_off, 1, sig ? writech : putch);
598 }
599 
600 static void
601 endcolor_ansi(void)
602 {
603 
604 	printf("\33[m");
605 }
606 
607 static void
608 endcolor(int sig)
609 {
610 
611 	if (explicitansi)
612 		endcolor_ansi();
613 	else
614 		endcolor_termcap(sig);
615 }
616 
617 static int
618 colortype(mode_t mode)
619 {
620 	switch (mode & S_IFMT) {
621 	case S_IFDIR:
622 		if (mode & S_IWOTH)
623 			if (mode & S_ISTXT)
624 				printcolor(C_WSDIR);
625 			else
626 				printcolor(C_WDIR);
627 		else
628 			printcolor(C_DIR);
629 		return (1);
630 	case S_IFLNK:
631 		printcolor(C_LNK);
632 		return (1);
633 	case S_IFSOCK:
634 		printcolor(C_SOCK);
635 		return (1);
636 	case S_IFIFO:
637 		printcolor(C_FIFO);
638 		return (1);
639 	case S_IFBLK:
640 		printcolor(C_BLK);
641 		return (1);
642 	case S_IFCHR:
643 		printcolor(C_CHR);
644 		return (1);
645 	default:;
646 	}
647 	if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
648 		if (mode & S_ISUID)
649 			printcolor(C_SUID);
650 		else if (mode & S_ISGID)
651 			printcolor(C_SGID);
652 		else
653 			printcolor(C_EXEC);
654 		return (1);
655 	}
656 	return (0);
657 }
658 
659 void
660 parsecolors(const char *cs)
661 {
662 	int i;
663 	int j;
664 	size_t len;
665 	char c[2];
666 	short legacy_warn = 0;
667 
668 	if (cs == NULL)
669 		cs = "";	/* LSCOLORS not set */
670 	len = strlen(cs);
671 	for (i = 0; i < (int)C_NUMCOLORS; i++) {
672 		colors[i].bold = false;
673 		colors[i].underline = false;
674 
675 		if (len <= 2 * (size_t)i) {
676 			c[0] = defcolors[2 * i];
677 			c[1] = defcolors[2 * i + 1];
678 		} else {
679 			c[0] = cs[2 * i];
680 			c[1] = cs[2 * i + 1];
681 		}
682 		for (j = 0; j < 2; j++) {
683 			/* Legacy colours used 0-7 */
684 			if (c[j] >= '0' && c[j] <= '7') {
685 				colors[i].num[j] = c[j] - '0';
686 				if (!legacy_warn) {
687 					warnx("LSCOLORS should use "
688 					    "characters a-h instead of 0-9 ("
689 					    "see the manual page)");
690 				}
691 				legacy_warn = 1;
692 			} else if (c[j] >= 'a' && c[j] <= 'h')
693 				colors[i].num[j] = c[j] - 'a';
694 			else if (c[j] >= 'A' && c[j] <= 'H') {
695 				colors[i].num[j] = c[j] - 'A';
696 				if (j == 1)
697 					colors[i].underline = true;
698 				else
699 					colors[i].bold = true;
700 			} else if (tolower((unsigned char)c[j]) == 'x') {
701 				if (j == 1 && c[j] == 'X')
702 					colors[i].underline = true;
703 				colors[i].num[j] = -1;
704 			} else {
705 				warnx("invalid character '%c' in LSCOLORS"
706 				    " env var", c[j]);
707 				colors[i].num[j] = -1;
708 			}
709 		}
710 	}
711 }
712 
713 void
714 colorquit(int sig)
715 {
716 	endcolor(sig);
717 
718 	(void)signal(sig, SIG_DFL);
719 	(void)kill(getpid(), sig);
720 }
721 
722 #endif /* COLORLS */
723 
724 static void
725 printlink(const FTSENT *p)
726 {
727 	int lnklen;
728 	char name[MAXPATHLEN + 1];
729 	char path[MAXPATHLEN + 1];
730 
731 	if (p->fts_level == FTS_ROOTLEVEL)
732 		(void)snprintf(name, sizeof(name), "%s", p->fts_name);
733 	else
734 		(void)snprintf(name, sizeof(name),
735 		    "%s/%s", p->fts_parent->fts_accpath, p->fts_name);
736 	if ((lnklen = readlink(name, path, sizeof(path) - 1)) == -1) {
737 		(void)fprintf(stderr, "\nls: %s: %s\n", name, strerror(errno));
738 		return;
739 	}
740 	path[lnklen] = '\0';
741 	(void)printf(" -> ");
742 	(void)printname(path);
743 }
744 
745 static void
746 printsize(size_t width, off_t bytes)
747 {
748 
749 	if (f_humanval) {
750 		/*
751 		 * Reserve one space before the size and allocate room for
752 		 * the trailing '\0'.
753 		 */
754 		char buf[HUMANVALSTR_LEN - 1 + 1];
755 
756 		humanize_number(buf, sizeof(buf), (int64_t)bytes, "",
757 		    HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
758 		(void)printf("%*s ", (u_int)width, buf);
759 	} else if (f_thousands) {		/* with commas */
760 		/* This format assignment needed to work round gcc bug. */
761 		const char *format = "%*j'd ";
762 		(void)printf(format, (u_int)width, bytes);
763 	} else
764 		(void)printf("%*jd ", (u_int)width, bytes);
765 }
766 
767 /*
768  * Add a + after the standard rwxrwxrwx mode if the file has an
769  * ACL. strmode() reserves space at the end of the string.
770  */
771 static void
772 aclmode(char *buf, const FTSENT *p)
773 {
774 	char name[MAXPATHLEN + 1];
775 	int ret, trivial;
776 	static dev_t previous_dev = NODEV;
777 	static int supports_acls = -1;
778 	static int type = ACL_TYPE_ACCESS;
779 	acl_t facl;
780 
781 	/*
782 	 * XXX: ACLs are not supported on whiteouts and device files
783 	 * residing on UFS.
784 	 */
785 	if (S_ISCHR(p->fts_statp->st_mode) || S_ISBLK(p->fts_statp->st_mode) ||
786 	    S_ISWHT(p->fts_statp->st_mode))
787 		return;
788 
789 	if (previous_dev == p->fts_statp->st_dev && supports_acls == 0)
790 		return;
791 
792 	if (p->fts_level == FTS_ROOTLEVEL)
793 		snprintf(name, sizeof(name), "%s", p->fts_name);
794 	else
795 		snprintf(name, sizeof(name), "%s/%s",
796 		    p->fts_parent->fts_accpath, p->fts_name);
797 
798 	if (previous_dev != p->fts_statp->st_dev) {
799 		previous_dev = p->fts_statp->st_dev;
800 		supports_acls = 0;
801 
802 		ret = lpathconf(name, _PC_ACL_NFS4);
803 		if (ret > 0) {
804 			type = ACL_TYPE_NFS4;
805 			supports_acls = 1;
806 		} else if (ret < 0 && errno != EINVAL) {
807 			warn("%s", name);
808 			return;
809 		}
810 		if (supports_acls == 0) {
811 			ret = lpathconf(name, _PC_ACL_EXTENDED);
812 			if (ret > 0) {
813 				type = ACL_TYPE_ACCESS;
814 				supports_acls = 1;
815 			} else if (ret < 0 && errno != EINVAL) {
816 				warn("%s", name);
817 				return;
818 			}
819 		}
820 	}
821 	if (supports_acls == 0)
822 		return;
823 	facl = acl_get_link_np(name, type);
824 	if (facl == NULL) {
825 		warn("%s", name);
826 		return;
827 	}
828 	if (acl_is_trivial_np(facl, &trivial)) {
829 		acl_free(facl);
830 		warn("%s", name);
831 		return;
832 	}
833 	if (!trivial)
834 		buf[10] = '+';
835 	acl_free(facl);
836 }
837