diff options
author | Roger Knecht <rknecht@pm.me> | 2022-04-18 12:54:20 +0000 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2022-06-30 17:18:12 +0200 |
commit | 20a4f70ecaad79bb932af09b7317a058872cd867 (patch) | |
tree | aaf6a3b29f415615dc7d185b041bc0a31ae98360 | |
parent | 2617a5e4c600b4577b2c18f794701276e55da43b (diff) | |
download | busybox-w32-20a4f70ecaad79bb932af09b7317a058872cd867.tar.gz busybox-w32-20a4f70ecaad79bb932af09b7317a058872cd867.tar.bz2 busybox-w32-20a4f70ecaad79bb932af09b7317a058872cd867.zip |
tree: new applet
Adds the tree program to list directories and files in a tree structure.
function old new delta
tree_print - 343 +343
scandir64 - 330 +330
scandir - 330 +330
tree_main - 86 +86
.rodata 105150 105228 +78
packed_usage 34511 34557 +46
alphasort64 - 31 +31
alphasort - 31 +31
strcoll - 5 +5
applet_names 2801 2806 +5
applet_main 1616 1620 +4
applet_suid 101 102 +1
applet_install_loc 202 203 +1
------------------------------------------------------------------------------
(add/remove: 11/0 grow/shrink: 6/0 up/down: 1291/0) Total: 1291 bytes
Signed-off-by: Roger Knecht <rknecht@pm.me>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r-- | AUTHORS | 3 | ||||
-rw-r--r-- | miscutils/tree.c | 118 | ||||
-rwxr-xr-x | testsuite/tree.tests | 100 |
3 files changed, 221 insertions, 0 deletions
@@ -181,3 +181,6 @@ Jie Zhang <jie.zhang@analog.com> | |||
181 | 181 | ||
182 | Maxime Coste <mawww@kakoune.org> | 182 | Maxime Coste <mawww@kakoune.org> |
183 | paste implementation | 183 | paste implementation |
184 | |||
185 | Roger Knecht <rknecht@pm.me> | ||
186 | tree | ||
diff --git a/miscutils/tree.c b/miscutils/tree.c new file mode 100644 index 000000000..8b16c5383 --- /dev/null +++ b/miscutils/tree.c | |||
@@ -0,0 +1,118 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * Copyright (C) 2022 Roger Knecht <rknecht@pm.me> | ||
4 | * | ||
5 | * Licensed under GPLv2, see file LICENSE in this source tree. | ||
6 | */ | ||
7 | //config:config TREE | ||
8 | //config: bool "tree (0.6 kb)" | ||
9 | //config: default y | ||
10 | //config: help | ||
11 | //config: List files and directories in a tree structure. | ||
12 | |||
13 | //applet:IF_TREE(APPLET(tree, BB_DIR_USR_BIN, BB_SUID_DROP)) | ||
14 | |||
15 | //kbuild:lib-$(CONFIG_TREE) += tree.o | ||
16 | |||
17 | //usage:#define tree_trivial_usage NOUSAGE_STR | ||
18 | //usage:#define tree_full_usage "" | ||
19 | |||
20 | #include "libbb.h" | ||
21 | #include "common_bufsiz.h" | ||
22 | |||
23 | #define prefix_buf bb_common_bufsiz1 | ||
24 | |||
25 | static void tree_print(unsigned count[2], const char* directory_name, char* prefix_pos) | ||
26 | { | ||
27 | struct dirent **entries; | ||
28 | int index, size; | ||
29 | |||
30 | // read directory entries | ||
31 | size = scandir(directory_name, &entries, NULL, alphasort); | ||
32 | |||
33 | if (size < 0) { | ||
34 | fputs_stdout(directory_name); | ||
35 | puts(" [error opening dir]"); | ||
36 | return; | ||
37 | } | ||
38 | |||
39 | // print directory name | ||
40 | puts(directory_name); | ||
41 | |||
42 | // switch to sub directory | ||
43 | xchdir(directory_name); | ||
44 | |||
45 | // print all directory entries | ||
46 | for (index = 0; index < size;) { | ||
47 | struct dirent *dirent = entries[index++]; | ||
48 | |||
49 | // filter hidden files and directories | ||
50 | if (dirent->d_name[0] != '.') { | ||
51 | int status; | ||
52 | struct stat statBuf; | ||
53 | |||
54 | //TODO: when -l is implemented, use stat, not lstat, if -l | ||
55 | status = lstat(dirent->d_name, &statBuf); | ||
56 | |||
57 | if (index == size) { | ||
58 | strcpy(prefix_pos, "└── "); | ||
59 | } else { | ||
60 | strcpy(prefix_pos, "├── "); | ||
61 | } | ||
62 | fputs_stdout(prefix_buf); | ||
63 | |||
64 | if (status == 0 && S_ISLNK(statBuf.st_mode)) { | ||
65 | // handle symlink | ||
66 | char* symlink_path = xmalloc_readlink(dirent->d_name); | ||
67 | printf("%s -> %s\n", dirent->d_name, symlink_path); | ||
68 | free(symlink_path); | ||
69 | count[1]++; | ||
70 | } else if (status == 0 && S_ISDIR(statBuf.st_mode) | ||
71 | && (prefix_pos - prefix_buf) < (COMMON_BUFSIZE - 16) | ||
72 | ) { | ||
73 | // handle directory | ||
74 | char* pos; | ||
75 | if (index == size) { | ||
76 | pos = stpcpy(prefix_pos, " "); | ||
77 | } else { | ||
78 | pos = stpcpy(prefix_pos, "│ "); | ||
79 | } | ||
80 | tree_print(count, dirent->d_name, pos); | ||
81 | count[0]++; | ||
82 | } else { | ||
83 | // handle file | ||
84 | puts(dirent->d_name); | ||
85 | count[1]++; | ||
86 | } | ||
87 | } | ||
88 | |||
89 | // release directory entry | ||
90 | free(dirent); | ||
91 | } | ||
92 | |||
93 | // release directory array | ||
94 | free(entries); | ||
95 | |||
96 | // switch to parent directory | ||
97 | xchdir(".."); | ||
98 | } | ||
99 | |||
100 | int tree_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
101 | int tree_main(int argc UNUSED_PARAM, char **argv) | ||
102 | { | ||
103 | unsigned count[2] = { 0, 0 }; | ||
104 | |||
105 | setup_common_bufsiz(); | ||
106 | |||
107 | if (!argv[1]) | ||
108 | *argv-- = (char*)"."; | ||
109 | |||
110 | // list directories given as command line arguments | ||
111 | while (*(++argv)) | ||
112 | tree_print(count, *argv, prefix_buf); | ||
113 | |||
114 | // print statistic | ||
115 | printf("\n%u directories, %u files\n", count[0], count[1]); | ||
116 | |||
117 | return EXIT_SUCCESS; | ||
118 | } | ||
diff --git a/testsuite/tree.tests b/testsuite/tree.tests new file mode 100755 index 000000000..4f4a9e30b --- /dev/null +++ b/testsuite/tree.tests | |||
@@ -0,0 +1,100 @@ | |||
1 | #!/bin/sh | ||
2 | |||
3 | # Copyright 2022 by Roger Knecht <rknecht@pm.me> | ||
4 | # Licensed under GPLv2, see file LICENSE in this source tree. | ||
5 | |||
6 | . ./testing.sh -v | ||
7 | |||
8 | # testing "description" "command" "result" "infile" "stdin" | ||
9 | |||
10 | testing "tree error opening dir" \ | ||
11 | "tree tree.tempdir" \ | ||
12 | "\ | ||
13 | tree.tempdir [error opening dir]\n\ | ||
14 | \n\ | ||
15 | 0 directories, 0 files\n" \ | ||
16 | "" "" | ||
17 | |||
18 | mkdir -p tree2.tempdir | ||
19 | touch tree2.tempdir/testfile | ||
20 | |||
21 | testing "tree single file" \ | ||
22 | "cd tree2.tempdir && tree" \ | ||
23 | "\ | ||
24 | .\n\ | ||
25 | └── testfile\n\ | ||
26 | \n\ | ||
27 | 0 directories, 1 files\n" \ | ||
28 | "" "" | ||
29 | |||
30 | mkdir -p tree3.tempdir/test1 \ | ||
31 | tree3.tempdir/test2/a \ | ||
32 | tree3.tempdir/test2/b \ | ||
33 | tree3.tempdir/test3/c \ | ||
34 | tree3.tempdir/test3/d | ||
35 | |||
36 | touch tree3.tempdir/test2/a/testfile1 \ | ||
37 | tree3.tempdir/test2/a/testfile2 \ | ||
38 | tree3.tempdir/test2/a/testfile3 \ | ||
39 | tree3.tempdir/test2/b/testfile4 \ | ||
40 | tree3.tempdir/test3/c/testfile5 \ | ||
41 | tree3.tempdir/test3/d/testfile6 \ | ||
42 | tree3.tempdir/test3/d/.testfile7 | ||
43 | |||
44 | (cd tree3.tempdir/test2/a && ln -s ../b/testfile4 .) | ||
45 | (cd tree3.tempdir/test2/b && ln -s ../../test3 .) | ||
46 | |||
47 | testing "tree nested directories and files" \ | ||
48 | "cd tree3.tempdir && tree" \ | ||
49 | "\ | ||
50 | .\n\ | ||
51 | ├── test1\n\ | ||
52 | ├── test2\n\ | ||
53 | │ ├── a\n\ | ||
54 | │ │ ├── testfile1\n\ | ||
55 | │ │ ├── testfile2\n\ | ||
56 | │ │ ├── testfile3\n\ | ||
57 | │ │ └── testfile4 -> ../b/testfile4\n\ | ||
58 | │ └── b\n\ | ||
59 | │ ├── test3 -> ../../test3\n\ | ||
60 | │ └── testfile4\n\ | ||
61 | └── test3\n\ | ||
62 | ├── c\n\ | ||
63 | │ └── testfile5\n\ | ||
64 | └── d\n\ | ||
65 | └── testfile6\n\ | ||
66 | \n\ | ||
67 | 7 directories, 8 files\n" \ | ||
68 | "" "" | ||
69 | #note: tree v2.0.1 says "8 directories, 7 files": | ||
70 | #it counts "test3 -> ../../test3" as a directory, even though it does not follow this symlink | ||
71 | |||
72 | testing "tree multiple directories" \ | ||
73 | "tree tree2.tempdir tree3.tempdir" \ | ||
74 | "\ | ||
75 | tree2.tempdir\n\ | ||
76 | └── testfile\n\ | ||
77 | tree3.tempdir\n\ | ||
78 | ├── test1\n\ | ||
79 | ├── test2\n\ | ||
80 | │ ├── a\n\ | ||
81 | │ │ ├── testfile1\n\ | ||
82 | │ │ ├── testfile2\n\ | ||
83 | │ │ ├── testfile3\n\ | ||
84 | │ │ └── testfile4 -> ../b/testfile4\n\ | ||
85 | │ └── b\n\ | ||
86 | │ ├── test3 -> ../../test3\n\ | ||
87 | │ └── testfile4\n\ | ||
88 | └── test3\n\ | ||
89 | ├── c\n\ | ||
90 | │ └── testfile5\n\ | ||
91 | └── d\n\ | ||
92 | └── testfile6\n\ | ||
93 | \n\ | ||
94 | 7 directories, 9 files\n" \ | ||
95 | "" "" | ||
96 | #note: tree v2.0.1 says "8 directories, 7 files" (not "8 files", probably a/testfile4 -> ../b/testfile4 and b/testfile4 are counted as one file, not 2?) | ||
97 | |||
98 | rm -rf tree.tempdir tree2.tempdir tree3.tempdir | ||
99 | |||
100 | exit $FAILCOUNT | ||