master xplshn/aruu / cmd / posix / ls.c
  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}