[git commit] tree: new applet
Denys Vlasenko
vda.linux at googlemail.com
Thu Jun 30 15:18:12 UTC 2022
commit: https://git.busybox.net/busybox/commit/?id=20a4f70ecaad79bb932af09b7317a058872cd867
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master
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 at pm.me>
Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
AUTHORS | 3 ++
miscutils/tree.c | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++
testsuite/tree.tests | 100 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 221 insertions(+)
diff --git a/AUTHORS b/AUTHORS
index 5c9a634c9..9ec0e2ee4 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -181,3 +181,6 @@ Jie Zhang <jie.zhang at analog.com>
Maxime Coste <mawww at kakoune.org>
paste implementation
+
+Roger Knecht <rknecht at pm.me>
+ 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 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2022 Roger Knecht <rknecht at pm.me>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+//config:config TREE
+//config: bool "tree (0.6 kb)"
+//config: default y
+//config: help
+//config: List files and directories in a tree structure.
+
+//applet:IF_TREE(APPLET(tree, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_TREE) += tree.o
+
+//usage:#define tree_trivial_usage NOUSAGE_STR
+//usage:#define tree_full_usage ""
+
+#include "libbb.h"
+#include "common_bufsiz.h"
+
+#define prefix_buf bb_common_bufsiz1
+
+static void tree_print(unsigned count[2], const char* directory_name, char* prefix_pos)
+{
+ struct dirent **entries;
+ int index, size;
+
+ // read directory entries
+ size = scandir(directory_name, &entries, NULL, alphasort);
+
+ if (size < 0) {
+ fputs_stdout(directory_name);
+ puts(" [error opening dir]");
+ return;
+ }
+
+ // print directory name
+ puts(directory_name);
+
+ // switch to sub directory
+ xchdir(directory_name);
+
+ // print all directory entries
+ for (index = 0; index < size;) {
+ struct dirent *dirent = entries[index++];
+
+ // filter hidden files and directories
+ if (dirent->d_name[0] != '.') {
+ int status;
+ struct stat statBuf;
+
+//TODO: when -l is implemented, use stat, not lstat, if -l
+ status = lstat(dirent->d_name, &statBuf);
+
+ if (index == size) {
+ strcpy(prefix_pos, "âââ ");
+ } else {
+ strcpy(prefix_pos, "âââ ");
+ }
+ fputs_stdout(prefix_buf);
+
+ if (status == 0 && S_ISLNK(statBuf.st_mode)) {
+ // handle symlink
+ char* symlink_path = xmalloc_readlink(dirent->d_name);
+ printf("%s -> %s\n", dirent->d_name, symlink_path);
+ free(symlink_path);
+ count[1]++;
+ } else if (status == 0 && S_ISDIR(statBuf.st_mode)
+ && (prefix_pos - prefix_buf) < (COMMON_BUFSIZE - 16)
+ ) {
+ // handle directory
+ char* pos;
+ if (index == size) {
+ pos = stpcpy(prefix_pos, " ");
+ } else {
+ pos = stpcpy(prefix_pos, "â  ");
+ }
+ tree_print(count, dirent->d_name, pos);
+ count[0]++;
+ } else {
+ // handle file
+ puts(dirent->d_name);
+ count[1]++;
+ }
+ }
+
+ // release directory entry
+ free(dirent);
+ }
+
+ // release directory array
+ free(entries);
+
+ // switch to parent directory
+ xchdir("..");
+}
+
+int tree_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tree_main(int argc UNUSED_PARAM, char **argv)
+{
+ unsigned count[2] = { 0, 0 };
+
+ setup_common_bufsiz();
+
+ if (!argv[1])
+ *argv-- = (char*)".";
+
+ // list directories given as command line arguments
+ while (*(++argv))
+ tree_print(count, *argv, prefix_buf);
+
+ // print statistic
+ printf("\n%u directories, %u files\n", count[0], count[1]);
+
+ return EXIT_SUCCESS;
+}
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 @@
+#!/bin/sh
+
+# Copyright 2022 by Roger Knecht <rknecht at pm.me>
+# Licensed under GPLv2, see file LICENSE in this source tree.
+
+. ./testing.sh -v
+
+# testing "description" "command" "result" "infile" "stdin"
+
+testing "tree error opening dir" \
+ "tree tree.tempdir" \
+ "\
+tree.tempdir [error opening dir]\n\
+\n\
+0 directories, 0 files\n" \
+ "" ""
+
+mkdir -p tree2.tempdir
+touch tree2.tempdir/testfile
+
+testing "tree single file" \
+ "cd tree2.tempdir && tree" \
+ "\
+.\n\
+âââ testfile\n\
+\n\
+0 directories, 1 files\n" \
+ "" ""
+
+mkdir -p tree3.tempdir/test1 \
+ tree3.tempdir/test2/a \
+ tree3.tempdir/test2/b \
+ tree3.tempdir/test3/c \
+ tree3.tempdir/test3/d
+
+touch tree3.tempdir/test2/a/testfile1 \
+ tree3.tempdir/test2/a/testfile2 \
+ tree3.tempdir/test2/a/testfile3 \
+ tree3.tempdir/test2/b/testfile4 \
+ tree3.tempdir/test3/c/testfile5 \
+ tree3.tempdir/test3/d/testfile6 \
+ tree3.tempdir/test3/d/.testfile7
+
+(cd tree3.tempdir/test2/a && ln -s ../b/testfile4 .)
+(cd tree3.tempdir/test2/b && ln -s ../../test3 .)
+
+testing "tree nested directories and files" \
+ "cd tree3.tempdir && tree" \
+ "\
+.\n\
+âââ test1\n\
+âââ test2\n\
+â  âââ a\n\
+â  â  âââ testfile1\n\
+â  â  âââ testfile2\n\
+â  â  âââ testfile3\n\
+â  â  âââ testfile4 -> ../b/testfile4\n\
+â  âââ b\n\
+â  âââ test3 -> ../../test3\n\
+â  âââ testfile4\n\
+âââ test3\n\
+ âââ c\n\
+ â  âââ testfile5\n\
+ âââ d\n\
+ âââ testfile6\n\
+\n\
+7 directories, 8 files\n" \
+ "" ""
+#note: tree v2.0.1 says "8 directories, 7 files":
+#it counts "test3 -> ../../test3" as a directory, even though it does not follow this symlink
+
+testing "tree multiple directories" \
+ "tree tree2.tempdir tree3.tempdir" \
+ "\
+tree2.tempdir\n\
+âââ testfile\n\
+tree3.tempdir\n\
+âââ test1\n\
+âââ test2\n\
+â  âââ a\n\
+â  â  âââ testfile1\n\
+â  â  âââ testfile2\n\
+â  â  âââ testfile3\n\
+â  â  âââ testfile4 -> ../b/testfile4\n\
+â  âââ b\n\
+â  âââ test3 -> ../../test3\n\
+â  âââ testfile4\n\
+âââ test3\n\
+ âââ c\n\
+ â  âââ testfile5\n\
+ âââ d\n\
+ âââ testfile6\n\
+\n\
+7 directories, 9 files\n" \
+ "" ""
+#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?)
+
+rm -rf tree.tempdir tree2.tempdir tree3.tempdir
+
+exit $FAILCOUNT
More information about the busybox-cvs
mailing list