ls.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Assertions.h>
  7. #include <AK/HashMap.h>
  8. #include <AK/NumberFormat.h>
  9. #include <AK/QuickSort.h>
  10. #include <AK/String.h>
  11. #include <AK/StringBuilder.h>
  12. #include <AK/URL.h>
  13. #include <AK/Utf8View.h>
  14. #include <AK/Vector.h>
  15. #include <LibCore/ArgsParser.h>
  16. #include <LibCore/DateTime.h>
  17. #include <LibCore/DirIterator.h>
  18. #include <LibCore/File.h>
  19. #include <ctype.h>
  20. #include <dirent.h>
  21. #include <errno.h>
  22. #include <fcntl.h>
  23. #include <grp.h>
  24. #include <inttypes.h>
  25. #include <pwd.h>
  26. #include <stdio.h>
  27. #include <string.h>
  28. #include <sys/ioctl.h>
  29. #include <sys/stat.h>
  30. #include <sys/types.h>
  31. #include <time.h>
  32. #include <unistd.h>
  33. static int do_file_system_object_long(const char* path);
  34. static int do_file_system_object_short(const char* path);
  35. static bool print_names(const char* path, size_t longest_name, const Vector<String>& names);
  36. static bool flag_classify = false;
  37. static bool flag_colorize = false;
  38. static bool flag_long = false;
  39. static bool flag_show_dotfiles = false;
  40. static bool flag_show_almost_all_dotfiles = false;
  41. static bool flag_ignore_backups = false;
  42. static bool flag_list_directories_only = false;
  43. static bool flag_show_inode = false;
  44. static bool flag_print_numeric = false;
  45. static bool flag_hide_group = false;
  46. static bool flag_human_readable = false;
  47. static bool flag_sort_by_timestamp = false;
  48. static bool flag_reverse_sort = false;
  49. static bool flag_disable_hyperlinks = false;
  50. static bool flag_recursive = false;
  51. static size_t terminal_rows = 0;
  52. static size_t terminal_columns = 0;
  53. static bool output_is_terminal = false;
  54. static HashMap<uid_t, String> users;
  55. static HashMap<gid_t, String> groups;
  56. static bool is_a_tty = false;
  57. int main(int argc, char** argv)
  58. {
  59. if (pledge("stdio rpath tty", nullptr) < 0) {
  60. perror("pledge");
  61. return 1;
  62. }
  63. struct winsize ws;
  64. int rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
  65. if (rc == 0) {
  66. terminal_rows = ws.ws_row;
  67. terminal_columns = ws.ws_col;
  68. output_is_terminal = true;
  69. }
  70. is_a_tty = isatty(STDOUT_FILENO);
  71. if (!is_a_tty) {
  72. flag_disable_hyperlinks = true;
  73. } else {
  74. flag_colorize = true;
  75. }
  76. if (pledge("stdio rpath", nullptr) < 0) {
  77. perror("pledge");
  78. return 1;
  79. }
  80. Vector<String> paths;
  81. Core::ArgsParser args_parser;
  82. args_parser.set_general_help("List files in a directory.");
  83. args_parser.add_option(flag_show_dotfiles, "Show dotfiles", "all", 'a');
  84. args_parser.add_option(flag_show_almost_all_dotfiles, "Do not list implied . and .. directories", nullptr, 'A');
  85. args_parser.add_option(flag_ignore_backups, "Do not list implied entries ending with ~", "--ignore-backups", 'B');
  86. args_parser.add_option(flag_list_directories_only, "List directories themselves, not their contents", "directory", 'd');
  87. args_parser.add_option(flag_long, "Display long info", "long", 'l');
  88. args_parser.add_option(flag_sort_by_timestamp, "Sort files by timestamp", nullptr, 't');
  89. args_parser.add_option(flag_reverse_sort, "Reverse sort order", "reverse", 'r');
  90. args_parser.add_option(flag_classify, "Append a file type indicator to entries", "classify", 'F');
  91. args_parser.add_option(flag_colorize, "Use pretty colors", nullptr, 'G');
  92. args_parser.add_option(flag_show_inode, "Show inode ids", "inode", 'i');
  93. args_parser.add_option(flag_print_numeric, "In long format, display numeric UID/GID", "numeric-uid-gid", 'n');
  94. args_parser.add_option(flag_hide_group, "In long format, do not show group information", nullptr, 'o');
  95. args_parser.add_option(flag_human_readable, "Print human-readable sizes", "human-readable", 'h');
  96. args_parser.add_option(flag_disable_hyperlinks, "Disable hyperlinks", "no-hyperlinks", 'K');
  97. args_parser.add_option(flag_recursive, "List subdirectories recursively", "recursive", 'R');
  98. args_parser.add_positional_argument(paths, "Directory to list", "path", Core::ArgsParser::Required::No);
  99. args_parser.parse(argc, argv);
  100. if (flag_show_almost_all_dotfiles)
  101. flag_show_dotfiles = true;
  102. if (flag_long) {
  103. setpwent();
  104. for (auto* pwd = getpwent(); pwd; pwd = getpwent())
  105. users.set(pwd->pw_uid, pwd->pw_name);
  106. endpwent();
  107. setgrent();
  108. for (auto* grp = getgrent(); grp; grp = getgrent())
  109. groups.set(grp->gr_gid, grp->gr_name);
  110. endgrent();
  111. }
  112. auto do_file_system_object = [&](const char* path) {
  113. if (flag_long)
  114. return do_file_system_object_long(path);
  115. return do_file_system_object_short(path);
  116. };
  117. if (paths.is_empty())
  118. paths.append(".");
  119. quick_sort(paths, [](const String& a, const String& b) {
  120. return a < b;
  121. });
  122. int status = 0;
  123. for (size_t i = 0; i < paths.size(); i++) {
  124. auto path = paths[i];
  125. if (flag_recursive && Core::File::is_directory(path)) {
  126. size_t subdirs = 0;
  127. Core::DirIterator di(path, Core::DirIterator::SkipParentAndBaseDir);
  128. if (di.has_error()) {
  129. status = 1;
  130. fprintf(stderr, "%s: %s\n", path.characters(), di.error_string());
  131. }
  132. while (di.has_next()) {
  133. String directory = di.next_full_path();
  134. if (Core::File::is_directory(directory) && !Core::File::is_link(directory)) {
  135. ++subdirs;
  136. paths.insert(i + subdirs, move(directory));
  137. }
  138. }
  139. }
  140. bool show_dir_separator = paths.size() > 1 && Core::File::is_directory(path) && !flag_list_directories_only;
  141. if (show_dir_separator) {
  142. printf("%s:\n", path.characters());
  143. }
  144. auto rc = do_file_system_object(path.characters());
  145. if (rc != 0)
  146. status = rc;
  147. if (show_dir_separator && i != paths.size() - 1) {
  148. puts("");
  149. }
  150. }
  151. return status;
  152. }
  153. static int print_escaped(const char* name)
  154. {
  155. int printed = 0;
  156. Utf8View utf8_name(name);
  157. if (utf8_name.validate()) {
  158. printf("%s", name);
  159. return utf8_name.length();
  160. }
  161. for (int i = 0; name[i] != '\0'; i++) {
  162. if (isprint(name[i])) {
  163. putchar(name[i]);
  164. printed++;
  165. } else {
  166. printed += printf("\\%03d", name[i]);
  167. }
  168. }
  169. return printed;
  170. }
  171. static String& hostname()
  172. {
  173. static String s_hostname;
  174. if (s_hostname.is_null()) {
  175. char buffer[HOST_NAME_MAX];
  176. if (gethostname(buffer, sizeof(buffer)) == 0)
  177. s_hostname = buffer;
  178. else
  179. s_hostname = "localhost";
  180. }
  181. return s_hostname;
  182. }
  183. static size_t print_name(const struct stat& st, const String& name, const char* path_for_link_resolution, const char* path_for_hyperlink)
  184. {
  185. if (!flag_disable_hyperlinks) {
  186. auto full_path = Core::File::real_path_for(path_for_hyperlink);
  187. if (!full_path.is_null()) {
  188. auto url = URL::create_with_file_scheme(full_path, {}, hostname());
  189. out("\033]8;;{}\033\\", url.serialize());
  190. }
  191. }
  192. size_t nprinted = 0;
  193. if (!flag_colorize || !output_is_terminal) {
  194. nprinted = printf("%s", name.characters());
  195. } else {
  196. const char* begin_color = "";
  197. const char* end_color = "\033[0m";
  198. if (st.st_mode & S_ISVTX)
  199. begin_color = "\033[42;30;1m";
  200. else if (st.st_mode & S_ISUID)
  201. begin_color = "\033[41;1m";
  202. else if (st.st_mode & S_ISGID)
  203. begin_color = "\033[43;1m";
  204. else if (S_ISLNK(st.st_mode))
  205. begin_color = "\033[36;1m";
  206. else if (S_ISDIR(st.st_mode))
  207. begin_color = "\033[34;1m";
  208. else if (st.st_mode & 0111)
  209. begin_color = "\033[32;1m";
  210. else if (S_ISSOCK(st.st_mode))
  211. begin_color = "\033[35;1m";
  212. else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode))
  213. begin_color = "\033[33;1m";
  214. printf("%s", begin_color);
  215. nprinted = print_escaped(name.characters());
  216. printf("%s", end_color);
  217. }
  218. if (S_ISLNK(st.st_mode)) {
  219. if (path_for_link_resolution) {
  220. auto link_destination = Core::File::read_link(path_for_link_resolution);
  221. if (link_destination.is_null()) {
  222. perror("readlink");
  223. } else {
  224. nprinted += printf(" -> ") + print_escaped(link_destination.characters());
  225. }
  226. } else {
  227. if (flag_classify)
  228. nprinted += printf("@");
  229. }
  230. } else if (S_ISDIR(st.st_mode)) {
  231. if (flag_classify)
  232. nprinted += printf("/");
  233. } else if (st.st_mode & 0111) {
  234. if (flag_classify)
  235. nprinted += printf("*");
  236. }
  237. if (!flag_disable_hyperlinks) {
  238. printf("\033]8;;\033\\");
  239. }
  240. return nprinted;
  241. }
  242. static bool print_filesystem_object(const String& path, const String& name, const struct stat& st)
  243. {
  244. if (flag_show_inode)
  245. printf("%s ", String::formatted("{}", st.st_ino).characters());
  246. if (S_ISDIR(st.st_mode))
  247. printf("d");
  248. else if (S_ISLNK(st.st_mode))
  249. printf("l");
  250. else if (S_ISBLK(st.st_mode))
  251. printf("b");
  252. else if (S_ISCHR(st.st_mode))
  253. printf("c");
  254. else if (S_ISFIFO(st.st_mode))
  255. printf("f");
  256. else if (S_ISSOCK(st.st_mode))
  257. printf("s");
  258. else if (S_ISREG(st.st_mode))
  259. printf("-");
  260. else
  261. printf("?");
  262. printf("%c%c%c%c%c%c%c%c",
  263. st.st_mode & S_IRUSR ? 'r' : '-',
  264. st.st_mode & S_IWUSR ? 'w' : '-',
  265. st.st_mode & S_ISUID ? 's' : (st.st_mode & S_IXUSR ? 'x' : '-'),
  266. st.st_mode & S_IRGRP ? 'r' : '-',
  267. st.st_mode & S_IWGRP ? 'w' : '-',
  268. st.st_mode & S_ISGID ? 's' : (st.st_mode & S_IXGRP ? 'x' : '-'),
  269. st.st_mode & S_IROTH ? 'r' : '-',
  270. st.st_mode & S_IWOTH ? 'w' : '-');
  271. if (st.st_mode & S_ISVTX)
  272. printf("t");
  273. else
  274. printf("%c", st.st_mode & S_IXOTH ? 'x' : '-');
  275. printf(" %3u", st.st_nlink);
  276. auto username = users.get(st.st_uid);
  277. if (!flag_print_numeric && username.has_value()) {
  278. printf(" %7s", username.value().characters());
  279. } else {
  280. printf(" %7u", st.st_uid);
  281. }
  282. if (!flag_hide_group) {
  283. auto groupname = groups.get(st.st_gid);
  284. if (!flag_print_numeric && groupname.has_value()) {
  285. printf(" %7s", groupname.value().characters());
  286. } else {
  287. printf(" %7u", st.st_gid);
  288. }
  289. }
  290. if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
  291. printf(" %4u,%4u ", major(st.st_rdev), minor(st.st_rdev));
  292. } else {
  293. if (flag_human_readable) {
  294. printf(" %10s ", human_readable_size(st.st_size).characters());
  295. } else {
  296. printf(" %10" PRIu64 " ", (uint64_t)st.st_size);
  297. }
  298. }
  299. printf(" %s ", Core::DateTime::from_timestamp(st.st_mtime).to_string().characters());
  300. print_name(st, name, path.characters(), path.characters());
  301. printf("\n");
  302. return true;
  303. }
  304. static int do_file_system_object_long(const char* path)
  305. {
  306. if (flag_list_directories_only) {
  307. struct stat stat;
  308. int rc = lstat(path, &stat);
  309. if (rc < 0) {
  310. perror("lstat");
  311. memset(&stat, 0, sizeof(stat));
  312. }
  313. if (print_filesystem_object(path, path, stat))
  314. return 0;
  315. return 2;
  316. }
  317. auto flags = Core::DirIterator::SkipDots;
  318. if (flag_show_dotfiles)
  319. flags = Core::DirIterator::Flags::NoFlags;
  320. if (flag_show_almost_all_dotfiles)
  321. flags = Core::DirIterator::SkipParentAndBaseDir;
  322. Core::DirIterator di(path, flags);
  323. if (di.has_error()) {
  324. if (di.error() == ENOTDIR) {
  325. struct stat stat;
  326. int rc = lstat(path, &stat);
  327. if (rc < 0) {
  328. perror("lstat");
  329. memset(&stat, 0, sizeof(stat));
  330. }
  331. if (print_filesystem_object(path, path, stat))
  332. return 0;
  333. return 2;
  334. }
  335. fprintf(stderr, "%s: %s\n", path, di.error_string());
  336. return 1;
  337. }
  338. struct FileMetadata {
  339. String name;
  340. String path;
  341. struct stat stat;
  342. };
  343. Vector<FileMetadata> files;
  344. while (di.has_next()) {
  345. FileMetadata metadata;
  346. metadata.name = di.next_path();
  347. VERIFY(!metadata.name.is_empty());
  348. if (metadata.name.ends_with('~') && flag_ignore_backups && metadata.name != path)
  349. continue;
  350. StringBuilder builder;
  351. builder.append(path);
  352. builder.append('/');
  353. builder.append(metadata.name);
  354. metadata.path = builder.to_string();
  355. VERIFY(!metadata.path.is_null());
  356. int rc = lstat(metadata.path.characters(), &metadata.stat);
  357. if (rc < 0) {
  358. perror("lstat");
  359. memset(&metadata.stat, 0, sizeof(metadata.stat));
  360. }
  361. files.append(move(metadata));
  362. }
  363. quick_sort(files, [](auto& a, auto& b) {
  364. if (flag_sort_by_timestamp) {
  365. if (flag_reverse_sort)
  366. return a.stat.st_mtime < b.stat.st_mtime;
  367. return a.stat.st_mtime > b.stat.st_mtime;
  368. }
  369. // Fine, sort by name then!
  370. if (flag_reverse_sort)
  371. return a.name > b.name;
  372. return a.name < b.name;
  373. });
  374. for (auto& file : files) {
  375. if (!print_filesystem_object(file.path, file.name, file.stat))
  376. return 2;
  377. }
  378. return 0;
  379. }
  380. static bool print_filesystem_object_short(const char* path, const char* name, size_t* nprinted)
  381. {
  382. struct stat st;
  383. int rc = lstat(path, &st);
  384. if (rc == -1) {
  385. printf("lstat(%s) failed: %s\n", path, strerror(errno));
  386. return false;
  387. }
  388. if (flag_show_inode)
  389. printf("%s ", String::formatted("{}", st.st_ino).characters());
  390. *nprinted = print_name(st, name, nullptr, path);
  391. return true;
  392. }
  393. static bool print_names(const char* path, size_t longest_name, const Vector<String>& names)
  394. {
  395. size_t printed_on_row = 0;
  396. size_t nprinted = 0;
  397. for (size_t i = 0; i < names.size(); ++i) {
  398. auto& name = names[i];
  399. StringBuilder builder;
  400. builder.append(path);
  401. builder.append('/');
  402. builder.append(name);
  403. if (!print_filesystem_object_short(builder.to_string().characters(), name.characters(), &nprinted))
  404. return 2;
  405. int offset = 0;
  406. if (terminal_columns > longest_name)
  407. offset = terminal_columns % longest_name / (terminal_columns / longest_name);
  408. // The offset must be at least 2 because:
  409. // - With each file an additional char is printed e.g. '@','*'.
  410. // - Each filename must be separated by a space.
  411. size_t column_width = longest_name + max(offset, 2);
  412. printed_on_row += column_width;
  413. if (is_a_tty) {
  414. for (size_t j = nprinted; i != (names.size() - 1) && j < column_width; ++j)
  415. printf(" ");
  416. }
  417. if ((printed_on_row + column_width) >= terminal_columns) {
  418. printf("\n");
  419. printed_on_row = 0;
  420. }
  421. }
  422. return printed_on_row;
  423. }
  424. int do_file_system_object_short(const char* path)
  425. {
  426. if (flag_list_directories_only) {
  427. size_t nprinted = 0;
  428. bool status = print_filesystem_object_short(path, path, &nprinted);
  429. printf("\n");
  430. if (status)
  431. return 0;
  432. return 2;
  433. }
  434. auto flags = Core::DirIterator::SkipDots;
  435. if (flag_show_dotfiles)
  436. flags = Core::DirIterator::Flags::NoFlags;
  437. if (flag_show_almost_all_dotfiles)
  438. flags = Core::DirIterator::SkipParentAndBaseDir;
  439. Core::DirIterator di(path, flags);
  440. if (di.has_error()) {
  441. if (di.error() == ENOTDIR) {
  442. size_t nprinted = 0;
  443. bool status = print_filesystem_object_short(path, path, &nprinted);
  444. printf("\n");
  445. if (status)
  446. return 0;
  447. return 2;
  448. }
  449. fprintf(stderr, "%s: %s\n", path, di.error_string());
  450. return 1;
  451. }
  452. Vector<String> names;
  453. size_t longest_name = 0;
  454. while (di.has_next()) {
  455. String name = di.next_path();
  456. if (name.ends_with('~') && flag_ignore_backups && name != path)
  457. continue;
  458. names.append(name);
  459. if (names.last().length() > longest_name)
  460. longest_name = name.length();
  461. }
  462. quick_sort(names);
  463. if (print_names(path, longest_name, names))
  464. printf("\n");
  465. return 0;
  466. }