ls.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * tiny-ls.c version 0.1.0: A minimalist 'ls'
  4. * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
  5. *
  6. * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
  7. */
  8. /*
  9. * To achieve a small memory footprint, this version of 'ls' doesn't do any
  10. * file sorting, and only has the most essential command line switches
  11. * (i.e., the ones I couldn't live without :-) All features which involve
  12. * linking in substantial chunks of libc can be disabled.
  13. *
  14. * Although I don't really want to add new features to this program to
  15. * keep it small, I *am* interested to receive bug fixes and ways to make
  16. * it more portable.
  17. *
  18. * KNOWN BUGS:
  19. * 1. ls -l of a directory doesn't give "total <blocks>" header
  20. * 2. ls of a symlink to a directory doesn't list directory contents
  21. * 3. hidden files can make column width too large
  22. *
  23. * NON-OPTIMAL BEHAVIOUR:
  24. * 1. autowidth reads directories twice
  25. * 2. if you do a short directory listing without filetype characters
  26. * appended, there's no need to stat each one
  27. * PORTABILITY:
  28. * 1. requires lstat (BSD) - how do you do it without?
  29. */
  30. #include "busybox.h"
  31. #include <getopt.h>
  32. enum {
  33. TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
  34. COLUMN_GAP = 2, /* includes the file type char */
  35. /* what is the overall style of the listing */
  36. STYLE_COLUMNS = 1 << 21, /* fill columns */
  37. STYLE_LONG = 2 << 21, /* one record per line, extended info */
  38. STYLE_SINGLE = 3 << 21, /* one record per line */
  39. STYLE_MASK = STYLE_SINGLE,
  40. /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
  41. /* what file information will be listed */
  42. LIST_INO = 1 << 0,
  43. LIST_BLOCKS = 1 << 1,
  44. LIST_MODEBITS = 1 << 2,
  45. LIST_NLINKS = 1 << 3,
  46. LIST_ID_NAME = 1 << 4,
  47. LIST_ID_NUMERIC = 1 << 5,
  48. LIST_CONTEXT = 1 << 6,
  49. LIST_SIZE = 1 << 7,
  50. LIST_DEV = 1 << 8,
  51. LIST_DATE_TIME = 1 << 9,
  52. LIST_FULLTIME = 1 << 10,
  53. LIST_FILENAME = 1 << 11,
  54. LIST_SYMLINK = 1 << 12,
  55. LIST_FILETYPE = 1 << 13,
  56. LIST_EXEC = 1 << 14,
  57. LIST_MASK = (LIST_EXEC << 1) - 1,
  58. /* what files will be displayed */
  59. DISP_DIRNAME = 1 << 15, /* 2 or more items? label directories */
  60. DISP_HIDDEN = 1 << 16, /* show filenames starting with . */
  61. DISP_DOT = 1 << 17, /* show . and .. */
  62. DISP_NOLIST = 1 << 18, /* show directory as itself, not contents */
  63. DISP_RECURSIVE = 1 << 19, /* show directory and everything below it */
  64. DISP_ROWS = 1 << 20, /* print across rows */
  65. DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
  66. /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
  67. SORT_FORWARD = 0, /* sort in reverse order */
  68. SORT_REVERSE = 1 << 27, /* sort in reverse order */
  69. SORT_NAME = 0, /* sort by file name */
  70. SORT_SIZE = 1 << 28, /* sort by file size */
  71. SORT_ATIME = 2 << 28, /* sort by last access time */
  72. SORT_CTIME = 3 << 28, /* sort by last change time */
  73. SORT_MTIME = 4 << 28, /* sort by last modification time */
  74. SORT_VERSION = 5 << 28, /* sort by version */
  75. SORT_EXT = 6 << 28, /* sort by file name extension */
  76. SORT_DIR = 7 << 28, /* sort by file or directory */
  77. SORT_MASK = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES,
  78. /* which of the three times will be used */
  79. TIME_CHANGE = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
  80. TIME_ACCESS = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS,
  81. TIME_MASK = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
  82. FOLLOW_LINKS = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS,
  83. LS_DISP_HR = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE,
  84. LIST_SHORT = LIST_FILENAME,
  85. LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
  86. LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK,
  87. SPLIT_DIR = 1,
  88. SPLIT_FILE = 0,
  89. SPLIT_SUBDIR = 2,
  90. };
  91. #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
  92. #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
  93. #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
  94. #define COLOR(mode) ("\000\043\043\043\042\000\043\043"\
  95. "\000\000\044\000\043\000\000\040" [TYPEINDEX(mode)])
  96. #define ATTR(mode) ("\00\00\01\00\01\00\01\00"\
  97. "\00\00\01\00\01\00\00\01" [TYPEINDEX(mode)])
  98. /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
  99. #if ENABLE_FEATURE_LS_COLOR
  100. static int show_color;
  101. /* long option entry used only for --color, which has no short option
  102. * equivalent */
  103. static const struct option ls_color_opt[] = {
  104. { "color", optional_argument, NULL, 1 },
  105. { NULL, 0, NULL, 0 }
  106. };
  107. #else
  108. enum { show_color = 0 };
  109. #endif
  110. /*
  111. * a directory entry and its stat info are stored here
  112. */
  113. struct dnode { /* the basic node */
  114. char *name; /* the dir entry name */
  115. char *fullname; /* the dir entry name */
  116. int allocated;
  117. struct stat dstat; /* the file stat info */
  118. USE_SELINUX(security_context_t sid;)
  119. struct dnode *next; /* point at the next node */
  120. };
  121. typedef struct dnode dnode_t;
  122. static struct dnode **list_dir(const char *);
  123. static struct dnode **dnalloc(int);
  124. static int list_single(struct dnode *);
  125. static unsigned all_fmt;
  126. #if ENABLE_FEATURE_AUTOWIDTH
  127. static unsigned tabstops = COLUMN_GAP;
  128. static unsigned terminal_width = TERMINAL_WIDTH;
  129. #else
  130. enum {
  131. tabstops = COLUMN_GAP,
  132. terminal_width = TERMINAL_WIDTH,
  133. };
  134. #endif
  135. static int status = EXIT_SUCCESS;
  136. static struct dnode *my_stat(char *fullname, char *name)
  137. {
  138. struct stat dstat;
  139. struct dnode *cur;
  140. USE_SELINUX(security_context_t sid = NULL;)
  141. if (all_fmt & FOLLOW_LINKS) {
  142. #if ENABLE_SELINUX
  143. if (is_selinux_enabled()) {
  144. getfilecon(fullname, &sid);
  145. }
  146. #endif
  147. if (stat(fullname, &dstat)) {
  148. bb_perror_msg("%s", fullname);
  149. status = EXIT_FAILURE;
  150. return 0;
  151. }
  152. } else {
  153. #if ENABLE_SELINUX
  154. if (is_selinux_enabled()) {
  155. lgetfilecon(fullname,&sid);
  156. }
  157. #endif
  158. if (lstat(fullname, &dstat)) {
  159. bb_perror_msg("%s", fullname);
  160. status = EXIT_FAILURE;
  161. return 0;
  162. }
  163. }
  164. cur = xmalloc(sizeof(struct dnode));
  165. cur->fullname = fullname;
  166. cur->name = name;
  167. cur->dstat = dstat;
  168. USE_SELINUX(cur->sid = sid;)
  169. return cur;
  170. }
  171. #if ENABLE_FEATURE_LS_COLOR
  172. static char fgcolor(mode_t mode)
  173. {
  174. /* Check wheter the file is existing (if so, color it red!) */
  175. if (errno == ENOENT)
  176. return '\037';
  177. if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
  178. return COLOR(0xF000); /* File is executable ... */
  179. return COLOR(mode);
  180. }
  181. static char bgcolor(mode_t mode)
  182. {
  183. if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
  184. return ATTR(0xF000); /* File is executable ... */
  185. return ATTR(mode);
  186. }
  187. #endif
  188. #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
  189. static char append_char(mode_t mode)
  190. {
  191. if (!(all_fmt & LIST_FILETYPE))
  192. return '\0';
  193. if (S_ISDIR(mode))
  194. return '/';
  195. if (!(all_fmt & LIST_EXEC))
  196. return '\0';
  197. if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
  198. return '*';
  199. return APPCHAR(mode);
  200. }
  201. #endif
  202. #define countdirs(A, B) count_dirs((A), (B), 1)
  203. #define countsubdirs(A, B) count_dirs((A), (B), 0)
  204. static int count_dirs(struct dnode **dn, int nfiles, int notsubdirs)
  205. {
  206. int i, dirs;
  207. if (!dn)
  208. return 0;
  209. dirs = 0;
  210. for (i = 0; i < nfiles; i++) {
  211. char *name;
  212. if (!S_ISDIR(dn[i]->dstat.st_mode))
  213. continue;
  214. name = dn[i]->name;
  215. if (notsubdirs
  216. || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
  217. ) {
  218. dirs++;
  219. }
  220. }
  221. return dirs;
  222. }
  223. static int countfiles(struct dnode **dnp)
  224. {
  225. int nfiles;
  226. struct dnode *cur;
  227. if (dnp == NULL)
  228. return 0;
  229. nfiles = 0;
  230. for (cur = dnp[0]; cur->next; cur = cur->next)
  231. nfiles++;
  232. nfiles++;
  233. return nfiles;
  234. }
  235. /* get memory to hold an array of pointers */
  236. static struct dnode **dnalloc(int num)
  237. {
  238. if (num < 1)
  239. return NULL;
  240. return xzalloc(num * sizeof(struct dnode *));
  241. }
  242. #if ENABLE_FEATURE_LS_RECURSIVE
  243. static void dfree(struct dnode **dnp, int nfiles)
  244. {
  245. int i;
  246. if (dnp == NULL)
  247. return;
  248. for (i = 0; i < nfiles; i++) {
  249. struct dnode *cur = dnp[i];
  250. if (cur->allocated)
  251. free(cur->fullname); /* free the filename */
  252. free(cur); /* free the dnode */
  253. }
  254. free(dnp); /* free the array holding the dnode pointers */
  255. }
  256. #else
  257. #define dfree(...) ((void)0)
  258. #endif
  259. static struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which)
  260. {
  261. int dncnt, i, d;
  262. struct dnode **dnp;
  263. if (dn == NULL || nfiles < 1)
  264. return NULL;
  265. /* count how many dirs and regular files there are */
  266. if (which == SPLIT_SUBDIR)
  267. dncnt = countsubdirs(dn, nfiles);
  268. else {
  269. dncnt = countdirs(dn, nfiles); /* assume we are looking for dirs */
  270. if (which == SPLIT_FILE)
  271. dncnt = nfiles - dncnt; /* looking for files */
  272. }
  273. /* allocate a file array and a dir array */
  274. dnp = dnalloc(dncnt);
  275. /* copy the entrys into the file or dir array */
  276. for (d = i = 0; i < nfiles; i++) {
  277. if (S_ISDIR(dn[i]->dstat.st_mode)) {
  278. char *name;
  279. if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
  280. continue;
  281. name = dn[i]->name;
  282. if ((which & SPLIT_DIR)
  283. || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
  284. ) {
  285. dnp[d++] = dn[i];
  286. }
  287. } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
  288. dnp[d++] = dn[i];
  289. }
  290. }
  291. return dnp;
  292. }
  293. #if ENABLE_FEATURE_LS_SORTFILES
  294. static int sortcmp(const void *a, const void *b)
  295. {
  296. struct dnode *d1 = *(struct dnode **)a;
  297. struct dnode *d2 = *(struct dnode **)b;
  298. unsigned sort_opts = all_fmt & SORT_MASK;
  299. int dif;
  300. dif = 0; /* assume SORT_NAME */
  301. // TODO: use pre-initialized function pointer
  302. // instead of branch forest
  303. if (sort_opts == SORT_SIZE) {
  304. dif = (int) (d2->dstat.st_size - d1->dstat.st_size);
  305. } else if (sort_opts == SORT_ATIME) {
  306. dif = (int) (d2->dstat.st_atime - d1->dstat.st_atime);
  307. } else if (sort_opts == SORT_CTIME) {
  308. dif = (int) (d2->dstat.st_ctime - d1->dstat.st_ctime);
  309. } else if (sort_opts == SORT_MTIME) {
  310. dif = (int) (d2->dstat.st_mtime - d1->dstat.st_mtime);
  311. } else if (sort_opts == SORT_DIR) {
  312. dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
  313. /* } else if (sort_opts == SORT_VERSION) { */
  314. /* } else if (sort_opts == SORT_EXT) { */
  315. }
  316. if (dif == 0) {
  317. /* sort by name - may be a tie_breaker for time or size cmp */
  318. if (ENABLE_LOCALE_SUPPORT) dif = strcoll(d1->name, d2->name);
  319. else dif = strcmp(d1->name, d2->name);
  320. }
  321. if (all_fmt & SORT_REVERSE) {
  322. dif = -dif;
  323. }
  324. return dif;
  325. }
  326. static void dnsort(struct dnode **dn, int size)
  327. {
  328. qsort(dn, size, sizeof(*dn), sortcmp);
  329. }
  330. #else
  331. #define dnsort(dn, size) ((void)0)
  332. #endif
  333. static void showfiles(struct dnode **dn, int nfiles)
  334. {
  335. int i, ncols, nrows, row, nc;
  336. int column = 0;
  337. int nexttab = 0;
  338. int column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */
  339. if (dn == NULL || nfiles < 1)
  340. return;
  341. if (all_fmt & STYLE_LONG) {
  342. ncols = 1;
  343. } else {
  344. /* find the longest file name, use that as the column width */
  345. for (i = 0; i < nfiles; i++) {
  346. int len = strlen(dn[i]->name);
  347. if (column_width < len)
  348. column_width = len;
  349. }
  350. column_width += tabstops +
  351. USE_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
  352. ((all_fmt & LIST_INO) ? 8 : 0) +
  353. ((all_fmt & LIST_BLOCKS) ? 5 : 0);
  354. ncols = (int) (terminal_width / column_width);
  355. }
  356. if (ncols > 1) {
  357. nrows = nfiles / ncols;
  358. if (nrows * ncols < nfiles)
  359. nrows++; /* round up fractionals */
  360. } else {
  361. nrows = nfiles;
  362. ncols = 1;
  363. }
  364. for (row = 0; row < nrows; row++) {
  365. for (nc = 0; nc < ncols; nc++) {
  366. /* reach into the array based on the column and row */
  367. i = (nc * nrows) + row; /* assume display by column */
  368. if (all_fmt & DISP_ROWS)
  369. i = (row * ncols) + nc; /* display across row */
  370. if (i < nfiles) {
  371. if (column > 0) {
  372. nexttab -= column;
  373. printf("%*s", nexttab, "");
  374. column += nexttab;
  375. }
  376. nexttab = column + column_width;
  377. column += list_single(dn[i]);
  378. }
  379. }
  380. putchar('\n');
  381. column = 0;
  382. }
  383. }
  384. static void showdirs(struct dnode **dn, int ndirs, int first)
  385. {
  386. int i, nfiles;
  387. struct dnode **subdnp;
  388. int dndirs;
  389. struct dnode **dnd;
  390. if (dn == NULL || ndirs < 1)
  391. return;
  392. for (i = 0; i < ndirs; i++) {
  393. if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
  394. if (!first)
  395. puts("");
  396. first = 0;
  397. printf("%s:\n", dn[i]->fullname);
  398. }
  399. subdnp = list_dir(dn[i]->fullname);
  400. nfiles = countfiles(subdnp);
  401. if (nfiles > 0) {
  402. /* list all files at this level */
  403. dnsort(subdnp, nfiles);
  404. showfiles(subdnp, nfiles);
  405. if (ENABLE_FEATURE_LS_RECURSIVE) {
  406. if (all_fmt & DISP_RECURSIVE) {
  407. /* recursive- list the sub-dirs */
  408. dnd = splitdnarray(subdnp, nfiles, SPLIT_SUBDIR);
  409. dndirs = countsubdirs(subdnp, nfiles);
  410. if (dndirs > 0) {
  411. dnsort(dnd, dndirs);
  412. showdirs(dnd, dndirs, 0);
  413. /* free the array of dnode pointers to the dirs */
  414. free(dnd);
  415. }
  416. }
  417. /* free the dnodes and the fullname mem */
  418. dfree(subdnp, nfiles);
  419. }
  420. }
  421. }
  422. }
  423. static struct dnode **list_dir(const char *path)
  424. {
  425. struct dnode *dn, *cur, **dnp;
  426. struct dirent *entry;
  427. DIR *dir;
  428. int i, nfiles;
  429. if (path == NULL)
  430. return NULL;
  431. dn = NULL;
  432. nfiles = 0;
  433. dir = warn_opendir(path);
  434. if (dir == NULL) {
  435. status = EXIT_FAILURE;
  436. return NULL; /* could not open the dir */
  437. }
  438. while ((entry = readdir(dir)) != NULL) {
  439. char *fullname;
  440. /* are we going to list the file- it may be . or .. or a hidden file */
  441. if (entry->d_name[0] == '.') {
  442. if ((entry->d_name[1] == 0 || (
  443. entry->d_name[1] == '.'
  444. && entry->d_name[2] == 0))
  445. && !(all_fmt & DISP_DOT))
  446. continue;
  447. if (!(all_fmt & DISP_HIDDEN))
  448. continue;
  449. }
  450. fullname = concat_path_file(path, entry->d_name);
  451. cur = my_stat(fullname, strrchr(fullname, '/') + 1);
  452. if (!cur) {
  453. free(fullname);
  454. continue;
  455. }
  456. cur->allocated = 1;
  457. cur->next = dn;
  458. dn = cur;
  459. nfiles++;
  460. }
  461. closedir(dir);
  462. /* now that we know how many files there are
  463. * allocate memory for an array to hold dnode pointers
  464. */
  465. if (dn == NULL)
  466. return NULL;
  467. dnp = dnalloc(nfiles);
  468. for (i = 0, cur = dn; i < nfiles; i++) {
  469. dnp[i] = cur; /* save pointer to node in array */
  470. cur = cur->next;
  471. }
  472. return dnp;
  473. }
  474. #if ENABLE_FEATURE_LS_TIMESTAMPS
  475. /* Do time() just once. Saves one syscall per file for "ls -l" */
  476. /* Initialized in main() */
  477. static time_t current_time_t;
  478. #endif
  479. static int list_single(struct dnode *dn)
  480. {
  481. int i, column = 0;
  482. #if ENABLE_FEATURE_LS_TIMESTAMPS
  483. char *filetime;
  484. time_t ttime, age;
  485. #endif
  486. #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
  487. struct stat info;
  488. char append;
  489. #endif
  490. if (dn->fullname == NULL)
  491. return 0;
  492. #if ENABLE_FEATURE_LS_TIMESTAMPS
  493. ttime = dn->dstat.st_mtime; /* the default time */
  494. if (all_fmt & TIME_ACCESS)
  495. ttime = dn->dstat.st_atime;
  496. if (all_fmt & TIME_CHANGE)
  497. ttime = dn->dstat.st_ctime;
  498. filetime = ctime(&ttime);
  499. #endif
  500. #if ENABLE_FEATURE_LS_FILETYPES
  501. append = append_char(dn->dstat.st_mode);
  502. #endif
  503. for (i = 0; i <= 31; i++) {
  504. switch (all_fmt & (1 << i)) {
  505. case LIST_INO:
  506. column += printf("%7ld ", (long) dn->dstat.st_ino);
  507. break;
  508. case LIST_BLOCKS:
  509. column += printf("%4"OFF_FMT"d ", (off_t) dn->dstat.st_blocks >> 1);
  510. break;
  511. case LIST_MODEBITS:
  512. column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
  513. break;
  514. case LIST_NLINKS:
  515. column += printf("%4ld ", (long) dn->dstat.st_nlink);
  516. break;
  517. case LIST_ID_NAME:
  518. #if ENABLE_FEATURE_LS_USERNAME
  519. printf("%-8.8s %-8.8s",
  520. get_cached_username(dn->dstat.st_uid),
  521. get_cached_groupname(dn->dstat.st_gid));
  522. column += 17;
  523. break;
  524. #endif
  525. case LIST_ID_NUMERIC:
  526. column += printf("%-8d %-8d", dn->dstat.st_uid, dn->dstat.st_gid);
  527. break;
  528. case LIST_SIZE:
  529. case LIST_DEV:
  530. if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
  531. column += printf("%4d, %3d ", (int) major(dn->dstat.st_rdev),
  532. (int) minor(dn->dstat.st_rdev));
  533. } else {
  534. if (all_fmt & LS_DISP_HR) {
  535. column += printf("%9s ",
  536. make_human_readable_str(dn->dstat.st_size, 1, 0));
  537. } else {
  538. column += printf("%9"OFF_FMT"d ", (off_t) dn->dstat.st_size);
  539. }
  540. }
  541. break;
  542. #if ENABLE_FEATURE_LS_TIMESTAMPS
  543. case LIST_FULLTIME:
  544. printf("%24.24s ", filetime);
  545. column += 25;
  546. break;
  547. case LIST_DATE_TIME:
  548. if ((all_fmt & LIST_FULLTIME) == 0) {
  549. /* current_time_t ~== time(NULL) */
  550. age = current_time_t - ttime;
  551. printf("%6.6s ", filetime + 4);
  552. if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
  553. /* hh:mm if less than 6 months old */
  554. printf("%5.5s ", filetime + 11);
  555. } else {
  556. printf(" %4.4s ", filetime + 20);
  557. }
  558. column += 13;
  559. }
  560. break;
  561. #endif
  562. #if ENABLE_SELINUX
  563. case LIST_CONTEXT:
  564. {
  565. char context[80];
  566. int len = 0;
  567. if (dn->sid) {
  568. /* I assume sid initilized with NULL */
  569. len = strlen(dn->sid) + 1;
  570. safe_strncpy(context, dn->sid, len);
  571. freecon(dn->sid);
  572. } else {
  573. safe_strncpy(context, "unknown", 8);
  574. }
  575. printf("%-32s ", context);
  576. column += MAX(33, len);
  577. }
  578. break;
  579. #endif
  580. case LIST_FILENAME:
  581. errno = 0;
  582. #if ENABLE_FEATURE_LS_COLOR
  583. if (show_color && !lstat(dn->fullname, &info)) {
  584. printf("\033[%d;%dm", bgcolor(info.st_mode),
  585. fgcolor(info.st_mode));
  586. }
  587. #endif
  588. column += printf("%s", dn->name);
  589. if (show_color) {
  590. printf("\033[0m");
  591. }
  592. break;
  593. case LIST_SYMLINK:
  594. if (S_ISLNK(dn->dstat.st_mode)) {
  595. char *lpath = xreadlink(dn->fullname);
  596. if (!lpath) break;
  597. printf(" -> ");
  598. #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
  599. if (!stat(dn->fullname, &info)) {
  600. append = append_char(info.st_mode);
  601. }
  602. #endif
  603. #if ENABLE_FEATURE_LS_COLOR
  604. if (show_color) {
  605. errno = 0;
  606. printf("\033[%d;%dm", bgcolor(info.st_mode),
  607. fgcolor(info.st_mode));
  608. }
  609. #endif
  610. column += printf("%s", lpath) + 4;
  611. if (show_color) {
  612. printf("\033[0m");
  613. }
  614. free(lpath);
  615. }
  616. break;
  617. #if ENABLE_FEATURE_LS_FILETYPES
  618. case LIST_FILETYPE:
  619. if (append) {
  620. putchar(append);
  621. column++;
  622. }
  623. break;
  624. #endif
  625. }
  626. }
  627. return column;
  628. }
  629. /* "[-]Cadil1", POSIX mandated options, busybox always supports */
  630. /* "[-]gnsx", POSIX non-mandated options, busybox always supports */
  631. /* "[-]Ak" GNU options, busybox always supports */
  632. /* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
  633. /* "[-]p", POSIX non-mandated options, busybox optionally supports */
  634. /* "[-]SXvThw", GNU options, busybox optionally supports */
  635. /* "[-]K", SELinux mandated options, busybox optionally supports */
  636. /* "[-]e", I think we made this one up */
  637. static const char ls_options[] = "Cadil1gnsxAk"
  638. USE_FEATURE_LS_TIMESTAMPS("cetu")
  639. USE_FEATURE_LS_SORTFILES("SXrv")
  640. USE_FEATURE_LS_FILETYPES("Fp")
  641. USE_FEATURE_LS_FOLLOWLINKS("L")
  642. USE_FEATURE_LS_RECURSIVE("R")
  643. USE_FEATURE_HUMAN_READABLE("h")
  644. USE_SELINUX("K")
  645. USE_FEATURE_AUTOWIDTH("T:w:");
  646. enum {
  647. LIST_MASK_TRIGGER = 0,
  648. STYLE_MASK_TRIGGER = STYLE_MASK,
  649. DISP_MASK_TRIGGER = DISP_ROWS,
  650. SORT_MASK_TRIGGER = SORT_MASK,
  651. };
  652. static const unsigned opt_flags[] = {
  653. LIST_SHORT | STYLE_COLUMNS, /* C */
  654. DISP_HIDDEN | DISP_DOT, /* a */
  655. DISP_NOLIST, /* d */
  656. LIST_INO, /* i */
  657. LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */
  658. LIST_SHORT | STYLE_SINGLE, /* 1 */
  659. 0, /* g - ingored */
  660. LIST_ID_NUMERIC, /* n */
  661. LIST_BLOCKS, /* s */
  662. DISP_ROWS, /* x */
  663. DISP_HIDDEN, /* A */
  664. ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
  665. #if ENABLE_FEATURE_LS_TIMESTAMPS
  666. TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
  667. LIST_FULLTIME, /* e */
  668. ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
  669. TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
  670. #endif
  671. #if ENABLE_FEATURE_LS_SORTFILES
  672. SORT_SIZE, /* S */
  673. SORT_EXT, /* X */
  674. SORT_REVERSE, /* r */
  675. SORT_VERSION, /* v */
  676. #endif
  677. #if ENABLE_FEATURE_LS_FILETYPES
  678. LIST_FILETYPE | LIST_EXEC, /* F */
  679. LIST_FILETYPE, /* p */
  680. #endif
  681. #if ENABLE_FEATURE_LS_FOLLOWLINKS
  682. FOLLOW_LINKS, /* L */
  683. #endif
  684. #if ENABLE_FEATURE_LS_RECURSIVE
  685. DISP_RECURSIVE, /* R */
  686. #endif
  687. #if ENABLE_FEATURE_HUMAN_READABLE
  688. LS_DISP_HR, /* h */
  689. #endif
  690. #if ENABLE_SELINUX
  691. LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
  692. #endif
  693. #if ENABLE_FEATURE_AUTOWIDTH
  694. 0, 0, /* T, w - ignored */
  695. #endif
  696. (1U<<31)
  697. };
  698. int ls_main(int argc, char **argv)
  699. {
  700. struct dnode **dnd;
  701. struct dnode **dnf;
  702. struct dnode **dnp;
  703. struct dnode *dn;
  704. struct dnode *cur;
  705. unsigned opt;
  706. int nfiles = 0;
  707. int dnfiles;
  708. int dndirs;
  709. int oi;
  710. int ac;
  711. int i;
  712. char **av;
  713. USE_FEATURE_AUTOWIDTH(char *tabstops_str = NULL;)
  714. USE_FEATURE_AUTOWIDTH(char *terminal_width_str = NULL;)
  715. USE_FEATURE_LS_COLOR(char *color_opt;)
  716. setvbuf(stdout, bb_common_bufsiz1, _IOFBF, BUFSIZ);
  717. #if ENABLE_FEATURE_LS_TIMESTAMPS
  718. time(&current_time_t);
  719. #endif
  720. all_fmt = LIST_SHORT |
  721. (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
  722. #if ENABLE_FEATURE_AUTOWIDTH
  723. /* Obtain the terminal width */
  724. get_terminal_width_height(STDOUT_FILENO, &terminal_width, NULL);
  725. /* Go one less... */
  726. terminal_width--;
  727. #endif
  728. /* process options */
  729. USE_FEATURE_LS_COLOR(applet_long_options = ls_color_opt;)
  730. #if ENABLE_FEATURE_AUTOWIDTH
  731. opt = getopt32(argc, argv, ls_options, &tabstops_str, &terminal_width_str
  732. USE_FEATURE_LS_COLOR(, &color_opt));
  733. if (tabstops_str)
  734. tabstops = xatou(tabstops_str);
  735. if (terminal_width_str)
  736. terminal_width = xatou(terminal_width_str);
  737. #else
  738. opt = getopt32(argc, argv, ls_options USE_FEATURE_LS_COLOR(, &color_opt));
  739. #endif
  740. for (i = 0; opt_flags[i] != (1U<<31); i++) {
  741. if (opt & (1 << i)) {
  742. unsigned flags = opt_flags[i];
  743. if (flags & LIST_MASK_TRIGGER)
  744. all_fmt &= ~LIST_MASK;
  745. if (flags & STYLE_MASK_TRIGGER)
  746. all_fmt &= ~STYLE_MASK;
  747. if (flags & SORT_MASK_TRIGGER)
  748. all_fmt &= ~SORT_MASK;
  749. if (flags & DISP_MASK_TRIGGER)
  750. all_fmt &= ~DISP_MASK;
  751. if (flags & TIME_MASK)
  752. all_fmt &= ~TIME_MASK;
  753. if (flags & LIST_CONTEXT)
  754. all_fmt |= STYLE_SINGLE;
  755. if (LS_DISP_HR && opt == 'l')
  756. all_fmt &= ~LS_DISP_HR;
  757. all_fmt |= flags;
  758. }
  759. }
  760. #if ENABLE_FEATURE_LS_COLOR
  761. /* find color bit value - last position for short getopt */
  762. if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
  763. char *p = getenv("LS_COLORS");
  764. /* LS_COLORS is unset, or (not empty && not "none") ? */
  765. if (!p || (p[0] && strcmp(p, "none")))
  766. show_color = 1;
  767. }
  768. if (opt & (1 << i)) { /* next flag after short options */
  769. if (!color_opt || !strcmp("always", color_opt))
  770. show_color = 1;
  771. else if (color_opt && !strcmp("never", color_opt))
  772. show_color = 0;
  773. else if (color_opt && !strcmp("auto", color_opt) && isatty(STDOUT_FILENO))
  774. show_color = 1;
  775. }
  776. #endif
  777. /* sort out which command line options take precedence */
  778. if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
  779. all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
  780. if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
  781. if (all_fmt & TIME_CHANGE)
  782. all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
  783. if (all_fmt & TIME_ACCESS)
  784. all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
  785. }
  786. if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
  787. all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
  788. if (ENABLE_FEATURE_LS_USERNAME)
  789. if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
  790. all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
  791. /* choose a display format */
  792. if (!(all_fmt & STYLE_MASK))
  793. all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
  794. /*
  795. * when there are no cmd line args we have to supply a default "." arg.
  796. * we will create a second argv array, "av" that will hold either
  797. * our created "." arg, or the real cmd line args. The av array
  798. * just holds the pointers- we don't move the date the pointers
  799. * point to.
  800. */
  801. ac = argc - optind; /* how many cmd line args are left */
  802. if (ac < 1) {
  803. static const char *const dotdir[] = { "." };
  804. av = (char **) dotdir;
  805. ac = 1;
  806. } else {
  807. av = argv + optind;
  808. }
  809. /* now, everything is in the av array */
  810. if (ac > 1)
  811. all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
  812. /* stuff the command line file names into a dnode array */
  813. dn = NULL;
  814. for (oi = 0; oi < ac; oi++) {
  815. cur = my_stat(av[oi], av[oi]);
  816. if (!cur)
  817. continue;
  818. cur->allocated = 0;
  819. cur->next = dn;
  820. dn = cur;
  821. nfiles++;
  822. }
  823. /* now that we know how many files there are
  824. * allocate memory for an array to hold dnode pointers
  825. */
  826. dnp = dnalloc(nfiles);
  827. for (i = 0, cur = dn; i < nfiles; i++) {
  828. dnp[i] = cur; /* save pointer to node in array */
  829. cur = cur->next;
  830. }
  831. if (all_fmt & DISP_NOLIST) {
  832. dnsort(dnp, nfiles);
  833. if (nfiles > 0)
  834. showfiles(dnp, nfiles);
  835. } else {
  836. dnd = splitdnarray(dnp, nfiles, SPLIT_DIR);
  837. dnf = splitdnarray(dnp, nfiles, SPLIT_FILE);
  838. dndirs = countdirs(dnp, nfiles);
  839. dnfiles = nfiles - dndirs;
  840. if (dnfiles > 0) {
  841. dnsort(dnf, dnfiles);
  842. showfiles(dnf, dnfiles);
  843. if (ENABLE_FEATURE_CLEAN_UP)
  844. free(dnf);
  845. }
  846. if (dndirs > 0) {
  847. dnsort(dnd, dndirs);
  848. showdirs(dnd, dndirs, dnfiles == 0);
  849. if (ENABLE_FEATURE_CLEAN_UP)
  850. free(dnd);
  851. }
  852. }
  853. if (ENABLE_FEATURE_CLEAN_UP)
  854. dfree(dnp, nfiles);
  855. return status;
  856. }