ls.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  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(...) do {} while(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) do {} while(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. static int list_single(struct dnode *dn)
  475. {
  476. int i, column = 0;
  477. #if ENABLE_FEATURE_LS_USERNAME
  478. char scratch[16];
  479. #endif
  480. #if ENABLE_FEATURE_LS_TIMESTAMPS
  481. char *filetime;
  482. time_t ttime, age;
  483. #endif
  484. #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
  485. struct stat info;
  486. char append;
  487. #endif
  488. if (dn->fullname == NULL)
  489. return 0;
  490. #if ENABLE_FEATURE_LS_TIMESTAMPS
  491. ttime = dn->dstat.st_mtime; /* the default time */
  492. if (all_fmt & TIME_ACCESS)
  493. ttime = dn->dstat.st_atime;
  494. if (all_fmt & TIME_CHANGE)
  495. ttime = dn->dstat.st_ctime;
  496. filetime = ctime(&ttime);
  497. #endif
  498. #if ENABLE_FEATURE_LS_FILETYPES
  499. append = append_char(dn->dstat.st_mode);
  500. #endif
  501. for (i = 0; i <= 31; i++) {
  502. switch (all_fmt & (1 << i)) {
  503. case LIST_INO:
  504. column += printf("%7ld ", (long) dn->dstat.st_ino);
  505. break;
  506. case LIST_BLOCKS:
  507. column += printf("%4"OFF_FMT"d ", (off_t) dn->dstat.st_blocks >> 1);
  508. break;
  509. case LIST_MODEBITS:
  510. column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
  511. break;
  512. case LIST_NLINKS:
  513. column += printf("%4ld ", (long) dn->dstat.st_nlink);
  514. break;
  515. case LIST_ID_NAME:
  516. #if ENABLE_FEATURE_LS_USERNAME
  517. bb_getpwuid(scratch, dn->dstat.st_uid, sizeof(scratch));
  518. printf("%-8.8s ", scratch);
  519. bb_getgrgid(scratch, dn->dstat.st_gid, sizeof(scratch));
  520. printf("%-8.8s", scratch);
  521. column += 17;
  522. break;
  523. #endif
  524. case LIST_ID_NUMERIC:
  525. column += printf("%-8d %-8d", dn->dstat.st_uid, dn->dstat.st_gid);
  526. break;
  527. case LIST_SIZE:
  528. case LIST_DEV:
  529. if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
  530. column += printf("%4d, %3d ", (int) major(dn->dstat.st_rdev),
  531. (int) minor(dn->dstat.st_rdev));
  532. } else {
  533. if (all_fmt & LS_DISP_HR) {
  534. column += printf("%9s ",
  535. make_human_readable_str(dn->dstat.st_size, 1, 0));
  536. } else {
  537. column += printf("%9"OFF_FMT"d ", (off_t) dn->dstat.st_size);
  538. }
  539. }
  540. break;
  541. #if ENABLE_FEATURE_LS_TIMESTAMPS
  542. case LIST_FULLTIME:
  543. printf("%24.24s ", filetime);
  544. column += 25;
  545. break;
  546. case LIST_DATE_TIME:
  547. if ((all_fmt & LIST_FULLTIME) == 0) {
  548. age = time(NULL) - ttime;
  549. printf("%6.6s ", filetime + 4);
  550. if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
  551. /* hh:mm if less than 6 months old */
  552. printf("%5.5s ", filetime + 11);
  553. } else {
  554. printf(" %4.4s ", filetime + 20);
  555. }
  556. column += 13;
  557. }
  558. break;
  559. #endif
  560. #if ENABLE_SELINUX
  561. case LIST_CONTEXT:
  562. {
  563. char context[80];
  564. int len = 0;
  565. if (dn->sid) {
  566. /* I assume sid initilized with NULL */
  567. len = strlen(dn->sid) + 1;
  568. safe_strncpy(context, dn->sid, len);
  569. freecon(dn->sid);
  570. } else {
  571. safe_strncpy(context, "unknown", 8);
  572. }
  573. printf("%-32s ", context);
  574. column += MAX(33, len);
  575. }
  576. break;
  577. #endif
  578. case LIST_FILENAME:
  579. errno = 0;
  580. #if ENABLE_FEATURE_LS_COLOR
  581. if (show_color && !lstat(dn->fullname, &info)) {
  582. printf("\033[%d;%dm", bgcolor(info.st_mode),
  583. fgcolor(info.st_mode));
  584. }
  585. #endif
  586. column += printf("%s", dn->name);
  587. if (show_color) {
  588. printf("\033[0m");
  589. }
  590. break;
  591. case LIST_SYMLINK:
  592. if (S_ISLNK(dn->dstat.st_mode)) {
  593. char *lpath = xreadlink(dn->fullname);
  594. if (!lpath) break;
  595. printf(" -> ");
  596. #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
  597. if (!stat(dn->fullname, &info)) {
  598. append = append_char(info.st_mode);
  599. }
  600. #endif
  601. #if ENABLE_FEATURE_LS_COLOR
  602. if (show_color) {
  603. errno = 0;
  604. printf("\033[%d;%dm", bgcolor(info.st_mode),
  605. fgcolor(info.st_mode));
  606. }
  607. #endif
  608. column += printf("%s", lpath) + 4;
  609. if (show_color) {
  610. printf("\033[0m");
  611. }
  612. free(lpath);
  613. }
  614. break;
  615. #if ENABLE_FEATURE_LS_FILETYPES
  616. case LIST_FILETYPE:
  617. if (append) {
  618. putchar(append);
  619. column++;
  620. }
  621. break;
  622. #endif
  623. }
  624. }
  625. return column;
  626. }
  627. /* "[-]Cadil1", POSIX mandated options, busybox always supports */
  628. /* "[-]gnsx", POSIX non-mandated options, busybox always supports */
  629. /* "[-]Ak" GNU options, busybox always supports */
  630. /* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
  631. /* "[-]p", POSIX non-mandated options, busybox optionally supports */
  632. /* "[-]SXvThw", GNU options, busybox optionally supports */
  633. /* "[-]K", SELinux mandated options, busybox optionally supports */
  634. /* "[-]e", I think we made this one up */
  635. static const char ls_options[] = "Cadil1gnsxAk"
  636. USE_FEATURE_LS_TIMESTAMPS("cetu")
  637. USE_FEATURE_LS_SORTFILES("SXrv")
  638. USE_FEATURE_LS_FILETYPES("Fp")
  639. USE_FEATURE_LS_FOLLOWLINKS("L")
  640. USE_FEATURE_LS_RECURSIVE("R")
  641. USE_FEATURE_HUMAN_READABLE("h")
  642. USE_SELINUX("K")
  643. USE_FEATURE_AUTOWIDTH("T:w:");
  644. enum {
  645. LIST_MASK_TRIGGER = 0,
  646. STYLE_MASK_TRIGGER = STYLE_MASK,
  647. DISP_MASK_TRIGGER = DISP_ROWS,
  648. SORT_MASK_TRIGGER = SORT_MASK,
  649. };
  650. static const unsigned opt_flags[] = {
  651. LIST_SHORT | STYLE_COLUMNS, /* C */
  652. DISP_HIDDEN | DISP_DOT, /* a */
  653. DISP_NOLIST, /* d */
  654. LIST_INO, /* i */
  655. LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */
  656. LIST_SHORT | STYLE_SINGLE, /* 1 */
  657. 0, /* g - ingored */
  658. LIST_ID_NUMERIC, /* n */
  659. LIST_BLOCKS, /* s */
  660. DISP_ROWS, /* x */
  661. DISP_HIDDEN, /* A */
  662. ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
  663. #if ENABLE_FEATURE_LS_TIMESTAMPS
  664. TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
  665. LIST_FULLTIME, /* e */
  666. ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
  667. TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
  668. #endif
  669. #if ENABLE_FEATURE_LS_SORTFILES
  670. SORT_SIZE, /* S */
  671. SORT_EXT, /* X */
  672. SORT_REVERSE, /* r */
  673. SORT_VERSION, /* v */
  674. #endif
  675. #if ENABLE_FEATURE_LS_FILETYPES
  676. LIST_FILETYPE | LIST_EXEC, /* F */
  677. LIST_FILETYPE, /* p */
  678. #endif
  679. #if ENABLE_FEATURE_LS_FOLLOWLINKS
  680. FOLLOW_LINKS, /* L */
  681. #endif
  682. #if ENABLE_FEATURE_LS_RECURSIVE
  683. DISP_RECURSIVE, /* R */
  684. #endif
  685. #if ENABLE_FEATURE_HUMAN_READABLE
  686. LS_DISP_HR, /* h */
  687. #endif
  688. #if ENABLE_SELINUX
  689. LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
  690. #endif
  691. #if ENABLE_FEATURE_AUTOWIDTH
  692. 0, 0, /* T, w - ignored */
  693. #endif
  694. (1U<<31)
  695. };
  696. int ls_main(int argc, char **argv)
  697. {
  698. struct dnode **dnd;
  699. struct dnode **dnf;
  700. struct dnode **dnp;
  701. struct dnode *dn;
  702. struct dnode *cur;
  703. unsigned opt;
  704. int nfiles = 0;
  705. int dnfiles;
  706. int dndirs;
  707. int oi;
  708. int ac;
  709. int i;
  710. char **av;
  711. USE_FEATURE_AUTOWIDTH(char *tabstops_str = NULL;)
  712. USE_FEATURE_AUTOWIDTH(char *terminal_width_str = NULL;)
  713. USE_FEATURE_LS_COLOR(char *color_opt;)
  714. all_fmt = LIST_SHORT |
  715. (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
  716. #if ENABLE_FEATURE_AUTOWIDTH
  717. /* Obtain the terminal width */
  718. get_terminal_width_height(STDOUT_FILENO, &terminal_width, NULL);
  719. /* Go one less... */
  720. terminal_width--;
  721. #endif
  722. /* process options */
  723. USE_FEATURE_LS_COLOR(applet_long_options = ls_color_opt;)
  724. #if ENABLE_FEATURE_AUTOWIDTH
  725. opt = getopt32(argc, argv, ls_options, &tabstops_str, &terminal_width_str
  726. USE_FEATURE_LS_COLOR(, &color_opt));
  727. if (tabstops_str)
  728. tabstops = xatou(tabstops_str);
  729. if (terminal_width_str)
  730. terminal_width = xatou(terminal_width_str);
  731. #else
  732. opt = getopt32(argc, argv, ls_options USE_FEATURE_LS_COLOR(, &color_opt));
  733. #endif
  734. for (i = 0; opt_flags[i] != (1U<<31); i++) {
  735. if (opt & (1 << i)) {
  736. unsigned flags = opt_flags[i];
  737. if (flags & LIST_MASK_TRIGGER)
  738. all_fmt &= ~LIST_MASK;
  739. if (flags & STYLE_MASK_TRIGGER)
  740. all_fmt &= ~STYLE_MASK;
  741. if (flags & SORT_MASK_TRIGGER)
  742. all_fmt &= ~SORT_MASK;
  743. if (flags & DISP_MASK_TRIGGER)
  744. all_fmt &= ~DISP_MASK;
  745. if (flags & TIME_MASK)
  746. all_fmt &= ~TIME_MASK;
  747. if (flags & LIST_CONTEXT)
  748. all_fmt |= STYLE_SINGLE;
  749. if (LS_DISP_HR && opt == 'l')
  750. all_fmt &= ~LS_DISP_HR;
  751. all_fmt |= flags;
  752. }
  753. }
  754. #if ENABLE_FEATURE_LS_COLOR
  755. /* find color bit value - last position for short getopt */
  756. if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
  757. char *p = getenv("LS_COLORS");
  758. /* LS_COLORS is unset, or (not empty && not "none") ? */
  759. if (!p || (p[0] && strcmp(p, "none")))
  760. show_color = 1;
  761. }
  762. if (opt & (1 << i)) { /* next flag after short options */
  763. if (!color_opt || !strcmp("always", color_opt))
  764. show_color = 1;
  765. else if (color_opt && !strcmp("never", color_opt))
  766. show_color = 0;
  767. else if (color_opt && !strcmp("auto", color_opt) && isatty(STDOUT_FILENO))
  768. show_color = 1;
  769. }
  770. #endif
  771. /* sort out which command line options take precedence */
  772. if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
  773. all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
  774. if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
  775. if (all_fmt & TIME_CHANGE)
  776. all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
  777. if (all_fmt & TIME_ACCESS)
  778. all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
  779. }
  780. if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
  781. all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
  782. if (ENABLE_FEATURE_LS_USERNAME)
  783. if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
  784. all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
  785. /* choose a display format */
  786. if (!(all_fmt & STYLE_MASK))
  787. all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
  788. /*
  789. * when there are no cmd line args we have to supply a default "." arg.
  790. * we will create a second argv array, "av" that will hold either
  791. * our created "." arg, or the real cmd line args. The av array
  792. * just holds the pointers- we don't move the date the pointers
  793. * point to.
  794. */
  795. ac = argc - optind; /* how many cmd line args are left */
  796. if (ac < 1) {
  797. static const char *const dotdir[] = { "." };
  798. av = (char **) dotdir;
  799. ac = 1;
  800. } else {
  801. av = argv + optind;
  802. }
  803. /* now, everything is in the av array */
  804. if (ac > 1)
  805. all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
  806. /* stuff the command line file names into a dnode array */
  807. dn = NULL;
  808. for (oi = 0; oi < ac; oi++) {
  809. cur = my_stat(av[oi], av[oi]);
  810. if (!cur)
  811. continue;
  812. cur->allocated = 0;
  813. cur->next = dn;
  814. dn = cur;
  815. nfiles++;
  816. }
  817. /* now that we know how many files there are
  818. * allocate memory for an array to hold dnode pointers
  819. */
  820. dnp = dnalloc(nfiles);
  821. for (i = 0, cur = dn; i < nfiles; i++) {
  822. dnp[i] = cur; /* save pointer to node in array */
  823. cur = cur->next;
  824. }
  825. if (all_fmt & DISP_NOLIST) {
  826. dnsort(dnp, nfiles);
  827. if (nfiles > 0)
  828. showfiles(dnp, nfiles);
  829. } else {
  830. dnd = splitdnarray(dnp, nfiles, SPLIT_DIR);
  831. dnf = splitdnarray(dnp, nfiles, SPLIT_FILE);
  832. dndirs = countdirs(dnp, nfiles);
  833. dnfiles = nfiles - dndirs;
  834. if (dnfiles > 0) {
  835. dnsort(dnf, dnfiles);
  836. showfiles(dnf, dnfiles);
  837. if (ENABLE_FEATURE_CLEAN_UP)
  838. free(dnf);
  839. }
  840. if (dndirs > 0) {
  841. dnsort(dnd, dndirs);
  842. showdirs(dnd, dndirs, dnfiles == 0);
  843. if (ENABLE_FEATURE_CLEAN_UP)
  844. free(dnd);
  845. }
  846. }
  847. if (ENABLE_FEATURE_CLEAN_UP)
  848. dfree(dnp, nfiles);
  849. return status;
  850. }