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