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