1 /* ls.c - list files 2 * 3 * Copyright 2012 Andre Renaud <andre (at) bluewatersys.com> 4 * Copyright 2012 Rob Landley <rob (at) landley.net> 5 * 6 * See http://opengroup.org/onlinepubs/9699919799/utilities/ls.html 7 8 USE_LS(NEWTOY(ls, USE_LS_COLOR("(color):;")"ZgoACFHLRSacdfhiklmnpqrstux1[-Cxm1][-Cxml][-Cxmo][-Cxmg][-cu][-ftS][-HL]", TOYFLAG_BIN|TOYFLAG_LOCALE)) 9 10 config LS 11 bool "ls" 12 default y 13 help 14 usage: ls [-ACFHLRSZacdfhiklmnpqrstux1] [directory...] 15 16 list files 17 18 what to show: 19 -a all files including .hidden -c use ctime for timestamps 20 -d directory, not contents -i inode number 21 -k block sizes in kilobytes -p put a '/' after dir names 22 -q unprintable chars as '?' -s size (in blocks) 23 -u use access time for timestamps -A list all files but . and .. 24 -H follow command line symlinks -L follow symlinks 25 -R recursively list files in subdirs -F append /dir *exe @sym |FIFO 26 -Z security context 27 28 output formats: 29 -1 list one file per line -C columns (sorted vertically) 30 -g like -l but no owner -h human readable sizes 31 -l long (show full details) -m comma separated 32 -n like -l but numeric uid/gid -o like -l but no group 33 -x columns (horizontal sort) 34 35 sorting (default is alphabetical): 36 -f unsorted -r reverse -t timestamp -S size 37 38 config LS_COLOR 39 bool "ls --color" 40 default y 41 depends on LS 42 help 43 usage: ls --color[=auto] 44 45 --color device=yellow symlink=turquoise/red dir=blue socket=purple 46 files: exe=green suid=red suidfile=redback stickydir=greenback 47 =auto means detect if output is a tty. 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 57 GLOBALS( 58 char *color; 59 60 struct dirtree *files, *singledir; 61 62 unsigned screen_width; 63 int nl_title; 64 char uid_buf[12], gid_buf[12]; 65 ) 66 67 // Does two things: 1) Returns wcwidth(utf8) version of strlen, 68 // 2) replaces unprintable characters input string with '?' wildcard char. 69 int strwidth(char *s) 70 { 71 int total = 0, width, len; 72 wchar_t c; 73 74 if (!CFG_TOYBOX_I18N) { 75 total = strlen(s); 76 if (toys.optflags & FLAG_q) for (; *s; s++) if (!isprint(*s)) *s = '?'; 77 } else while (*s) { 78 len = mbrtowc(&c, s, MB_CUR_MAX, 0); 79 if (len < 1 || (width = wcwidth(c)) < 0) { 80 total++; 81 if (toys.optflags & FLAG_q) *s = '?'; 82 s++; 83 } else { 84 s += len; 85 total += width; 86 } 87 } 88 89 return total; 90 } 91 92 static char endtype(struct stat *st) 93 { 94 mode_t mode = st->st_mode; 95 if ((toys.optflags&(FLAG_F|FLAG_p)) && S_ISDIR(mode)) return '/'; 96 if (toys.optflags & FLAG_F) { 97 if (S_ISLNK(mode)) return '@'; 98 if (S_ISREG(mode) && (mode&0111)) return '*'; 99 if (S_ISFIFO(mode)) return '|'; 100 if (S_ISSOCK(mode)) return '='; 101 } 102 return 0; 103 } 104 105 static char *getusername(uid_t uid) 106 { 107 struct passwd *pw = getpwuid(uid); 108 109 sprintf(TT.uid_buf, "%u", (unsigned)uid); 110 return pw ? pw->pw_name : TT.uid_buf; 111 } 112 113 static char *getgroupname(gid_t gid) 114 { 115 struct group *gr = getgrgid(gid); 116 117 sprintf(TT.gid_buf, "%u", (unsigned)gid); 118 return gr ? gr->gr_name : TT.gid_buf; 119 } 120 121 static int numlen(long long ll) 122 { 123 return snprintf(0, 0, "%llu", ll); 124 } 125 126 // Figure out size of printable entry fields for display indent/wrap 127 128 static void entrylen(struct dirtree *dt, unsigned *len) 129 { 130 struct stat *st = &(dt->st); 131 unsigned flags = toys.optflags; 132 char tmp[64]; 133 134 *len = strwidth(dt->name); 135 if (endtype(st)) ++*len; 136 if (flags & FLAG_m) ++*len; 137 138 len[1] = (flags & FLAG_i) ? numlen(st->st_ino) : 0; 139 if (flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g)) { 140 unsigned fn = flags & FLAG_n; 141 len[2] = numlen(st->st_nlink); 142 len[3] = fn ? numlen(st->st_uid) : strwidth(getusername(st->st_uid)); 143 len[4] = fn ? numlen(st->st_gid) : strwidth(getgroupname(st->st_gid)); 144 if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) { 145 // cheating slightly here: assuming minor is always 3 digits to avoid 146 // tracking another column 147 len[5] = numlen(major(st->st_rdev))+5; 148 } else if (flags & FLAG_h) { 149 human_readable(tmp, st->st_size, 0); 150 len[5] = strwidth(tmp); 151 } else len[5] = numlen(st->st_size); 152 } 153 154 len[6] = (flags & FLAG_s) ? numlen(st->st_blocks) : 0; 155 len[7] = (flags & FLAG_Z) ? strwidth((char *)dt->extra) : 0; 156 } 157 158 static 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 = (toys.optflags & FLAG_r) ? -1 : 1; 163 164 if (toys.optflags & 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 (toys.optflags & 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 } 172 if (!ret) ret = strcmp(dta->name, dtb->name); 173 return ret * reverse; 174 } 175 176 // callback from dirtree_recurse() determining how to handle this entry. 177 178 static int filter(struct dirtree *new) 179 { 180 int flags = toys.optflags; 181 182 // Special case to handle enormous dirs without running out of memory. 183 if (flags == (FLAG_1|FLAG_f)) { 184 xprintf("%s\n", new->name); 185 return 0; 186 } 187 188 if (flags & FLAG_Z) { 189 if (!CFG_TOYBOX_LSM_NONE) { 190 191 // (Wouldn't it be nice if the lsm functions worked like openat(), 192 // fchmodat(), mknodat(), readlinkat() so we could do this without 193 // even O_PATH? But no, this is 1990's tech.) 194 int fd = openat(dirtree_parentfd(new), new->name, 195 O_PATH|(O_NOFOLLOW*!(toys.optflags&FLAG_L))); 196 197 if (fd != -1) { 198 if (-1 == lsm_fget_context(fd, (char **)&new->extra) && errno == EBADF) 199 { 200 char hack[32]; 201 202 // Work around kernel bug that won't let us read this "metadata" from 203 // the filehandle unless we have permission to read the data. (We can 204 // query the same data in by path, but can't do it through an O_PATH 205 // filehandle, because reasons. But for some reason, THIS is ok? If 206 // they ever fix the kernel, this should stop triggering.) 207 208 sprintf(hack, "/proc/self/fd/%d", fd); 209 lsm_lget_context(hack, (char **)&new->extra); 210 } 211 close(fd); 212 } 213 } 214 if (CFG_TOYBOX_LSM_NONE || !new->extra) new->extra = (long)xstrdup("?"); 215 } 216 217 if (flags & FLAG_u) new->st.st_mtime = new->st.st_atime; 218 if (flags & FLAG_c) new->st.st_mtime = new->st.st_ctime; 219 if (flags & FLAG_k) new->st.st_blocks = (new->st.st_blocks + 1) / 2; 220 221 if (flags & (FLAG_a|FLAG_f)) return DIRTREE_SAVE; 222 if (!(flags & FLAG_A) && new->name[0]=='.') return 0; 223 224 return dirtree_notdotdot(new) & DIRTREE_SAVE; 225 } 226 227 // For column view, calculate horizontal position (for padding) and return 228 // index of next entry to display. 229 230 static unsigned long next_column(unsigned long ul, unsigned long dtlen, 231 unsigned columns, unsigned *xpos) 232 { 233 unsigned long transition; 234 unsigned height, widecols; 235 236 // Horizontal sort is easy 237 if (!(toys.optflags & FLAG_C)) { 238 *xpos = ul % columns; 239 return ul; 240 } 241 242 // vertical sort 243 244 // For -x, calculate height of display, rounded up 245 height = (dtlen+columns-1)/columns; 246 247 // Sanity check: does wrapping render this column count impossible 248 // due to the right edge wrapping eating a whole row? 249 if (height*columns - dtlen >= height) { 250 *xpos = columns; 251 return 0; 252 } 253 254 // Uneven rounding goes along right edge 255 widecols = dtlen % height; 256 if (!widecols) widecols = height; 257 transition = widecols * columns; 258 if (ul < transition) { 259 *xpos = ul % columns; 260 return (*xpos*height) + (ul/columns); 261 } 262 263 ul -= transition; 264 *xpos = ul % (columns-1); 265 266 return (*xpos*height) + widecols + (ul/(columns-1)); 267 } 268 269 int color_from_mode(mode_t mode) 270 { 271 int color = 0; 272 273 if (S_ISDIR(mode)) color = 256+34; 274 else if (S_ISLNK(mode)) color = 256+36; 275 else if (S_ISBLK(mode) || S_ISCHR(mode)) color = 256+33; 276 else if (S_ISREG(mode) && (mode&0111)) color = 256+32; 277 else if (S_ISFIFO(mode)) color = 33; 278 else if (S_ISSOCK(mode)) color = 256+35; 279 280 return color; 281 } 282 283 // Display a list of dirtree entries, according to current format 284 // Output types -1, -l, -C, or stream 285 286 static void listfiles(int dirfd, struct dirtree *indir) 287 { 288 struct dirtree *dt, **sort; 289 unsigned long dtlen, ul = 0; 290 unsigned width, flags = toys.optflags, totals[8], len[8], totpad = 0, 291 *colsizes = (unsigned *)(toybuf+260), columns = (sizeof(toybuf)-260)/4; 292 char tmp[64]; 293 294 if (-1 == dirfd) { 295 strwidth(indir->name); 296 perror_msg_raw(indir->name); 297 298 return; 299 } 300 301 memset(totals, 0, sizeof(totals)); 302 if (CFG_TOYBOX_ON_ANDROID || CFG_TOYBOX_DEBUG) memset(len, 0, sizeof(len)); 303 304 // Top level directory was already populated by main() 305 if (!indir->parent) { 306 // Silently descend into single directory listed by itself on command line. 307 // In this case only show dirname/total header when given -R. 308 dt = indir->child; 309 if (dt && S_ISDIR(dt->st.st_mode) && !dt->next && !(flags&(FLAG_d|FLAG_R))) 310 { 311 listfiles(open(dt->name, 0), TT.singledir = dt); 312 313 return; 314 } 315 316 // Do preprocessing (Dirtree didn't populate, so callback wasn't called.) 317 for (;dt; dt = dt->next) filter(dt); 318 if (flags == (FLAG_1|FLAG_f)) return; 319 } else { 320 // Read directory contents. We dup() the fd because this will close it. 321 // This reads/saves contents to display later, except for in "ls -1f" mode. 322 indir->dirfd = dup(dirfd); 323 dirtree_recurse(indir, filter, DIRTREE_SYMFOLLOW*!!(flags&FLAG_L)); 324 } 325 326 // Copy linked list to array and sort it. Directories go in array because 327 // we visit them in sorted order too. (The nested loops let us measure and 328 // fill with the same inner loop.) 329 for (sort = 0;;sort = xmalloc(dtlen*sizeof(void *))) { 330 for (dtlen = 0, dt = indir->child; dt; dt = dt->next, dtlen++) 331 if (sort) sort[dtlen] = dt; 332 if (sort || !dtlen) break; 333 } 334 335 // Label directory if not top of tree, or if -R 336 if (indir->parent && (TT.singledir!=indir || (flags&FLAG_R))) 337 { 338 char *path = dirtree_path(indir, 0); 339 340 if (TT.nl_title++) xputc('\n'); 341 xprintf("%s:\n", path); 342 free(path); 343 } 344 345 // Measure each entry to work out whitespace padding and total blocks 346 if (!(flags & FLAG_f)) { 347 unsigned long long blocks = 0; 348 349 qsort(sort, dtlen, sizeof(void *), (void *)compare); 350 for (ul = 0; ul<dtlen; ul++) { 351 entrylen(sort[ul], len); 352 for (width = 0; width<8; width++) 353 if (len[width]>totals[width]) totals[width] = len[width]; 354 blocks += sort[ul]->st.st_blocks; 355 } 356 totpad = totals[1]+!!totals[1]+totals[6]+!!totals[6]+totals[7]+!!totals[7]; 357 if ((flags&(FLAG_h|FLAG_l|FLAG_o|FLAG_n|FLAG_g|FLAG_s)) && indir->parent) { 358 if (flags&FLAG_h) { 359 human_readable(tmp, blocks*512, 0); 360 xprintf("total %s\n", tmp); 361 } else xprintf("total %llu\n", blocks); 362 } 363 } 364 365 // Find largest entry in each field for display alignment 366 if (flags & (FLAG_C|FLAG_x)) { 367 368 // columns can't be more than toybuf can hold, or more than files, 369 // or > 1/2 screen width (one char filename, one space). 370 if (columns > TT.screen_width/2) columns = TT.screen_width/2; 371 if (columns > dtlen) columns = dtlen; 372 373 // Try to fit as many columns as we can, dropping down by one each time 374 for (;columns > 1; columns--) { 375 unsigned c, totlen = columns; 376 377 memset(colsizes, 0, columns*sizeof(unsigned)); 378 for (ul=0; ul<dtlen; ul++) { 379 entrylen(sort[next_column(ul, dtlen, columns, &c)], len); 380 *len += totpad; 381 if (c == columns) break; 382 // Expand this column if necessary, break if that puts us over budget 383 if (*len > colsizes[c]) { 384 totlen += (*len)-colsizes[c]; 385 colsizes[c] = *len; 386 if (totlen > TT.screen_width) break; 387 } 388 } 389 // If everything fit, stop here 390 if (ul == dtlen) break; 391 } 392 } 393 394 // Loop through again to produce output. 395 memset(toybuf, ' ', 256); 396 width = 0; 397 for (ul = 0; ul<dtlen; ul++) { 398 unsigned curcol, color = 0; 399 unsigned long next = next_column(ul, dtlen, columns, &curcol); 400 struct stat *st = &(sort[next]->st); 401 mode_t mode = st->st_mode; 402 char et = endtype(st); 403 404 // Skip directories at the top of the tree when -d isn't set 405 if (S_ISDIR(mode) && !indir->parent && !(flags & FLAG_d)) continue; 406 TT.nl_title=1; 407 408 // Handle padding and wrapping for display purposes 409 entrylen(sort[next], len); 410 if (ul) { 411 if (flags & FLAG_m) xputc(','); 412 if (flags & (FLAG_C|FLAG_x)) { 413 if (!curcol) xputc('\n'); 414 } else if ((flags & FLAG_1) || width+1+*len > TT.screen_width) { 415 xputc('\n'); 416 width = 0; 417 } else { 418 xputc(' '); 419 width++; 420 } 421 } 422 width += *len; 423 424 if (flags & FLAG_i) 425 xprintf("%*lu ", totals[1], (unsigned long)st->st_ino); 426 if (flags & FLAG_s) 427 xprintf("%*lu ", totals[6], (unsigned long)st->st_blocks); 428 429 if (flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g)) { 430 struct tm *tm; 431 char *ss; 432 433 // (long) is to coerce the st types into something we know we can print. 434 mode_to_string(mode, tmp); 435 printf("%s% *ld", tmp, totals[2]+1, (long)st->st_nlink); 436 437 // print user 438 if (!(flags&FLAG_g)) { 439 if (flags&FLAG_n) sprintf(ss = tmp, "%u", (unsigned)st->st_uid); 440 else strwidth(ss = getusername(st->st_uid)); 441 printf(" %-*s", (int)totals[3], ss); 442 } 443 444 // print group 445 if (!(flags&FLAG_o)) { 446 if (flags&FLAG_n) sprintf(ss = tmp, "%u", (unsigned)st->st_gid); 447 else strwidth(ss = getgroupname(st->st_gid)); 448 printf(" %-*s", (int)totals[4], ss); 449 } 450 451 if (flags & FLAG_Z) 452 printf(" %-*s", -(int)totals[7], (char *)sort[next]->extra); 453 454 // print major/minor, or size 455 if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) 456 printf("% *d,% 4d", totals[5]-4, major(st->st_rdev),minor(st->st_rdev)); 457 else if (flags&FLAG_h) { 458 human_readable(tmp, st->st_size, 0); 459 xprintf("%*s", totals[5]+1, tmp); 460 } else printf("% *lld", totals[5]+1, (long long)st->st_size); 461 462 // print time, always in --time-style=long-iso 463 tm = localtime(&(st->st_mtime)); 464 strftime(tmp, sizeof(tmp), "%F %H:%M", tm); 465 xprintf(" %s ", tmp); 466 } else if (flags & FLAG_Z) 467 printf("%-*s ", (int)totals[7], (char *)sort[next]->extra); 468 469 if (flags & FLAG_color) { 470 color = color_from_mode(st->st_mode); 471 if (color) printf("\033[%d;%dm", color>>8, color&255); 472 } 473 474 if (flags & FLAG_q) { 475 char *p; 476 for (p=sort[next]->name; *p; p++) fputc(isprint(*p) ? *p : '?', stdout); 477 } else xprintf("%s", sort[next]->name); 478 if (color) xprintf("\033[0m"); 479 480 if ((flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g)) && S_ISLNK(mode)) { 481 printf(" -> "); 482 if (flags & FLAG_color) { 483 struct stat st2; 484 485 if (fstatat(dirfd, sort[next]->symlink, &st2, 0)) color = 256+31; 486 else color = color_from_mode(st2.st_mode); 487 488 if (color) printf("\033[%d;%dm", color>>8, color&255); 489 } 490 491 printf("%s", sort[next]->symlink); 492 if (color) printf("\033[0m"); 493 } 494 495 if (et) xputc(et); 496 497 // Pad columns 498 if (flags & (FLAG_C|FLAG_x)) { 499 curcol = colsizes[curcol]-(*len)-totpad; 500 if (curcol < 255) xprintf("%s", toybuf+255-curcol); 501 } 502 } 503 504 if (width) xputc('\n'); 505 506 // Free directory entries, recursing first if necessary. 507 508 for (ul = 0; ul<dtlen; free(sort[ul++])) { 509 if ((flags & FLAG_d) || !S_ISDIR(sort[ul]->st.st_mode)) continue; 510 511 // Recurse into dirs if at top of the tree or given -R 512 if (!indir->parent || ((flags&FLAG_R) && dirtree_notdotdot(sort[ul]))) 513 listfiles(openat(dirfd, sort[ul]->name, 0), sort[ul]); 514 free((void *)sort[ul]->extra); 515 } 516 free(sort); 517 if (dirfd != AT_FDCWD) close(dirfd); 518 } 519 520 void ls_main(void) 521 { 522 char **s, *noargs[] = {".", 0}; 523 struct dirtree *dt; 524 525 TT.screen_width = 80; 526 terminal_size(&TT.screen_width, NULL); 527 if (TT.screen_width<2) TT.screen_width = 2; 528 529 // Do we have an implied -1 530 if (!isatty(1)) { 531 if (!(toys.optflags & FLAG_m)) toys.optflags |= FLAG_1; 532 if (TT.color) toys.optflags ^= FLAG_color; 533 } else if (toys.optflags&(FLAG_l|FLAG_o|FLAG_n|FLAG_g)) 534 toys.optflags |= FLAG_1; 535 else if (!(toys.optflags&(FLAG_1|FLAG_x|FLAG_m))) toys.optflags |= FLAG_C; 536 // The optflags parsing infrastructure should really do this for us, 537 // but currently it has "switch off when this is set", so "-dR" and "-Rd" 538 // behave differently 539 if (toys.optflags & FLAG_d) toys.optflags &= ~FLAG_R; 540 541 // Iterate through command line arguments, collecting directories and files. 542 // Non-absolute paths are relative to current directory. 543 TT.files = dirtree_start(0, 0); 544 TT.files->dirfd = AT_FDCWD; 545 for (s = *toys.optargs ? toys.optargs : noargs; *s; s++) { 546 dt = dirtree_start(*s, !(toys.optflags&(FLAG_l|FLAG_d|FLAG_F)) || 547 (toys.optflags&(FLAG_L|FLAG_H))); 548 549 // note: double_list->prev temporarirly goes in dirtree->parent 550 if (dt) dlist_add_nomalloc((void *)&TT.files->child, (void *)dt); 551 else toys.exitval = 1; 552 } 553 554 // Convert double_list into dirtree. 555 dlist_terminate(TT.files->child); 556 for (dt = TT.files->child; dt; dt = dt->next) dt->parent = TT.files; 557 558 // Display the files we collected 559 listfiles(AT_FDCWD, TT.files); 560 561 if (CFG_TOYBOX_FREE) free(TT.files); 562 } 563