1 /*        $NetBSD: print.c,v 1.59 2024/12/11 12:56:31 simonb 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.59 2024/12/11 12:56:31 simonb 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           printtotal(dp);                                   /* "total: %u\n" */
235 
236           if (termwidth < 2 * colwidth) {
237                     printscol(dp);
238                     return;
239           }
240 
241           /*
242            * Have to do random access in the linked list -- build a table
243            * of pointers.
244            */
245           if (dp->entries > lastentries) {
246                     FTSENT **newarray;
247 
248                     newarray = realloc(array, dp->entries * sizeof(FTSENT *));
249                     if (newarray == NULL) {
250                               warn(NULL);
251                               printscol(dp);
252                               return;
253                     }
254                     lastentries = dp->entries;
255                     array = newarray;
256           }
257           for (p = dp->list, num = 0; p; p = p->fts_link)
258                     if (p->fts_number != NO_PRINT)
259                               array[num++] = p;
260 
261           numcols = termwidth / colwidth;
262           colwidth = termwidth / numcols;                   /* spread out if possible */
263           numrows = num / numcols;
264           if (num % numcols)
265                     ++numrows;
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           printtotal(dp);                                   /* "total: %u\n" */
302 
303           if (termwidth < 2 * colwidth) {
304                     printscol(dp);
305                     return;
306           }
307 
308           numcols = termwidth / colwidth;
309           colwidth = termwidth / numcols;                   /* spread out if possible */
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),
444                                   dp->btotal * POSIX_BLOCK_SIZE,
445                                   "", HN_AUTOSCALE,
446                                   (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1)
447                                         err(1, "humanize_number");
448                               (void)printf("total %s\n", szbuf);
449                     } else {
450                               (void)printf(f_commas ? "total %'llu\n" :
451                                   "total %llu\n", (unsigned long long)
452                                   howmany(dp->btotal, blocksize));
453                     }
454           }
455 }
456 
457 static int
printtype(u_int mode)458 printtype(u_int mode)
459 {
460           switch (mode & S_IFMT) {
461           case S_IFDIR:
462                     (void)putchar('/');
463                     return (1);
464           case S_IFIFO:
465                     (void)putchar('|');
466                     return (1);
467           case S_IFLNK:
468                     (void)putchar('@');
469                     return (1);
470           case S_IFSOCK:
471                     (void)putchar('=');
472                     return (1);
473           case S_IFWHT:
474                     (void)putchar('%');
475                     return (1);
476           }
477           if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
478                     (void)putchar('*');
479                     return (1);
480           }
481           return (0);
482 }
483 
484 static void
printlink(FTSENT * p)485 printlink(FTSENT *p)
486 {
487           int lnklen;
488           char name[MAXPATHLEN + 1], path[MAXPATHLEN + 1];
489 
490           if (p->fts_level == FTS_ROOTLEVEL)
491                     (void)snprintf(name, sizeof(name), "%s", p->fts_name);
492           else
493                     (void)snprintf(name, sizeof(name),
494                         "%s/%s", p->fts_parent->fts_accpath, p->fts_name);
495           if ((lnklen = readlink(name, path, sizeof(path) - 1)) == -1) {
496                     (void)fprintf(stderr, "\nls: %s: %s\n", name, strerror(errno));
497                     return;
498           }
499           path[lnklen] = '\0';
500           (void)printf(" -> ");
501           if (f_octal || f_octal_escape)
502                     (void)safe_print(path);
503           else if (f_nonprint)
504                     (void)printescaped(path);
505           else
506                     (void)printf("%s", path);
507 }
508 
509 #ifndef SMALL
510 /*
511  * Add a + after the standard rwxrwxrwx mode if the file has an
512  * ACL. strmode() reserves space at the end of the string.
513  */
514 static void
aclmode(char * buf,const FTSENT * p)515 aclmode(char *buf, const FTSENT *p)
516 {
517           char name[MAXPATHLEN + 1];
518           int ret, trivial;
519           static dev_t previous_dev = NODEV;
520           static int supports_acls = -1;
521           static int type = ACL_TYPE_ACCESS;
522           acl_t facl;
523 
524           /*
525            * XXX: ACLs are not supported on whiteouts and device files
526            * residing on UFS.
527            */
528           if (S_ISCHR(p->fts_statp->st_mode) || S_ISBLK(p->fts_statp->st_mode) ||
529               S_ISWHT(p->fts_statp->st_mode))
530                     return;
531 
532           if (previous_dev == p->fts_statp->st_dev && supports_acls == 0)
533                     return;
534 
535           if (p->fts_level == FTS_ROOTLEVEL)
536                     snprintf(name, sizeof(name), "%s", p->fts_name);
537           else
538                     snprintf(name, sizeof(name), "%s/%s",
539                         p->fts_parent->fts_accpath, p->fts_name);
540 
541           if (supports_acls == -1 || previous_dev != p->fts_statp->st_dev) {
542                     previous_dev = p->fts_statp->st_dev;
543                     supports_acls = 0;
544 
545                     ret = lpathconf(name, _PC_ACL_NFS4);
546                     if (ret > 0) {
547                               type = ACL_TYPE_NFS4;
548                               supports_acls = 1;
549                     } else if (ret < 0 && errno != EINVAL) {
550                               warn("%s", name);
551                               return;
552                     }
553                     if (supports_acls == 0) {
554                               ret = lpathconf(name, _PC_ACL_EXTENDED);
555                               if (ret > 0) {
556                                         type = ACL_TYPE_ACCESS;
557                                         supports_acls = 1;
558                               } else if (ret < 0 && errno != EINVAL) {
559                                         warn("%s", name);
560                                         return;
561                               }
562                     }
563           }
564           if (supports_acls == 0)
565                     return;
566           facl = acl_get_link_np(name, type);
567           if (facl == NULL) {
568                     warn("%s", name);
569                     return;
570           }
571           if (acl_is_trivial_np(facl, &trivial)) {
572                     acl_free(facl);
573                     warn("%s", name);
574                     return;
575           }
576           if (!trivial)
577                     buf[10] = '+';
578           acl_free(facl);
579 }
580 #endif
581