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