toybox/toys/posix/ls.c
<<
>>
Prefs
   1/* ls.c - list files
   2 *
   3 * Copyright 2012 Andre Renaud <andre@bluewatersys.com>
   4 * Copyright 2012 Rob Landley <rob@landley.net>
   5 *
   6 * See http://opengroup.org/onlinepubs/9699919799/utilities/ls.html
   7 *
   8 * Deviations from posix:
   9 *   add -b (and default to it instead of -q for an unambiguous representation
  10 *   that doesn't cause collisions)
  11 *   add -Z -ll --color
  12 *   Posix says the -l date format should vary based on how recent it is
  13 *   and we do --time-style=long-iso instead
  14
  15USE_LS(NEWTOY(ls, "(color):;(full-time)(show-control-chars)ZgoACFHLRSabcdfhikl@mnpqrstuw#=80<0x1[-Cxm1][-Cxml][-Cxmo][-Cxmg][-cu][-ftS][-HL][!qb]", TOYFLAG_BIN|TOYFLAG_LOCALE))
  16
  17config LS
  18  bool "ls"
  19  default y
  20  help
  21    usage: ls [-ACFHLRSZacdfhiklmnpqrstuwx1] [--color[=auto]] [FILE...]
  22
  23    List files.
  24
  25    what to show:
  26    -a  all files including .hidden    -b  escape nongraphic chars
  27    -c  use ctime for timestamps       -d  directory, not contents
  28    -i  inode number                   -p  put a '/' after dir names
  29    -q  unprintable chars as '?'       -s  storage used (1024 byte units)
  30    -u  use access time for timestamps -A  list all files but . and ..
  31    -H  follow command line symlinks   -L  follow symlinks
  32    -R  recursively list in subdirs    -F  append /dir *exe @sym |FIFO
  33    -Z  security context
  34
  35    output formats:
  36    -1  list one file per line         -C  columns (sorted vertically)
  37    -g  like -l but no owner           -h  human readable sizes
  38    -l  long (show full details)       -m  comma separated
  39    -n  like -l but numeric uid/gid    -o  like -l but no group
  40    -w  set column width               -x  columns (horizontal sort)
  41    -ll long with nanoseconds (--full-time)
  42    --color  device=yellow  symlink=turquoise/red  dir=blue  socket=purple
  43             files: exe=green  suid=red  suidfile=redback  stickydir=greenback
  44             =auto means detect if output is a tty.
  45
  46    sorting (default is alphabetical):
  47    -f  unsorted    -r  reverse    -t  timestamp    -S  size
  48*/
  49
  50#define FOR_ls
  51#include "toys.h"
  52
  53// test sst output (suid/sticky in ls flaglist)
  54
  55// ls -lR starts .: then ./subdir:
  56
  57GLOBALS(
  58  long w;
  59  long l;
  60  char *color;
  61
  62  struct dirtree *files, *singledir;
  63  unsigned screen_width;
  64  int nl_title;
  65  char *escmore;
  66)
  67
  68// Callback from crunch_str to represent unprintable chars
  69static int crunch_qb(FILE *out, int cols, int wc)
  70{
  71  int len = 1;
  72  char buf[32];
  73
  74  if (FLAG(q)) *buf = '?';
  75  else {
  76    if (wc<256) *buf = wc;
  77    // scrute the inscrutable, eff the ineffable, print the unprintable
  78    else if ((len = wcrtomb(buf, wc, 0) ) == -1) len = 1;
  79    if (FLAG(b)) {
  80      char *to = buf, *from = buf+24;
  81      int i, j;
  82
  83      memcpy(from, to, 8);
  84      for (i = 0; i<len; i++) {
  85        *to++ = '\\';
  86        if (strchr(TT.escmore, from[i])) *to++ = from[i];
  87        else if (-1 != (j = stridx("\\\a\b\e\f\n\r\t\v", from[i])))
  88          *to++ = "\\abefnrtv"[j];
  89        else to += sprintf(to, "%03o", from[i]);
  90      }
  91      len = to-buf;
  92    }
  93  }
  94
  95  if (cols<len) len = cols;
  96  if (out) fwrite(buf, len, 1, out);
  97
  98  return len;
  99}
 100
 101// Returns wcwidth(utf8) version of strlen with -qb escapes
 102static int strwidth(char *s)
 103{
 104  return crunch_str(&s, INT_MAX, 0, TT.escmore, crunch_qb);
 105}
 106
 107static char endtype(struct stat *st)
 108{
 109  mode_t mode = st->st_mode;
 110  if ((FLAG(F)||FLAG(p)) && S_ISDIR(mode)) return '/';
 111  if (FLAG(F)) {
 112    if (S_ISLNK(mode)) return '@';
 113    if (S_ISREG(mode) && (mode&0111)) return '*';
 114    if (S_ISFIFO(mode)) return '|';
 115    if (S_ISSOCK(mode)) return '=';
 116  }
 117  return 0;
 118}
 119
 120static int numlen(long long ll)
 121{
 122  return snprintf(0, 0, "%llu", ll);
 123}
 124
 125static int print_with_h(char *s, long long value, int units)
 126{
 127  if (FLAG(h)) return human_readable(s, value*units, 0);
 128  else return sprintf(s, "%lld", value);
 129}
 130
 131// Figure out size of printable entry fields for display indent/wrap
 132
 133static void entrylen(struct dirtree *dt, unsigned *len)
 134{
 135  struct stat *st = &(dt->st);
 136  char tmp[64];
 137
 138  *len = strwidth(dt->name);
 139  if (endtype(st)) ++*len;
 140  if (FLAG(m)) ++*len;
 141
 142  len[1] = FLAG(i) ? numlen(st->st_ino) : 0;
 143  if (FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)) {
 144    len[2] = numlen(st->st_nlink);
 145    len[3] = FLAG(n) ? numlen(st->st_uid) : strwidth(getusername(st->st_uid));
 146    len[4] = FLAG(n) ? numlen(st->st_gid) : strwidth(getgroupname(st->st_gid));
 147    if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) {
 148      // cheating slightly here: assuming minor is always 3 digits to avoid
 149      // tracking another column
 150      len[5] = numlen(dev_major(st->st_rdev))+5;
 151    } else len[5] = print_with_h(tmp, st->st_size, 1);
 152  }
 153
 154  len[6] = FLAG(s) ? print_with_h(tmp, st->st_blocks, 1024) : 0;
 155  len[7] = FLAG(Z) ? strwidth((char *)dt->extra) : 0;
 156}
 157
 158static int compare(void *a, void *b)
 159{
 160  struct dirtree *dta = *(struct dirtree **)a;
 161  struct dirtree *dtb = *(struct dirtree **)b;
 162  int ret = 0, reverse = FLAG(r) ? -1 : 1;
 163
 164  if (FLAG(S)) {
 165    if (dta->st.st_size > dtb->st.st_size) ret = -1;
 166    else if (dta->st.st_size < dtb->st.st_size) ret = 1;
 167  }
 168  if (FLAG(t)) {
 169    if (dta->st.st_mtime > dtb->st.st_mtime) ret = -1;
 170    else if (dta->st.st_mtime < dtb->st.st_mtime) ret = 1;
 171    else if (dta->st.st_mtim.tv_nsec > dtb->st.st_mtim.tv_nsec) ret = -1;
 172    else if (dta->st.st_mtim.tv_nsec < dtb->st.st_mtim.tv_nsec) ret = 1;
 173  }
 174  if (!ret) ret = strcmp(dta->name, dtb->name);
 175  return ret * reverse;
 176}
 177
 178// callback from dirtree_recurse() determining how to handle this entry.
 179
 180static int filter(struct dirtree *new)
 181{
 182  // Special case to handle enormous dirs without running out of memory.
 183  if (toys.optflags == (FLAG_1|FLAG_f)) {
 184    xprintf("%s\n", new->name);
 185    return 0;
 186  }
 187
 188  if (FLAG(Z)) {
 189    if (!CFG_TOYBOX_LSM_NONE) {
 190      // Linux doesn't support fgetxattr(2) on O_PATH file descriptors (though
 191      // bionic works around that), and there are no *xattrat(2) calls, so we
 192      // just use lgetxattr(2).
 193      char *path = dirtree_path(new, 0);
 194
 195      (FLAG(L) ? lsm_get_context : lsm_lget_context)(path,(char **)&new->extra);
 196      free(path);
 197    }
 198    if (CFG_TOYBOX_LSM_NONE || !new->extra) new->extra = (long)xstrdup("?");
 199  }
 200
 201  if (FLAG(u)) new->st.st_mtime = new->st.st_atime;
 202  if (FLAG(c)) new->st.st_mtime = new->st.st_ctime;
 203  new->st.st_blocks >>= 1; // Use 1KiB blocks rather than 512B blocks.
 204
 205  if (FLAG(a)||FLAG(f)) return DIRTREE_SAVE;
 206  if (!FLAG(A) && new->name[0]=='.') return 0;
 207
 208  return dirtree_notdotdot(new) & DIRTREE_SAVE;
 209}
 210
 211// For column view, calculate horizontal position (for padding) and return
 212// index of next entry to display.
 213
 214static unsigned long next_column(unsigned long ul, unsigned long dtlen,
 215  unsigned columns, unsigned *xpos)
 216{
 217  unsigned height, extra;
 218
 219  // Horizontal sort is easy
 220  if (!FLAG(C)) {
 221    *xpos = ul % columns;
 222    return ul;
 223  }
 224
 225  // vertical sort (-x), uneven rounding goes along right edge
 226  height = (dtlen+columns-1)/columns; // round up
 227  extra = dtlen%height; // how many rows are wider?
 228  if (extra && ul >= extra*columns) ul -= extra*columns--;
 229  else extra = 0;
 230
 231  return (*xpos = ul % columns)*height + extra + ul/columns;
 232}
 233
 234static int color_from_mode(mode_t mode)
 235{
 236  int color = 0;
 237
 238  if (S_ISDIR(mode)) color = 256+34;
 239  else if (S_ISLNK(mode)) color = 256+36;
 240  else if (S_ISBLK(mode) || S_ISCHR(mode)) color = 256+33;
 241  else if (S_ISREG(mode) && (mode&0111)) color = 256+32;
 242  else if (S_ISFIFO(mode)) color = 33;
 243  else if (S_ISSOCK(mode)) color = 256+35;
 244
 245  return color;
 246}
 247
 248static void zprint(int zap, char *pat, int len, unsigned long arg)
 249{
 250  char tmp[32];
 251
 252  sprintf(tmp, "%%*%s", zap ? "s" : pat);
 253  if (zap && pat[strlen(pat)-1]==' ') strcat(tmp, " ");
 254  printf(tmp, len, zap ? (unsigned long)"?" : arg);
 255}
 256
 257// Display a list of dirtree entries, according to current format
 258// Output types -1, -l, -C, or stream
 259
 260static void listfiles(int dirfd, struct dirtree *indir)
 261{
 262  struct dirtree *dt, **sort;
 263  unsigned long dtlen, ul = 0;
 264  unsigned width, totals[8], len[8], totpad = 0,
 265    *colsizes = (unsigned *)toybuf, columns = sizeof(toybuf)/4;
 266  char tmp[64];
 267
 268  if (-1 == dirfd) {
 269    perror_msg_raw(indir->name);
 270
 271    return;
 272  }
 273
 274  memset(totals, 0, sizeof(totals));
 275  memset(len, 0, sizeof(len));
 276
 277  // Top level directory was already populated by main()
 278  if (!indir->parent) {
 279    // Silently descend into single directory listed by itself on command line.
 280    // In this case only show dirname/total header when given -R.
 281    dt = indir->child;
 282    if (dt && S_ISDIR(dt->st.st_mode) && !dt->next && !(FLAG(d)||FLAG(R))) {
 283      listfiles(open(dt->name, 0), TT.singledir = dt);
 284
 285      return;
 286    }
 287
 288    // Do preprocessing (Dirtree didn't populate, so callback wasn't called.)
 289    for (;dt; dt = dt->next) filter(dt);
 290    if (toys.optflags == (FLAG_1|FLAG_f)) return;
 291  // Read directory contents. We dup() the fd because this will close it.
 292  // This reads/saves contents to display later, except for in "ls -1f" mode.
 293  } else dirtree_recurse(indir, filter, dup(dirfd),
 294      DIRTREE_STATLESS|DIRTREE_SYMFOLLOW*!!FLAG(L));
 295
 296  // Copy linked list to array and sort it. Directories go in array because
 297  // we visit them in sorted order too. (The nested loops let us measure and
 298  // fill with the same inner loop.)
 299  for (sort = 0;;sort = xmalloc(dtlen*sizeof(void *))) {
 300    for (dtlen = 0, dt = indir->child; dt; dt = dt->next, dtlen++)
 301      if (sort) sort[dtlen] = dt;
 302    if (sort || !dtlen) break;
 303  }
 304
 305  // Label directory if not top of tree, or if -R
 306  if (indir->parent && (TT.singledir!=indir || FLAG(R))) {
 307    char *path = dirtree_path(indir, 0);
 308
 309    if (TT.nl_title++) xputc('\n');
 310    xprintf("%s:\n", path);
 311    free(path);
 312  }
 313
 314  // Measure each entry to work out whitespace padding and total blocks
 315  if (!FLAG(f)) {
 316    unsigned long long blocks = 0;
 317
 318    qsort(sort, dtlen, sizeof(void *), (void *)compare);
 319    for (ul = 0; ul<dtlen; ul++) {
 320      entrylen(sort[ul], len);
 321      for (width = 0; width<8; width++)
 322        if (len[width]>totals[width]) totals[width] = len[width];
 323      blocks += sort[ul]->st.st_blocks;
 324    }
 325    totpad = totals[1]+!!totals[1]+totals[6]+!!totals[6]+totals[7]+!!totals[7];
 326    if ((FLAG(h)||FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)||FLAG(s)) && indir->parent)
 327    {
 328      print_with_h(tmp, blocks, 1024);
 329      xprintf("total %s\n", tmp);
 330    }
 331  }
 332
 333  // Find largest entry in each field for display alignment
 334  if (FLAG(C)||FLAG(x)) {
 335
 336    // columns can't be more than toybuf can hold, or more than files,
 337    // or > 1/2 screen width (one char filename, one space).
 338    if (columns > TT.screen_width/2) columns = TT.screen_width/2;
 339    if (columns > dtlen) columns = dtlen;
 340
 341    // Try to fit as many columns as we can, dropping down by one each time
 342    for (;columns > 1; columns--) {
 343      unsigned c, cc, totlen = columns;
 344
 345      memset(colsizes, 0, columns*sizeof(unsigned));
 346      for (ul = 0; ul<dtlen; ul++) {
 347        cc = next_column(ul, dtlen, columns, &c);
 348        if (cc>=dtlen) break; // tilt: remainder bigger than height
 349        entrylen(sort[cc], len);
 350        if (c<columns-1) *len += totpad+2;  // 2 spaces between filenames
 351        // Expand this column if necessary, break if that puts us over budget
 352        if (*len > colsizes[c]) {
 353          totlen += (*len)-colsizes[c];
 354          colsizes[c] = *len;
 355          if (totlen > TT.screen_width) break;
 356        }
 357      }
 358      // If everything fit, stop here
 359      if (ul == dtlen) break;
 360    }
 361  }
 362
 363  // Loop through again to produce output.
 364  width = 0;
 365  for (ul = 0; ul<dtlen; ul++) {
 366    int ii, zap;
 367    unsigned curcol, lastlen = *len, color = 0;
 368    struct stat *st = &((dt = sort[next_column(ul,dtlen,columns,&curcol)])->st);
 369    mode_t mode = st->st_mode;
 370    char et = endtype(st), *ss;
 371
 372    // If we couldn't stat, output ? for most fields
 373    zap = !st->st_blksize && !st->st_dev && !st->st_ino;
 374
 375    // Skip directories at the top of the tree when -d isn't set
 376    if (S_ISDIR(mode) && !indir->parent && !FLAG(d)) continue;
 377    TT.nl_title=1;
 378
 379    // Handle padding and wrapping for display purposes
 380    entrylen(dt, len);
 381    if (ul) {
 382      int mm = !!FLAG(m);
 383
 384      if (mm) xputc(',');
 385      if (FLAG(C)||FLAG(x)) {
 386        if (!curcol) xputc('\n');
 387        else {
 388          if (ul) next_column(ul-1, dtlen, columns, &curcol);
 389          printf("%*c", colsizes[ul ? curcol : 0]-lastlen-totpad, ' ');
 390        }
 391      } else if (FLAG(1) || width+1+*len > TT.screen_width) {
 392        xputc('\n');
 393        width = 0;
 394      } else {
 395        printf("  "+mm, 0); // shut up the stupid compiler
 396        width += 2-mm;
 397      }
 398    }
 399    width += *len;
 400
 401    if (FLAG(i)) zprint(zap, "lu ", totals[1], st->st_ino);
 402
 403    if (FLAG(s)) {
 404      print_with_h(tmp, st->st_blocks, 1024);
 405      zprint(zap, "s ", totals[6], (unsigned long)tmp);
 406    }
 407
 408    if (FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)) {
 409      mode_to_string(mode, tmp);
 410      if (zap) memset(tmp+1, '?', 9);
 411      printf("%s", tmp);
 412      zprint(zap, "ld", totals[2]+1, st->st_nlink);
 413
 414      // print user
 415      if (!FLAG(g)) {
 416        putchar(' ');
 417        ii = -totals[3];
 418        if (zap || FLAG(n)) zprint(zap, "lu", ii, st->st_uid);
 419        else draw_trim_esc(getusername(st->st_uid), ii, abs(ii), TT.escmore,
 420                           crunch_qb);
 421      }
 422
 423      // print group
 424      if (!FLAG(o)) {
 425        putchar(' ');
 426        ii = -totals[4];
 427        if (zap || FLAG(n)) zprint(zap, "lu", ii, st->st_gid);
 428        else draw_trim_esc(getgroupname(st->st_gid), ii, abs(ii), TT.escmore,
 429                           crunch_qb);
 430      }
 431    }
 432    if (FLAG(Z)) printf(" %-*s "+!FLAG(l), -(int)totals[7], (char *)dt->extra);
 433
 434    if (FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)) {
 435      struct tm *tm;
 436
 437      // print major/minor, or size
 438      if (!zap && (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)))
 439        printf("% *d,% 4d", totals[5]-4, dev_major(st->st_rdev),
 440          dev_minor(st->st_rdev));
 441      else {
 442        print_with_h(tmp, st->st_size, 1);
 443        zprint(zap, "s", totals[5]+1, (unsigned long)tmp);
 444      }
 445
 446      // print time, always in --time-style=long-iso
 447      tm = localtime(&(st->st_mtime));
 448      strftime(tmp, sizeof(tmp), " %F %H:%M", tm);
 449      if (TT.l>1) {
 450        char *s = tmp+strlen(tmp);
 451
 452        s += sprintf(s, ":%02d.%09d ", tm->tm_sec, (int)st->st_mtim.tv_nsec);
 453        strftime(s, sizeof(tmp)-(s-tmp), "%z", tm);
 454      }
 455      zprint(zap, "s ", 17+(TT.l>1)*13, (unsigned long)tmp);
 456    }
 457
 458    if (FLAG(color)) {
 459      color = color_from_mode(st->st_mode);
 460      if (color) printf("\e[%d;%dm", color>>8, color&255);
 461    }
 462
 463    ss = dt->name;
 464    crunch_str(&ss, INT_MAX, stdout, TT.escmore, crunch_qb);
 465    if (color) printf("\e[0m");
 466
 467    if ((FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)) && S_ISLNK(mode)) {
 468      printf(" -> ");
 469      if (!zap && FLAG(color)) {
 470        struct stat st2;
 471
 472        if (fstatat(dirfd, dt->symlink, &st2, 0)) color = 256+31;
 473        else color = color_from_mode(st2.st_mode);
 474
 475        if (color) printf("\e[%d;%dm", color>>8, color&255);
 476      }
 477
 478      zprint(zap, "s", 0, (unsigned long)dt->symlink);
 479      if (!zap && color) printf("\e[0m");
 480    }
 481
 482    if (et) xputc(et);
 483  }
 484
 485  if (width) xputc('\n');
 486
 487  // Free directory entries, recursing first if necessary.
 488
 489  for (ul = 0; ul<dtlen; free(sort[ul++])) {
 490    if (FLAG(d) || !S_ISDIR(sort[ul]->st.st_mode)) continue;
 491
 492    // Recurse into dirs if at top of the tree or given -R
 493    if (!indir->parent || (FLAG(R) && dirtree_notdotdot(sort[ul])))
 494      listfiles(openat(dirfd, sort[ul]->name, 0), sort[ul]);
 495    free((void *)sort[ul]->extra);
 496  }
 497  free(sort);
 498  if (dirfd != AT_FDCWD) close(dirfd);
 499}
 500
 501void ls_main(void)
 502{
 503  char **s, *noargs[] = {".", 0};
 504  struct dirtree *dt;
 505
 506  if (FLAG(full_time)) {
 507    toys.optflags |= FLAG_l;
 508    TT.l = 2;
 509  }
 510
 511  // Do we have an implied -1
 512  if (isatty(1)) {
 513    if (!FLAG(show_control_chars)) toys.optflags |= FLAG_b;
 514    if (FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)) toys.optflags |= FLAG_1;
 515    else if (!(FLAG(1)||FLAG(x)||FLAG(m))) toys.optflags |= FLAG_C;
 516  } else {
 517    if (!FLAG(m)) toys.optflags |= FLAG_1;
 518    if (TT.color) toys.optflags ^= FLAG_color;
 519  }
 520
 521  TT.screen_width = 80;
 522  if (FLAG(w)) TT.screen_width = TT.w+2;
 523  else terminal_size(&TT.screen_width, NULL);
 524  if (TT.screen_width<2) TT.screen_width = 2;
 525  if (FLAG(b)) TT.escmore = " \\";
 526
 527  // The optflags parsing infrastructure should really do this for us,
 528  // but currently it has "switch off when this is set", so "-dR" and "-Rd"
 529  // behave differently
 530  if (FLAG(d)) toys.optflags &= ~FLAG_R;
 531
 532  // Iterate through command line arguments, collecting directories and files.
 533  // Non-absolute paths are relative to current directory. Top of tree is
 534  // a dummy node to collect command line arguments into pseudo-directory.
 535  TT.files = dirtree_add_node(0, 0, 0);
 536  TT.files->dirfd = AT_FDCWD;
 537  for (s = *toys.optargs ? toys.optargs : noargs; *s; s++) {
 538    int sym = !(FLAG(l)||FLAG(d)||FLAG(F)) || FLAG(L) || FLAG(H);
 539
 540    dt = dirtree_add_node(0, *s, DIRTREE_STATLESS|DIRTREE_SYMFOLLOW*sym);
 541
 542    // note: double_list->prev temporarily goes in dirtree->parent
 543    if (dt) {
 544      if (dt->again&2) {
 545        perror_msg_raw(*s);
 546        free(dt);
 547      } else dlist_add_nomalloc((void *)&TT.files->child, (void *)dt);
 548    } else toys.exitval = 1;
 549  }
 550
 551  // Convert double_list into dirtree.
 552  dlist_terminate(TT.files->child);
 553  for (dt = TT.files->child; dt; dt = dt->next) dt->parent = TT.files;
 554
 555  // Display the files we collected
 556  listfiles(AT_FDCWD, TT.files);
 557
 558  if (CFG_TOYBOX_FREE) free(TT.files);
 559}
 560