[PATCH] Added miscutil/tree.c

rknecht at pm.me rknecht at pm.me
Sun Apr 10 17:34:22 UTC 2022


This are the bloatcheck results:

function                                             old     new   delta
print_directory                                        -     872    +872
tree_main                                              -     152    +152
.rodata                                            95677   95761     +84
compare_dirent_alphabetically                          -      13     +13
applet_main                                         3192    3200      +8
applet_names                                        2747    2752      +5
DEFAULT_PATH                                           -       2      +2
DEFAULT_PREFIX                                         -       1      +1
packed_usage                                       34414   33626    -788
------------------------------------------------------------------------------
(add/remove: 6/0 grow/shrink: 3/1 up/down: 1137/-788)         Total: 349 bytes
   text	   data	    bss	    dec	    hex	filename
1048249	  16515	   1816	1066580	 104654	busybox_old
1048611	  16525	   1816	1066952	 1047c8	busybox_unstripped


------- Original Message -------
On Sunday, April 10th, 2022 at 1:24 PM, <rknecht at pm.me> wrote:


>
>
> From: Roger Knecht rknecht at pm.me
>
>
> Adds a new program which mimics the tree tool to list directories and files in a tree structure.
>
> Signed-off-by: Roger Knecht rknecht at pm.me
>
> ---
> Example:
> `$ ./busybox tree tree.tempdir/ tree.tempdir/ ├── test1 ├── test2 │ ├── a │ │ ├── testfile1 │ │ ├── testfile2 │ │ └── testfile3 │ └── b │ └── testfile4 └── test3 ├── c │ └── testfile5 └── d ├── testfile6 └── testfile7 7 directories, 7 files`
>
> Note: To keep things simple this patch does not contain any command line options.
> I will add more options once this patch is accepted.
>
> AUTHORS | 3 +
> miscutils/tree.c | 189 +++++++++++++++++++++++++++++++++++++++++++
> testsuite/tree.tests | 68 ++++++++++++++++
> 3 files changed, 260 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..37065a58d
> --- /dev/null
> +++ b/miscutils/tree.c
> @@ -0,0 +1,189 @@
> +/* 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"
> +//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 ""
> +
> +//usage:#define find_trivial_usage
> +//usage: "[PATH]"
> +//usage:#define find_full_usage "\n\n"
> +//usage: "Print files and directories in a tree structure."
> +//usage: "Defaults: PATH is current directory\n"
> +
> +#include "libbb.h"
> +
> +#define INITIAL_ENTRY_ALLOCATION 10
> +
> +#define PREFIX_CHILD "├── "
> +#define PREFIX_LAST_CHILD "└── "
> +#define PREFIX_GRAND_CHILD "│ "
> +#define PREFIX_LAST_GRAND_CHILD " "
> +
> +static char DEFAULT_PATH[] = ".";
> +static char DEFAULT_PREFIX[] = "";
> +
> +struct directory {
> + char name;
> + char* path;
> + char* prefix_child;
> + char* prefix_last_child;
> + char* prefix_grand_child;
> + char* prefix_last_grand_child;
> +};
> +
> +struct statistic {
> + int directories;
> + int files;
> +};
> +
> +static int compare_dirent_alphabetically(const void* a, const void* b) {
> + return strcmp(((struct dirent*)a)->d_name, ((struct dirent*)b)->d_name);
>
> +}
> +
> +static int read_sorted_directory_entries(char* path, struct dirent** entries) {
> + size_t index, size;
> + struct dirent dirent;
> + DIR dir;
> +
> + size = INITIAL_ENTRY_ALLOCATION;
> + index = 0;
> + *entries = NULL;
> +
> + // alloc directory entry array
> + if (!(*entries = malloc(sizeof(struct dirent) * size)))
> + return -ENOMEM;
> +
> + // open directory
> + if (!(dir = opendir(path)))
> + return -EACCES;
> +
> + // read each directory entry
> + while ((dirent = readdir(dir)) != NULL) {
> + // ignore hidden files
> + if (strncmp(dirent->d_name, ".", 1) == 0)
>
> + continue;
> +
> + // check for free array slot
> + if (index >= size)
>
> + // enlarge directory entry array
> + if (!(*entries = realloc(*entries, sizeof(struct dirent) * (size *= 2)))) {
> + closedir(dir);
> + return -ENOMEM;
> + }
> +
> + // copy directory entry
> + memcpy(&(entries)[index], dirent, sizeof(struct dirent));
> + index++;
> + }
> +
> + closedir(dir);
> +
> + // sort directory alphabetically
> + qsort(entries, index, sizeof(struct dirent), compare_dirent_alphabetically);
> +
> + return index;
> +}
> +
> +static void print_directory(struct directory directory, struct statistic statistic, bool is_last) {
> + struct dirent *entries, *dirent;
> + struct directory child_directory;
> + char *prefix, *child_prefix;
> + size_t child_prefix_strlen;
> + int index, entry_count;
> + bool is_last_child;
> +
> + // read all directory entries
> + entry_count = read_sorted_directory_entries(directory->path, &entries);
>
> +
> + // set directory prefix
> + prefix = is_last ? directory->prefix_last_child : directory->prefix_child;
>
> +
> + // check if directory is readable
> + if (entry_count < 0) {
> + printf("%s%s [error opening dir]\n", prefix, directory->name);
>
> + return;
> + }
> +
> + // print directory name
> + printf("%s%s\n", prefix, directory->name);
>
> +
> + // print all directory entries
> + for (index = 0; index < entry_count; index++) {
> + dirent = &entries[index];
> +
> + is_last_child = (index + 1) >= entry_count;
>
> + child_prefix = is_last ? directory->prefix_last_grand_child : directory->prefix_grand_child;
>
> + child_prefix_strlen = strlen(child_prefix);
> +
> + // allocate memory for child directory
> + child_directory.name = dirent->d_name;
>
> + child_directory.path = alloca(strlen(directory->path) + strlen(dirent->d_name) + 2);
>
> + child_directory.prefix_child = alloca(child_prefix_strlen + sizeof(PREFIX_CHILD));
> + child_directory.prefix_last_child = alloca(child_prefix_strlen + sizeof(PREFIX_LAST_CHILD));
> + child_directory.prefix_grand_child = alloca(child_prefix_strlen + sizeof(PREFIX_GRAND_CHILD));
> + child_directory.prefix_last_grand_child = alloca(child_prefix_strlen + sizeof(PREFIX_LAST_GRAND_CHILD));
> +
> + // concate child directory paths
> + sprintf(child_directory.path, "%s/%s", directory->path, dirent->d_name);
>
> + sprintf(child_directory.prefix_child, "%s%s", child_prefix, PREFIX_CHILD);
> + sprintf(child_directory.prefix_last_child, "%s%s", child_prefix, PREFIX_LAST_CHILD);
> + sprintf(child_directory.prefix_grand_child, "%s%s", child_prefix, PREFIX_GRAND_CHILD);
> + sprintf(child_directory.prefix_last_grand_child, "%s%s", child_prefix, PREFIX_LAST_GRAND_CHILD);
> +
> + switch (dirent->d_type) {
>
> + // directories
> + case DT_DIR:
> + statistic->directories++;
>
> + print_directory(&child_directory, statistic, is_last_child);
> + break;
> +
> + // regular files, symlinks, etc.
> + default:
> + statistic->files++;
>
> + printf("%s%s\n", is_last_child ? child_directory.prefix_last_child : child_directory.prefix_child, dirent->d_name);
>
> + break;
> + }
> + }
> +
> + free(entries);
> +}
> +
> +int tree_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
> +int tree_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
> +{
> + struct directory directory;
> + struct statistic statistic;
> +
> + memset(&statistic, 0, sizeof(statistic));
> +
> + // set directory path
> + directory.path = directory.name = (argc > 1) ? argv[1] : DEFAULT_PATH;
>
> +
> + // initialize empty prefixes
> + directory.prefix_child = directory.prefix_last_child
> + = directory.prefix_grand_child
> + = directory.prefix_last_grand_child
> + = DEFAULT_PREFIX;
> +
> + // print directories and files
> + print_directory(&directory, &statistic, true);
> +
> + // print statistic
> + printf("\n%d directories, %d files\n", statistic.directories, statistic.files);
> +
> + return EXIT_SUCCESS;
> +}
> diff --git a/testsuite/tree.tests b/testsuite/tree.tests
> new file mode 100755
> index 000000000..37586e530
> --- /dev/null
> +++ b/testsuite/tree.tests
> @@ -0,0 +1,68 @@
> +#!/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\
> + └── testfile7\n\
> +\n\
> +7 directories, 7 files\n" \
> + "" ""
> +
> +rm -rf tree.tempdir tree2.tempdir tree3.tempdir
> +
> +exit $FAILCOUNT
> --
> 2.17.1


More information about the busybox mailing list