busybox/miscutils/tree.c
<<
>>
Prefs
   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#include "unicode.h"
  23
  24#define prefix_buf bb_common_bufsiz1
  25
  26static void tree_print(unsigned count[2], const char* directory_name, char* prefix_pos)
  27{
  28        struct dirent **entries;
  29        int index, size;
  30        const char *bar = "|   ";
  31        const char *mid = "|-- ";
  32        const char *end = "`-- ";
  33
  34#if ENABLE_UNICODE_SUPPORT
  35        if (unicode_status == UNICODE_ON) {
  36                bar = "│   ";
  37                mid = "├── ";
  38                end = "└── ";
  39        }
  40#endif
  41
  42        // read directory entries
  43        size = scandir(directory_name, &entries, NULL, alphasort);
  44
  45        if (size < 0) {
  46                fputs_stdout(directory_name);
  47                puts(" [error opening dir]");
  48                return;
  49        }
  50
  51        // print directory name
  52        puts(directory_name);
  53
  54        // switch to sub directory
  55        xchdir(directory_name);
  56
  57        // print all directory entries
  58        for (index = 0; index < size;) {
  59                struct dirent *dirent = entries[index++];
  60
  61                // filter hidden files and directories
  62                if (dirent->d_name[0] != '.') {
  63                        int status;
  64                        struct stat statBuf;
  65
  66//TODO: when -l is implemented, use stat, not lstat, if -l
  67                        status = lstat(dirent->d_name, &statBuf);
  68
  69                        if (index == size) {
  70                                strcpy(prefix_pos, end);
  71                        } else {
  72                                strcpy(prefix_pos, mid);
  73                        }
  74                        fputs_stdout(prefix_buf);
  75
  76                        if (status == 0 && S_ISLNK(statBuf.st_mode)) {
  77                                // handle symlink
  78                                char* symlink_path = xmalloc_readlink(dirent->d_name);
  79                                printf("%s -> %s\n", dirent->d_name, symlink_path);
  80                                free(symlink_path);
  81                                count[1]++;
  82                        } else if (status == 0 && S_ISDIR(statBuf.st_mode)
  83                         && (prefix_pos - prefix_buf) < (COMMON_BUFSIZE - 16)
  84                        ) {
  85                                // handle directory
  86                                char* pos;
  87                                if (index == size) {
  88                                        pos = stpcpy(prefix_pos, "    ");
  89                                } else {
  90                                        pos = stpcpy(prefix_pos, bar);
  91                                }
  92                                tree_print(count, dirent->d_name, pos);
  93                                count[0]++;
  94                        } else {
  95                                // handle file
  96                                puts(dirent->d_name);
  97                                count[1]++;
  98                        }
  99                }
 100
 101                // release directory entry
 102                free(dirent);
 103        }
 104
 105        // release directory array
 106        free(entries);
 107
 108        // switch to parent directory
 109        xchdir("..");
 110}
 111
 112int tree_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 113int tree_main(int argc UNUSED_PARAM, char **argv)
 114{
 115        unsigned count[2] = { 0, 0 };
 116
 117        setup_common_bufsiz();
 118        init_unicode();
 119
 120        if (!argv[1])
 121                *argv-- = (char*)".";
 122
 123        // list directories given as command line arguments
 124        while (*(++argv))
 125                tree_print(count, *argv, prefix_buf);
 126
 127        // print statistic
 128        printf("\n%u directories, %u files\n", count[0], count[1]);
 129
 130        return EXIT_SUCCESS;
 131}
 132