On Sat, 16 Apr 2022 16:54:19 +0000
Roger Knecht <rkne...@pm.me> wrote:

> 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

Hi,
just out of curiosity, for fun and to refresh my C skills I wrote
an alternative version of tree that is somewhat similar to yours
in using scandir.

One problem I see in your V2 is that unlike real tree it does not
handle multiple paths as command line args:

tree dir dir2 dirn

Also the use of  DT_DIR is problematic as only some filesystems
have full support.


Bloatcheck is:

function                                             old     new   delta
tree                                                   -     501    +501
tree_main                                              -     125    +125
.rodata                                            99024   99106     +82
packed_usage                                       34414   34448     +34
applet_main                                         3192    3200      +8
applet_names                                        2747    2752      +5
------------------------------------------------------------------------------
(add/remove: 3/0 grow/shrink: 4/0 up/down: 755/0)             Total: 755 bytes
   text    data     bss     dec     hex filename
1002271   16403    1848 1020522   f926a busybox_old
1003180   16427    1848 1021455   f960f busybox_unstripped

Features are:
1) correct link handling:

./busybox tree ../../test/
../../test/
├── test1
│   └── a
├── test2
│   ├── b
│   ├── c
│   └── test3
├── test4 -> test2
└── test5

2) Uses scandir and passes the testsuite.
3) It is worth mentioning that by using alphasort
    as sorting algorithm the output of complex trees
    is somewhat different in the ordering of files
     ( for example uppercase , lowercase)
    as the real tree program.

For easier review code is attached as patch and 
also in the mail body.

#include "libbb.h"

#define HIDDEN(s)        ((s)[0] == '.')

#define CHILD            "├── "
#define LAST_CHILD       "└── "
#define GRAND_CHILD      "│   "
#define LAST_GRAND_CHILD "    "

#define MAX_CHILD_SIZE (MAX(MAX(MAX((sizeof(CHILD)), \
                                                                        
(sizeof(LAST_CHILD))), \
                                                                        
(sizeof(GRAND_CHILD))), \
                                                                        
(sizeof(LAST_GRAND_CHILD))))

static void tree(char *path, int *dcount, int *fcount, char **list)
{
        int i = 0;
        int n;
        int l;
        char *fullpath = NULL;
        char *linkname = NULL;
        struct dirent **namelist;

        if ((n = scandir(path, &namelist, NULL, alphasort)) < 0) {
                printf("%s [error opening dir]\n", path);
                return;
        } else if (!**list) {
                printf("%s\n", path);
        }

        l = strlen(*list);

        while (i < n) {
                if (!DOT_OR_DOTDOT(namelist[i]->d_name) && 
!HIDDEN(namelist[i]->d_name)) {
                        *list = xrealloc(*list, l + MAX_CHILD_SIZE);
                        strcpy(*list + l, (i == (n - 1)) ? LAST_CHILD : CHILD);
                        printf("%s%s", *list, namelist[i]->d_name);
                        fullpath = concat_path_file(path, namelist[i]->d_name);
                        if((linkname = xmalloc_readlink(fullpath)) != NULL) {
                                printf(" -> %s", linkname);
                        }
                        bb_putchar('\n');
                        if (is_directory(fullpath, /*followLinks*/ 1)) {
                                (*dcount)++;
                                strcpy(*list + l, (i == (n - 1)) ? 
LAST_GRAND_CHILD : GRAND_CHILD);
                                if (linkname == NULL) {
                                        tree(fullpath, dcount, fcount, list);
                                }
                        } else {
                                (*fcount)++;
                        }
                        free(linkname);
                        free(fullpath);
                }
                free(namelist[i]);
                i++;
        }
        strcpy(*list + l,  "");
        free(namelist);
}

int tree_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int tree_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
{
        int dircount = 0;
        int filecount = 0;
        char *list = xzalloc(1);

        if (argc == 1) {
                *argv = (char *) ".";
        } else {
                argv++;
        }

        do {
                tree(*argv, &dircount, &filecount, &list);
        } while (*++argv);
        printf("\n%d directories, %d files\n", dircount, filecount);
        if (ENABLE_FEATURE_CLEAN_UP) {
                 free(list);
        }
        return EXIT_SUCCESS;
}
  
Hints on how to shrink it even more, improvements and critics are welcome! 
Enjoy.

Ciao,
Tito
     
Busybox version of tree, lists contents of directories
in a tree-like format.
Tests written by Roger Knecht <rkne...@pm.me>.

Signed-off-by: Tito Ragusa <farmat...@tiscali.it>

--- /dev/null	2022-04-16 12:45:08.784170006 +0200
+++ busybox/miscutils/tree.c	2022-04-16 18:27:07.242306963 +0200
@@ -0,0 +1,110 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2022 Tito Ragusa <farmat...@tiscali.it>
+ *
+ * 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 tree_trivial_usage
+//usage:       "[PATH]"
+//usage:#define tree_full_usage "\n\n"
+//usage:       "Print files and directories in a tree structure."
+//usage:       "Defaults: PATH is current directory\n"
+
+#include "libbb.h"
+
+#define HIDDEN(s)        ((s)[0] == '.')
+
+#define CHILD            "├── "
+#define LAST_CHILD       "└── "
+#define GRAND_CHILD      "│   "
+#define LAST_GRAND_CHILD "    "
+
+#define MAX_CHILD_SIZE (MAX(MAX(MAX((sizeof(CHILD)), \
+									(sizeof(LAST_CHILD))), \
+									(sizeof(GRAND_CHILD))), \
+									(sizeof(LAST_GRAND_CHILD))))
+
+static void tree(char *path, int *dcount, int *fcount, char **list)
+{
+	int i = 0;
+	int n;
+	int l;
+	char *fullpath = NULL;
+	char *linkname = NULL;
+	struct dirent **namelist;
+
+	if ((n = scandir(path, &namelist, NULL, alphasort)) < 0) {
+		printf("%s [error opening dir]\n", path);
+		return;
+	} else if (!**list) {
+		printf("%s\n", path);
+	}
+
+	l = strlen(*list);
+
+	while (i < n) {
+		if (!DOT_OR_DOTDOT(namelist[i]->d_name) && !HIDDEN(namelist[i]->d_name)) {
+			*list = xrealloc(*list, l + MAX_CHILD_SIZE);
+			strcpy(*list + l, (i == (n - 1)) ? LAST_CHILD : CHILD);
+			printf("%s%s", *list, namelist[i]->d_name);
+			fullpath = concat_path_file(path, namelist[i]->d_name);
+			if((linkname = xmalloc_readlink(fullpath)) != NULL) {
+				printf(" -> %s", linkname);
+			}
+			bb_putchar('\n');
+			if (is_directory(fullpath, /*followLinks*/ 1)) {
+				(*dcount)++;
+				strcpy(*list + l, (i == (n - 1)) ? LAST_GRAND_CHILD : GRAND_CHILD);
+				if (linkname == NULL) {
+					tree(fullpath, dcount, fcount, list);
+				}
+			} else {
+				(*fcount)++;
+			}
+			free(linkname);
+			free(fullpath);
+		}
+		free(namelist[i]);
+		i++;
+	}
+	strcpy(*list + l,  "");
+	free(namelist);
+}
+
+int tree_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tree_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+	int dircount = 0;
+	int filecount = 0;
+	char *list = xzalloc(1);
+
+	if (argc == 1) {
+		*argv = (char *) ".";
+	} else {
+		argv++;
+	}
+
+	do {
+		tree(*argv, &dircount, &filecount, &list);
+	} while (*++argv);
+	printf("\n%d directories, %d files\n", dircount, filecount);
+	if (ENABLE_FEATURE_CLEAN_UP) {
+		 free(list);
+	}
+	return EXIT_SUCCESS;
+}
+
--- /dev/null	2022-04-16 12:45:08.784170006 +0200
+++ busybox/testsuite/tree.tests	2022-04-10 21:05:22.643361410 +0200
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+# Copyright 2022 by Roger Knecht <rkne...@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
_______________________________________________
busybox mailing list
busybox@busybox.net
http://lists.busybox.net/mailman/listinfo/busybox

Reply via email to