summaryrefslogtreecommitdiff
path: root/miscutils/tree.c
blob: 0b142c85cb436ecf752375990f077ccb57281c61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/* vi: set sw=4 ts=4: */
/*
 * Copyright (C) 2022 Roger Knecht <rknecht@pm.me>
 *
 * Licensed under GPLv2, see file LICENSE in this source tree.
 */
//config:config TREE
//config:	bool "tree (2.5 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"
#include "unicode.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;
	const char *bar = "|   ";
	const char *mid = "|-- ";
	const char *end = "`-- ";

#if ENABLE_UNICODE_SUPPORT
	if (unicode_status == UNICODE_ON) {
		bar = "│   ";
		mid = "├── ";
		end = "└── ";
	}
#endif

	// 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, end);
			} else {
				strcpy(prefix_pos, mid);
			}
			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, bar);
				}
				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();
	init_unicode();

	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;
}