ls.c 26 KB

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