xref: /netbsd/bin/ls/print.c (revision 7c907856)
1 /*	$NetBSD: print.c,v 1.57 2020/05/17 23:34:11 christos 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 #if 0
38 static char sccsid[] = "@(#)print.c	8.5 (Berkeley) 7/28/94";
39 #else
40 __RCSID("$NetBSD: print.c,v 1.57 2020/05/17 23:34:11 christos Exp $");
41 #endif
42 #endif /* not lint */
43 
44 #include <sys/param.h>
45 #include <sys/stat.h>
46 #ifndef SMALL
47 #include <sys/acl.h>
48 #endif
49 
50 #include <err.h>
51 #include <errno.h>
52 #include <inttypes.h>
53 #include <fts.h>
54 #include <grp.h>
55 #include <pwd.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <time.h>
60 #include <tzfile.h>
61 #include <unistd.h>
62 #include <util.h>
63 
64 #include "ls.h"
65 #include "extern.h"
66 
67 extern int termwidth;
68 
69 static int	printaname(FTSENT *, int, int);
70 static void	printlink(FTSENT *);
71 static void	printtime(time_t);
72 static void	printtotal(DISPLAY *dp);
73 static int	printtype(u_int);
74 #ifndef SMALL
75 static void	aclmode(char *, const FTSENT *);
76 #endif
77 
78 static time_t	now;
79 
80 #define	IS_NOPRINT(p)	((p)->fts_number == NO_PRINT)
81 
82 static int
safe_printpath(const FTSENT * p)83 safe_printpath(const FTSENT *p) {
84 	int chcnt;
85 
86 	if (f_fullpath) {
87 		chcnt = safe_print(p->fts_path);
88 		chcnt += safe_print("/");
89 	} else
90 		chcnt = 0;
91 	return chcnt + safe_print(p->fts_name);
92 }
93 
94 static int
printescapedpath(const FTSENT * p)95 printescapedpath(const FTSENT *p) {
96 	int chcnt;
97 
98 	if (f_fullpath) {
99 		chcnt = printescaped(p->fts_path);
100 		chcnt += printescaped("/");
101 	} else
102 		chcnt = 0;
103 
104 	return chcnt + printescaped(p->fts_name);
105 }
106 
107 static int
printpath(const FTSENT * p)108 printpath(const FTSENT *p) {
109 	if (f_fullpath)
110 		return printf("%s/%s", p->fts_path, p->fts_name);
111 	else
112 		return printf("%s", p->fts_name);
113 }
114 
115 void
printscol(DISPLAY * dp)116 printscol(DISPLAY *dp)
117 {
118 	FTSENT *p;
119 
120 	for (p = dp->list; p; p = p->fts_link) {
121 		if (IS_NOPRINT(p))
122 			continue;
123 		(void)printaname(p, dp->s_inode, dp->s_block);
124 		(void)putchar('\n');
125 	}
126 }
127 
128 void
printlong(DISPLAY * dp)129 printlong(DISPLAY *dp)
130 {
131 	struct stat *sp;
132 	FTSENT *p;
133 	NAMES *np;
134 	char buf[20], szbuf[5];
135 
136 	now = time(NULL);
137 
138 	if (!f_leafonly)
139 		printtotal(dp);		/* "total: %u\n" */
140 
141 	for (p = dp->list; p; p = p->fts_link) {
142 		if (IS_NOPRINT(p))
143 			continue;
144 		sp = p->fts_statp;
145 		if (f_inode)
146 			(void)printf("%*"PRIu64" ", dp->s_inode, sp->st_ino);
147 		if (f_size) {
148 			if (f_humanize) {
149 				if ((humanize_number(szbuf, sizeof(szbuf),
150 				    sp->st_blocks * S_BLKSIZE,
151 				    "", HN_AUTOSCALE,
152 				    (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1)
153 					err(1, "humanize_number");
154 				(void)printf("%*s ", dp->s_block, szbuf);
155 			} else {
156 				(void)printf(f_commas ? "%'*llu " : "%*llu ",
157 				    dp->s_block,
158 				    (unsigned long long)howmany(sp->st_blocks,
159 				    blocksize));
160 			}
161 		}
162 		(void)strmode(sp->st_mode, buf);
163 #ifndef SMALL
164 		aclmode(buf, p);
165 #endif
166 		np = p->fts_pointer;
167 		(void)printf("%s %*lu ", buf, dp->s_nlink,
168 		    (unsigned long)sp->st_nlink);
169 		if (!f_grouponly)
170 			(void)printf("%-*s  ", dp->s_user, np->user);
171 		(void)printf("%-*s  ", dp->s_group, np->group);
172 		if (f_flags)
173 			(void)printf("%-*s ", dp->s_flags, np->flags);
174 		if (S_ISCHR(sp->st_mode) || S_ISBLK(sp->st_mode))
175 			(void)printf("%*lld, %*lld ",
176 			    dp->s_major, (long long)major(sp->st_rdev),
177 			    dp->s_minor, (long long)minor(sp->st_rdev));
178 		else
179 			if (f_humanize) {
180 				if ((humanize_number(szbuf, sizeof(szbuf),
181 				    sp->st_size, "", HN_AUTOSCALE,
182 				    (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1)
183 					err(1, "humanize_number");
184 				(void)printf("%*s ", dp->s_size, szbuf);
185 			} else {
186 				(void)printf(f_commas ? "%'*llu " : "%*llu ",
187 				    dp->s_size, (unsigned long long)
188 				    sp->st_size);
189 			}
190 		if (f_accesstime)
191 			printtime(sp->st_atime);
192 		else if (f_statustime)
193 			printtime(sp->st_ctime);
194 		else
195 			printtime(sp->st_mtime);
196 		if (f_octal || f_octal_escape)
197 			(void)safe_printpath(p);
198 		else if (f_nonprint)
199 			(void)printescapedpath(p);
200 		else
201 			(void)printpath(p);
202 
203 		if (f_type || (f_typedir && S_ISDIR(sp->st_mode)))
204 			(void)printtype(sp->st_mode);
205 		if (S_ISLNK(sp->st_mode))
206 			printlink(p);
207 		(void)putchar('\n');
208 	}
209 }
210 
211 void
printcol(DISPLAY * dp)212 printcol(DISPLAY *dp)
213 {
214 	static FTSENT **array;
215 	static int lastentries = -1;
216 	FTSENT *p;
217 	int base, chcnt, col, colwidth, num;
218 	int numcols, numrows, row;
219 
220 	colwidth = dp->maxlen;
221 	if (f_inode)
222 		colwidth += dp->s_inode + 1;
223 	if (f_size) {
224 		if (f_humanize)
225 			colwidth += dp->s_size + 1;
226 		else
227 			colwidth += dp->s_block + 1;
228 	}
229 	if (f_type || f_typedir)
230 		colwidth += 1;
231 
232 	colwidth += 1;
233 
234 	if (termwidth < 2 * colwidth) {
235 		printscol(dp);
236 		return;
237 	}
238 
239 	/*
240 	 * Have to do random access in the linked list -- build a table
241 	 * of pointers.
242 	 */
243 	if (dp->entries > lastentries) {
244 		FTSENT **newarray;
245 
246 		newarray = realloc(array, dp->entries * sizeof(FTSENT *));
247 		if (newarray == NULL) {
248 			warn(NULL);
249 			printscol(dp);
250 			return;
251 		}
252 		lastentries = dp->entries;
253 		array = newarray;
254 	}
255 	for (p = dp->list, num = 0; p; p = p->fts_link)
256 		if (p->fts_number != NO_PRINT)
257 			array[num++] = p;
258 
259 	numcols = termwidth / colwidth;
260 	colwidth = termwidth / numcols;		/* spread out if possible */
261 	numrows = num / numcols;
262 	if (num % numcols)
263 		++numrows;
264 
265 	printtotal(dp);				/* "total: %u\n" */
266 
267 	for (row = 0; row < numrows; ++row) {
268 		for (base = row, chcnt = col = 0; col < numcols; ++col) {
269 			chcnt = printaname(array[base], dp->s_inode,
270 			    f_humanize ? dp->s_size : dp->s_block);
271 			if ((base += numrows) >= num)
272 				break;
273 			while (chcnt++ < colwidth)
274 				(void)putchar(' ');
275 		}
276 		(void)putchar('\n');
277 	}
278 }
279 
280 void
printacol(DISPLAY * dp)281 printacol(DISPLAY *dp)
282 {
283 	FTSENT *p;
284 	int chcnt, col, colwidth;
285 	int numcols;
286 
287 	colwidth = dp->maxlen;
288 	if (f_inode)
289 		colwidth += dp->s_inode + 1;
290 	if (f_size) {
291 		if (f_humanize)
292 			colwidth += dp->s_size + 1;
293 		else
294 			colwidth += dp->s_block + 1;
295 	}
296 	if (f_type || f_typedir)
297 		colwidth += 1;
298 
299 	colwidth += 1;
300 
301 	if (termwidth < 2 * colwidth) {
302 		printscol(dp);
303 		return;
304 	}
305 
306 	numcols = termwidth / colwidth;
307 	colwidth = termwidth / numcols;		/* spread out if possible */
308 
309 	printtotal(dp);				/* "total: %u\n" */
310 
311 	chcnt = col = 0;
312 	for (p = dp->list; p; p = p->fts_link) {
313 		if (IS_NOPRINT(p))
314 			continue;
315 		if (col >= numcols) {
316 			chcnt = col = 0;
317 			(void)putchar('\n');
318 		}
319 		chcnt = printaname(p, dp->s_inode,
320 		    f_humanize ? dp->s_size : dp->s_block);
321 		while (chcnt++ < colwidth)
322 			(void)putchar(' ');
323 		col++;
324 	}
325 	(void)putchar('\n');
326 }
327 
328 void
printstream(DISPLAY * dp)329 printstream(DISPLAY *dp)
330 {
331 	FTSENT *p;
332 	int col;
333 	int extwidth;
334 
335 	extwidth = 0;
336 	if (f_inode)
337 		extwidth += dp->s_inode + 1;
338 	if (f_size) {
339 		if (f_humanize)
340 			extwidth += dp->s_size + 1;
341 		else
342 			extwidth += dp->s_block + 1;
343 	}
344 	if (f_type)
345 		extwidth += 1;
346 
347 	for (col = 0, p = dp->list; p != NULL; p = p->fts_link) {
348 		if (IS_NOPRINT(p))
349 			continue;
350 		if (col > 0) {
351 			(void)putchar(','), col++;
352 			if (col + 1 + extwidth + (int)p->fts_namelen >= termwidth)
353 				(void)putchar('\n'), col = 0;
354 			else
355 				(void)putchar(' '), col++;
356 		}
357 		col += printaname(p, dp->s_inode,
358 		    f_humanize ? dp->s_size : dp->s_block);
359 	}
360 	(void)putchar('\n');
361 }
362 
363 /*
364  * print [inode] [size] name
365  * return # of characters printed, no trailing characters.
366  */
367 static int
printaname(FTSENT * p,int inodefield,int sizefield)368 printaname(FTSENT *p, int inodefield, int sizefield)
369 {
370 	struct stat *sp;
371 	int chcnt;
372 	char szbuf[5];
373 
374 	sp = p->fts_statp;
375 	chcnt = 0;
376 	if (f_inode)
377 		chcnt += printf("%*"PRIu64" ", inodefield, sp->st_ino);
378 	if (f_size) {
379 		if (f_humanize) {
380 			if ((humanize_number(szbuf, sizeof(szbuf), sp->st_size,
381 			    "", HN_AUTOSCALE,
382 			    (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1)
383 				err(1, "humanize_number");
384 			chcnt += printf("%*s ", sizefield, szbuf);
385 		} else {
386 			chcnt += printf(f_commas ? "%'*llu " : "%*llu ",
387 			    sizefield, (unsigned long long)
388 			    howmany(sp->st_blocks, blocksize));
389 		}
390 	}
391 	if (f_octal || f_octal_escape)
392 		chcnt += safe_printpath(p);
393 	else if (f_nonprint)
394 		chcnt += printescapedpath(p);
395 	else
396 		chcnt += printpath(p);
397 	if (f_type || (f_typedir && S_ISDIR(sp->st_mode)))
398 		chcnt += printtype(sp->st_mode);
399 	return (chcnt);
400 }
401 
402 static void
printtime(time_t ftime)403 printtime(time_t ftime)
404 {
405 	int i;
406 	const char *longstring;
407 
408 	if ((longstring = ctime(&ftime)) == NULL) {
409 			   /* 012345678901234567890123 */
410 		longstring = "????????????????????????";
411 	}
412 	for (i = 4; i < 11; ++i)
413 		(void)putchar(longstring[i]);
414 
415 #define	SIXMONTHS	((DAYSPERNYEAR / 2) * SECSPERDAY)
416 	if (f_sectime)
417 		for (i = 11; i < 24; i++)
418 			(void)putchar(longstring[i]);
419 	else if (ftime + SIXMONTHS > now && ftime - SIXMONTHS < now)
420 		for (i = 11; i < 16; ++i)
421 			(void)putchar(longstring[i]);
422 	else {
423 		(void)putchar(' ');
424 		for (i = 20; i < 24; ++i)
425 			(void)putchar(longstring[i]);
426 	}
427 	(void)putchar(' ');
428 }
429 
430 /*
431  * Display total used disk space in the form "total: %u\n".
432  * Note: POSIX (IEEE Std 1003.1-2001) says this should be always in 512 blocks,
433  * but we humanise it with -h, or separate it with commas with -M, and use 1024
434  * with -k.
435  */
436 static void
printtotal(DISPLAY * dp)437 printtotal(DISPLAY *dp)
438 {
439 	char szbuf[5];
440 
441 	if (dp->list->fts_level != FTS_ROOTLEVEL && (f_longform || f_size)) {
442 		if (f_humanize) {
443 			if ((humanize_number(szbuf, sizeof(szbuf), (int64_t)dp->stotal,
444 			    "", HN_AUTOSCALE,
445 			    (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1)
446 				err(1, "humanize_number");
447 			(void)printf("total %s\n", szbuf);
448 		} else {
449 			(void)printf(f_commas ? "total %'llu\n" :
450 			    "total %llu\n", (unsigned long long)
451 			    howmany(dp->btotal, blocksize));
452 		}
453 	}
454 }
455 
456 static int
printtype(u_int mode)457 printtype(u_int mode)
458 {
459 	switch (mode & S_IFMT) {
460 	case S_IFDIR:
461 		(void)putchar('/');
462 		return (1);
463 	case S_IFIFO:
464 		(void)putchar('|');
465 		return (1);
466 	case S_IFLNK:
467 		(void)putchar('@');
468 		return (1);
469 	case S_IFSOCK:
470 		(void)putchar('=');
471 		return (1);
472 	case S_IFWHT:
473 		(void)putchar('%');
474 		return (1);
475 	}
476 	if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
477 		(void)putchar('*');
478 		return (1);
479 	}
480 	return (0);
481 }
482 
483 static void
printlink(FTSENT * p)484 printlink(FTSENT *p)
485 {
486 	int lnklen;
487 	char name[MAXPATHLEN + 1], path[MAXPATHLEN + 1];
488 
489 	if (p->fts_level == FTS_ROOTLEVEL)
490 		(void)snprintf(name, sizeof(name), "%s", p->fts_name);
491 	else
492 		(void)snprintf(name, sizeof(name),
493 		    "%s/%s", p->fts_parent->fts_accpath, p->fts_name);
494 	if ((lnklen = readlink(name, path, sizeof(path) - 1)) == -1) {
495 		(void)fprintf(stderr, "\nls: %s: %s\n", name, strerror(errno));
496 		return;
497 	}
498 	path[lnklen] = '\0';
499 	(void)printf(" -> ");
500 	if (f_octal || f_octal_escape)
501 		(void)safe_print(path);
502 	else if (f_nonprint)
503 		(void)printescaped(path);
504 	else
505 		(void)printf("%s", path);
506 }
507 
508 #ifndef SMALL
509 /*
510  * Add a + after the standard rwxrwxrwx mode if the file has an
511  * ACL. strmode() reserves space at the end of the string.
512  */
513 static void
aclmode(char * buf,const FTSENT * p)514 aclmode(char *buf, const FTSENT *p)
515 {
516 	char name[MAXPATHLEN + 1];
517 	int ret, trivial;
518 	static dev_t previous_dev = NODEV;
519 	static int supports_acls = -1;
520 	static int type = ACL_TYPE_ACCESS;
521 	acl_t facl;
522 
523 	/*
524 	 * XXX: ACLs are not supported on whiteouts and device files
525 	 * residing on UFS.
526 	 */
527 	if (S_ISCHR(p->fts_statp->st_mode) || S_ISBLK(p->fts_statp->st_mode) ||
528 	    S_ISWHT(p->fts_statp->st_mode))
529 		return;
530 
531 	if (previous_dev == p->fts_statp->st_dev && supports_acls == 0)
532 		return;
533 
534 	if (p->fts_level == FTS_ROOTLEVEL)
535 		snprintf(name, sizeof(name), "%s", p->fts_name);
536 	else
537 		snprintf(name, sizeof(name), "%s/%s",
538 		    p->fts_parent->fts_accpath, p->fts_name);
539 
540 	if (supports_acls == -1 || previous_dev != p->fts_statp->st_dev) {
541 		previous_dev = p->fts_statp->st_dev;
542 		supports_acls = 0;
543 
544 		ret = lpathconf(name, _PC_ACL_NFS4);
545 		if (ret > 0) {
546 			type = ACL_TYPE_NFS4;
547 			supports_acls = 1;
548 		} else if (ret < 0 && errno != EINVAL) {
549 			warn("%s", name);
550 			return;
551 		}
552 		if (supports_acls == 0) {
553 			ret = lpathconf(name, _PC_ACL_EXTENDED);
554 			if (ret > 0) {
555 				type = ACL_TYPE_ACCESS;
556 				supports_acls = 1;
557 			} else if (ret < 0 && errno != EINVAL) {
558 				warn("%s", name);
559 				return;
560 			}
561 		}
562 	}
563 	if (supports_acls == 0)
564 		return;
565 	facl = acl_get_link_np(name, type);
566 	if (facl == NULL) {
567 		warn("%s", name);
568 		return;
569 	}
570 	if (acl_is_trivial_np(facl, &trivial)) {
571 		acl_free(facl);
572 		warn("%s", name);
573 		return;
574 	}
575 	if (!trivial)
576 		buf[10] = '+';
577 	acl_free(facl);
578 }
579 #endif
580