xref: /freebsd/bin/ls/print.c (revision 0b3105a37d7adcadcb720112fed4dc4e8040be99)
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
35 static char sccsid[] = "@(#)print.c	8.4 (Berkeley) 4/17/94";
36 #endif /* not lint */
37 #endif
38 #include <sys/cdefs.h>
39 __FBSDID("$FreeBSD$");
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 
68 static int	printaname(const FTSENT *, u_long, u_long);
69 static void	printdev(size_t, dev_t);
70 static void	printlink(const FTSENT *);
71 static void	printtime(const char *, time_t);
72 static int	printtype(u_int);
73 static void	printsize(const char *, size_t, off_t);
74 #ifdef COLORLS
75 static void	endcolor(int);
76 static int	colortype(mode_t);
77 #endif
78 static 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> */
84 typedef 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 
101 static const char *defcolors = "exfxcxdxbxegedabagacad";
102 
103 /* colors for file types */
104 static struct {
105 	int	num[2];
106 	int	bold;
107 } colors[C_NUMCOLORS];
108 #endif
109 
110 static size_t padding_for_month[12];
111 static size_t month_max_size = 0;
112 
113 void
114 printscol(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  */
133 int
134 printname(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 
146 static const char *
147 get_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 
169 static size_t
170 mbswidth(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 
188 static void
189 compute_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  */
213 char *
214 getname(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 
224 void
225 printlong(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/%*u} {td:user/%-*s}{e:user/%ju}  {td:group/%-*s}{e:group/%ju}  ",
263 				buf, (int) sp->st_mode & ALLPERMS, dp->s_nlink, 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/%*u} {t:user/%-*s}  {t:group/%-*s}  ",
267 				buf, (int) sp->st_mode & ALLPERMS, dp->s_nlink, 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 
328 void
329 printstream(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 
357 void
358 printcol(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  */
457 static int
458 printaname(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  */
491 static void
492 printdev(size_t width, dev_t dev)
493 {
494 	xo_emit("{:device/%#*jx} ", (u_int)width, (uintmax_t)dev);
495 }
496 
497 static size_t
498 ls_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 
522 static void
523 printtime(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 
557 static int
558 printtype(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
596 static int
597 putch(int c)
598 {
599 	xo_emit("{D:/%c}", c);
600 	return 0;
601 }
602 
603 static int
604 writech(int c)
605 {
606 	char tmp = (char)c;
607 
608 	(void)write(STDOUT_FILENO, &tmp, 1);
609 	return 0;
610 }
611 
612 static void
613 printcolor(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 
632 static void
633 endcolor(int sig)
634 {
635 	tputs(ansi_coloff, 1, sig ? writech : putch);
636 	tputs(attrs_off, 1, sig ? writech : putch);
637 }
638 
639 static int
640 colortype(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 
681 void
682 parsecolors(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 
729 void
730 colorquit(int sig)
731 {
732 	endcolor(sig);
733 
734 	(void)signal(sig, SIG_DFL);
735 	(void)kill(getpid(), sig);
736 }
737 
738 #endif /* COLORLS */
739 
740 static void
741 printlink(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 
761 static void
762 printsize(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  */
790 static void
791 aclmode(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