tree: new applet
Adds the tree program to list directories and files in a tree structure. function old new delta tree_print - 343 +343 scandir64 - 330 +330 scandir - 330 +330 tree_main - 86 +86 .rodata 105150 105228 +78 packed_usage 34511 34557 +46 alphasort64 - 31 +31 alphasort - 31 +31 strcoll - 5 +5 applet_names 2801 2806 +5 applet_main 1616 1620 +4 applet_suid 101 102 +1 applet_install_loc 202 203 +1 ------------------------------------------------------------------------------ (add/remove: 11/0 grow/shrink: 6/0 up/down: 1291/0) Total: 1291 bytes Signed-off-by: Roger Knecht <rknecht@pm.me> Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
		
				
					committed by
					
						 Denys Vlasenko
						Denys Vlasenko
					
				
			
			
				
	
			
			
			
						parent
						
							2617a5e4c6
						
					
				
				
					commit
					20a4f70eca
				
			
							
								
								
									
										3
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								AUTHORS
									
									
									
									
									
								
							| @@ -181,3 +181,6 @@ Jie Zhang <jie.zhang@analog.com> | |||||||
|  |  | ||||||
| Maxime Coste <mawww@kakoune.org> | Maxime Coste <mawww@kakoune.org> | ||||||
|     paste implementation |     paste implementation | ||||||
|  |  | ||||||
|  | Roger Knecht <rknecht@pm.me> | ||||||
|  |     tree | ||||||
|   | |||||||
							
								
								
									
										118
									
								
								miscutils/tree.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								miscutils/tree.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | |||||||
|  | /* 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 (0.6 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" | ||||||
|  |  | ||||||
|  | #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; | ||||||
|  |  | ||||||
|  | 	// 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, "└── "); | ||||||
|  | 			} else { | ||||||
|  | 				strcpy(prefix_pos, "├── "); | ||||||
|  | 			} | ||||||
|  | 			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, "│   "); | ||||||
|  | 				} | ||||||
|  | 				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(); | ||||||
|  |  | ||||||
|  | 	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; | ||||||
|  | } | ||||||
							
								
								
									
										100
									
								
								testsuite/tree.tests
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										100
									
								
								testsuite/tree.tests
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,100 @@ | |||||||
|  | #!/bin/sh | ||||||
|  |  | ||||||
|  | # Copyright 2022 by Roger Knecht <rknecht@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 | ||||||
|  |  | ||||||
|  | (cd tree3.tempdir/test2/a && ln -s ../b/testfile4 .) | ||||||
|  | (cd tree3.tempdir/test2/b && ln -s ../../test3 .) | ||||||
|  |  | ||||||
|  | testing "tree nested directories and files" \ | ||||||
|  | 	"cd tree3.tempdir && tree" \ | ||||||
|  | 	"\ | ||||||
|  | .\n\ | ||||||
|  | ├── test1\n\ | ||||||
|  | ├── test2\n\ | ||||||
|  | │   ├── a\n\ | ||||||
|  | │   │   ├── testfile1\n\ | ||||||
|  | │   │   ├── testfile2\n\ | ||||||
|  | │   │   ├── testfile3\n\ | ||||||
|  | │   │   └── testfile4 -> ../b/testfile4\n\ | ||||||
|  | │   └── b\n\ | ||||||
|  | │       ├── test3 -> ../../test3\n\ | ||||||
|  | │       └── testfile4\n\ | ||||||
|  | └── test3\n\ | ||||||
|  |     ├── c\n\ | ||||||
|  |     │   └── testfile5\n\ | ||||||
|  |     └── d\n\ | ||||||
|  |         └── testfile6\n\ | ||||||
|  | \n\ | ||||||
|  | 7 directories, 8 files\n" \ | ||||||
|  | 	"" "" | ||||||
|  | #note: tree v2.0.1 says "8 directories, 7 files": | ||||||
|  | #it counts "test3 -> ../../test3" as a directory, even though it does not follow this symlink | ||||||
|  |  | ||||||
|  | testing "tree multiple directories" \ | ||||||
|  | 	"tree tree2.tempdir tree3.tempdir" \ | ||||||
|  | 	"\ | ||||||
|  | tree2.tempdir\n\ | ||||||
|  | └── testfile\n\ | ||||||
|  | tree3.tempdir\n\ | ||||||
|  | ├── test1\n\ | ||||||
|  | ├── test2\n\ | ||||||
|  | │   ├── a\n\ | ||||||
|  | │   │   ├── testfile1\n\ | ||||||
|  | │   │   ├── testfile2\n\ | ||||||
|  | │   │   ├── testfile3\n\ | ||||||
|  | │   │   └── testfile4 -> ../b/testfile4\n\ | ||||||
|  | │   └── b\n\ | ||||||
|  | │       ├── test3 -> ../../test3\n\ | ||||||
|  | │       └── testfile4\n\ | ||||||
|  | └── test3\n\ | ||||||
|  |     ├── c\n\ | ||||||
|  |     │   └── testfile5\n\ | ||||||
|  |     └── d\n\ | ||||||
|  |         └── testfile6\n\ | ||||||
|  | \n\ | ||||||
|  | 7 directories, 9 files\n" \ | ||||||
|  | 	"" "" | ||||||
|  | #note: tree v2.0.1 says "8 directories, 7 files" (not "8 files", probably a/testfile4 -> ../b/testfile4 and b/testfile4 are counted as one file, not 2?) | ||||||
|  |  | ||||||
|  | rm -rf tree.tempdir tree2.tempdir tree3.tempdir | ||||||
|  |  | ||||||
|  | exit $FAILCOUNT | ||||||
		Reference in New Issue
	
	Block a user