1/* see license file for copyright and license details */
2
3#include <sys/stat.h>
4#include <sys/types.h>
5#ifndef major
6#include <sys/sysmacros.h>
7#endif
8
9#include <dirent.h>
10#include <grp.h>
11#include <pwd.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <time.h>
16#include <unistd.h>
17
18#include "utf.h"
19#include "util.h"
20
21struct entry {
22 char *name;
23 mode_t mode, tmode;
24 nlink_t nlink;
25 uid_t uid;
26 gid_t gid;
27 off_t size;
28 struct timespec t;
29 dev_t dev;
30 dev_t rdev;
31 ino_t ino, tino;
32};
33
34static struct {
35 dev_t dev;
36 ino_t ino;
37} *tree;
38
39static int ret = 0;
40static int Aflag = 0;
41static int aflag = 0;
42static int cflag = 0;
43static int dflag = 0;
44static int Fflag = 0;
45static int fflag = 0;
46static int Hflag = 0;
47static int hflag = 0;
48static int iflag = 0;
49static int Lflag = 0;
50static int lflag = 0;
51static int nflag = 0;
52static int pflag = 0;
53static int qflag = 0;
54static int Rflag = 0;
55static int rflag = 0;
56static int Uflag = 0;
57static int uflag = 0;
58static int first = 1;
59static char sort = 0;
60static int showdirs;
61
62static int gflag = 0;
63static int oflag = 0;
64
65static int Cflag = 0;
66static int one_flag = 0;
67static int termwidth = 80;
68
69#if FEATURE_LS_COLOR
70#define COLOR_DIR "\033[1;34m"
71#define COLOR_LNK "\033[1;36m"
72#define COLOR_FIFO "\033[33m"
73#define COLOR_SOCK "\033[1;35m"
74#define COLOR_DEV "\033[1;33m"
75#define COLOR_EXE "\033[1;32m"
76#define COLOR_RST "\033[0m"
77
78enum { COLOR_NEVER, COLOR_ALWAYS, COLOR_AUTO };
79static int color_mode = COLOR_NEVER;
80#endif
81
82static void ls(const char *, const struct entry *, int);
83static void printname_colored(const char *, mode_t);
84static void printcols(const struct entry *, size_t);
85static void output(const struct entry *);
86
87static void
88mkent(struct entry *ent, char *path, int dostat, int follow)
89{
90 struct stat st;
91
92 ent->name = path;
93 if (!dostat)
94 return;
95 if ((follow ? stat : lstat)(path, &st) < 0)
96 eprintf("%s %s:", follow ? "stat" : "lstat", path);
97 ent->mode = st.st_mode;
98 ent->nlink = st.st_nlink;
99 ent->uid = st.st_uid;
100 ent->gid = st.st_gid;
101 ent->size = st.st_size;
102 if (cflag)
103 ent->t = st.st_ctim;
104 else if (uflag)
105 ent->t = st.st_atim;
106 else
107 ent->t = st.st_mtim;
108 ent->dev = st.st_dev;
109 ent->rdev = st.st_rdev;
110 ent->ino = st.st_ino;
111 if (S_ISLNK(ent->mode)) {
112 if (stat(path, &st) == 0) {
113 ent->tmode = st.st_mode;
114 ent->dev = st.st_dev;
115 ent->tino = st.st_ino;
116 } else {
117 ent->tmode = ent->tino = 0;
118 }
119 }
120}
121
122static char *
123indicator(mode_t mode)
124{
125 if (pflag || Fflag)
126 if (S_ISDIR(mode))
127 return "/";
128
129 if (Fflag) {
130 if (S_ISLNK(mode))
131 return "@";
132 else if (S_ISFIFO(mode))
133 return "|";
134 else if (S_ISSOCK(mode))
135 return "=";
136 else if (mode & S_IXUSR || mode & S_IXGRP || mode & S_IXOTH)
137 return "*";
138 }
139
140 return "";
141}
142
143static void
144printname(const char *name)
145{
146 const char *c;
147 Rune r;
148 size_t l;
149
150 for (c = name; *c; c += l) {
151 l = chartorune(&r, c);
152 if (!qflag || isprintrune(r))
153 fwrite(c, 1, l, stdout);
154 else
155 putchar('?');
156 }
157}
158
159static int
160should_color(void)
161{
162#if FEATURE_LS_COLOR
163 if (color_mode == COLOR_ALWAYS)
164 return 1;
165 if (color_mode == COLOR_AUTO)
166 return isatty(STDOUT_FILENO);
167#endif
168 return 0;
169}
170
171static void
172printname_colored(const char *name, mode_t mode)
173{
174#if FEATURE_LS_COLOR
175 int need_reset = 0;
176
177 if (should_color()) {
178 if (S_ISDIR(mode)) {
179 fputs(COLOR_DIR, stdout);
180 need_reset = 1;
181 } else if (S_ISLNK(mode)) {
182 fputs(COLOR_LNK, stdout);
183 need_reset = 1;
184 } else if (S_ISFIFO(mode)) {
185 fputs(COLOR_FIFO, stdout);
186 need_reset = 1;
187 } else if (S_ISSOCK(mode)) {
188 fputs(COLOR_SOCK, stdout);
189 need_reset = 1;
190 } else if (S_ISBLK(mode) || S_ISCHR(mode)) {
191 fputs(COLOR_DEV, stdout);
192 need_reset = 1;
193 } else if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
194 fputs(COLOR_EXE, stdout);
195 need_reset = 1;
196 }
197 }
198 printname(name);
199 if (need_reset)
200 fputs(COLOR_RST, stdout);
201#else
202 (void)mode;
203 printname(name);
204#endif
205}
206
207#include <sys/ioctl.h>
208
209static void
210gettermwidth(void)
211{
212 struct winsize ws;
213
214 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0)
215 termwidth = ws.ws_col;
216 else
217 termwidth = 80;
218}
219
220static size_t
221entrywidth(const struct entry *ent)
222{
223 size_t w;
224 char buf[32];
225
226 w = utflen(ent->name);
227 if (iflag) {
228 snprintf(buf, sizeof(buf), "%lu ", (unsigned long)ent->ino);
229 w += strlen(buf);
230 }
231 w += strlen(indicator(ent->mode));
232 return w;
233}
234
235static void
236printcols(const struct entry *ents, size_t n)
237{
238 int i, r, c, ncols, nrows, total_width;
239 int *colwidths;
240 int maxcols;
241
242 if (n == 0)
243 return;
244
245 gettermwidth();
246
247 colwidths = ecalloc(n, sizeof(*colwidths));
248
249 maxcols = termwidth / 2;
250 if (maxcols > (int)n)
251 maxcols = n;
252
253 for (ncols = maxcols; ncols > 1; ncols--) {
254 nrows = (n + ncols - 1) / ncols;
255 total_width = 0;
256
257 for (c = 0; c < ncols; c++) {
258 int maxw = 0;
259 for (r = 0; r < nrows; r++) {
260 int idx = c * nrows + r;
261 if (idx < (int)n) {
262 int w = entrywidth(&ents[idx]);
263 if (w > maxw)
264 maxw = w;
265 }
266 }
267 colwidths[c] = maxw;
268 total_width += maxw;
269 }
270 total_width += 2 * (ncols - 1);
271
272 if (total_width < termwidth)
273 break;
274 }
275
276 if (ncols <= 1) {
277 for (i = 0; i < (int)n; i++) {
278 output(&ents[i]);
279 }
280 free(colwidths);
281 return;
282 }
283
284 nrows = (n + ncols - 1) / ncols;
285 for (r = 0; r < nrows; r++) {
286 for (c = 0; c < ncols; c++) {
287 int idx = c * nrows + r;
288 if (idx < (int)n) {
289 int w = entrywidth(&ents[idx]);
290 if (iflag)
291 printf("%lu ", (unsigned long)ents[idx].ino);
292 printname_colored(ents[idx].name, ents[idx].mode);
293 fputs(indicator(ents[idx].mode), stdout);
294
295 if (c < ncols - 1 && (c + 1) * nrows + r < (int)n) {
296 int pad = colwidths[c] - w + 2;
297 while (pad-- > 0)
298 putchar(' ');
299 }
300 }
301 }
302 putchar('\n');
303 }
304
305 free(colwidths);
306}
307
308static void
309output(const struct entry *ent)
310{
311 struct group *gr;
312 struct passwd *pw;
313 struct tm *tm;
314 ssize_t len;
315 char *fmt, buf[BUFSIZ], pwname[_SC_LOGIN_NAME_MAX], grname[_SC_LOGIN_NAME_MAX],
316 mode[] = "----------";
317
318 if (iflag)
319 printf("%lu ", (unsigned long)ent->ino);
320 if (!lflag) {
321 printname_colored(ent->name, ent->mode);
322 puts(indicator(ent->mode));
323 return;
324 }
325 if (S_ISREG(ent->mode))
326 mode[0] = '-';
327 else if (S_ISBLK(ent->mode))
328 mode[0] = 'b';
329 else if (S_ISCHR(ent->mode))
330 mode[0] = 'c';
331 else if (S_ISDIR(ent->mode))
332 mode[0] = 'd';
333 else if (S_ISFIFO(ent->mode))
334 mode[0] = 'p';
335 else if (S_ISLNK(ent->mode))
336 mode[0] = 'l';
337 else if (S_ISSOCK(ent->mode))
338 mode[0] = 's';
339 else
340 mode[0] = '?';
341
342 if (ent->mode & S_IRUSR)
343 mode[1] = 'r';
344 if (ent->mode & S_IWUSR)
345 mode[2] = 'w';
346 if (ent->mode & S_IXUSR)
347 mode[3] = 'x';
348 if (ent->mode & S_IRGRP)
349 mode[4] = 'r';
350 if (ent->mode & S_IWGRP)
351 mode[5] = 'w';
352 if (ent->mode & S_IXGRP)
353 mode[6] = 'x';
354 if (ent->mode & S_IROTH)
355 mode[7] = 'r';
356 if (ent->mode & S_IWOTH)
357 mode[8] = 'w';
358 if (ent->mode & S_IXOTH)
359 mode[9] = 'x';
360
361 if (ent->mode & S_ISUID)
362 mode[3] = (mode[3] == 'x') ? 's' : 'S';
363 if (ent->mode & S_ISGID)
364 mode[6] = (mode[6] == 'x') ? 's' : 'S';
365 if (ent->mode & S_ISVTX)
366 mode[9] = (mode[9] == 'x') ? 't' : 'T';
367
368 if (!nflag && (pw = getpwuid(ent->uid)))
369 snprintf(pwname, sizeof(pwname), "%s", pw->pw_name);
370 else
371 snprintf(pwname, sizeof(pwname), "%d", ent->uid);
372
373 if (!nflag && (gr = getgrgid(ent->gid)))
374 snprintf(grname, sizeof(grname), "%s", gr->gr_name);
375 else
376 snprintf(grname, sizeof(grname), "%d", ent->gid);
377
378 if (time(NULL) > ent->t.tv_sec + (180 * 24 * 60 * 60)) /* 6 months ago? */
379 fmt = "%b %d %Y";
380 else
381 fmt = "%b %d %H:%M";
382
383 if ((tm = localtime(&ent->t.tv_sec)))
384 strftime(buf, sizeof(buf), fmt, tm);
385 else
386 snprintf(buf, sizeof(buf), "%lld", (long long)(ent->t.tv_sec));
387 printf("%s %4ld ", mode, (long)ent->nlink);
388 if (!gflag)
389 printf("%-8.8s ", pwname);
390 if (!oflag)
391 printf("%-8.8s ", grname);
392
393 if (S_ISBLK(ent->mode) || S_ISCHR(ent->mode))
394 printf("%4u, %4u ", major(ent->rdev), minor(ent->rdev));
395 else if (hflag)
396 printf("%10s ", humansize(ent->size));
397 else
398 printf("%10lu ", (unsigned long)ent->size);
399 printf("%s ", buf);
400 printname_colored(ent->name, ent->mode);
401 fputs(indicator(ent->mode), stdout);
402 if (S_ISLNK(ent->mode)) {
403 if ((len = readlink(ent->name, buf, sizeof(buf) - 1)) < 0)
404 eprintf("readlink %s:", ent->name);
405 buf[len] = '\0';
406 printf(" -> ");
407 printname_colored(buf, ent->tmode);
408 fputs(indicator(ent->tmode), stdout);
409 }
410 putchar('\n');
411}
412
413static int
414entcmp(const void *va, const void *vb)
415{
416 int cmp = 0;
417 const struct entry *a = va, *b = vb;
418
419 switch (sort) {
420 // ?man -S: sort by file size
421 case 'S':
422 cmp = b->size - a->size;
423 break;
424 // ?man -t: sort by modification time
425 case 't':
426 if (!(cmp = b->t.tv_sec - a->t.tv_sec))
427 cmp = b->t.tv_nsec - a->t.tv_nsec;
428 break;
429 }
430
431 if (!cmp)
432 cmp = strcmp(a->name, b->name);
433
434 return rflag ? 0 - cmp : cmp;
435}
436
437static void
438lsdir(const char *path, const struct entry *dir)
439{
440 DIR *dp;
441 struct entry *ent, *ents = NULL;
442 struct dirent *d;
443 size_t i, n = 0;
444 char prefix[PATH_MAX];
445
446 if (!(dp = opendir(dir->name))) {
447 ret = 1;
448 weprintf("opendir %s%s:", path, dir->name);
449 return;
450 }
451 if (chdir(dir->name) < 0)
452 eprintf("chdir %s:", dir->name);
453
454 while ((d = readdir(dp))) {
455 if (d->d_name[0] == '.' && !aflag && !Aflag)
456 continue;
457 else if (Aflag)
458 if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0)
459 continue;
460
461 ents = ereallocarray(ents, ++n, sizeof(*ents));
462 mkent(
463 &ents[n - 1],
464 estrdup(d->d_name),
465 Fflag || iflag || lflag || pflag || Rflag || sort || should_color(),
466 Lflag
467 );
468 }
469
470 closedir(dp);
471
472 if (!Uflag)
473 qsort(ents, n, sizeof(*ents), entcmp);
474
475 if (path[0] || showdirs) {
476 fputs(path, stdout);
477 printname(dir->name);
478 puts(":");
479 }
480 if (!lflag && Cflag) {
481 printcols(ents, n);
482 } else {
483 for (i = 0; i < n; i++)
484 output(&ents[i]);
485 }
486
487 if (Rflag) {
488 if (snprintf(prefix, PATH_MAX, "%s%s/", path, dir->name) >= PATH_MAX)
489 eprintf("path too long: %s%s\n", path, dir->name);
490
491 for (i = 0; i < n; i++) {
492 ent = &ents[i];
493 if (strcmp(ent->name, ".") == 0 || strcmp(ent->name, "..") == 0)
494 continue;
495 if (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode) && !Lflag)
496 continue;
497
498 ls(prefix, ent, 1);
499 }
500 }
501
502 for (i = 0; i < n; ++i)
503 free(ents[i].name);
504 free(ents);
505}
506
507static int
508visit(const struct entry *ent)
509{
510 dev_t dev;
511 ino_t ino;
512 int i;
513
514 dev = ent->dev;
515 ino = S_ISLNK(ent->mode) ? ent->tino : ent->ino;
516
517 for (i = 0; i < PATH_MAX && tree[i].ino; ++i) {
518 if (ino == tree[i].ino && dev == tree[i].dev)
519 return -1;
520 }
521
522 tree[i].ino = ino;
523 tree[i].dev = dev;
524
525 return i;
526}
527
528static void
529ls(const char *path, const struct entry *ent, int listdir)
530{
531 int treeind;
532 char cwd[PATH_MAX];
533
534 if (!listdir) {
535 output(ent);
536 } else if (S_ISDIR(ent->mode) || (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode))) {
537 if ((treeind = visit(ent)) < 0) {
538 ret = 1;
539 weprintf("%s%s: Already visited\n", path, ent->name);
540 return;
541 }
542
543 if (!getcwd(cwd, PATH_MAX))
544 eprintf("getcwd:");
545
546 if (first)
547 first = 0;
548 else
549 putchar('\n');
550
551 lsdir(path, ent);
552 tree[treeind].ino = 0;
553
554 if (chdir(cwd) < 0)
555 eprintf("chdir %s:", cwd);
556 }
557}
558
559static void
560usage(void)
561{
562 eprintf(
563 "usage: %s [-1ACacdFfGghiLlnopqRrtUu] "
564 "[--color[=always|never|auto]] [file ...]\n",
565 argv0
566 );
567}
568
569// ?man ls: list directory contents
570// ?man arguments: [file ...]
571// ?man list information about files and directories
572int
573main(int argc, char *argv[])
574{
575 struct entry ent, *dents, *fents;
576 size_t i, ds, fs;
577#if FEATURE_LS_COLOR
578 char *val;
579#endif
580
581 if (isatty(STDOUT_FILENO))
582 Cflag = 1;
583 else
584 one_flag = 1;
585
586#if FEATURE_LS_COLOR
587 if ((val = getenv("CLICOLOR_FORCE"))) {
588 if (*val && strcmp(val, "0") != 0)
589 color_mode = COLOR_ALWAYS;
590 else
591 color_mode = COLOR_NEVER;
592 } else if ((val = getenv("CLICOLOR"))) {
593 if (*val && strcmp(val, "0") != 0)
594 color_mode = COLOR_AUTO;
595 else
596 color_mode = COLOR_NEVER;
597 }
598#endif
599
600 tree = ereallocarray(NULL, PATH_MAX, sizeof(*tree));
601
602 ARGBEGIN
603 {
604 // ?man -1: list one file per line
605 case '1':
606 one_flag = 1;
607 Cflag = 0;
608 lflag = 0;
609 break;
610 // ?man -A: list all entries except dot and dot dot
611 case 'A':
612 Aflag = 1;
613 break;
614 // ?man -a: list all entries including those starting with a dot
615 case 'a':
616 aflag = 1;
617 break;
618 // ?man -c: sort by ctime or use ctime for long listing
619 case 'c':
620 cflag = 1;
621 uflag = 0;
622 break;
623 // ?man -C: list entries in columns sorted vertically
624 case 'C':
625 Cflag = 1;
626 one_flag = 0;
627 lflag = 0;
628 break;
629 // ?man -d: list directory entries instead of their contents
630 case 'd':
631 dflag = 1;
632 break;
633 // ?man -f: do not sort and enable a and U
634 case 'f':
635 aflag = 1;
636 fflag = 1;
637 Uflag = 1;
638 break;
639 // ?man -F: append type indicators
640 case 'F':
641 Fflag = 1;
642 break;
643#if FEATURE_LS_COLOR
644 // ?man -G: enable colored output
645 case 'G':
646 color_mode = COLOR_AUTO;
647 break;
648#endif
649 // ?man -g: list in long format without owner name
650 case 'g':
651 gflag = 1;
652 lflag = 1;
653 Cflag = 0;
654 one_flag = 0;
655 break;
656 // ?man -H: follow symlinks on the command line
657 case 'H':
658 Hflag = 1;
659 break;
660 // ?man -h: print human readable sizes
661 case 'h':
662 hflag = 1;
663 break;
664 // ?man -i: print inode number of each file
665 case 'i':
666 iflag = 1;
667 break;
668 // ?man -L: follow all symlinks
669 case 'L':
670 Lflag = 1;
671 break;
672 // ?man -l: use a long listing format
673 case 'l':
674 lflag = 1;
675 Cflag = 0;
676 one_flag = 0;
677 break;
678 // ?man -n: list numeric uids and gids
679 case 'n':
680 lflag = 1;
681 nflag = 1;
682 Cflag = 0;
683 one_flag = 0;
684 break;
685 // ?man -o: list in long format without group name
686 case 'o':
687 oflag = 1;
688 lflag = 1;
689 Cflag = 0;
690 one_flag = 0;
691 break;
692 // ?man -p: append slash indicator to directories
693 case 'p':
694 pflag = 1;
695 break;
696 // ?man -q: print non printable characters as question marks
697 case 'q':
698 qflag = 1;
699 break;
700 // ?man -R: list subdirectories recursively
701 case 'R':
702 Rflag = 1;
703 break;
704 // ?man -r: reverse sort order
705 case 'r':
706 rflag = 1;
707 break;
708 // ?man -S: sort by file size
709 case 'S':
710 sort = 'S';
711 break;
712 // ?man -t: sort by modification time
713 case 't':
714 sort = 't';
715 break;
716 // ?man -U: do not sort
717 case 'U':
718 Uflag = 1;
719 break;
720 // ?man -u: sort by atime or use atime for long listing
721 case 'u':
722 uflag = 1;
723 cflag = 0;
724 break;
725 // ?man --: specify - option
726 case '-':
727#if FEATURE_LS_COLOR
728 // ?man --color [when]: control coloring
729 if (strncmp(argv[0], "-color", 6) == 0) {
730 char *val = NULL;
731 if (argv[0][6] == '=') {
732 val = &argv[0][7];
733 } else if (argv[0][6] == '\0') {
734 val = "always";
735 }
736 if (val) {
737 if (strcmp(val, "always") == 0)
738 color_mode = COLOR_ALWAYS;
739 else if (strcmp(val, "never") == 0)
740 color_mode = COLOR_NEVER;
741 else if (strcmp(val, "auto") == 0)
742 color_mode = COLOR_AUTO;
743 else {
744 fprintf(
745 stderr,
746 "ls: invalid --color value: "
747 "%s\n",
748 val
749 );
750 usage();
751 }
752 }
753 brk_ = 1;
754 } else {
755 usage();
756 }
757#else
758 usage();
759#endif
760 break;
761 default:
762 usage();
763 }
764 ARGEND
765
766 switch (argc) {
767 case 0:
768 *--argv = ".", ++argc;
769 /* fallthrough */
770 case 1:
771 mkent(&ent, argv[0], 1, Hflag || Lflag);
772 ls("",
773 &ent,
774 (!dflag && S_ISDIR(ent.mode))
775 || (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) && !(dflag || Fflag || lflag)));
776
777 break;
778 default:
779 for (i = ds = fs = 0, fents = dents = NULL; i < (size_t)argc; ++i) {
780 mkent(&ent, argv[i], 1, Hflag || Lflag);
781
782 if ((!dflag && S_ISDIR(ent.mode))
783 || (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) && !(dflag || Fflag || lflag))) {
784 dents = ereallocarray(dents, ++ds, sizeof(*dents));
785 memcpy(&dents[ds - 1], &ent, sizeof(ent));
786 } else {
787 fents = ereallocarray(fents, ++fs, sizeof(*fents));
788 memcpy(&fents[fs - 1], &ent, sizeof(ent));
789 }
790 }
791
792 showdirs = ds > 1 || (ds && fs);
793
794 qsort(fents, fs, sizeof(ent), entcmp);
795 qsort(dents, ds, sizeof(ent), entcmp);
796
797 if (!lflag && Cflag && fs > 0) {
798 printcols(fents, fs);
799 } else {
800 for (i = 0; i < fs; ++i)
801 ls("", &fents[i], 0);
802 }
803 free(fents);
804 if (fs && ds)
805 putchar('\n');
806 for (i = 0; i < ds; ++i)
807 ls("", &dents[i], 1);
808 free(dents);
809 }
810
811 return (fshut(stdout, "<stdout>") | ret);
812}