[PATCH v2] Added miscutil/tree.c

Roger Knecht rknecht at pm.me
Sat Apr 16 16:54:19 UTC 2022


Adds the tree program to list directories and files in a tree structure.

function                                             old     new   delta
static.tree_print                                      -     352    +352
.rodata                                            95677   95756     +79
tree_main                                              -      69     +69
globals                                                -      24     +24
applet_main                                         3192    3200      +8
applet_names                                        2747    2752      +5
packed_usage                                       34414   34396     -18
------------------------------------------------------------------------------
(add/remove: 4/0 grow/shrink: 3/1 up/down: 537/-18)           Total: 519 bytes
---
As Ron pointed out V1 was overwriting the find help text and therefore invalidating
the bloatcheck. V1 added 1171 bytes (not 349 bytes as originally mentioned).

V2 is reducing the size by using scandir(), chdir() and avoiding string concatenations.

Changelog:

V2:
- Fixed tree help text
- Reduced size by 652 bytes

 AUTHORS              |   3 +
 miscutils/tree.c     | 128 +++++++++++++++++++++++++++++++++++++++++++
 testsuite/tree.tests |  67 ++++++++++++++++++++++
 3 files changed, 198 insertions(+)
 create mode 100644 miscutils/tree.c
 create mode 100755 testsuite/tree.tests

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..4f1a4873d
--- /dev/null
+++ b/miscutils/tree.c
@@ -0,0 +1,128 @@
+/* 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.5 kb)"
+//config:	default n
+//config:	help
+//config:	List files and directories in a tree structure.
+//config:
+
+//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"
+
+#define PREFIX_CHILD "├── "
+#define PREFIX_LAST_CHILD "└── "
+#define PREFIX_GRAND_CHILD "│   "
+#define PREFIX_LAST_GRAND_CHILD "    "
+#define DEFAULT_PATH "."
+
+struct directory {
+	struct directory* child;
+	char *prefix;
+};
+
+static struct globals {
+	struct directory root;
+	int directories;
+	int files;
+} globals;
+
+static void tree_print(char* directory_name, struct directory* directory) {
+	struct dirent **entries, *dirent;
+	struct directory child_directory, *it;
+	int size, index;
+	bool is_not_last;
+
+	// read directory entries
+	size = scandir(directory_name, &entries, NULL, alphasort);
+
+	if (size < 0) {
+		fputs(directory_name, stdout);
+		puts(" [error opening dir]");
+		return;
+	}
+
+	// print directory name
+	puts(directory_name);
+
+	// switch to sub directory
+	chdir(directory_name);
+
+	// print all directory entries
+	for (index = 0; index < size; index++) {
+		dirent = entries[index];
+		is_not_last = (index + 1) < size;
+
+		// filter hidden files and directories
+		if (strncmp(dirent->d_name, ".", 1) == 0) {
+			free(dirent);
+			continue;
+		}
+
+		// print tree line prefix
+		it = &globals.root;
+		while ((it = it->child))
+			fputs(it->prefix, stdout);
+
+		if (is_not_last) {
+			child_directory.prefix = PREFIX_GRAND_CHILD;
+			fputs(PREFIX_CHILD, stdout);
+		} else {
+			child_directory.prefix = PREFIX_LAST_GRAND_CHILD;
+			fputs(PREFIX_LAST_CHILD, stdout);
+		}
+
+		switch (dirent->d_type) {
+			// directories
+			case DT_DIR:
+				globals.directories++;
+				child_directory.child = NULL;
+				directory->child = &child_directory;
+				tree_print(dirent->d_name, &child_directory);
+				directory->child = NULL;
+				break;
+
+			// regular files, symlinks, etc.
+			default:
+				globals.files++;
+				puts(dirent->d_name);
+				break;
+		}
+
+		// release directory entry
+		free(dirent);
+	}
+
+	// release directory array
+	free(entries);
+
+	// switch to parent directory
+	chdir("..");
+}
+
+int tree_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tree_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+	char* path;
+
+	// check if path is passed as argument
+	path = (argc > 1) ? argv[1] : DEFAULT_PATH;
+
+	// list directories and files
+	tree_print(path, &globals.root);
+
+	// print statistic
+	printf("\n%d directories, %d files\n", globals.directories, globals.files);
+
+	return EXIT_SUCCESS;
+}
diff --git a/testsuite/tree.tests b/testsuite/tree.tests
new file mode 100755
index 000000000..49dcc0661
--- /dev/null
+++ b/testsuite/tree.tests
@@ -0,0 +1,67 @@
+#!/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
+
+testing "tree nested directories and files" \
+	"cd tree3.tempdir && tree" \
+	"\
+.\n\
+├── test1\n\
+├── test2\n\
+│   ├── a\n\
+│   │   ├── testfile1\n\
+│   │   ├── testfile2\n\
+│   │   └── testfile3\n\
+│   └── b\n\
+│       └── testfile4\n\
+└── test3\n\
+    ├── c\n\
+    │   └── testfile5\n\
+    └── d\n\
+        └── testfile6\n\
+\n\
+7 directories, 6 files\n" \
+	"" ""
+
+rm -rf tree.tempdir tree2.tempdir tree3.tempdir
+
+exit $FAILCOUNT
--
2.17.1




More information about the busybox mailing list