├── .gitignore ├── COPYING ├── Makefile.am ├── src ├── help.h ├── quit.h ├── browser.h ├── shell.h ├── delete.h ├── exclude.h ├── quit.c ├── path.h ├── shell.c ├── dirlist.h ├── exclude.c ├── global.h ├── dir.h ├── dir_export.c ├── path.c ├── delete.c ├── help.c ├── dir_mem.c ├── dir_common.c ├── util.h ├── dirlist.c ├── main.c ├── util.c ├── dir_scan.c ├── browser.c └── dir_import.c ├── README ├── configure.ac ├── static └── build.sh ├── deps ├── yopt.h └── khashl.h ├── ChangeLog └── doc └── ncdu.pod /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | Makefile.in 3 | aclocal.m4 4 | autom4te.cache/ 5 | compile 6 | config.h 7 | config.h.in 8 | config.log 9 | config.status 10 | configure 11 | depcomp 12 | install-sh 13 | missing 14 | .deps/ 15 | .dirstamp 16 | *.o 17 | stamp-h1 18 | ncdu 19 | ncdu.1 20 | *~ 21 | *.swp 22 | static/* 23 | !static/build.sh 24 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2020 Yoran Heling 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CPPFLAGS=-I$(srcdir)/deps 2 | bin_PROGRAMS=ncdu 3 | 4 | ncdu_SOURCES=\ 5 | src/browser.c\ 6 | src/delete.c\ 7 | src/dirlist.c\ 8 | src/dir_common.c\ 9 | src/dir_export.c\ 10 | src/dir_import.c\ 11 | src/dir_mem.c\ 12 | src/dir_scan.c\ 13 | src/exclude.c\ 14 | src/help.c\ 15 | src/shell.c\ 16 | src/quit.c\ 17 | src/main.c\ 18 | src/path.c\ 19 | src/util.c 20 | 21 | noinst_HEADERS=\ 22 | deps/yopt.h\ 23 | deps/khashl.h\ 24 | src/browser.h\ 25 | src/delete.h\ 26 | src/dir.h\ 27 | src/dirlist.h\ 28 | src/exclude.h\ 29 | src/global.h\ 30 | src/help.h\ 31 | src/shell.h\ 32 | src/quit.h\ 33 | src/path.h\ 34 | src/util.h 35 | 36 | 37 | man_MANS=ncdu.1 38 | EXTRA_DIST=ncdu.1 doc/ncdu.pod 39 | 40 | # Don't "clean" ncdu.1, it should be in the tarball so that pod2man isn't a 41 | # build dependency for those who use the tarball. 42 | ncdu.1: $(srcdir)/doc/ncdu.pod 43 | pod2man --center "ncdu manual" --release "@PACKAGE@-@VERSION@" "$(srcdir)/doc/ncdu.pod" >ncdu.1 44 | 45 | update-deps: 46 | wget -q https://raw.github.com/attractivechaos/klib/master/khashl.h -O "$(srcdir)/deps/khashl.h" 47 | wget -q http://g.blicky.net/ylib.git/plain/yopt.h -O "$(srcdir)/deps/yopt.h" 48 | -------------------------------------------------------------------------------- /src/help.h: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #ifndef _help_h 27 | #define _help_h 28 | 29 | #include "global.h" 30 | 31 | int help_key(int); 32 | void help_draw(void); 33 | void help_init(void); 34 | 35 | 36 | #endif 37 | 38 | -------------------------------------------------------------------------------- /src/quit.h: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2015-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #ifndef _quit_h 27 | #define _quit_h 28 | 29 | #include "global.h" 30 | 31 | int quit_key(int); 32 | void quit_draw(void); 33 | void quit_init(void); 34 | 35 | 36 | #endif 37 | 38 | -------------------------------------------------------------------------------- /src/browser.h: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #ifndef _browser_h 27 | #define _browser_h 28 | 29 | #include "global.h" 30 | 31 | int browse_key(int); 32 | void browse_draw(void); 33 | void browse_init(struct dir *); 34 | 35 | 36 | #endif 37 | 38 | -------------------------------------------------------------------------------- /src/shell.h: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | Shell support: Copyright (c) 2014 Thomas Jarosch 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | #ifndef _shell_h 28 | #define _shell_h 29 | 30 | #include "global.h" 31 | 32 | void shell_draw(void); 33 | void shell_init(void); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/delete.h: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #ifndef _delete_h 27 | #define _delete_h 28 | 29 | #include "global.h" 30 | 31 | void delete_process(void); 32 | int delete_key(int); 33 | void delete_draw(void); 34 | void delete_init(struct dir *, struct dir *); 35 | 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/exclude.h: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #ifndef _exclude_h 27 | #define _exclude_h 28 | 29 | void exclude_add(char *); 30 | int exclude_addfile(char *); 31 | int exclude_match(char *); 32 | void exclude_clear(void); 33 | int has_cachedir_tag(const char *name); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/quit.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2015-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include "global.h" 27 | 28 | #include 29 | 30 | int quit_key(int ch) { 31 | switch(ch) { 32 | case 'y': 33 | case 'Y': 34 | return 1; 35 | default: 36 | pstate = ST_BROWSE; 37 | } 38 | return 0; 39 | } 40 | 41 | void quit_draw() { 42 | browse_draw(); 43 | 44 | nccreate(4,30, "ncdu confirm quit"); 45 | ncaddstr(2,2, "Really quit? (y/N)"); 46 | } 47 | 48 | void quit_init() { 49 | pstate = ST_QUIT; 50 | } 51 | -------------------------------------------------------------------------------- /src/path.h: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | /* 26 | path.c reimplements realpath() and chdir(), both functions accept 27 | arbitrary long path names not limited by PATH_MAX. 28 | 29 | Caveats/bugs: 30 | - path_real uses chdir(), so it's not thread safe 31 | - Process requires +x access for all directory components 32 | - Potentionally slow 33 | - path_real doesn't check for the existence of the last component 34 | - cwd is unreliable after path_real 35 | */ 36 | 37 | #ifndef _path_h 38 | #define _path_h 39 | 40 | /* path_real reimplements realpath(). The returned string is allocated 41 | by malloc() and should be manually free()d by the programmer. */ 42 | extern char *path_real(const char *); 43 | 44 | /* works exactly the same as chdir() */ 45 | extern int path_chdir(const char *); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ncdu 1.15.1 2 | =========== 3 | 4 | DESCRIPTION 5 | 6 | ncdu (NCurses Disk Usage) is a curses-based version of 7 | the well-known 'du', and provides a fast way to see what 8 | directories are using your disk space. 9 | 10 | 11 | REQUIREMENTS 12 | 13 | In order to compile and install ncdu, you need to have 14 | at least... 15 | 16 | - a POSIX-compliant operating system (Linux, BSD, etc) 17 | - curses libraries and header files 18 | 19 | 20 | INSTALL 21 | 22 | The usual: 23 | 24 | ./configure --prefix=/usr 25 | make 26 | make install 27 | 28 | If you're building directly from the git repository, make sure you have perl 29 | (or rather, pod2man), pkg-config and GNU autoconf/automake installed, then 30 | run 'autoreconf -i', and you're ready to continue with the usual ./configure 31 | and make route. 32 | 33 | 34 | COPYING 35 | 36 | Copyright (c) 2007-2020 Yoran Heling 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining 39 | a copy of this software and associated documentation files (the 40 | "Software"), to deal in the Software without restriction, including 41 | without limitation the rights to use, copy, modify, merge, publish, 42 | distribute, sublicense, and/or sell copies of the Software, and to 43 | permit persons to whom the Software is furnished to do so, subject to 44 | the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included 47 | in all copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 50 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 51 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 52 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 53 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 54 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 55 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 56 | -------------------------------------------------------------------------------- /src/shell.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | Shell support: Copyright (c) 2014 Thomas Jarosch 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | #include "config.h" 28 | #include "global.h" 29 | #include "dirlist.h" 30 | #include "util.h" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | void shell_draw() { 38 | const char *full_path; 39 | int res; 40 | 41 | /* suspend ncurses mode */ 42 | def_prog_mode(); 43 | endwin(); 44 | 45 | full_path = getpath(dirlist_par); 46 | res = chdir(full_path); 47 | if (res != 0) { 48 | reset_prog_mode(); 49 | clear(); 50 | printw("ERROR: Can't change directory: %s (errcode: %d)\n" 51 | "\n" 52 | "Press any key to continue.", 53 | full_path, res); 54 | } else { 55 | const char *shell = getenv("NCDU_SHELL"); 56 | if (shell == NULL) { 57 | shell = getenv("SHELL"); 58 | if (shell == NULL) 59 | shell = DEFAULT_SHELL; 60 | } 61 | 62 | res = system(shell); 63 | 64 | /* resume ncurses mode */ 65 | reset_prog_mode(); 66 | 67 | if (res == -1 || !WIFEXITED(res) || WEXITSTATUS(res) == 127) { 68 | clear(); 69 | printw("ERROR: Can't execute shell interpreter: %s\n" 70 | "\n" 71 | "Press any key to continue.", 72 | shell); 73 | } 74 | } 75 | 76 | refresh(); 77 | pstate = ST_BROWSE; 78 | } 79 | 80 | void shell_init() { 81 | pstate = ST_SHELL; 82 | } 83 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | 2 | AC_INIT([ncdu],[1.15.1],[projects@yorhel.nl]) 3 | AC_CONFIG_SRCDIR([src/global.h]) 4 | AC_CONFIG_HEADER([config.h]) 5 | AM_INIT_AUTOMAKE([foreign std-options subdir-objects]) 6 | 7 | # Check for programs. 8 | AC_PROG_CC 9 | AC_PROG_INSTALL 10 | AC_PROG_RANLIB 11 | PKG_PROG_PKG_CONFIG 12 | 13 | # Check for header files. 14 | AC_CHECK_HEADERS( 15 | [limits.h sys/time.h sys/types.h sys/stat.h dirent.h unistd.h fnmatch.h ncurses.h],[], 16 | AC_MSG_ERROR([required header file not found])) 17 | 18 | AC_CHECK_HEADERS([locale.h sys/statfs.h linux/magic.h]) 19 | 20 | # Check for typedefs, structures, and compiler characteristics. 21 | AC_TYPE_INT64_T 22 | AC_TYPE_UINT64_T 23 | AC_SYS_LARGEFILE 24 | AC_STRUCT_ST_BLOCKS 25 | 26 | # Check for library functions. 27 | AC_CHECK_FUNCS( 28 | [getcwd gettimeofday fnmatch chdir rmdir unlink lstat system getenv],[], 29 | AC_MSG_ERROR([required function missing])) 30 | 31 | AC_CHECK_FUNCS(statfs) 32 | 33 | AC_CHECK_HEADERS([sys/attr.h]) 34 | 35 | AC_CHECK_FUNCS([getattrlist]) 36 | 37 | AC_CHECK_DECLS([ATTR_CMNEXT_NOFIRMLINKPATH], [], [], [[#include ]]) 38 | 39 | # Look for ncurses library to link to 40 | ncurses=auto 41 | AC_ARG_WITH([ncurses], 42 | [AS_HELP_STRING([--with-ncurses], [compile/link with ncurses library] )], 43 | [ncurses=ncurses]) 44 | AC_ARG_WITH([ncursesw], 45 | [AS_HELP_STRING([--with-ncursesw], [compile/link with wide-char ncurses library @<:@default@:>@])], 46 | [ncurses=ncursesw]) 47 | if test "$ncurses" = "auto" -o "$ncurses" = "ncursesw"; then 48 | PKG_CHECK_MODULES([NCURSES], [ncursesw], [LIBS="$LIBS $NCURSES_LIBS"; ncurses=ncursesw], 49 | [AC_CHECK_LIB([ncursesw], 50 | [initscr], 51 | [LIBS="$LIBS -lncursesw"; ncurses=ncursesw], 52 | [ncurses=ncurses]) 53 | ]) 54 | fi 55 | if test "$ncurses" = "ncurses"; then 56 | PKG_CHECK_MODULES([NCURSES], [ncurses], [LIBS="$LIBS $NCURSES_LIBS"], 57 | [AC_CHECK_LIB([ncurses], 58 | [initscr], 59 | [LIBS="$LIBS -lncurses"], 60 | [AC_MSG_ERROR(ncurses library is required)]) 61 | ]) 62 | fi 63 | 64 | # Configure default shell for spawning shell when $SHELL is not set 65 | AC_ARG_WITH([shell], 66 | [AS_HELP_STRING([--with-shell], 67 | [used interpreter as default shell (default is /bin/sh)])], 68 | [DEFAULT_SHELL=$withval], 69 | [DEFAULT_SHELL=/bin/sh]) 70 | AC_MSG_NOTICE([Using $DEFAULT_SHELL as the default shell if \$SHELL is not set]) 71 | AC_DEFINE_UNQUOTED(DEFAULT_SHELL, "$DEFAULT_SHELL", [Used default shell interpreter]) 72 | 73 | 74 | AC_CONFIG_FILES([Makefile]) 75 | AC_OUTPUT 76 | -------------------------------------------------------------------------------- /src/dirlist.h: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | /* Note: all functions below include a 'reference to parent dir' node at the 27 | * top of the list. */ 28 | 29 | #ifndef _dirlist_h 30 | #define _dirlist_h 31 | 32 | #include "global.h" 33 | 34 | 35 | #define DL_NOCHANGE -1 36 | #define DL_COL_NAME 0 37 | #define DL_COL_SIZE 1 38 | #define DL_COL_ASIZE 2 39 | #define DL_COL_ITEMS 3 40 | #define DL_COL_MTIME 4 41 | 42 | 43 | void dirlist_open(struct dir *); 44 | 45 | /* Get the next non-hidden item, 46 | * NULL = get first non-hidden item */ 47 | struct dir *dirlist_next(struct dir *); 48 | 49 | /* Get the struct dir item relative to the selected item, or the item nearest to the requested item 50 | * i = 0 get selected item 51 | * hidden items aren't considered */ 52 | struct dir *dirlist_get(int i); 53 | 54 | /* Get/set the first visible item in the list on the screen */ 55 | struct dir *dirlist_top(int hint); 56 | 57 | /* Set selected dir (must be in the currently opened directory, obviously) */ 58 | void dirlist_select(struct dir *); 59 | 60 | /* Change sort column (arguments should have a NO_CHANGE option) */ 61 | void dirlist_set_sort(int column, int desc, int df); 62 | 63 | /* Set the hidden thingy */ 64 | void dirlist_set_hidden(int hidden); 65 | 66 | 67 | /* DO NOT WRITE TO ANY OF THE BELOW VARIABLES FROM OUTSIDE OF dirlist.c! */ 68 | 69 | /* The 'reference to parent dir' */ 70 | extern struct dir *dirlist_parent; 71 | 72 | /* The actual parent dir */ 73 | extern struct dir *dirlist_par; 74 | 75 | /* current sorting configuration (set with dirlist_set_sort()) */ 76 | extern int dirlist_sort_desc, dirlist_sort_col, dirlist_sort_df; 77 | 78 | /* set with dirlist_set_hidden() */ 79 | extern int dirlist_hidden; 80 | 81 | /* maximum size of an item in the opened dir */ 82 | extern int64_t dirlist_maxs, dirlist_maxa; 83 | 84 | 85 | #endif 86 | 87 | -------------------------------------------------------------------------------- /src/exclude.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include "global.h" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | 34 | static struct exclude { 35 | char *pattern; 36 | struct exclude *next; 37 | } *excludes = NULL; 38 | 39 | 40 | 41 | void exclude_add(char *pat) { 42 | struct exclude **n; 43 | 44 | n = &excludes; 45 | while(*n != NULL) 46 | n = &((*n)->next); 47 | 48 | *n = (struct exclude *) xcalloc(1, sizeof(struct exclude)); 49 | (*n)->pattern = (char *) xmalloc(strlen(pat)+1); 50 | strcpy((*n)->pattern, pat); 51 | } 52 | 53 | 54 | int exclude_addfile(char *file) { 55 | FILE *f; 56 | char buf[256]; 57 | int len; 58 | 59 | if((f = fopen(file, "r")) == NULL) 60 | return 1; 61 | 62 | while(fgets(buf, 256, f) != NULL) { 63 | len = strlen(buf)-1; 64 | while(len >=0 && (buf[len] == '\r' || buf[len] == '\n')) 65 | buf[len--] = '\0'; 66 | if(len < 0) 67 | continue; 68 | exclude_add(buf); 69 | } 70 | 71 | fclose(f); 72 | return 0; 73 | } 74 | 75 | 76 | int exclude_match(char *path) { 77 | struct exclude *n; 78 | char *c; 79 | 80 | for(n=excludes; n!=NULL; n=n->next) { 81 | if(!fnmatch(n->pattern, path, 0)) 82 | return 1; 83 | for(c = path; *c; c++) 84 | if(*c == '/' && c[1] != '/' && !fnmatch(n->pattern, c+1, 0)) 85 | return 1; 86 | } 87 | return 0; 88 | } 89 | 90 | 91 | void exclude_clear() { 92 | struct exclude *n, *l; 93 | 94 | for(n=excludes; n!=NULL; n=l) { 95 | l = n->next; 96 | free(n->pattern); 97 | free(n); 98 | } 99 | excludes = NULL; 100 | } 101 | 102 | 103 | /* 104 | * Exclusion of directories that contain only cached information. 105 | * See http://www.brynosaurus.com/cachedir/ 106 | */ 107 | #define CACHEDIR_TAG_FILENAME "CACHEDIR.TAG" 108 | #define CACHEDIR_TAG_SIGNATURE "Signature: 8a477f597d28d172789f06886806bc55" 109 | 110 | int has_cachedir_tag(const char *name) { 111 | static int path_l = 1024; 112 | static char *path = NULL; 113 | int l; 114 | char buf[sizeof CACHEDIR_TAG_SIGNATURE - 1]; 115 | FILE *f; 116 | int match = 0; 117 | 118 | /* Compute the required length for `path`. */ 119 | l = strlen(name) + sizeof CACHEDIR_TAG_FILENAME + 2; 120 | if(l > path_l || path == NULL) { 121 | path_l = path_l * 2; 122 | if(path_l < l) 123 | path_l = l; 124 | /* We don't need to copy the content of `path`, so it's more efficient to 125 | * use `free` + `malloc`. */ 126 | free(path); 127 | path = xmalloc(path_l); 128 | } 129 | snprintf(path, path_l, "%s/%s", name, CACHEDIR_TAG_FILENAME); 130 | f = fopen(path, "rb"); 131 | 132 | if(f != NULL) { 133 | match = ((fread(buf, 1, sizeof buf, f) == sizeof buf) && 134 | !memcmp(buf, CACHEDIR_TAG_SIGNATURE, sizeof buf)); 135 | fclose(f); 136 | } 137 | return match; 138 | } 139 | -------------------------------------------------------------------------------- /static/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script is based on static/build.sh from the ncdc git repo. 4 | # Only i486 and arm arches are supported. i486 should perform well enough, so 5 | # x86_64 isn't really necessary. I can't test any other arches. 6 | # 7 | # This script assumes that you have the musl-cross cross compilers installed in 8 | # $MUSL_CROSS_PATH. 9 | # 10 | # Usage: 11 | # ./build.sh $arch 12 | # where $arch = 'arm', 'i486' or 'x86_64' 13 | 14 | MUSL_CROSS_PATH=/opt/cross 15 | NCURSES_VERSION=6.0 16 | 17 | export CFLAGS="-O3 -g -static" 18 | 19 | # (The variables below are automatically set by the functions, they're defined 20 | # here to make sure they have global scope and for documentation purposes.) 21 | 22 | # This is the arch we're compiling for, e.g. arm/mipsel. 23 | TARGET= 24 | # This is the name of the toolchain we're using, and thus the value we should 25 | # pass to autoconf's --host argument. 26 | HOST= 27 | # Installation prefix. 28 | PREFIX= 29 | # Path of the extracted source code of the package we're currently building. 30 | srcdir= 31 | 32 | mkdir -p tarballs 33 | 34 | 35 | # "Fetch, Extract, Move" 36 | fem() { # base-url name targerdir extractdir 37 | echo "====== Fetching and extracting $1 $2" 38 | cd tarballs 39 | if [ -n "$4" ]; then 40 | EDIR="$4" 41 | else 42 | EDIR=$(basename $(basename $(basename $2 .tar.bz2) .tar.gz) .tar.xz) 43 | fi 44 | if [ ! -e "$2" ]; then 45 | wget "$1$2" || exit 46 | fi 47 | if [ ! -d "$3" ]; then 48 | tar -xvf "$2" || exit 49 | mv "$EDIR" "$3" 50 | fi 51 | cd .. 52 | } 53 | 54 | 55 | prebuild() { # dirname 56 | if [ -e "$TARGET/$1/_built" ]; then 57 | echo "====== Skipping build for $TARGET/$1 (assumed to be done)" 58 | return 1 59 | fi 60 | echo "====== Starting build for $TARGET/$1" 61 | rm -rf "$TARGET/$1" 62 | mkdir -p "$TARGET/$1" 63 | cd "$TARGET/$1" 64 | srcdir="../../tarballs/$1" 65 | return 0 66 | } 67 | 68 | 69 | postbuild() { 70 | touch _built 71 | cd ../.. 72 | } 73 | 74 | 75 | getncurses() { 76 | fem http://ftp.gnu.org/pub/gnu/ncurses/ ncurses-$NCURSES_VERSION.tar.gz ncurses 77 | prebuild ncurses || return 78 | $srcdir/configure --prefix=$PREFIX\ 79 | --without-cxx --without-cxx-binding --without-ada --without-manpages --without-progs\ 80 | --without-tests --without-curses-h --without-pkg-config --without-shared --without-debug\ 81 | --without-gpm --without-sysmouse --enable-widec --with-default-terminfo-dir=/usr/share/terminfo\ 82 | --with-terminfo-dirs=/usr/share/terminfo:/lib/terminfo:/usr/local/share/terminfo\ 83 | --with-fallbacks="screen linux vt100 xterm xterm-256color" --host=$HOST\ 84 | CPPFLAGS=-D_GNU_SOURCE || exit 85 | make || exit 86 | make install.libs || exit 87 | postbuild 88 | } 89 | 90 | 91 | getncdu() { 92 | prebuild ncdu || return 93 | srcdir=../../.. 94 | $srcdir/configure --host=$HOST --with-ncursesw PKG_CONFIG=false\ 95 | CPPFLAGS="-I$PREFIX/include -I$PREFIX/include/ncursesw"\ 96 | LDFLAGS="-static -L$PREFIX/lib -lncursesw" CFLAGS="$CFLAGS -Wall -Wextra" || exit 97 | make || exit 98 | 99 | VER=`cd '../../..' && git describe --abbrev=5 --dirty= | sed s/^v//` 100 | tar -czf ../../ncdu-linux-$TARGET-$VER-unstripped.tar.gz ncdu 101 | $HOST-strip ncdu 102 | tar -czf ../../ncdu-linux-$TARGET-$VER.tar.gz ncdu 103 | echo "====== ncdu-linux-$TARGET-$VER.tar.gz and -unstripped created." 104 | 105 | postbuild 106 | } 107 | 108 | 109 | buildarch() { 110 | TARGET=$1 111 | case $TARGET in 112 | arm) HOST=arm-linux-musleabi DIR=arm-linux-musleabi ;; 113 | aarch64)HOST=aarch64-linux-musl DIR=aarch64-linux-musl ;; 114 | i486) HOST=i486-linux-musl DIR=i486-linux-musl ;; 115 | x86_64) HOST=x86_64-linux-musl DIR=x86_64-linux-musl ;; 116 | *) echo "Unknown target: $TARGET" ;; 117 | esac 118 | PREFIX="`pwd`/$TARGET/inst" 119 | mkdir -p $TARGET $PREFIX 120 | ln -s lib $PREFIX/lib64 121 | 122 | OLDPATH="$PATH" 123 | export PATH="$PATH:$MUSL_CROSS_PATH/$DIR/bin" 124 | getncurses 125 | getncdu 126 | PATH="$OLDPATH" 127 | } 128 | 129 | 130 | buildarch $1 131 | -------------------------------------------------------------------------------- /src/global.h: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #ifndef _global_h 27 | #define _global_h 28 | 29 | #include "config.h" 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #ifdef HAVE_INTTYPES_H 38 | # include 39 | #endif 40 | #ifdef HAVE_STDINT_H 41 | # include 42 | #endif 43 | 44 | /* File Flags (struct dir -> flags) */ 45 | #define FF_DIR 0x01 46 | #define FF_FILE 0x02 47 | #define FF_ERR 0x04 /* error while reading this item */ 48 | #define FF_OTHFS 0x08 /* excluded because it was another filesystem */ 49 | #define FF_EXL 0x10 /* excluded using exclude patterns */ 50 | #define FF_SERR 0x20 /* error in subdirectory */ 51 | #define FF_HLNKC 0x40 /* hard link candidate (file with st_nlink > 1) */ 52 | #define FF_BSEL 0x80 /* selected */ 53 | #define FF_EXT 0x100 /* extended struct available */ 54 | #define FF_KERNFS 0x200 /* excluded because it was a Linux pseudo filesystem */ 55 | #define FF_FRMLNK 0x400 /* excluded because it was a firmlink */ 56 | 57 | /* Program states */ 58 | #define ST_CALC 0 59 | #define ST_BROWSE 1 60 | #define ST_DEL 2 61 | #define ST_HELP 3 62 | #define ST_SHELL 4 63 | #define ST_QUIT 5 64 | 65 | 66 | /* structure representing a file or directory */ 67 | struct dir { 68 | int64_t size, asize; 69 | uint64_t ino, dev; 70 | struct dir *parent, *next, *prev, *sub, *hlnk; 71 | int items; 72 | unsigned short flags; 73 | char name[]; 74 | }; 75 | 76 | /* A note on the ino and dev fields above: ino is usually represented as ino_t, 77 | * which POSIX specifies to be an unsigned integer. dev is usually represented 78 | * as dev_t, which may be either a signed or unsigned integer, and in practice 79 | * both are used. dev represents an index / identifier of a device or 80 | * filesystem, and I'm unsure whether a negative value has any meaning in that 81 | * context. Hence my choice of using an unsigned integer. Negative values, if 82 | * we encounter them, will just get typecasted into a positive value. No 83 | * information is lost in this conversion, and the semantics remain the same. 84 | */ 85 | 86 | /* Extended information for a struct dir. This struct is stored in the same 87 | * memory region as struct dir, placed after the name field. See util.h for 88 | * macros to help manage this. */ 89 | struct dir_ext { 90 | uint64_t mtime; 91 | int uid, gid; 92 | unsigned short mode; 93 | }; 94 | 95 | 96 | /* program state */ 97 | extern int pstate; 98 | /* read-only flag, 1+ = disable deletion, 2+ = also disable shell */ 99 | extern int read_only; 100 | /* minimum screen update interval when calculating, in ms */ 101 | extern long update_delay; 102 | /* filter directories with CACHEDIR.TAG */ 103 | extern int cachedir_tags; 104 | /* flag if we should ask for confirmation when quitting */ 105 | extern int confirm_quit; 106 | /* flag whether we want to enable use of struct dir_ext */ 107 | extern int extended_info; 108 | /* flag whether we want to follow symlinks */ 109 | extern int follow_symlinks; 110 | /* flag whether we want to follow firmlinks */ 111 | extern int follow_firmlinks; 112 | 113 | /* handle input from keyboard and update display */ 114 | int input_handle(int); 115 | 116 | /* de-initialize ncurses */ 117 | void close_nc(void); 118 | 119 | 120 | /* import all other global functions and variables */ 121 | #include "browser.h" 122 | #include "delete.h" 123 | #include "dir.h" 124 | #include "dirlist.h" 125 | #include "exclude.h" 126 | #include "help.h" 127 | #include "path.h" 128 | #include "util.h" 129 | #include "shell.h" 130 | #include "quit.h" 131 | 132 | #endif 133 | -------------------------------------------------------------------------------- /src/dir.h: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #ifndef _dir_h 27 | #define _dir_h 28 | 29 | /* The dir_* functions and files implement the SCAN state and are organized as 30 | * follows: 31 | * 32 | * Input: 33 | * Responsible for getting a directory structure into ncdu. Will call the 34 | * Output functions for data and the UI functions for feedback. Currently 35 | * there is only one input implementation: dir_scan.c 36 | * Output: 37 | * Called by the Input handling code when there's some new file/directory 38 | * information. The Output code is responsible for doing something with it 39 | * and determines what action should follow after the Input is done. 40 | * Currently there is only one output implementation: dir_mem.c. 41 | * Common: 42 | * Utility functions and UI code for use by the Input handling code to draw 43 | * progress/error information on the screen, handle any user input and misc. 44 | * stuff. 45 | */ 46 | 47 | 48 | /* "Interface" that Input code should call and Output code should implement. */ 49 | struct dir_output { 50 | /* Called when there is new file/dir info. Call stack for an example 51 | * directory structure: 52 | * / item('/') 53 | * /subdir item('subdir') 54 | * /subdir/f item('f') 55 | * .. item(NULL) 56 | * /abc item('abc') 57 | * .. item(NULL) 58 | * Every opened dir is followed by a call to NULL. There is only one top-level 59 | * dir item. The name of the top-level dir item is the absolute path to the 60 | * scanned directory. 61 | * 62 | * The *item struct has the following fields set when item() is called: 63 | * size, asize, ino, dev, flags (only DIR,FILE,ERR,OTHFS,EXL,HLNKC). 64 | * All other fields/flags should be initialized to NULL or 0. 65 | * The name and dir_ext fields are given separately. 66 | * All pointers may be overwritten or freed in subsequent calls, so this 67 | * function should make a copy if necessary. 68 | * 69 | * The function should return non-zero on error, at which point errno is 70 | * assumed to be set to something sensible. 71 | */ 72 | int (*item)(struct dir *, const char *, struct dir_ext *); 73 | 74 | /* Finalizes the output to go to the next program state or exit ncdu. Called 75 | * after item(NULL) has been called for the root item or before any item() 76 | * has been called at all. 77 | * Argument indicates success (0) or failure (1). 78 | * Failure happens when the root directory couldn't be opened, chdir, lstat, 79 | * read, when it is empty, or when the user aborted the operation. 80 | * Return value should be 0 to continue running ncdu, 1 to exit. 81 | */ 82 | int (*final)(int); 83 | 84 | /* The output code is responsible for updating these stats. Can be 0 when not 85 | * available. */ 86 | int64_t size; 87 | int items; 88 | }; 89 | 90 | 91 | /* Initializes the SCAN state and dir_output for immediate browsing. 92 | * On success: 93 | * If a dir item is given, overwrites it with the new dir struct. 94 | * Then calls browse_init(new_dir_struct->sub). 95 | * On failure: 96 | * If a dir item is given, will just call browse_init(orig). 97 | * Otherwise, will exit ncdu. 98 | */ 99 | void dir_mem_init(struct dir *); 100 | 101 | /* Initializes the SCAN state and dir_output for exporting to a file. */ 102 | int dir_export_init(const char *fn); 103 | 104 | 105 | /* Function set by input code. Returns dir_output.final(). */ 106 | extern int (*dir_process)(void); 107 | 108 | /* Scanning a live directory */ 109 | extern int dir_scan_smfs; 110 | void dir_scan_init(const char *path); 111 | 112 | /* Importing a file */ 113 | extern int dir_import_active; 114 | int dir_import_init(const char *fn); 115 | 116 | #if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS 117 | extern int exclude_kernfs; 118 | #endif 119 | 120 | 121 | /* The currently configured output functions. */ 122 | extern struct dir_output dir_output; 123 | 124 | /* Current path that we're working with. These are defined in dir_common.c. */ 125 | extern char *dir_curpath; 126 | void dir_curpath_set(const char *); 127 | void dir_curpath_enter(const char *); 128 | void dir_curpath_leave(void); 129 | 130 | /* Sets the path where the last error occurred, or reset on NULL. */ 131 | void dir_setlasterr(const char *); 132 | 133 | /* Error message on fatal error, or NULL if there hasn't been a fatal error yet. */ 134 | extern char *dir_fatalerr; 135 | void dir_seterr(const char *, ...); 136 | 137 | extern int dir_ui; 138 | int dir_key(int); 139 | void dir_draw(void); 140 | 141 | #endif 142 | -------------------------------------------------------------------------------- /deps/yopt.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012-2013 Yoran Heling 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | /* This is a simple command-line option parser. Operation is similar to 24 | * getopt_long(), except with a cleaner API. 25 | * 26 | * This is implemented in a single header file, as it's pretty small and you 27 | * generally only use an option parser in a single .c file in your program. 28 | * 29 | * Supports (examples from GNU tar(1)): 30 | * "--gzip" 31 | * "--file " 32 | * "--file=" 33 | * "-z" 34 | * "-f " 35 | * "-f" 36 | * "-zf " 37 | * "-zf" 38 | * "--" (To stop looking for further options) 39 | * "" (Non-option arguments) 40 | * 41 | * Issues/non-features: 42 | * - An option either requires an argument or it doesn't. 43 | * - No way to specify how often an option can/should be used. 44 | * - No way to specify the type of an argument (filename/integer/enum/whatever) 45 | */ 46 | 47 | 48 | #ifndef YOPT_H 49 | #define YOPT_H 50 | 51 | 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | 58 | typedef struct { 59 | /* Value yopt_next() will return for this option */ 60 | int val; 61 | /* Whether this option needs an argument */ 62 | int needarg; 63 | /* Name(s) of this option, prefixed with '-' or '--' and separated by a 64 | * comma. E.g. "-z", "--gzip", "-z,--gzip". 65 | * An option can have any number of aliases. 66 | */ 67 | const char *name; 68 | } yopt_opt_t; 69 | 70 | 71 | typedef struct { 72 | int argc; 73 | int cur; 74 | int argsep; /* '--' found */ 75 | char **argv; 76 | char *sh; 77 | const yopt_opt_t *opts; 78 | char errbuf[128]; 79 | } yopt_t; 80 | 81 | 82 | /* opts must be an array of options, terminated with an option with val=0 */ 83 | static inline void yopt_init(yopt_t *o, int argc, char **argv, const yopt_opt_t *opts) { 84 | o->argc = argc; 85 | o->argv = argv; 86 | o->opts = opts; 87 | o->cur = 0; 88 | o->argsep = 0; 89 | o->sh = NULL; 90 | } 91 | 92 | 93 | static inline const yopt_opt_t *_yopt_find(const yopt_opt_t *o, const char *v) { 94 | const char *tn, *tv; 95 | 96 | for(; o->val; o++) { 97 | tn = o->name; 98 | while(*tn) { 99 | tv = v; 100 | while(*tn && *tn != ',' && *tv && *tv != '=' && *tn == *tv) { 101 | tn++; 102 | tv++; 103 | } 104 | if(!(*tn && *tn != ',') && !(*tv && *tv != '=')) 105 | return o; 106 | while(*tn && *tn != ',') 107 | tn++; 108 | while(*tn == ',') 109 | tn++; 110 | } 111 | } 112 | 113 | return NULL; 114 | } 115 | 116 | 117 | static inline int _yopt_err(yopt_t *o, char **val, const char *fmt, ...) { 118 | va_list va; 119 | va_start(va, fmt); 120 | vsnprintf(o->errbuf, sizeof(o->errbuf), fmt, va); 121 | va_end(va); 122 | *val = o->errbuf; 123 | return -2; 124 | } 125 | 126 | 127 | /* Return values: 128 | * 0 -> Non-option argument, val is its value 129 | * -1 -> Last argument has been processed 130 | * -2 -> Error, val will contain the error message. 131 | * x -> Option with val = x found. If the option requires an argument, its 132 | * value will be in val. 133 | */ 134 | static inline int yopt_next(yopt_t *o, char **val) { 135 | const yopt_opt_t *opt; 136 | char sh[3]; 137 | 138 | *val = NULL; 139 | if(o->sh) 140 | goto inshort; 141 | 142 | if(++o->cur >= o->argc) 143 | return -1; 144 | 145 | if(!o->argsep && o->argv[o->cur][0] == '-' && o->argv[o->cur][1] == '-' && o->argv[o->cur][2] == 0) { 146 | o->argsep = 1; 147 | if(++o->cur >= o->argc) 148 | return -1; 149 | } 150 | 151 | if(o->argsep || *o->argv[o->cur] != '-') { 152 | *val = o->argv[o->cur]; 153 | return 0; 154 | } 155 | 156 | if(o->argv[o->cur][1] != '-') { 157 | o->sh = o->argv[o->cur]+1; 158 | goto inshort; 159 | } 160 | 161 | /* Now we're supposed to have a long option */ 162 | if(!(opt = _yopt_find(o->opts, o->argv[o->cur]))) 163 | return _yopt_err(o, val, "Unknown option '%s'", o->argv[o->cur]); 164 | if((*val = strchr(o->argv[o->cur], '=')) != NULL) 165 | (*val)++; 166 | if(!opt->needarg && *val) 167 | return _yopt_err(o, val, "Option '%s' does not accept an argument", o->argv[o->cur]); 168 | if(opt->needarg && !*val) { 169 | if(o->cur+1 >= o->argc) 170 | return _yopt_err(o, val, "Option '%s' requires an argument", o->argv[o->cur]); 171 | *val = o->argv[++o->cur]; 172 | } 173 | return opt->val; 174 | 175 | /* And here we're supposed to have a short option */ 176 | inshort: 177 | sh[0] = '-'; 178 | sh[1] = *o->sh; 179 | sh[2] = 0; 180 | if(!(opt = _yopt_find(o->opts, sh))) 181 | return _yopt_err(o, val, "Unknown option '%s'", sh); 182 | o->sh++; 183 | if(opt->needarg && *o->sh) 184 | *val = o->sh; 185 | else if(opt->needarg) { 186 | if(++o->cur >= o->argc) 187 | return _yopt_err(o, val, "Option '%s' requires an argument", sh); 188 | *val = o->argv[o->cur]; 189 | } 190 | if(!*o->sh || opt->needarg) 191 | o->sh = NULL; 192 | return opt->val; 193 | } 194 | 195 | 196 | #endif 197 | 198 | /* vim: set noet sw=4 ts=4: */ 199 | -------------------------------------------------------------------------------- /src/dir_export.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include "global.h" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | 34 | static FILE *stream; 35 | 36 | /* Stack of device IDs, also used to keep track of the level of nesting */ 37 | static struct stack { 38 | uint64_t *list; 39 | int size, top; 40 | } stack; 41 | 42 | 43 | static void output_string(const char *str) { 44 | for(; *str; str++) { 45 | switch(*str) { 46 | case '\n': fputs("\\n", stream); break; 47 | case '\r': fputs("\\r", stream); break; 48 | case '\b': fputs("\\b", stream); break; 49 | case '\t': fputs("\\t", stream); break; 50 | case '\f': fputs("\\f", stream); break; 51 | case '\\': fputs("\\\\", stream); break; 52 | case '"': fputs("\\\"", stream); break; 53 | default: 54 | if((unsigned char)*str <= 31 || (unsigned char)*str == 127) 55 | fprintf(stream, "\\u00%02x", *str); 56 | else 57 | fputc(*str, stream); 58 | break; 59 | } 60 | } 61 | } 62 | 63 | 64 | static void output_int(uint64_t n) { 65 | char tmp[20]; 66 | int i = 0; 67 | 68 | do 69 | tmp[i++] = n % 10; 70 | while((n /= 10) > 0); 71 | 72 | while(i--) 73 | fputc(tmp[i]+'0', stream); 74 | } 75 | 76 | 77 | static void output_info(struct dir *d, const char *name, struct dir_ext *e) { 78 | if(!extended_info || !(d->flags & FF_EXT)) 79 | e = NULL; 80 | 81 | fputs("{\"name\":\"", stream); 82 | output_string(name); 83 | fputc('"', stream); 84 | 85 | /* No need for asize/dsize if they're 0 (which happens with excluded or failed-to-stat files) */ 86 | if(d->asize) { 87 | fputs(",\"asize\":", stream); 88 | output_int((uint64_t)d->asize); 89 | } 90 | if(d->size) { 91 | fputs(",\"dsize\":", stream); 92 | output_int((uint64_t)d->size); 93 | } 94 | 95 | if(d->dev != nstack_top(&stack, 0)) { 96 | fputs(",\"dev\":", stream); 97 | output_int(d->dev); 98 | } 99 | fputs(",\"ino\":", stream); 100 | output_int(d->ino); 101 | 102 | if(e) { 103 | fputs(",\"uid\":", stream); 104 | output_int(e->uid); 105 | fputs(",\"gid\":", stream); 106 | output_int(e->gid); 107 | fputs(",\"mode\":", stream); 108 | output_int(e->mode); 109 | fputs(",\"mtime\":", stream); 110 | output_int(e->mtime); 111 | } 112 | 113 | /* TODO: Including the actual number of links would be nicer. */ 114 | if(d->flags & FF_HLNKC) 115 | fputs(",\"hlnkc\":true", stream); 116 | if(d->flags & FF_ERR) 117 | fputs(",\"read_error\":true", stream); 118 | /* excluded/error'd files are "unknown" with respect to the "notreg" field. */ 119 | if(!(d->flags & (FF_DIR|FF_FILE|FF_ERR|FF_EXL|FF_OTHFS|FF_KERNFS|FF_FRMLNK))) 120 | fputs(",\"notreg\":true", stream); 121 | if(d->flags & FF_EXL) 122 | fputs(",\"excluded\":\"pattern\"", stream); 123 | else if(d->flags & FF_OTHFS) 124 | fputs(",\"excluded\":\"othfs\"", stream); 125 | else if(d->flags & FF_KERNFS) 126 | fputs(",\"excluded\":\"kernfs\"", stream); 127 | else if(d->flags & FF_FRMLNK) 128 | fputs(",\"excluded\":\"frmlnk\"", stream); 129 | 130 | fputc('}', stream); 131 | } 132 | 133 | 134 | /* Note on error handling: For convenience, we just keep writing to *stream 135 | * without checking the return values of the functions. Only at the and of each 136 | * item() call do we check for ferror(). This greatly simplifies the code, but 137 | * assumes that calls to fwrite()/fput./etc don't do any weird stuff when 138 | * called with a stream that's in an error state. */ 139 | static int item(struct dir *item, const char *name, struct dir_ext *ext) { 140 | if(!item) { 141 | nstack_pop(&stack); 142 | if(!stack.top) { /* closing of the root item */ 143 | fputs("]]", stream); 144 | return fclose(stream); 145 | } else /* closing of a regular directory item */ 146 | fputs("]", stream); 147 | return ferror(stream); 148 | } 149 | 150 | dir_output.items++; 151 | 152 | /* File header. 153 | * TODO: Add scan options? */ 154 | if(!stack.top) { 155 | fputs("[1,1,{\"progname\":\""PACKAGE"\",\"progver\":\""PACKAGE_VERSION"\",\"timestamp\":", stream); 156 | output_int((uint64_t)time(NULL)); 157 | fputc('}', stream); 158 | } 159 | 160 | fputs(",\n", stream); 161 | if(item->flags & FF_DIR) 162 | fputc('[', stream); 163 | 164 | output_info(item, name, ext); 165 | 166 | if(item->flags & FF_DIR) 167 | nstack_push(&stack, item->dev); 168 | 169 | return ferror(stream); 170 | } 171 | 172 | 173 | static int final(int fail) { 174 | nstack_free(&stack); 175 | return fail ? 1 : 1; /* Silences -Wunused-parameter */ 176 | } 177 | 178 | 179 | int dir_export_init(const char *fn) { 180 | if(strcmp(fn, "-") == 0) 181 | stream = stdout; 182 | else if((stream = fopen(fn, "w")) == NULL) 183 | return 1; 184 | 185 | nstack_init(&stack); 186 | 187 | pstate = ST_CALC; 188 | dir_output.item = item; 189 | dir_output.final = final; 190 | dir_output.size = 0; 191 | dir_output.items = 0; 192 | return 0; 193 | } 194 | 195 | -------------------------------------------------------------------------------- /src/path.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include "global.h" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #ifndef LINK_MAX 36 | # ifdef _POSIX_LINK_MAX 37 | # define LINK_MAX _POSIX_LINK_MAX 38 | # else 39 | # define LINK_MAX 32 40 | # endif 41 | #endif 42 | 43 | 44 | #define RPATH_CNKSZ 256 45 | 46 | 47 | /* splits a path into components and does a bit of cannonicalization. 48 | a pointer to a reversed array of components is stored in res and the 49 | number of components is returned. 50 | cur is modified, and res has to be free()d after use */ 51 | static int path_split(char *cur, char ***res) { 52 | char **old; 53 | int i, j, n; 54 | 55 | cur += strspn(cur, "/"); 56 | n = strlen(cur); 57 | 58 | /* replace slashes with zeros */ 59 | for(i=j=0; i=0; ) { 75 | if(!strcmp(old[j], "..")) { 76 | n++; 77 | continue; 78 | } 79 | if(!strcmp(old[j], ".")) 80 | continue; 81 | if(n) { 82 | n--; 83 | continue; 84 | } 85 | (*res)[i++] = old[j]; 86 | } 87 | free(old); 88 | return i; 89 | } 90 | 91 | 92 | /* copies path and prepends cwd if needed, to ensure an absolute path 93 | return value has to be free()'d manually */ 94 | static char *path_absolute(const char *path) { 95 | int i, n; 96 | char *ret; 97 | 98 | /* not an absolute path? prepend cwd */ 99 | if(path[0] != '/') { 100 | n = RPATH_CNKSZ; 101 | ret = xmalloc(n); 102 | errno = 0; 103 | while(!getcwd(ret, n) && errno == ERANGE) { 104 | n += RPATH_CNKSZ; 105 | ret = xrealloc(ret, n); 106 | errno = 0; 107 | } 108 | if(errno) { 109 | free(ret); 110 | return NULL; 111 | } 112 | 113 | i = strlen(path) + strlen(ret) + 2; 114 | if(i > n) 115 | ret = xrealloc(ret, i); 116 | strcat(ret, "/"); 117 | strcat(ret, path); 118 | /* otherwise, just make a copy */ 119 | } else { 120 | ret = xmalloc(strlen(path)+1); 121 | strcpy(ret, path); 122 | } 123 | return ret; 124 | } 125 | 126 | 127 | /* NOTE: cwd and the memory cur points to are unreliable after calling this 128 | * function. 129 | * TODO: This code is rather fragile and inefficient. A rewrite is in order. 130 | */ 131 | static char *path_real_rec(char *cur, int *links) { 132 | int i, n, tmpl, lnkl = 0; 133 | char **arr, *tmp, *lnk = NULL, *ret = NULL; 134 | 135 | tmpl = strlen(cur)+1; 136 | tmp = xmalloc(tmpl); 137 | 138 | /* split path */ 139 | i = path_split(cur, &arr); 140 | 141 | /* re-create full path */ 142 | strcpy(tmp, "/"); 143 | if(i > 0) { 144 | lnkl = RPATH_CNKSZ; 145 | lnk = xmalloc(lnkl); 146 | if(chdir("/") < 0) 147 | goto path_real_done; 148 | } 149 | 150 | while(--i>=0) { 151 | if(arr[i][0] == 0) 152 | continue; 153 | /* check for symlink */ 154 | while((n = readlink(arr[i], lnk, lnkl)) == lnkl || (n < 0 && errno == ERANGE)) { 155 | lnkl += RPATH_CNKSZ; 156 | lnk = xrealloc(lnk, lnkl); 157 | } 158 | if(n < 0 && errno != EINVAL) 159 | goto path_real_done; 160 | if(n > 0) { 161 | if(++*links > LINK_MAX) { 162 | errno = ELOOP; 163 | goto path_real_done; 164 | } 165 | lnk[n++] = 0; 166 | /* create new path */ 167 | if(lnk[0] != '/') 168 | n += strlen(tmp); 169 | if(tmpl < n) { 170 | tmpl = n; 171 | tmp = xrealloc(tmp, tmpl); 172 | } 173 | if(lnk[0] != '/') 174 | strcat(tmp, lnk); 175 | else 176 | strcpy(tmp, lnk); 177 | /* append remaining directories */ 178 | while(--i>=0) { 179 | n += strlen(arr[i])+1; 180 | if(tmpl < n) { 181 | tmpl = n; 182 | tmp = xrealloc(tmp, tmpl); 183 | } 184 | strcat(tmp, "/"); 185 | strcat(tmp, arr[i]); 186 | } 187 | /* call path_real_rec() with the new path */ 188 | ret = path_real_rec(tmp, links); 189 | goto path_real_done; 190 | } 191 | /* not a symlink, append component and go to the next part */ 192 | strcat(tmp, arr[i]); 193 | if(i) { 194 | if(chdir(arr[i]) < 0) 195 | goto path_real_done; 196 | strcat(tmp, "/"); 197 | } 198 | } 199 | ret = tmp; 200 | 201 | path_real_done: 202 | if(ret != tmp) 203 | free(tmp); 204 | if(lnkl > 0) 205 | free(lnk); 206 | free(arr); 207 | return ret; 208 | } 209 | 210 | 211 | char *path_real(const char *orig) { 212 | int links = 0; 213 | char *tmp, *ret; 214 | 215 | if(orig == NULL) 216 | return NULL; 217 | 218 | if((tmp = path_absolute(orig)) == NULL) 219 | return NULL; 220 | ret = path_real_rec(tmp, &links); 221 | free(tmp); 222 | return ret; 223 | } 224 | 225 | 226 | int path_chdir(const char *path) { 227 | char **arr, *cur; 228 | int i, r = -1; 229 | 230 | if((cur = path_absolute(path)) == NULL) 231 | return -1; 232 | 233 | i = path_split(cur, &arr); 234 | if(chdir("/") < 0) 235 | goto path_chdir_done; 236 | while(--i >= 0) 237 | if(chdir(arr[i]) < 0) 238 | goto path_chdir_done; 239 | r = 0; 240 | 241 | path_chdir_done: 242 | free(cur); 243 | free(arr); 244 | return r; 245 | } 246 | 247 | -------------------------------------------------------------------------------- /src/delete.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | 27 | #include "global.h" 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | 34 | #define DS_CONFIRM 0 35 | #define DS_PROGRESS 1 36 | #define DS_FAILED 2 37 | 38 | 39 | static struct dir *root, *nextsel, *curdir; 40 | static char noconfirm = 0, ignoreerr = 0, state; 41 | static signed char seloption; 42 | static int lasterrno; 43 | 44 | 45 | static void delete_draw_confirm(void) { 46 | nccreate(6, 60, "Confirm delete"); 47 | 48 | ncprint(1, 2, "Are you sure you want to delete \"%s\"%c", 49 | cropstr(root->name, 21), root->flags & FF_DIR ? ' ' : '?'); 50 | if(root->flags & FF_DIR && root->sub != NULL) 51 | ncprint(2, 18, "and all of its contents?"); 52 | 53 | if(seloption == 0) 54 | attron(A_REVERSE); 55 | ncaddstr(4, 15, "yes"); 56 | attroff(A_REVERSE); 57 | if(seloption == 1) 58 | attron(A_REVERSE); 59 | ncaddstr(4, 24, "no"); 60 | attroff(A_REVERSE); 61 | if(seloption == 2) 62 | attron(A_REVERSE); 63 | ncaddstr(4, 31, "don't ask me again"); 64 | attroff(A_REVERSE); 65 | 66 | ncmove(4, seloption == 0 ? 15 : seloption == 1 ? 24 : 31); 67 | } 68 | 69 | 70 | static void delete_draw_progress(void) { 71 | nccreate(6, 60, "Deleting..."); 72 | 73 | ncaddstr(1, 2, cropstr(getpath(curdir), 47)); 74 | ncaddstr(4, 41, "Press "); 75 | addchc(UIC_KEY, 'q'); 76 | addstrc(UIC_DEFAULT, " to abort"); 77 | } 78 | 79 | 80 | static void delete_draw_error(void) { 81 | nccreate(6, 60, "Error!"); 82 | 83 | ncprint(1, 2, "Can't delete %s:", cropstr(getpath(curdir), 42)); 84 | ncaddstr(2, 4, strerror(lasterrno)); 85 | 86 | if(seloption == 0) 87 | attron(A_REVERSE); 88 | ncaddstr(4, 14, "abort"); 89 | attroff(A_REVERSE); 90 | if(seloption == 1) 91 | attron(A_REVERSE); 92 | ncaddstr(4, 23, "ignore"); 93 | attroff(A_REVERSE); 94 | if(seloption == 2) 95 | attron(A_REVERSE); 96 | ncaddstr(4, 33, "ignore all"); 97 | attroff(A_REVERSE); 98 | } 99 | 100 | 101 | void delete_draw() { 102 | browse_draw(); 103 | switch(state) { 104 | case DS_CONFIRM: delete_draw_confirm(); break; 105 | case DS_PROGRESS: delete_draw_progress(); break; 106 | case DS_FAILED: delete_draw_error(); break; 107 | } 108 | } 109 | 110 | 111 | int delete_key(int ch) { 112 | /* confirm */ 113 | if(state == DS_CONFIRM) 114 | switch(ch) { 115 | case KEY_LEFT: 116 | case 'h': 117 | if(--seloption < 0) 118 | seloption = 0; 119 | break; 120 | case KEY_RIGHT: 121 | case 'l': 122 | if(++seloption > 2) 123 | seloption = 2; 124 | break; 125 | case '\n': 126 | if(seloption == 1) 127 | return 1; 128 | if(seloption == 2) 129 | noconfirm++; 130 | state = DS_PROGRESS; 131 | break; 132 | case 'q': 133 | return 1; 134 | } 135 | /* processing deletion */ 136 | else if(state == DS_PROGRESS) 137 | switch(ch) { 138 | case 'q': 139 | return 1; 140 | } 141 | /* error */ 142 | else if(state == DS_FAILED) 143 | switch(ch) { 144 | case KEY_LEFT: 145 | case 'h': 146 | if(--seloption < 0) 147 | seloption = 0; 148 | break; 149 | case KEY_RIGHT: 150 | case 'l': 151 | if(++seloption > 2) 152 | seloption = 2; 153 | break; 154 | case 10: 155 | if(seloption == 0) 156 | return 1; 157 | if(seloption == 2) 158 | ignoreerr++; 159 | state = DS_PROGRESS; 160 | break; 161 | case 'q': 162 | return 1; 163 | } 164 | 165 | return 0; 166 | } 167 | 168 | 169 | static int delete_dir(struct dir *dr) { 170 | struct dir *nxt, *cur; 171 | int r; 172 | 173 | /* check for input or screen resizes */ 174 | curdir = dr; 175 | if(input_handle(1)) 176 | return 1; 177 | 178 | /* do the actual deleting */ 179 | if(dr->flags & FF_DIR) { 180 | if((r = chdir(dr->name)) < 0) 181 | goto delete_nxt; 182 | if(dr->sub != NULL) { 183 | nxt = dr->sub; 184 | while(nxt != NULL) { 185 | cur = nxt; 186 | nxt = cur->next; 187 | if(delete_dir(cur)) 188 | return 1; 189 | } 190 | } 191 | if((r = chdir("..")) < 0) 192 | goto delete_nxt; 193 | r = dr->sub == NULL ? rmdir(dr->name) : 0; 194 | } else 195 | r = unlink(dr->name); 196 | 197 | delete_nxt: 198 | /* error occurred, ask user what to do */ 199 | if(r == -1 && !ignoreerr) { 200 | state = DS_FAILED; 201 | lasterrno = errno; 202 | curdir = dr; 203 | while(state == DS_FAILED) 204 | if(input_handle(0)) 205 | return 1; 206 | } else if(!(dr->flags & FF_DIR && dr->sub != NULL)) { 207 | freedir(dr); 208 | return 0; 209 | } 210 | return root == dr ? 1 : 0; 211 | } 212 | 213 | 214 | void delete_process() { 215 | struct dir *par; 216 | 217 | /* confirm */ 218 | seloption = 1; 219 | while(state == DS_CONFIRM && !noconfirm) 220 | if(input_handle(0)) { 221 | browse_init(root->parent); 222 | return; 223 | } 224 | 225 | /* chdir */ 226 | if(path_chdir(getpath(root->parent)) < 0) { 227 | state = DS_FAILED; 228 | lasterrno = errno; 229 | while(state == DS_FAILED) 230 | if(input_handle(0)) 231 | return; 232 | } 233 | 234 | /* delete */ 235 | seloption = 0; 236 | state = DS_PROGRESS; 237 | par = root->parent; 238 | delete_dir(root); 239 | if(nextsel) 240 | nextsel->flags |= FF_BSEL; 241 | browse_init(par); 242 | if(nextsel) 243 | dirlist_top(-4); 244 | } 245 | 246 | 247 | void delete_init(struct dir *dr, struct dir *s) { 248 | state = DS_CONFIRM; 249 | root = curdir = dr; 250 | pstate = ST_DEL; 251 | nextsel = s; 252 | } 253 | 254 | -------------------------------------------------------------------------------- /src/help.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include "global.h" 27 | 28 | #include 29 | #include 30 | 31 | 32 | static int page, start; 33 | 34 | 35 | #define KEYS 19 36 | static const char *keys[KEYS*2] = { 37 | /*|----key----| |----------------description----------------|*/ 38 | "up, k", "Move cursor up", 39 | "down, j", "Move cursor down", 40 | "right/enter", "Open selected directory", 41 | "left, <, h", "Open parent directory", 42 | "n", "Sort by name (ascending/descending)", 43 | "s", "Sort by size (ascending/descending)", 44 | "C", "Sort by items (ascending/descending)", 45 | "M", "Sort by mtime (-e flag)", 46 | "d", "Delete selected file or directory", 47 | "t", "Toggle dirs before files when sorting", 48 | "g", "Show percentage and/or graph", 49 | "a", "Toggle between apparent size and disk usage", 50 | "c", "Toggle display of child item counts", 51 | "m", "Toggle display of latest mtime (-e flag)", 52 | "e", "Show/hide hidden or excluded files", 53 | "i", "Show information about selected item", 54 | "r", "Recalculate the current directory", 55 | "b", "Spawn shell in current directory", 56 | "q", "Quit ncdu" 57 | }; 58 | 59 | 60 | #define FLAGS 9 61 | static const char *flags[FLAGS*2] = { 62 | "!", "An error occurred while reading this directory", 63 | ".", "An error occurred while reading a subdirectory", 64 | "<", "File or directory is excluded from the statistics", 65 | "e", "Empty directory", 66 | ">", "Directory was on another filesystem", 67 | "@", "This is not a file nor a dir (symlink, socket, ...)", 68 | "^", "Excluded Linux pseudo-filesystem", 69 | "H", "Same file was already counted (hard link)", 70 | "F", "Excluded firmlink", 71 | }; 72 | 73 | void help_draw() { 74 | int i, line; 75 | 76 | browse_draw(); 77 | 78 | nccreate(15, 60, "ncdu help"); 79 | ncaddstr(13, 42, "Press "); 80 | uic_set(UIC_KEY); 81 | addch('q'); 82 | uic_set(UIC_DEFAULT); 83 | addstr(" to close"); 84 | 85 | nctab(30, page == 1, 1, "Keys"); 86 | nctab(39, page == 2, 2, "Format"); 87 | nctab(50, page == 3, 3, "About"); 88 | 89 | switch(page) { 90 | case 1: 91 | line = 1; 92 | for(i=start*2; i"); 160 | ncaddstr(10, 16, "https://dev.yorhel.nl/ncdu/"); 161 | break; 162 | } 163 | } 164 | 165 | 166 | int help_key(int ch) { 167 | switch(ch) { 168 | case '1': 169 | case '2': 170 | case '3': 171 | page = ch-'0'; 172 | start = 0; 173 | break; 174 | case KEY_RIGHT: 175 | case KEY_NPAGE: 176 | case 'l': 177 | if(++page > 3) 178 | page = 3; 179 | start = 0; 180 | break; 181 | case KEY_LEFT: 182 | case KEY_PPAGE: 183 | case 'h': 184 | if(--page < 1) 185 | page = 1; 186 | start = 0; 187 | break; 188 | case KEY_DOWN: 189 | case ' ': 190 | case 'j': 191 | if((page == 1 && start < KEYS-10) || (page == 2 && start < FLAGS-7)) 192 | start++; 193 | break; 194 | case KEY_UP: 195 | case 'k': 196 | if(start > 0) 197 | start--; 198 | break; 199 | default: 200 | pstate = ST_BROWSE; 201 | } 202 | return 0; 203 | } 204 | 205 | 206 | void help_init() { 207 | page = 1; 208 | start = 0; 209 | pstate = ST_HELP; 210 | } 211 | 212 | 213 | -------------------------------------------------------------------------------- /src/dir_mem.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include "global.h" 27 | 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | 34 | static struct dir *root; /* root directory struct we're scanning */ 35 | static struct dir *curdir; /* directory item that we're currently adding items to */ 36 | static struct dir *orig; /* original directory, when refreshing an already scanned dir */ 37 | 38 | /* Table of struct dir items with more than one link (in order to detect hard links) */ 39 | #define hlink_hash(d) (kh_hash_uint64((khint64_t)d->dev) ^ kh_hash_uint64((khint64_t)d->ino)) 40 | #define hlink_equal(a, b) ((a)->dev == (b)->dev && (a)->ino == (b)->ino) 41 | KHASHL_SET_INIT(KH_LOCAL, hl_t, hl, struct dir *, hlink_hash, hlink_equal) 42 | static hl_t *links = NULL; 43 | 44 | 45 | /* recursively checks a dir structure for hard links and fills the lookup array */ 46 | static void hlink_init(struct dir *d) { 47 | struct dir *t; 48 | 49 | for(t=d->sub; t!=NULL; t=t->next) 50 | hlink_init(t); 51 | 52 | if(!(d->flags & FF_HLNKC)) 53 | return; 54 | int r; 55 | hl_put(links, d, &r); 56 | } 57 | 58 | 59 | /* checks an individual file for hard links and updates its cicrular linked 60 | * list, also updates the sizes of the parent dirs */ 61 | static void hlink_check(struct dir *d) { 62 | struct dir *t, *pt, *par; 63 | int i; 64 | 65 | /* add to links table */ 66 | khint_t k = hl_put(links, d, &i); 67 | 68 | /* found in the table? update hlnk */ 69 | if(!i) { 70 | t = kh_key(links, k); 71 | d->hlnk = t->hlnk == NULL ? t : t->hlnk; 72 | t->hlnk = d; 73 | } 74 | 75 | /* now update the sizes of the parent directories, 76 | * This works by only counting this file in the parent directories where this 77 | * file hasn't been counted yet, which can be determined from the hlnk list. 78 | * XXX: This may not be the most efficient algorithm to do this */ 79 | for(i=1,par=d->parent; i&∥ par=par->parent) { 80 | if(d->hlnk) 81 | for(t=d->hlnk; i&&t!=d; t=t->hlnk) 82 | for(pt=t->parent; i&&pt; pt=pt->parent) 83 | if(pt==par) 84 | i=0; 85 | if(i) { 86 | par->size = adds64(par->size, d->size); 87 | par->asize = adds64(par->asize, d->asize); 88 | } 89 | } 90 | } 91 | 92 | 93 | /* Add item to the correct place in the memory structure */ 94 | static void item_add(struct dir *item) { 95 | if(!root) { 96 | root = item; 97 | /* Make sure that the *root appears to be part of the same dir structure as 98 | * *orig, otherwise the directory size calculation will be incorrect in the 99 | * case of hard links. */ 100 | if(orig) 101 | root->parent = orig->parent; 102 | } else { 103 | item->parent = curdir; 104 | item->next = curdir->sub; 105 | if(item->next) 106 | item->next->prev = item; 107 | curdir->sub = item; 108 | } 109 | } 110 | 111 | 112 | static int item(struct dir *dir, const char *name, struct dir_ext *ext) { 113 | struct dir *t, *item; 114 | 115 | /* Go back to parent dir */ 116 | if(!dir) { 117 | curdir = curdir->parent; 118 | return 0; 119 | } 120 | 121 | if(!root && orig) 122 | name = orig->name; 123 | 124 | if(!extended_info) 125 | dir->flags &= ~FF_EXT; 126 | item = xmalloc(dir->flags & FF_EXT ? dir_ext_memsize(name) : dir_memsize(name)); 127 | memcpy(item, dir, offsetof(struct dir, name)); 128 | strcpy(item->name, name); 129 | if(dir->flags & FF_EXT) 130 | memcpy(dir_ext_ptr(item), ext, sizeof(struct dir_ext)); 131 | 132 | item_add(item); 133 | 134 | /* Ensure that any next items will go to this directory */ 135 | if(item->flags & FF_DIR) 136 | curdir = item; 137 | 138 | /* Special-case the name of the root item to be empty instead of "/". This is 139 | * what getpath() expects. */ 140 | if(item == root && strcmp(item->name, "/") == 0) 141 | item->name[0] = 0; 142 | 143 | /* Update stats of parents. Don't update the size/asize fields if this is a 144 | * possible hard link, because hlnk_check() will take care of it in that 145 | * case. */ 146 | if(item->flags & FF_HLNKC) { 147 | addparentstats(item->parent, 0, 0, 0, 1); 148 | hlink_check(item); 149 | } else if(item->flags & FF_EXT) { 150 | addparentstats(item->parent, item->size, item->asize, dir_ext_ptr(item)->mtime, 1); 151 | } else { 152 | addparentstats(item->parent, item->size, item->asize, 0, 1); 153 | } 154 | 155 | /* propagate ERR and SERR back up to the root */ 156 | if(item->flags & FF_SERR || item->flags & FF_ERR) 157 | for(t=item->parent; t; t=t->parent) 158 | t->flags |= FF_SERR; 159 | 160 | dir_output.size = root->size; 161 | dir_output.items = root->items; 162 | 163 | return 0; 164 | } 165 | 166 | 167 | static int final(int fail) { 168 | hl_destroy(links); 169 | links = NULL; 170 | 171 | if(fail) { 172 | freedir(root); 173 | if(orig) { 174 | browse_init(orig); 175 | return 0; 176 | } else 177 | return 1; 178 | } 179 | 180 | /* success, update references and free original item */ 181 | if(orig) { 182 | root->next = orig->next; 183 | root->prev = orig->prev; 184 | if(root->parent && root->parent->sub == orig) 185 | root->parent->sub = root; 186 | if(root->prev) 187 | root->prev->next = root; 188 | if(root->next) 189 | root->next->prev = root; 190 | orig->next = orig->prev = NULL; 191 | freedir(orig); 192 | } 193 | 194 | browse_init(root); 195 | dirlist_top(-3); 196 | return 0; 197 | } 198 | 199 | 200 | void dir_mem_init(struct dir *_orig) { 201 | orig = _orig; 202 | root = curdir = NULL; 203 | pstate = ST_CALC; 204 | 205 | dir_output.item = item; 206 | dir_output.final = final; 207 | dir_output.size = 0; 208 | dir_output.items = 0; 209 | 210 | /* Init hash table for hard link detection */ 211 | links = hl_init(); 212 | if(orig) 213 | hlink_init(getroot(orig)); 214 | } 215 | 216 | -------------------------------------------------------------------------------- /src/dir_common.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include "global.h" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | 34 | int (*dir_process)(void); 35 | char *dir_curpath; /* Full path of the last seen item. */ 36 | struct dir_output dir_output; 37 | char *dir_fatalerr; /* Error message on a fatal error. (NULL if there was no fatal error) */ 38 | int dir_ui; /* User interface to use */ 39 | static int confirm_quit_while_scanning_stage_1_passed; /* Additional check before quitting */ 40 | static char *lasterr; /* Path where the last error occurred. */ 41 | static int curpathl; /* Allocated length of dir_curpath */ 42 | static int lasterrl; /* ^ of lasterr */ 43 | 44 | 45 | static void curpath_resize(int s) { 46 | if(curpathl < s) { 47 | curpathl = s < 128 ? 128 : s < curpathl*2 ? curpathl*2 : s; 48 | dir_curpath = xrealloc(dir_curpath, curpathl); 49 | } 50 | } 51 | 52 | 53 | void dir_curpath_set(const char *path) { 54 | curpath_resize(strlen(path)+1); 55 | strcpy(dir_curpath, path); 56 | } 57 | 58 | 59 | void dir_curpath_enter(const char *name) { 60 | curpath_resize(strlen(dir_curpath)+strlen(name)+2); 61 | if(dir_curpath[1]) 62 | strcat(dir_curpath, "/"); 63 | strcat(dir_curpath, name); 64 | } 65 | 66 | 67 | /* removes last component from dir_curpath */ 68 | void dir_curpath_leave() { 69 | char *tmp; 70 | if((tmp = strrchr(dir_curpath, '/')) == NULL) 71 | strcpy(dir_curpath, "/"); 72 | else if(tmp != dir_curpath) 73 | tmp[0] = 0; 74 | else 75 | tmp[1] = 0; 76 | } 77 | 78 | 79 | void dir_setlasterr(const char *path) { 80 | if(!path) { 81 | free(lasterr); 82 | lasterr = NULL; 83 | lasterrl = 0; 84 | return; 85 | } 86 | int req = strlen(path)+1; 87 | if(lasterrl < req) { 88 | lasterrl = req; 89 | lasterr = xrealloc(lasterr, lasterrl); 90 | } 91 | strcpy(lasterr, path); 92 | } 93 | 94 | 95 | void dir_seterr(const char *fmt, ...) { 96 | free(dir_fatalerr); 97 | dir_fatalerr = NULL; 98 | if(!fmt) 99 | return; 100 | 101 | va_list va; 102 | va_start(va, fmt); 103 | dir_fatalerr = xmalloc(1024); /* Should be enough for everything... */ 104 | vsnprintf(dir_fatalerr, 1023, fmt, va); 105 | dir_fatalerr[1023] = 0; 106 | va_end(va); 107 | } 108 | 109 | 110 | static void draw_progress(void) { 111 | static const char scantext[] = "Scanning..."; 112 | static const char loadtext[] = "Loading..."; 113 | static size_t anpos = 0; 114 | const char *antext = dir_import_active ? loadtext : scantext; 115 | char ani[16] = {0}; 116 | size_t i; 117 | int width = wincols-5; 118 | 119 | nccreate(10, width, antext); 120 | 121 | ncaddstr(2, 2, "Total items: "); 122 | uic_set(UIC_NUM); 123 | printw("%-9d", dir_output.items); 124 | 125 | if(dir_output.size) { 126 | ncaddstrc(UIC_DEFAULT, 2, 24, "size: "); 127 | printsize(UIC_DEFAULT, dir_output.size); 128 | } 129 | 130 | uic_set(UIC_DEFAULT); 131 | ncprint(3, 2, "Current item: %s", cropstr(dir_curpath, width-18)); 132 | if(confirm_quit_while_scanning_stage_1_passed) { 133 | ncaddstr(8, width-26, "Press "); 134 | addchc(UIC_KEY, 'y'); 135 | addstrc(UIC_DEFAULT, " to confirm abort"); 136 | } else { 137 | ncaddstr(8, width-18, "Press "); 138 | addchc(UIC_KEY, 'q'); 139 | addstrc(UIC_DEFAULT, " to abort"); 140 | } 141 | 142 | /* show warning if we couldn't open a dir */ 143 | if(lasterr) { 144 | attron(A_BOLD); 145 | ncaddstr(5, 2, "Warning:"); 146 | attroff(A_BOLD); 147 | ncprint(5, 11, "error scanning %-32s", cropstr(lasterr, width-28)); 148 | ncaddstr(6, 3, "some directory sizes may not be correct"); 149 | } 150 | 151 | /* animation - but only if the screen refreshes more than or once every second */ 152 | if(update_delay <= 1000) { 153 | if(++anpos == strlen(antext)*2) 154 | anpos = 0; 155 | memset(ani, ' ', strlen(antext)); 156 | if(anpos < strlen(antext)) 157 | for(i=0; i<=anpos; i++) 158 | ani[i] = antext[i]; 159 | else 160 | for(i=strlen(antext)-1; i>anpos-strlen(antext); i--) 161 | ani[i] = antext[i]; 162 | } else 163 | strcpy(ani, antext); 164 | ncaddstr(8, 3, ani); 165 | } 166 | 167 | 168 | static void draw_error(char *cur, char *msg) { 169 | int width = wincols-5; 170 | nccreate(7, width, "Error!"); 171 | 172 | attron(A_BOLD); 173 | ncaddstr(2, 2, "Error:"); 174 | attroff(A_BOLD); 175 | 176 | ncprint(2, 9, "could not open %s", cropstr(cur, width-26)); 177 | ncprint(3, 4, "%s", cropstr(msg, width-8)); 178 | ncaddstr(5, width-30, "press any key to continue..."); 179 | } 180 | 181 | 182 | void dir_draw() { 183 | float f; 184 | const char *unit; 185 | 186 | switch(dir_ui) { 187 | case 0: 188 | if(dir_fatalerr) 189 | fprintf(stderr, "%s.\n", dir_fatalerr); 190 | break; 191 | case 1: 192 | if(dir_fatalerr) 193 | fprintf(stderr, "\r%s.\n", dir_fatalerr); 194 | else if(dir_output.size) { 195 | f = formatsize(dir_output.size, &unit); 196 | fprintf(stderr, "\r%-55s %8d files /%5.1f %s", 197 | cropstr(dir_curpath, 55), dir_output.items, f, unit); 198 | } else 199 | fprintf(stderr, "\r%-65s %8d files", cropstr(dir_curpath, 65), dir_output.items); 200 | break; 201 | case 2: 202 | browse_draw(); 203 | if(dir_fatalerr) 204 | draw_error(dir_curpath, dir_fatalerr); 205 | else 206 | draw_progress(); 207 | break; 208 | } 209 | } 210 | 211 | 212 | /* This function can't be called unless dir_ui == 2 213 | * (Doesn't really matter either way). */ 214 | int dir_key(int ch) { 215 | if(dir_fatalerr) 216 | return 1; 217 | if(confirm_quit && confirm_quit_while_scanning_stage_1_passed) { 218 | if (ch == 'y'|| ch == 'Y') { 219 | return 1; 220 | } else { 221 | confirm_quit_while_scanning_stage_1_passed = 0; 222 | return 0; 223 | } 224 | } else if(ch == 'q') { 225 | if(confirm_quit) { 226 | confirm_quit_while_scanning_stage_1_passed = 1; 227 | return 0; 228 | } else 229 | return 1; 230 | } 231 | return 0; 232 | } 233 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 1.15.1 - 2020-06-10 2 | - (Linux) Fix build on older Linux systems (Christian Göttsche) 3 | - (MacOS) Revert "Exclude firmlinks by default" behavior (until we have a better solution) 4 | - (MacOS) Add --exclude-firmlinks option to opt-in to the above behavior 5 | 6 | 1.15 - 2020-05-30 7 | - (Linux) Add --exclude-kernfs option to exclude pseudo filesystems (Christian Göttsche) 8 | - (MacOS) Exclude firmlinks by default (Saagar Jha) 9 | - (MacOS) Add --follow-firmlinks option to follow firmlinks (Saagar Jha) 10 | - Fix bug in calculating the apparent size of directories containing hardlinks 11 | - Fix integer overflow with directories containing >2GiB worth of file names 12 | - Fix yet another possible 100% CPU bug when losing terminal 13 | 14 | 1.14.2 - 2020-02-10 15 | - Fix compilation with GCC 10 (-fno-common) 16 | - Fix minor display issue when scanning 10M+ files 17 | - Slightly reduce memory usage for hard link detection 18 | 19 | 1.14.1 - 2019-08-05 20 | - Fix occasional early exit on OS X 21 | - Fix --exclude-caches 22 | - Improve handling of out-of-memory situations 23 | 24 | 1.14 - 2019-02-04 25 | - Add mtime display and sorting (Alex Wilson) 26 | - Add (limited) --follow-symlinks option (Simon Doppler) 27 | - Display larger file counts in browser UI 28 | - Add -V, --version, and --help alias flags 29 | - Fix crash when attempting to sort an empty directory 30 | - Fix 100% CPU bug when ncdu loses the terminal 31 | - Fix '--color=off' flag 32 | - Fix some typos 33 | 34 | 1.13 - 2018-01-29 35 | - Add "extended information" mode and -e flag 36 | - Add file mode, modification time and uid/gid to info window with -e 37 | - Add experimental color support and --color flag 38 | - Add -rr option to disable shell spawning 39 | - Remove directory nesting limit on file import 40 | - Fix handling of interrupts during file import 41 | - Fix undefined behavior that triggered crash on OS X 42 | 43 | 1.12 - 2016-08-24 44 | - Add NCDU_SHELL environment variable 45 | - Add --confirm-quit flag 46 | - Fix compilation due to missing sys/wait.h include 47 | 48 | 1.11 - 2015-04-05 49 | - Added 'b' key to spawn shell in the current directory 50 | - Support scanning (and refreshing) of empty directories 51 | - Added --si flag for base 10 prefixes 52 | - Fix toggle dirs before files 53 | 54 | 1.10 - 2013-05-09 55 | - Added 'c' key to display item counts 56 | - Added 'C' key to order by item counts 57 | - Added CACHEDIR.TAG support and --exclude-caches option 58 | - Use locale-dependent thousand separator 59 | - Use pkg-config to detect ncurses 60 | - Clip file/dir sizes to 8 EiB minus one byte 61 | - Fix buffer overflow when formatting huge file sizes 62 | 63 | 1.9 - 2012-09-27 64 | - Added option to dump scanned directory information to a file (-o) 65 | - Added option to load scanned directory information from a file (-f) 66 | - Added multiple scan and load interfaces (-0,-1,-2) 67 | - Fit loading and error windows to the terminal width (#13) 68 | - Fix symlink resolving bug (#18) 69 | - Fix path display when scanning an empty directory (#15) 70 | - Fix hang when terminal is resized to a too small size while loading 71 | - Use top-level automake build 72 | - Remove useless AUTHORS, INSTALL and NEWS files 73 | - ncdu.1 now uses POD as source format 74 | 75 | 1.8 - 2011-11-03 76 | - Use hash table to speed up hard link detection 77 | - Added read-only option (-r) 78 | - Use KiB instead of kiB (#3399279) 79 | 80 | 1.7 - 2010-08-13 81 | - List the detected hard links in file info window 82 | - Count the size of a hard linked file once for each directory it appears in 83 | - Fixed crash on browsing dirs with a small window size (#2991787) 84 | - Fixed buffer overflow when some directories can't be scanned (#2981704) 85 | - Fixed segfault when launched on a nonexistent directory (#3012787) 86 | - Fixed segfault when root dir only contains hidden files 87 | - Improved browsing performance 88 | - More intuitive multi-page browsing 89 | - Display size graph by default 90 | - Various minor fixes 91 | 92 | 1.6 - 2009-10-23 93 | - Implemented hard link detection 94 | - Properly select the next item after deletion 95 | - Removed reliance of dirfd() 96 | - Fixed non-void return in void delete_process() 97 | - Fixed several tiny memory leaks 98 | - Return to previously opened directory on failed recalculation 99 | - Properly display MiB units instead of MB (IEEE 1541 - bug #2831412) 100 | - Link to ncursesw when available 101 | - Improved support for non-ASCII characters 102 | - VIM keybindings for browsing through the tree (#2788249, #1880622) 103 | 104 | 1.5 - 2009-05-02 105 | - Fixed incorrect apparent size on directory refresh 106 | - Browsing keys now work while file info window is displayed 107 | - Current directory is assumed when no directory is specified 108 | - Size graph uses the apparent size if that is displayed 109 | - Items are ordered by displayed size rather than disk usage 110 | - Removed switching between powers of 1000/1024 111 | - Don't rely on the availability of suseconds_t 112 | - Correctly handle paths longer than PATH_MAX 113 | - Fixed various bugs related to rpath() 114 | - Major code rewrite 115 | - Fixed line width when displaying 100% 116 | 117 | 1.4 - 2008-09-10 118 | - Removed the startup window 119 | - Filenames ending with a tidle (~) will now also 120 | be hidden with the 'h'-key 121 | - Fixed buffer overflow when supplying a path longer 122 | than PATH_MAX (patch by Tobias Stoeckmann) 123 | - Used S_BLKSIZE instead of a hardcoded block size of 512 124 | - Fixed display of disk usage and apparent sizes 125 | - Updated ncdu -h 126 | - Included patches for Cygwin 127 | - Cursor now follows the selected item 128 | - Added spaces around path (debian #472194) 129 | - Fixed segfault on empty directory (debian #472294) 130 | - A few code rewrites and improvements 131 | 132 | 1.3 - 2007-08-05 133 | - Added 'r'-key to refresh the current directory 134 | - Removed option to calculate apparent size: both 135 | the disk usage and the apparent size are calculated. 136 | - Added 'a'-key to switch between showing apparent 137 | size and disk usage. 138 | - Added 'i'-key to display information about the 139 | selected item. 140 | - Small performance improvements 141 | - configure checks for ncurses.h (bug #1764304) 142 | 143 | 1.2 - 2007-07-24 144 | - Fixed some bugs on cygwin 145 | - Added du-like exclude patterns 146 | - Fixed bug #1758403: large directories work fine now 147 | - Rewrote a large part of the code 148 | - Fixed a bug with wide characters 149 | - Performance improvements when browsing large dirs 150 | 151 | 1.1 - 2007-04-30 152 | - Deleting files and directories is now possible from 153 | within ncdu. 154 | - The key for sorting directories between files has 155 | changed to 't' instead of 'd'. The 'd'-key is now 156 | used for deleting files. 157 | 158 | 1.0 - 2007-04-06 159 | - First stable release 160 | - Small code cleanup 161 | - Added a key to toggle between sorting dirs before 162 | files and dirs between files 163 | - Added graphs and percentages to the directory 164 | browser (can be enabled or disabled with the 'g'-key) 165 | 166 | 0.3 - 2007-03-04 167 | - When browsing back to the previous directory, the 168 | directory you're getting back from will be selected. 169 | - Added directory scanning in quiet mode to save 170 | bandwidth on remote connections. 171 | 172 | 0.2 - 2007-02-26 173 | - Fixed POSIX compliance: replaced realpath() with my 174 | own implementation, and gettimeofday() is not 175 | required anymore (but highly recommended) 176 | - Added a warning for terminals smaller than 60x16 177 | - Mountpoints (or any other directory pointing to 178 | another filesystem) are now considered to be 179 | directories rather than files. 180 | 181 | 0.1 - 2007-02-21 182 | - Initial version 183 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #ifndef _util_h 27 | #define _util_h 28 | 29 | #include "global.h" 30 | #include 31 | 32 | 33 | /* UI colors: (foreground, background, attrs) 34 | * NAME OFF DARK 35 | */ 36 | #define UI_COLORS \ 37 | C(DEFAULT, -1,-1,0 , -1, -1, 0 )\ 38 | C(BOX_TITLE, -1,-1,A_BOLD , COLOR_BLUE, -1, A_BOLD)\ 39 | C(HD, -1,-1,A_REVERSE , COLOR_BLACK, COLOR_CYAN, 0 ) /* header & footer */\ 40 | C(SEL, -1,-1,A_REVERSE , COLOR_WHITE, COLOR_GREEN,A_BOLD)\ 41 | C(NUM, -1,-1,0 , COLOR_YELLOW, -1, A_BOLD)\ 42 | C(NUM_HD, -1,-1,A_REVERSE , COLOR_YELLOW, COLOR_CYAN, A_BOLD)\ 43 | C(NUM_SEL, -1,-1,A_REVERSE , COLOR_YELLOW, COLOR_GREEN,A_BOLD)\ 44 | C(KEY, -1,-1,A_BOLD , COLOR_YELLOW, -1, A_BOLD)\ 45 | C(KEY_HD, -1,-1,A_BOLD|A_REVERSE, COLOR_YELLOW, COLOR_CYAN, A_BOLD)\ 46 | C(DIR, -1,-1,0 , COLOR_BLUE, -1, A_BOLD)\ 47 | C(DIR_SEL, -1,-1,A_REVERSE , COLOR_BLUE, COLOR_GREEN,A_BOLD)\ 48 | C(FLAG, -1,-1,0 , COLOR_RED, -1, 0 )\ 49 | C(FLAG_SEL, -1,-1,A_REVERSE , COLOR_RED, COLOR_GREEN,0 )\ 50 | C(GRAPH, -1,-1,0 , COLOR_MAGENTA,-1, 0 )\ 51 | C(GRAPH_SEL, -1,-1,A_REVERSE , COLOR_MAGENTA,COLOR_GREEN,0 ) 52 | 53 | enum ui_coltype { 54 | #define C(name, ...) UIC_##name, 55 | UI_COLORS 56 | #undef C 57 | UIC_NONE 58 | }; 59 | 60 | /* Color & attribute manipulation */ 61 | extern int uic_theme; 62 | 63 | void uic_init(void); 64 | void uic_set(enum ui_coltype); 65 | 66 | 67 | /* updated when window is resized */ 68 | extern int winrows, wincols; 69 | 70 | /* used by the nc* functions and macros */ 71 | extern int subwinr, subwinc; 72 | 73 | /* used by formatsize to choose between base 2 or 10 prefixes */ 74 | extern int si; 75 | 76 | 77 | /* Macros/functions for managing struct dir and struct dir_ext */ 78 | 79 | #define dir_memsize(n) (offsetof(struct dir, name)+1+strlen(n)) 80 | #define dir_ext_offset(n) ((dir_memsize(n) + 7) & ~7) 81 | #define dir_ext_memsize(n) (dir_ext_offset(n) + sizeof(struct dir_ext)) 82 | 83 | static inline struct dir_ext *dir_ext_ptr(struct dir *d) { 84 | return d->flags & FF_EXT 85 | ? (struct dir_ext *) ( ((char *)d) + dir_ext_offset(d->name) ) 86 | : NULL; 87 | } 88 | 89 | 90 | /* Instead of using several ncurses windows, we only draw to stdscr. 91 | * the functions nccreate, ncprint and the macros ncaddstr and ncaddch 92 | * mimic the behaviour of ncurses windows. 93 | * This works better than using ncurses windows when all windows are 94 | * created in the correct order: it paints directly on stdscr, so 95 | * wrefresh, wnoutrefresh and other window-specific functions are not 96 | * necessary. 97 | * Also, this method doesn't require any window objects, as you can 98 | * only create one window at a time. 99 | */ 100 | 101 | /* updates winrows, wincols, and displays a warning when the terminal 102 | * is smaller than the specified minimum size. */ 103 | int ncresize(int, int); 104 | 105 | /* creates a new centered window with border */ 106 | void nccreate(int, int, const char *); 107 | 108 | /* printf something somewhere in the last created window */ 109 | void ncprint(int, int, const char *, ...); 110 | 111 | /* Add a "tab" to a window */ 112 | void nctab(int, int, int, const char *); 113 | 114 | /* same as the w* functions of ncurses, with a color */ 115 | #define ncaddstr(r, c, s) mvaddstr(subwinr+(r), subwinc+(c), s) 116 | #define ncaddch(r, c, s) mvaddch(subwinr+(r), subwinc+(c), s) 117 | #define ncmove(r, c) move(subwinr+(r), subwinc+(c)) 118 | 119 | /* add stuff with a color */ 120 | #define mvaddstrc(t, r, c, s) do { uic_set(t); mvaddstr(r, c, s); } while(0) 121 | #define mvaddchc(t, r, c, s) do { uic_set(t); mvaddch(r, c, s); } while(0) 122 | #define addstrc(t, s) do { uic_set(t); addstr( s); } while(0) 123 | #define addchc(t, s) do { uic_set(t); addch( s); } while(0) 124 | #define ncaddstrc(t, r, c, s) do { uic_set(t); ncaddstr(r, c, s); } while(0) 125 | #define ncaddchc(t, r, c, s) do { uic_set(t); ncaddch(r, c, s); } while(0) 126 | #define mvhlinec(t, r, c, s, n) do { uic_set(t); mvhline(r, c, s, n); } while(0) 127 | 128 | /* crops a string into the specified length */ 129 | char *cropstr(const char *, int); 130 | 131 | /* Converts the given size in bytes into a float (0 <= f < 1000) and a unit string */ 132 | float formatsize(int64_t, const char **); 133 | 134 | /* print size in the form of xxx.x XB */ 135 | void printsize(enum ui_coltype, int64_t); 136 | 137 | /* int2string with thousand separators */ 138 | char *fullsize(int64_t); 139 | 140 | /* format's a file mode as a ls -l string */ 141 | char *fmtmode(unsigned short); 142 | 143 | /* read locale information from the environment */ 144 | void read_locale(void); 145 | 146 | /* recursively free()s a directory tree */ 147 | void freedir(struct dir *); 148 | 149 | /* generates full path from a dir item, 150 | returned pointer will be overwritten with a subsequent call */ 151 | const char *getpath(struct dir *); 152 | 153 | /* returns the root element of the given dir struct */ 154 | struct dir *getroot(struct dir *); 155 | 156 | /* Add two signed 64-bit integers. Returns INT64_MAX if the result would 157 | * overflow, or 0 if it would be negative. At least one of the integers must be 158 | * positive. 159 | * I use uint64_t's to detect the overflow, as (a + b < 0) relies on undefined 160 | * behaviour, and (INT64_MAX - b >= a) didn't work for some reason. */ 161 | #define adds64(a, b) ((a) > 0 && (b) > 0\ 162 | ? ((uint64_t)(a) + (uint64_t)(b) > (uint64_t)INT64_MAX ? INT64_MAX : (a)+(b))\ 163 | : (a)+(b) < 0 ? 0 : (a)+(b)) 164 | 165 | /* Adds a value to the size, asize and items fields of *d and its parents */ 166 | void addparentstats(struct dir *, int64_t, int64_t, uint64_t, int); 167 | 168 | 169 | /* A simple stack implemented in macros */ 170 | #define nstack_init(_s) do {\ 171 | (_s)->size = 10;\ 172 | (_s)->top = 0;\ 173 | (_s)->list = xmalloc(10*sizeof(*(_s)->list));\ 174 | } while(0) 175 | 176 | #define nstack_push(_s, _v) do {\ 177 | if((_s)->size <= (_s)->top) {\ 178 | (_s)->size *= 2;\ 179 | (_s)->list = xrealloc((_s)->list, (_s)->size*sizeof(*(_s)->list));\ 180 | }\ 181 | (_s)->list[(_s)->top++] = _v;\ 182 | } while(0) 183 | 184 | #define nstack_pop(_s) (_s)->top-- 185 | #define nstack_top(_s, _d) ((_s)->top > 0 ? (_s)->list[(_s)->top-1] : (_d)) 186 | #define nstack_free(_s) free((_s)->list) 187 | 188 | 189 | /* Malloc wrappers that exit on OOM */ 190 | void *xmalloc(size_t); 191 | void *xcalloc(size_t, size_t); 192 | void *xrealloc(void *, size_t); 193 | 194 | #endif 195 | 196 | -------------------------------------------------------------------------------- /src/dirlist.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include "global.h" 27 | 28 | #include 29 | #include 30 | 31 | 32 | /* public variables */ 33 | struct dir *dirlist_parent = NULL, 34 | *dirlist_par = NULL; 35 | int64_t dirlist_maxs = 0, 36 | dirlist_maxa = 0; 37 | 38 | int dirlist_sort_desc = 1, 39 | dirlist_sort_col = DL_COL_SIZE, 40 | dirlist_sort_df = 0, 41 | dirlist_hidden = 0; 42 | 43 | /* private state vars */ 44 | static struct dir *parent_alloc, *head, *head_real, *selected, *top = NULL; 45 | 46 | 47 | 48 | #define ISHIDDEN(d) (dirlist_hidden && (d) != dirlist_parent && (\ 49 | (d)->flags & FF_EXL || (d)->name[0] == '.' || (d)->name[strlen((d)->name)-1] == '~'\ 50 | )) 51 | 52 | 53 | static inline int cmp_mtime(struct dir *x, struct dir*y) { 54 | int64_t x_mtime = 0, y_mtime = 0; 55 | if (x->flags & FF_EXT) 56 | x_mtime = dir_ext_ptr(x)->mtime; 57 | if (y->flags & FF_EXT) 58 | y_mtime = dir_ext_ptr(y)->mtime; 59 | return (x_mtime > y_mtime ? 1 : (x_mtime == y_mtime ? 0 : -1)); 60 | } 61 | 62 | static int dirlist_cmp(struct dir *x, struct dir *y) { 63 | int r; 64 | 65 | /* dirs are always before files when that option is set */ 66 | if(dirlist_sort_df) { 67 | if(y->flags & FF_DIR && !(x->flags & FF_DIR)) 68 | return 1; 69 | else if(!(y->flags & FF_DIR) && x->flags & FF_DIR) 70 | return -1; 71 | } 72 | 73 | /* sort columns: 74 | * 1 -> 2 -> 3 -> 4 75 | * NAME: name -> size -> asize -> items 76 | * SIZE: size -> asize -> name -> items 77 | * ASIZE: asize -> size -> name -> items 78 | * ITEMS: items -> size -> asize -> name 79 | * 80 | * Note that the method used below is supposed to be fast, not readable :-) 81 | */ 82 | #define CMP_NAME strcmp(x->name, y->name) 83 | #define CMP_SIZE (x->size > y->size ? 1 : (x->size == y->size ? 0 : -1)) 84 | #define CMP_ASIZE (x->asize > y->asize ? 1 : (x->asize == y->asize ? 0 : -1)) 85 | #define CMP_ITEMS (x->items > y->items ? 1 : (x->items == y->items ? 0 : -1)) 86 | 87 | /* try 1 */ 88 | r = dirlist_sort_col == DL_COL_NAME ? CMP_NAME : 89 | dirlist_sort_col == DL_COL_SIZE ? CMP_SIZE : 90 | dirlist_sort_col == DL_COL_ASIZE ? CMP_ASIZE : 91 | dirlist_sort_col == DL_COL_ITEMS ? CMP_ITEMS : 92 | cmp_mtime(x, y); 93 | /* try 2 */ 94 | if(!r) 95 | r = dirlist_sort_col == DL_COL_SIZE ? CMP_ASIZE : CMP_SIZE; 96 | /* try 3 */ 97 | if(!r) 98 | r = (dirlist_sort_col == DL_COL_NAME || dirlist_sort_col == DL_COL_ITEMS) ? 99 | CMP_ASIZE : CMP_NAME; 100 | /* try 4 */ 101 | if(!r) 102 | r = dirlist_sort_col == DL_COL_ITEMS ? CMP_NAME : CMP_ITEMS; 103 | 104 | /* reverse when sorting in descending order */ 105 | if(dirlist_sort_desc && r != 0) 106 | r = r < 0 ? 1 : -1; 107 | 108 | return r; 109 | } 110 | 111 | 112 | static struct dir *dirlist_sort(struct dir *list) { 113 | struct dir *p, *q, *e, *tail; 114 | int insize, nmerges, psize, qsize, i; 115 | 116 | insize = 1; 117 | while(1) { 118 | p = list; 119 | list = NULL; 120 | tail = NULL; 121 | nmerges = 0; 122 | while(p) { 123 | nmerges++; 124 | q = p; 125 | psize = 0; 126 | for(i=0; inext; 129 | if(!q) break; 130 | } 131 | qsize = insize; 132 | while(psize > 0 || (qsize > 0 && q)) { 133 | if(psize == 0) { 134 | e = q; q = q->next; qsize--; 135 | } else if(qsize == 0 || !q) { 136 | e = p; p = p->next; psize--; 137 | } else if(dirlist_cmp(p,q) <= 0) { 138 | e = p; p = p->next; psize--; 139 | } else { 140 | e = q; q = q->next; qsize--; 141 | } 142 | if(tail) tail->next = e; 143 | else list = e; 144 | e->prev = tail; 145 | tail = e; 146 | } 147 | p = q; 148 | } 149 | tail->next = NULL; 150 | if(nmerges <= 1) { 151 | if(list->parent) 152 | list->parent->sub = list; 153 | return list; 154 | } 155 | insize *= 2; 156 | } 157 | } 158 | 159 | 160 | /* passes through the dir listing once and: 161 | * - makes sure one, and only one, visible item is selected 162 | * - updates the dirlist_(maxs|maxa) values 163 | * - makes sure that the FF_BSEL bits are correct */ 164 | static void dirlist_fixup(void) { 165 | struct dir *t; 166 | 167 | /* we're going to determine the selected items from the list itself, so reset this one */ 168 | selected = NULL; 169 | 170 | for(t=head; t; t=t->next) { 171 | /* not visible? not selected! */ 172 | if(ISHIDDEN(t)) 173 | t->flags &= ~FF_BSEL; 174 | else { 175 | /* visible and selected? make sure only one item is selected */ 176 | if(t->flags & FF_BSEL) { 177 | if(!selected) 178 | selected = t; 179 | else 180 | t->flags &= ~FF_BSEL; 181 | } 182 | } 183 | 184 | /* update dirlist_(maxs|maxa) */ 185 | if(t->size > dirlist_maxs) 186 | dirlist_maxs = t->size; 187 | if(t->asize > dirlist_maxa) 188 | dirlist_maxa = t->asize; 189 | } 190 | 191 | /* no selected items found after one pass? select the first visible item */ 192 | if(!selected) 193 | if((selected = dirlist_next(NULL))) 194 | selected->flags |= FF_BSEL; 195 | } 196 | 197 | 198 | void dirlist_open(struct dir *d) { 199 | dirlist_par = d; 200 | 201 | /* set the head of the list */ 202 | head_real = head = d == NULL ? NULL : d->sub; 203 | 204 | /* reset internal status */ 205 | dirlist_maxs = dirlist_maxa = 0; 206 | 207 | /* stop if this is not a directory list we can work with */ 208 | if(d == NULL) { 209 | dirlist_parent = NULL; 210 | return; 211 | } 212 | 213 | /* sort the dir listing */ 214 | if(head) 215 | head_real = head = dirlist_sort(head); 216 | 217 | /* set the reference to the parent dir */ 218 | if(d->parent) { 219 | if(!parent_alloc) 220 | parent_alloc = xcalloc(1, dir_memsize("..")); 221 | dirlist_parent = parent_alloc; 222 | strcpy(dirlist_parent->name, ".."); 223 | dirlist_parent->next = head; 224 | dirlist_parent->parent = d; 225 | dirlist_parent->sub = d; 226 | dirlist_parent->flags = FF_DIR; 227 | head = dirlist_parent; 228 | } else 229 | dirlist_parent = NULL; 230 | 231 | dirlist_fixup(); 232 | } 233 | 234 | 235 | struct dir *dirlist_next(struct dir *d) { 236 | if(!head) 237 | return NULL; 238 | if(!d) { 239 | if(!ISHIDDEN(head)) 240 | return head; 241 | else 242 | d = head; 243 | } 244 | while((d = d->next)) { 245 | if(!ISHIDDEN(d)) 246 | return d; 247 | } 248 | return NULL; 249 | } 250 | 251 | 252 | static struct dir *dirlist_prev(struct dir *d) { 253 | if(!head || !d) 254 | return NULL; 255 | while((d = d->prev)) { 256 | if(!ISHIDDEN(d)) 257 | return d; 258 | } 259 | if(dirlist_parent) 260 | return dirlist_parent; 261 | return NULL; 262 | } 263 | 264 | 265 | struct dir *dirlist_get(int i) { 266 | struct dir *t = selected, *d; 267 | 268 | if(!head) 269 | return NULL; 270 | 271 | if(ISHIDDEN(selected)) { 272 | selected = dirlist_next(NULL); 273 | return selected; 274 | } 275 | 276 | /* i == 0? return the selected item */ 277 | if(!i) 278 | return selected; 279 | 280 | /* positive number? simply move forward */ 281 | while(i > 0) { 282 | d = dirlist_next(t); 283 | if(!d) 284 | return t; 285 | t = d; 286 | if(!--i) 287 | return t; 288 | } 289 | 290 | /* otherwise, backward */ 291 | while(1) { 292 | d = dirlist_prev(t); 293 | if(!d) 294 | return t; 295 | t = d; 296 | if(!++i) 297 | return t; 298 | } 299 | } 300 | 301 | 302 | void dirlist_select(struct dir *d) { 303 | if(!d || !head || ISHIDDEN(d) || d->parent != head->parent) 304 | return; 305 | 306 | selected->flags &= ~FF_BSEL; 307 | selected = d; 308 | selected->flags |= FF_BSEL; 309 | } 310 | 311 | 312 | 313 | /* We need a hint in order to figure out which item should be on top: 314 | * 0 = only get the current top, don't set anything 315 | * 1 = selected has moved down 316 | * -1 = selected has moved up 317 | * -2 = selected = first item in the list (faster version of '1') 318 | * -3 = top should be considered as invalid (after sorting or opening another dir) 319 | * -4 = an item has been deleted 320 | * -5 = hidden flag has been changed 321 | * 322 | * Actions: 323 | * hint = -1 or -4 -> top = selected_is_visible ? top : selected 324 | * hint = -2 or -3 -> top = selected-(winrows-3)/2 325 | * hint = 1 -> top = selected_is_visible ? top : selected-(winrows-4) 326 | * hint = 0 or -5 -> top = selected_is_visible ? top : selected-(winrows-3)/2 327 | * 328 | * Regardless of the hint, the returned top will always be chosen such that the 329 | * selected item is visible. 330 | */ 331 | struct dir *dirlist_top(int hint) { 332 | struct dir *t; 333 | int i, visible = 0; 334 | 335 | if(hint == -2 || hint == -3) 336 | top = NULL; 337 | 338 | /* check whether the current selected item is within the visible window */ 339 | if(top) { 340 | i = winrows-3; 341 | t = dirlist_get(0); 342 | while(t && i--) { 343 | if(t == top) { 344 | visible++; 345 | break; 346 | } 347 | t = dirlist_prev(t); 348 | } 349 | } 350 | 351 | /* otherwise, get a new top */ 352 | if(!visible) 353 | top = hint == -1 || hint == -4 ? dirlist_get(0) : 354 | hint == 1 ? dirlist_get(-1*(winrows-4)) : 355 | dirlist_get(-1*(winrows-3)/2); 356 | 357 | /* also make sure that if the list is longer than the window and the last 358 | * item is visible, that this last item is also the last on the window */ 359 | t = top; 360 | i = winrows-3; 361 | while(t && i--) 362 | t = dirlist_next(t); 363 | t = top; 364 | do { 365 | top = t; 366 | t = dirlist_prev(t); 367 | } while(t && i-- > 0); 368 | 369 | return top; 370 | } 371 | 372 | 373 | void dirlist_set_sort(int col, int desc, int df) { 374 | /* update config */ 375 | if(col != DL_NOCHANGE) 376 | dirlist_sort_col = col; 377 | if(desc != DL_NOCHANGE) 378 | dirlist_sort_desc = desc; 379 | if(df != DL_NOCHANGE) 380 | dirlist_sort_df = df; 381 | 382 | /* sort the list (excluding the parent, which is always on top) */ 383 | if(head_real) 384 | head_real = dirlist_sort(head_real); 385 | if(dirlist_parent) 386 | dirlist_parent->next = head_real; 387 | else 388 | head = head_real; 389 | dirlist_top(-3); 390 | } 391 | 392 | 393 | void dirlist_set_hidden(int hidden) { 394 | dirlist_hidden = hidden; 395 | dirlist_fixup(); 396 | dirlist_top(-5); 397 | } 398 | 399 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include "global.h" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | 36 | #include 37 | 38 | 39 | int pstate; 40 | int read_only = 0; 41 | long update_delay = 100; 42 | int cachedir_tags = 0; 43 | int extended_info = 0; 44 | int follow_symlinks = 0; 45 | int follow_firmlinks = 1; 46 | int confirm_quit = 0; 47 | 48 | static int min_rows = 17, min_cols = 60; 49 | static int ncurses_init = 0; 50 | static int ncurses_tty = 0; /* Explicitly open /dev/tty instead of using stdio */ 51 | static long lastupdate = 999; 52 | 53 | 54 | static void screen_draw(void) { 55 | switch(pstate) { 56 | case ST_CALC: dir_draw(); break; 57 | case ST_BROWSE: browse_draw(); break; 58 | case ST_HELP: help_draw(); break; 59 | case ST_SHELL: shell_draw(); break; 60 | case ST_DEL: delete_draw(); break; 61 | case ST_QUIT: quit_draw(); break; 62 | } 63 | } 64 | 65 | 66 | /* wait: 67 | * -1: non-blocking, always draw screen 68 | * 0: blocking wait for input and always draw screen 69 | * 1: non-blocking, draw screen only if a configured delay has passed or after keypress 70 | */ 71 | int input_handle(int wait) { 72 | int ch; 73 | struct timeval tv; 74 | 75 | if(wait != 1) 76 | screen_draw(); 77 | else { 78 | gettimeofday(&tv, NULL); 79 | tv.tv_usec = (1000*(tv.tv_sec % 1000) + (tv.tv_usec / 1000)) / update_delay; 80 | if(lastupdate != tv.tv_usec) { 81 | screen_draw(); 82 | lastupdate = tv.tv_usec; 83 | } 84 | } 85 | 86 | /* No actual input handling is done if ncurses hasn't been initialized yet. */ 87 | if(!ncurses_init) 88 | return wait == 0 ? 1 : 0; 89 | 90 | nodelay(stdscr, wait?1:0); 91 | errno = 0; 92 | while((ch = getch()) != ERR) { 93 | if(ch == KEY_RESIZE) { 94 | if(ncresize(min_rows, min_cols)) 95 | min_rows = min_cols = 0; 96 | /* ncresize() may change nodelay state, make sure to revert it. */ 97 | nodelay(stdscr, wait?1:0); 98 | screen_draw(); 99 | continue; 100 | } 101 | switch(pstate) { 102 | case ST_CALC: return dir_key(ch); 103 | case ST_BROWSE: return browse_key(ch); 104 | case ST_HELP: return help_key(ch); 105 | case ST_DEL: return delete_key(ch); 106 | case ST_QUIT: return quit_key(ch); 107 | } 108 | screen_draw(); 109 | } 110 | if(errno == EPIPE || errno == EBADF || errno == EIO) 111 | return 1; 112 | return 0; 113 | } 114 | 115 | 116 | /* parse command line */ 117 | static void argv_parse(int argc, char **argv) { 118 | yopt_t yopt; 119 | int v; 120 | char *val; 121 | char *export = NULL; 122 | char *import = NULL; 123 | char *dir = NULL; 124 | 125 | static yopt_opt_t opts[] = { 126 | { 'h', 0, "-h,-?,--help" }, 127 | { 'q', 0, "-q" }, 128 | { 'v', 0, "-v,-V,--version" }, 129 | { 'x', 0, "-x" }, 130 | { 'e', 0, "-e" }, 131 | { 'r', 0, "-r" }, 132 | { 'o', 1, "-o" }, 133 | { 'f', 1, "-f" }, 134 | { '0', 0, "-0" }, 135 | { '1', 0, "-1" }, 136 | { '2', 0, "-2" }, 137 | { 1, 1, "--exclude" }, 138 | { 'X', 1, "-X,--exclude-from" }, 139 | { 'L', 0, "-L,--follow-symlinks" }, 140 | { 'C', 0, "--exclude-caches" }, 141 | { 2, 0, "--exclude-kernfs" }, 142 | { 3, 0, "--follow-firmlinks" }, /* undocumented, this behavior is the current default */ 143 | { 4, 0, "--exclude-firmlinks" }, 144 | { 's', 0, "--si" }, 145 | { 'Q', 0, "--confirm-quit" }, 146 | { 'c', 1, "--color" }, 147 | {0,0,NULL} 148 | }; 149 | 150 | dir_ui = -1; 151 | si = 0; 152 | 153 | yopt_init(&yopt, argc, argv, opts); 154 | while((v = yopt_next(&yopt, &val)) != -1) { 155 | switch(v) { 156 | case 0 : dir = val; break; 157 | case 'h': 158 | printf("ncdu \n\n"); 159 | printf(" -h,--help This help message\n"); 160 | printf(" -q Quiet mode, refresh interval 2 seconds\n"); 161 | printf(" -v,-V,--version Print version\n"); 162 | printf(" -x Same filesystem\n"); 163 | printf(" -e Enable extended information\n"); 164 | printf(" -r Read only\n"); 165 | printf(" -o FILE Export scanned directory to FILE\n"); 166 | printf(" -f FILE Import scanned directory from FILE\n"); 167 | printf(" -0,-1,-2 UI to use when scanning (0=none,2=full ncurses)\n"); 168 | printf(" --si Use base 10 (SI) prefixes instead of base 2\n"); 169 | printf(" --exclude PATTERN Exclude files that match PATTERN\n"); 170 | printf(" -X, --exclude-from FILE Exclude files that match any pattern in FILE\n"); 171 | printf(" -L, --follow-symlinks Follow symbolic links (excluding directories)\n"); 172 | printf(" --exclude-caches Exclude directories containing CACHEDIR.TAG\n"); 173 | #if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS 174 | printf(" --exclude-kernfs Exclude Linux pseudo filesystems (procfs,sysfs,cgroup,...)\n"); 175 | #endif 176 | #if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH 177 | printf(" --exclude-firmlinks Exclude firmlinks on macOS\n"); 178 | #endif 179 | printf(" --confirm-quit Confirm quitting ncdu\n"); 180 | printf(" --color SCHEME Set color scheme (off/dark)\n"); 181 | exit(0); 182 | case 'q': update_delay = 2000; break; 183 | case 'v': 184 | printf("ncdu %s\n", PACKAGE_VERSION); 185 | exit(0); 186 | case 'x': dir_scan_smfs = 1; break; 187 | case 'e': extended_info = 1; break; 188 | case 'r': read_only++; break; 189 | case 's': si = 1; break; 190 | case 'o': export = val; break; 191 | case 'f': import = val; break; 192 | case '0': dir_ui = 0; break; 193 | case '1': dir_ui = 1; break; 194 | case '2': dir_ui = 2; break; 195 | case 'Q': confirm_quit = 1; break; 196 | case 1 : exclude_add(val); break; /* --exclude */ 197 | case 'X': 198 | if(exclude_addfile(val)) { 199 | fprintf(stderr, "Can't open %s: %s\n", val, strerror(errno)); 200 | exit(1); 201 | } 202 | break; 203 | case 'L': follow_symlinks = 1; break; 204 | case 'C': 205 | cachedir_tags = 1; 206 | break; 207 | 208 | case 2 : /* --exclude-kernfs */ 209 | #if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS 210 | exclude_kernfs = 1; break; 211 | #else 212 | fprintf(stderr, "This feature is not supported on your platform\n"); 213 | exit(1); 214 | #endif 215 | case 3 : /* --follow-firmlinks */ 216 | #if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH 217 | follow_firmlinks = 1; break; 218 | #else 219 | fprintf(stderr, "This feature is not supported on your platform\n"); 220 | exit(1); 221 | #endif 222 | case 4 : /* --exclude-firmlinks */ 223 | #if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH 224 | follow_firmlinks = 0; break; 225 | #else 226 | fprintf(stderr, "This feature is not supported on your platform\n"); 227 | exit(1); 228 | #endif 229 | case 'c': 230 | if(strcmp(val, "off") == 0) { uic_theme = 0; } 231 | else if(strcmp(val, "dark") == 0) { uic_theme = 1; } 232 | else { 233 | fprintf(stderr, "Unknown --color option: %s\n", val); 234 | exit(1); 235 | } 236 | break; 237 | case -2: 238 | fprintf(stderr, "ncdu: %s.\n", val); 239 | exit(1); 240 | } 241 | } 242 | 243 | if(export) { 244 | if(dir_export_init(export)) { 245 | fprintf(stderr, "Can't open %s: %s\n", export, strerror(errno)); 246 | exit(1); 247 | } 248 | if(strcmp(export, "-") == 0) 249 | ncurses_tty = 1; 250 | } else 251 | dir_mem_init(NULL); 252 | 253 | if(import) { 254 | if(dir_import_init(import)) { 255 | fprintf(stderr, "Can't open %s: %s\n", import, strerror(errno)); 256 | exit(1); 257 | } 258 | if(strcmp(import, "-") == 0) 259 | ncurses_tty = 1; 260 | } else 261 | dir_scan_init(dir ? dir : "."); 262 | 263 | /* Use the single-line scan feedback by default when exporting to file, no 264 | * feedback when exporting to stdout. */ 265 | if(dir_ui == -1) 266 | dir_ui = export && strcmp(export, "-") == 0 ? 0 : export ? 1 : 2; 267 | } 268 | 269 | 270 | /* Initializes ncurses only when not done yet. */ 271 | static void init_nc(void) { 272 | int ok = 0; 273 | FILE *tty; 274 | SCREEN *term; 275 | 276 | if(ncurses_init) 277 | return; 278 | ncurses_init = 1; 279 | 280 | if(ncurses_tty) { 281 | tty = fopen("/dev/tty", "r+"); 282 | if(!tty) { 283 | fprintf(stderr, "Error opening /dev/tty: %s\n", strerror(errno)); 284 | exit(1); 285 | } 286 | term = newterm(NULL, tty, tty); 287 | if(term) 288 | set_term(term); 289 | ok = !!term; 290 | } else { 291 | /* Make sure the user doesn't accidentally pipe in data to ncdu's standard 292 | * input without using "-f -". An annoying input sequence could result in 293 | * the deletion of your files, which we want to prevent at all costs. */ 294 | if(!isatty(0)) { 295 | fprintf(stderr, "Standard input is not a TTY. Did you mean to import a file using '-f -'?\n"); 296 | exit(1); 297 | } 298 | ok = !!initscr(); 299 | } 300 | 301 | if(!ok) { 302 | fprintf(stderr, "Error while initializing ncurses.\n"); 303 | exit(1); 304 | } 305 | 306 | uic_init(); 307 | cbreak(); 308 | noecho(); 309 | curs_set(0); 310 | keypad(stdscr, TRUE); 311 | if(ncresize(min_rows, min_cols)) 312 | min_rows = min_cols = 0; 313 | } 314 | 315 | 316 | void close_nc() { 317 | if(ncurses_init) { 318 | erase(); 319 | refresh(); 320 | endwin(); 321 | } 322 | } 323 | 324 | 325 | /* main program */ 326 | int main(int argc, char **argv) { 327 | read_locale(); 328 | argv_parse(argc, argv); 329 | 330 | if(dir_ui == 2) 331 | init_nc(); 332 | 333 | while(1) { 334 | /* We may need to initialize/clean up the screen when switching from the 335 | * (sometimes non-ncurses) CALC state to something else. */ 336 | if(pstate != ST_CALC) { 337 | if(dir_ui == 1) 338 | fputc('\n', stderr); 339 | init_nc(); 340 | } 341 | 342 | if(pstate == ST_CALC) { 343 | if(dir_process()) { 344 | if(dir_ui == 1) 345 | fputc('\n', stderr); 346 | break; 347 | } 348 | } else if(pstate == ST_DEL) 349 | delete_process(); 350 | else if(input_handle(0)) 351 | break; 352 | } 353 | 354 | close_nc(); 355 | exclude_clear(); 356 | 357 | return 0; 358 | } 359 | 360 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include "util.h" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #ifdef HAVE_LOCALE_H 34 | #include 35 | #endif 36 | 37 | int uic_theme; 38 | int winrows, wincols; 39 | int subwinr, subwinc; 40 | int si; 41 | static char thou_sep; 42 | 43 | 44 | char *cropstr(const char *from, int s) { 45 | static char dat[4096]; 46 | int i, j, o = strlen(from); 47 | if(o < s) { 48 | strcpy(dat, from); 49 | return dat; 50 | } 51 | j=s/2-3; 52 | for(i=0; i 0); 110 | tmp[i] = '\0'; 111 | 112 | /* reverse and add thousand separators */ 113 | j = 0; 114 | while(i--) { 115 | dat[j++] = tmp[i]; 116 | if(i != 0 && i%3 == 0) 117 | dat[j++] = thou_sep; 118 | } 119 | dat[j] = '\0'; 120 | 121 | return dat; 122 | } 123 | 124 | 125 | char *fmtmode(unsigned short mode) { 126 | static char buf[11]; 127 | unsigned short ft = mode & S_IFMT; 128 | buf[0] = ft == S_IFDIR ? 'd' 129 | : ft == S_IFREG ? '-' 130 | : ft == S_IFLNK ? 'l' 131 | : ft == S_IFIFO ? 'p' 132 | : ft == S_IFSOCK ? 's' 133 | : ft == S_IFCHR ? 'c' 134 | : ft == S_IFBLK ? 'b' : '?'; 135 | buf[1] = mode & 0400 ? 'r' : '-'; 136 | buf[2] = mode & 0200 ? 'w' : '-'; 137 | buf[3] = mode & 0100 ? 'x' : '-'; 138 | buf[4] = mode & 0040 ? 'r' : '-'; 139 | buf[5] = mode & 0020 ? 'w' : '-'; 140 | buf[6] = mode & 0010 ? 'x' : '-'; 141 | buf[7] = mode & 0004 ? 'r' : '-'; 142 | buf[8] = mode & 0002 ? 'w' : '-'; 143 | buf[9] = mode & 0001 ? 'x' : '-'; 144 | buf[10] = 0; 145 | return buf; 146 | } 147 | 148 | 149 | void read_locale() { 150 | thou_sep = '.'; 151 | #ifdef HAVE_LOCALE_H 152 | setlocale(LC_ALL, ""); 153 | char *locale_thou_sep = localeconv()->thousands_sep; 154 | if(locale_thou_sep && 1 == strlen(locale_thou_sep)) 155 | thou_sep = locale_thou_sep[0]; 156 | #endif 157 | } 158 | 159 | 160 | int ncresize(int minrows, int mincols) { 161 | int ch; 162 | 163 | getmaxyx(stdscr, winrows, wincols); 164 | while((minrows && winrows < minrows) || (mincols && wincols < mincols)) { 165 | erase(); 166 | mvaddstr(0, 0, "Warning: terminal too small,"); 167 | mvaddstr(1, 1, "please either resize your terminal,"); 168 | mvaddstr(2, 1, "press i to ignore, or press q to quit."); 169 | refresh(); 170 | nodelay(stdscr, 0); 171 | ch = getch(); 172 | getmaxyx(stdscr, winrows, wincols); 173 | if(ch == 'q') { 174 | erase(); 175 | refresh(); 176 | endwin(); 177 | exit(0); 178 | } 179 | if(ch == 'i') 180 | return 1; 181 | } 182 | erase(); 183 | return 0; 184 | } 185 | 186 | 187 | void nccreate(int height, int width, const char *title) { 188 | int i; 189 | 190 | uic_set(UIC_DEFAULT); 191 | subwinr = winrows/2-height/2; 192 | subwinc = wincols/2-width/2; 193 | 194 | /* clear window */ 195 | for(i=0; iflags & FF_HLNKC)) 287 | return; 288 | 289 | /* remove size from parents. 290 | * This works the same as with adding: only the parents in which THIS is the 291 | * only occurrence of the hard link will be modified, if the same file still 292 | * exists within the parent it shouldn't get removed from the count. 293 | * XXX: Same note as for dir_mem.c / hlink_check(): 294 | * this is probably not the most efficient algorithm */ 295 | for(i=1,par=d->parent; i&∥ par=par->parent) { 296 | if(d->hlnk) 297 | for(t=d->hlnk; i&&t!=d; t=t->hlnk) 298 | for(pt=t->parent; i&&pt; pt=pt->parent) 299 | if(pt==par) 300 | i=0; 301 | if(i) { 302 | par->size = adds64(par->size, -d->size); 303 | par->asize = adds64(par->size, -d->asize); 304 | } 305 | } 306 | 307 | /* remove from hlnk */ 308 | if(d->hlnk) { 309 | for(t=d->hlnk; t->hlnk!=d; t=t->hlnk) 310 | ; 311 | t->hlnk = d->hlnk; 312 | } 313 | } 314 | 315 | 316 | static void freedir_rec(struct dir *dr) { 317 | struct dir *tmp, *tmp2; 318 | tmp2 = dr; 319 | while((tmp = tmp2) != NULL) { 320 | freedir_hlnk(tmp); 321 | /* remove item */ 322 | if(tmp->sub) freedir_rec(tmp->sub); 323 | tmp2 = tmp->next; 324 | free(tmp); 325 | } 326 | } 327 | 328 | 329 | void freedir(struct dir *dr) { 330 | if(!dr) 331 | return; 332 | 333 | /* free dr->sub recursively */ 334 | if(dr->sub) 335 | freedir_rec(dr->sub); 336 | 337 | /* update references */ 338 | if(dr->parent && dr->parent->sub == dr) 339 | dr->parent->sub = dr->next; 340 | if(dr->prev) 341 | dr->prev->next = dr->next; 342 | if(dr->next) 343 | dr->next->prev = dr->prev; 344 | 345 | freedir_hlnk(dr); 346 | 347 | /* update sizes of parent directories if this isn't a hard link. 348 | * If this is a hard link, freedir_hlnk() would have done so already 349 | * 350 | * mtime is 0 here because recalculating the maximum at every parent 351 | * dir is expensive, but might be good feature to add later if desired */ 352 | addparentstats(dr->parent, dr->flags & FF_HLNKC ? 0 : -dr->size, dr->flags & FF_HLNKC ? 0 : -dr->asize, 0, -(dr->items+1)); 353 | 354 | free(dr); 355 | } 356 | 357 | 358 | const char *getpath(struct dir *cur) { 359 | static char *dat; 360 | static int datl = 0; 361 | struct dir *d, **list; 362 | int c, i; 363 | 364 | if(!cur->name[0]) 365 | return "/"; 366 | 367 | c = i = 1; 368 | for(d=cur; d!=NULL; d=d->parent) { 369 | i += strlen(d->name)+1; 370 | c++; 371 | } 372 | 373 | if(datl == 0) { 374 | datl = i; 375 | dat = xmalloc(i); 376 | } else if(datl < i) { 377 | datl = i; 378 | dat = xrealloc(dat, i); 379 | } 380 | list = xmalloc(c*sizeof(struct dir *)); 381 | 382 | c = 0; 383 | for(d=cur; d!=NULL; d=d->parent) 384 | list[c++] = d; 385 | 386 | dat[0] = '\0'; 387 | while(c--) { 388 | if(list[c]->parent) 389 | strcat(dat, "/"); 390 | strcat(dat, list[c]->name); 391 | } 392 | free(list); 393 | return dat; 394 | } 395 | 396 | 397 | struct dir *getroot(struct dir *d) { 398 | while(d && d->parent) 399 | d = d->parent; 400 | return d; 401 | } 402 | 403 | 404 | void addparentstats(struct dir *d, int64_t size, int64_t asize, uint64_t mtime, int items) { 405 | struct dir_ext *e; 406 | while(d) { 407 | d->size = adds64(d->size, size); 408 | d->asize = adds64(d->asize, asize); 409 | d->items += items; 410 | if (d->flags & FF_EXT) { 411 | e = dir_ext_ptr(d); 412 | e->mtime = (e->mtime > mtime) ? e->mtime : mtime; 413 | } 414 | d = d->parent; 415 | } 416 | } 417 | 418 | 419 | /* Apparently we can just resume drawing after endwin() and ncurses will pick 420 | * up where it left. Probably not very portable... */ 421 | #define oom_msg "\nOut of memory, press enter to try again or Ctrl-C to give up.\n" 422 | #define wrap_oom(f) \ 423 | void *ptr;\ 424 | char buf[128];\ 425 | while((ptr = f) == NULL) {\ 426 | close_nc();\ 427 | write(2, oom_msg, sizeof(oom_msg));\ 428 | read(0, buf, sizeof(buf));\ 429 | }\ 430 | return ptr; 431 | 432 | void *xmalloc(size_t size) { wrap_oom(malloc(size)) } 433 | void *xcalloc(size_t n, size_t size) { wrap_oom(calloc(n, size)) } 434 | void *xrealloc(void *mem, size_t size) { wrap_oom(realloc(mem, size)) } 435 | -------------------------------------------------------------------------------- /src/dir_scan.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include "global.h" 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH 38 | #include 39 | #endif 40 | 41 | #if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS 42 | #include 43 | #include 44 | #endif 45 | 46 | 47 | /* set S_BLKSIZE if not defined already in sys/stat.h */ 48 | #ifndef S_BLKSIZE 49 | # define S_BLKSIZE 512 50 | #endif 51 | 52 | 53 | int dir_scan_smfs; /* Stay on the same filesystem */ 54 | 55 | static uint64_t curdev; /* current device we're scanning on */ 56 | 57 | /* scratch space */ 58 | static struct dir *buf_dir; 59 | static struct dir_ext buf_ext[1]; 60 | 61 | 62 | #if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS 63 | int exclude_kernfs; /* Exclude Linux pseudo filesystems */ 64 | 65 | static int is_kernfs(unsigned long type) { 66 | if( 67 | #ifdef BINFMTFS_MAGIC 68 | type == BINFMTFS_MAGIC || 69 | #endif 70 | #ifdef BPF_FS_MAGIC 71 | type == BPF_FS_MAGIC || 72 | #endif 73 | #ifdef CGROUP_SUPER_MAGIC 74 | type == CGROUP_SUPER_MAGIC || 75 | #endif 76 | #ifdef CGROUP2_SUPER_MAGIC 77 | type == CGROUP2_SUPER_MAGIC|| 78 | #endif 79 | #ifdef DEBUGFS_MAGIC 80 | type == DEBUGFS_MAGIC || 81 | #endif 82 | #ifdef DEVPTS_SUPER_MAGIC 83 | type == DEVPTS_SUPER_MAGIC || 84 | #endif 85 | #ifdef PROC_SUPER_MAGIC 86 | type == PROC_SUPER_MAGIC || 87 | #endif 88 | #ifdef PSTOREFS_MAGIC 89 | type == PSTOREFS_MAGIC || 90 | #endif 91 | #ifdef SECURITYFS_MAGIC 92 | type == SECURITYFS_MAGIC || 93 | #endif 94 | #ifdef SELINUX_MAGIC 95 | type == SELINUX_MAGIC || 96 | #endif 97 | #ifdef SYSFS_MAGIC 98 | type == SYSFS_MAGIC || 99 | #endif 100 | #ifdef TRACEFS_MAGIC 101 | type == TRACEFS_MAGIC || 102 | #endif 103 | 0 104 | ) 105 | return 1; 106 | 107 | return 0; 108 | } 109 | #endif 110 | 111 | /* Populates the buf_dir and buf_ext with information from the stat struct. 112 | * Sets everything necessary for output_dir.item() except FF_ERR and FF_EXL. */ 113 | static void stat_to_dir(struct stat *fs) { 114 | buf_dir->flags |= FF_EXT; /* We always read extended data because it doesn't have an additional cost */ 115 | buf_dir->ino = (uint64_t)fs->st_ino; 116 | buf_dir->dev = (uint64_t)fs->st_dev; 117 | 118 | if(S_ISREG(fs->st_mode)) 119 | buf_dir->flags |= FF_FILE; 120 | else if(S_ISDIR(fs->st_mode)) 121 | buf_dir->flags |= FF_DIR; 122 | 123 | if(!S_ISDIR(fs->st_mode) && fs->st_nlink > 1) 124 | buf_dir->flags |= FF_HLNKC; 125 | 126 | if(dir_scan_smfs && curdev != buf_dir->dev) 127 | buf_dir->flags |= FF_OTHFS; 128 | 129 | if(!(buf_dir->flags & (FF_OTHFS|FF_EXL|FF_KERNFS))) { 130 | buf_dir->size = fs->st_blocks * S_BLKSIZE; 131 | buf_dir->asize = fs->st_size; 132 | } 133 | 134 | buf_ext->mode = fs->st_mode; 135 | buf_ext->mtime = fs->st_mtime; 136 | buf_ext->uid = (int)fs->st_uid; 137 | buf_ext->gid = (int)fs->st_gid; 138 | } 139 | 140 | 141 | /* Reads all filenames in the currently chdir'ed directory and stores it as a 142 | * nul-separated list of filenames. The list ends with an empty filename (i.e. 143 | * two nuls). . and .. are not included. Returned memory should be freed. *err 144 | * is set to 1 if some error occurred. Returns NULL if that error was fatal. 145 | * The reason for reading everything in memory first and then walking through 146 | * the list is to avoid eating too many file descriptors in a deeply recursive 147 | * directory. */ 148 | static char *dir_read(int *err) { 149 | DIR *dir; 150 | struct dirent *item; 151 | char *buf = NULL; 152 | size_t buflen = 512; 153 | size_t off = 0; 154 | 155 | if((dir = opendir(".")) == NULL) { 156 | *err = 1; 157 | return NULL; 158 | } 159 | 160 | buf = xmalloc(buflen); 161 | errno = 0; 162 | 163 | while((item = readdir(dir)) != NULL) { 164 | if(item->d_name[0] == '.' && (item->d_name[1] == 0 || (item->d_name[1] == '.' && item->d_name[2] == 0))) 165 | continue; 166 | size_t req = off+3+strlen(item->d_name); 167 | if(req > buflen) { 168 | buflen = req < buflen*2 ? buflen*2 : req; 169 | buf = xrealloc(buf, buflen); 170 | } 171 | strcpy(buf+off, item->d_name); 172 | off += strlen(item->d_name)+1; 173 | } 174 | if(errno) 175 | *err = 1; 176 | if(closedir(dir) < 0) 177 | *err = 1; 178 | 179 | buf[off] = 0; 180 | buf[off+1] = 0; 181 | return buf; 182 | } 183 | 184 | 185 | static int dir_walk(char *); 186 | 187 | 188 | /* Tries to recurse into the current directory item (buf_dir is assumed to be the current dir) */ 189 | static int dir_scan_recurse(const char *name) { 190 | int fail = 0; 191 | char *dir; 192 | 193 | if(chdir(name)) { 194 | dir_setlasterr(dir_curpath); 195 | buf_dir->flags |= FF_ERR; 196 | if(dir_output.item(buf_dir, name, buf_ext) || dir_output.item(NULL, 0, NULL)) { 197 | dir_seterr("Output error: %s", strerror(errno)); 198 | return 1; 199 | } 200 | return 0; 201 | } 202 | 203 | if((dir = dir_read(&fail)) == NULL) { 204 | dir_setlasterr(dir_curpath); 205 | buf_dir->flags |= FF_ERR; 206 | if(dir_output.item(buf_dir, name, buf_ext) || dir_output.item(NULL, 0, NULL)) { 207 | dir_seterr("Output error: %s", strerror(errno)); 208 | return 1; 209 | } 210 | if(chdir("..")) { 211 | dir_seterr("Error going back to parent directory: %s", strerror(errno)); 212 | return 1; 213 | } else 214 | return 0; 215 | } 216 | 217 | /* readdir() failed halfway, not fatal. */ 218 | if(fail) 219 | buf_dir->flags |= FF_ERR; 220 | 221 | if(dir_output.item(buf_dir, name, buf_ext)) { 222 | dir_seterr("Output error: %s", strerror(errno)); 223 | return 1; 224 | } 225 | fail = dir_walk(dir); 226 | if(dir_output.item(NULL, 0, NULL)) { 227 | dir_seterr("Output error: %s", strerror(errno)); 228 | return 1; 229 | } 230 | 231 | /* Not being able to chdir back is fatal */ 232 | if(!fail && chdir("..")) { 233 | dir_seterr("Error going back to parent directory: %s", strerror(errno)); 234 | return 1; 235 | } 236 | 237 | return fail; 238 | } 239 | 240 | 241 | /* Scans and adds a single item. Recurses into dir_walk() again if this is a 242 | * directory. Assumes we're chdir'ed in the directory in which this item 243 | * resides. */ 244 | static int dir_scan_item(const char *name) { 245 | static struct stat st, stl; 246 | int fail = 0; 247 | 248 | #ifdef __CYGWIN__ 249 | /* /proc/registry names may contain slashes */ 250 | if(strchr(name, '/') || strchr(name, '\\')) { 251 | buf_dir->flags |= FF_ERR; 252 | dir_setlasterr(dir_curpath); 253 | } 254 | #endif 255 | 256 | if(exclude_match(dir_curpath)) 257 | buf_dir->flags |= FF_EXL; 258 | 259 | if(!(buf_dir->flags & (FF_ERR|FF_EXL)) && lstat(name, &st)) { 260 | buf_dir->flags |= FF_ERR; 261 | dir_setlasterr(dir_curpath); 262 | } 263 | 264 | #if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS 265 | if(exclude_kernfs && !(buf_dir->flags & (FF_ERR|FF_EXL)) && S_ISDIR(st.st_mode)) { 266 | struct statfs fst; 267 | if(statfs(name, &fst)) { 268 | buf_dir->flags |= FF_ERR; 269 | dir_setlasterr(dir_curpath); 270 | } else if(is_kernfs(fst.f_type)) 271 | buf_dir->flags |= FF_KERNFS; 272 | } 273 | #endif 274 | 275 | #if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH 276 | if(!follow_firmlinks) { 277 | struct attrlist list = { 278 | .bitmapcount = ATTR_BIT_MAP_COUNT, 279 | .forkattr = ATTR_CMNEXT_NOFIRMLINKPATH, 280 | }; 281 | struct { 282 | uint32_t length; 283 | attrreference_t reference; 284 | char extra[PATH_MAX]; 285 | } __attribute__((aligned(4), packed)) attributes; 286 | if (getattrlist(name, &list, &attributes, sizeof(attributes), FSOPT_ATTR_CMN_EXTENDED) == -1) { 287 | buf_dir->flags |= FF_ERR; 288 | dir_setlasterr(dir_curpath); 289 | } else if (strcmp(dir_curpath, (char *)&attributes.reference + attributes.reference.attr_dataoffset)) 290 | buf_dir->flags |= FF_FRMLNK; 291 | } 292 | #endif 293 | 294 | if(!(buf_dir->flags & (FF_ERR|FF_EXL))) { 295 | if(follow_symlinks && S_ISLNK(st.st_mode) && !stat(name, &stl) && !S_ISDIR(stl.st_mode)) 296 | stat_to_dir(&stl); 297 | else 298 | stat_to_dir(&st); 299 | } 300 | 301 | if(cachedir_tags && (buf_dir->flags & FF_DIR) && !(buf_dir->flags & (FF_ERR|FF_EXL|FF_OTHFS|FF_KERNFS|FF_FRMLNK))) 302 | if(has_cachedir_tag(name)) { 303 | buf_dir->flags |= FF_EXL; 304 | buf_dir->size = buf_dir->asize = 0; 305 | } 306 | 307 | /* Recurse into the dir or output the item */ 308 | if(buf_dir->flags & FF_DIR && !(buf_dir->flags & (FF_ERR|FF_EXL|FF_OTHFS|FF_KERNFS|FF_FRMLNK))) 309 | fail = dir_scan_recurse(name); 310 | else if(buf_dir->flags & FF_DIR) { 311 | if(dir_output.item(buf_dir, name, buf_ext) || dir_output.item(NULL, 0, NULL)) { 312 | dir_seterr("Output error: %s", strerror(errno)); 313 | fail = 1; 314 | } 315 | } else if(dir_output.item(buf_dir, name, buf_ext)) { 316 | dir_seterr("Output error: %s", strerror(errno)); 317 | fail = 1; 318 | } 319 | 320 | return fail || input_handle(1); 321 | } 322 | 323 | 324 | /* Walks through the directory that we're currently chdir'ed to. *dir contains 325 | * the filenames as returned by dir_read(), and will be freed automatically by 326 | * this function. */ 327 | static int dir_walk(char *dir) { 328 | int fail = 0; 329 | char *cur; 330 | 331 | fail = 0; 332 | for(cur=dir; !fail&&cur&&*cur; cur+=strlen(cur)+1) { 333 | dir_curpath_enter(cur); 334 | memset(buf_dir, 0, offsetof(struct dir, name)); 335 | memset(buf_ext, 0, sizeof(struct dir_ext)); 336 | fail = dir_scan_item(cur); 337 | dir_curpath_leave(); 338 | } 339 | 340 | free(dir); 341 | return fail; 342 | } 343 | 344 | 345 | static int process(void) { 346 | char *path; 347 | char *dir; 348 | int fail = 0; 349 | struct stat fs; 350 | 351 | memset(buf_dir, 0, offsetof(struct dir, name)); 352 | memset(buf_ext, 0, sizeof(struct dir_ext)); 353 | 354 | if((path = path_real(dir_curpath)) == NULL) 355 | dir_seterr("Error obtaining full path: %s", strerror(errno)); 356 | else { 357 | dir_curpath_set(path); 358 | free(path); 359 | } 360 | 361 | if(!dir_fatalerr && path_chdir(dir_curpath) < 0) 362 | dir_seterr("Error changing directory: %s", strerror(errno)); 363 | 364 | /* Can these even fail after a chdir? */ 365 | if(!dir_fatalerr && lstat(".", &fs) != 0) 366 | dir_seterr("Error obtaining directory information: %s", strerror(errno)); 367 | if(!dir_fatalerr && !S_ISDIR(fs.st_mode)) 368 | dir_seterr("Not a directory"); 369 | 370 | if(!dir_fatalerr && !(dir = dir_read(&fail))) 371 | dir_seterr("Error reading directory: %s", strerror(errno)); 372 | 373 | if(!dir_fatalerr) { 374 | curdev = (uint64_t)fs.st_dev; 375 | if(fail) 376 | buf_dir->flags |= FF_ERR; 377 | stat_to_dir(&fs); 378 | 379 | if(dir_output.item(buf_dir, dir_curpath, buf_ext)) { 380 | dir_seterr("Output error: %s", strerror(errno)); 381 | fail = 1; 382 | } 383 | if(!fail) 384 | fail = dir_walk(dir); 385 | if(!fail && dir_output.item(NULL, 0, NULL)) { 386 | dir_seterr("Output error: %s", strerror(errno)); 387 | fail = 1; 388 | } 389 | } 390 | 391 | while(dir_fatalerr && !input_handle(0)) 392 | ; 393 | return dir_output.final(dir_fatalerr || fail); 394 | } 395 | 396 | 397 | void dir_scan_init(const char *path) { 398 | dir_curpath_set(path); 399 | dir_setlasterr(NULL); 400 | dir_seterr(NULL); 401 | dir_process = process; 402 | if (!buf_dir) 403 | buf_dir = xmalloc(dir_memsize("")); 404 | pstate = ST_CALC; 405 | } 406 | -------------------------------------------------------------------------------- /doc/ncdu.pod: -------------------------------------------------------------------------------- 1 | =head1 NAME 2 | 3 | B - NCurses Disk Usage 4 | 5 | 6 | =head1 SYNOPSIS 7 | 8 | B [I] I 9 | 10 | 11 | =head1 DESCRIPTION 12 | 13 | ncdu (NCurses Disk Usage) is a curses-based version of the well-known 'du', and 14 | provides a fast way to see what directories are using your disk space. 15 | 16 | 17 | =head1 OPTIONS 18 | 19 | =head2 Mode Selection 20 | 21 | =over 22 | 23 | =item -h, --help 24 | 25 | Print a short help message and quit. 26 | 27 | =item -v, -V, --version 28 | 29 | Print ncdu version and quit. 30 | 31 | =item -f I 32 | 33 | Load the given file, which has earlier been created with the C<-o> option. If 34 | I is equivalent to C<->, the file is read from standard input. 35 | 36 | For the sake of preventing a screw-up, the current version of ncdu will assume 37 | that the directory information in the imported file does not represent the 38 | filesystem on which the file is being imported. That is, the refresh, file 39 | deletion and shell spawning options in the browser will be disabled. 40 | 41 | =item I 42 | 43 | Scan the given directory. 44 | 45 | =item -o I 46 | 47 | Export all necessary information to I instead of opening the browser 48 | interface. If I is C<->, the data is written to standard output. See the 49 | examples section below for some handy use cases. 50 | 51 | Be warned that the exported data may grow quite large when exporting a 52 | directory with many files. 10.000 files will get you an export in the order of 53 | 600 to 700 KiB uncompressed, or a little over 100 KiB when compressed with 54 | gzip. This scales linearly, so be prepared to handle a few tens of megabytes 55 | when dealing with millions of files. 56 | 57 | =item -e 58 | 59 | Enable extended information mode. This will, in addition to the usual file 60 | information, also read the ownership, permissions and last modification time 61 | for each file. This will result in higher memory usage (by roughly ~30%) and in 62 | a larger output file when exporting. 63 | 64 | When using the file export/import function, this flag will need to be added 65 | both when exporting (to make sure the information is added to the export), and 66 | when importing (to read this extra information in memory). This flag has no 67 | effect when importing a file that has been exported without the extended 68 | information. 69 | 70 | This enables viewing and sorting by the latest child mtime, or modified time, 71 | using 'm' and 'M', respectively. 72 | 73 | =back 74 | 75 | =head2 Interface options 76 | 77 | =over 78 | 79 | =item -0 80 | 81 | Don't give any feedback while scanning a directory or importing a file, other 82 | than when a fatal error occurs. Ncurses will not be initialized until the scan 83 | is complete. When exporting the data with C<-o>, ncurses will not be 84 | initialized at all. This option is the default when exporting to standard 85 | output. 86 | 87 | =item -1 88 | 89 | Similar to C<-0>, but does give feedback on the scanning progress with a single 90 | line of output. This option is the default when exporting to a file. 91 | 92 | In some cases, the ncurses browser interface which you'll see after the 93 | scan/import is complete may look garbled when using this option. If you're not 94 | exporting to a file, C<-2> is probably a better choice. 95 | 96 | =item -2 97 | 98 | Provide a full-screen ncurses interface while scanning a directory or importing 99 | a file. This is the only interface that provides feedback on any non-fatal 100 | errors while scanning. 101 | 102 | =item -q 103 | 104 | Quiet mode. While scanning or importing the directory, ncdu will update the 105 | screen 10 times a second by default, this will be decreased to once every 2 106 | seconds in quiet mode. Use this feature to save bandwidth over remote 107 | connections. This option has no effect when C<-0> is used. 108 | 109 | =item -r 110 | 111 | Read-only mode. This will disable the built-in file deletion feature. This 112 | option has no effect when C<-o> is used, because there will not be a browser 113 | interface in that case. It has no effect when C<-f> is used, either, because 114 | the deletion feature is disabled in that case anyway. 115 | 116 | WARNING: This option will only prevent deletion through the file browser. It is 117 | still possible to spawn a shell from ncdu and delete or modify files from 118 | there. To disable that feature as well, pass the C<-r> option twice (see 119 | C<-rr>). 120 | 121 | =item -rr 122 | 123 | In addition to C<-r>, this will also disable the shell spawning feature of the 124 | file browser. 125 | 126 | =item --si 127 | 128 | List sizes using base 10 prefixes, that is, powers of 1000 (KB, MB, etc), as 129 | defined in the International System of Units (SI), instead of the usual base 2 130 | prefixes, that is, powers of 1024 (KiB, MiB, etc). 131 | 132 | =item --confirm-quit 133 | 134 | Requires a confirmation before quitting ncdu. Very helpful when you 135 | accidentally press 'q' during or after a very long scan. 136 | 137 | =item --color I 138 | 139 | Select a color scheme. Currently only two schemes are recognized: I to 140 | disable colors (the default) and I for a color scheme intended for dark 141 | backgrounds. 142 | 143 | =back 144 | 145 | =head2 Scan Options 146 | 147 | These options affect the scanning progress, and have no effect when importing 148 | directory information from a file. 149 | 150 | =over 151 | 152 | =item -x 153 | 154 | Do not cross filesystem boundaries, i.e. only count files and directories on 155 | the same filesystem as the directory being scanned. 156 | 157 | =item --exclude I 158 | 159 | Exclude files that match I. The files will still be displayed by 160 | default, but are not counted towards the disk usage statistics. This argument 161 | can be added multiple times to add more patterns. 162 | 163 | =item -X I, --exclude-from I 164 | 165 | Exclude files that match any pattern in I. Patterns should be separated 166 | by a newline. 167 | 168 | =item --exclude-caches 169 | 170 | Exclude directories containing CACHEDIR.TAG. The directories will still be 171 | displayed, but not their content, and they are not counted towards the disk 172 | usage statistics. 173 | See http://www.brynosaurus.com/cachedir/ 174 | 175 | =item -L, --follow-symlinks 176 | 177 | Follow symlinks and count the size of the file they point to. As of ncdu 1.14, 178 | this option will not follow symlinks to directories and will count each 179 | symlinked file as a unique file (i.e. unlike how hard links are handled). This 180 | is subject to change in later versions. 181 | 182 | =item --exclude-firmlinks 183 | 184 | (MacOS only) Exclude firmlinks. 185 | 186 | =item --exclude-kernfs 187 | 188 | (Linux only) Exclude Linux pseudo filesystems, e.g. /proc (procfs), /sys (sysfs). 189 | 190 | The complete list of currently known pseudo filesystems is: binfmt, bpf, cgroup, 191 | cgroup2, debug, devpts, proc, pstore, security, selinux, sys, trace. 192 | 193 | =back 194 | 195 | 196 | =head1 KEYS 197 | 198 | =over 199 | 200 | =item ? 201 | 202 | Show help + keys + about screen 203 | 204 | =item up, down j, k 205 | 206 | Cycle through the items 207 | 208 | =item right, enter, l 209 | 210 | Open selected directory 211 | 212 | =item left, <, h 213 | 214 | Go to parent directory 215 | 216 | =item n 217 | 218 | Order by filename (press again for descending order) 219 | 220 | =item s 221 | 222 | Order by filesize (press again for descending order) 223 | 224 | =item C 225 | 226 | Order by number of items (press again for descending order) 227 | 228 | =item a 229 | 230 | Toggle between showing disk usage and showing apparent size. 231 | 232 | =item M 233 | 234 | Order by latest child mtime, or modified time. (press again for descending order) 235 | Requires the -e flag. 236 | 237 | =item d 238 | 239 | Delete the selected file or directory. An error message will be shown when the 240 | contents of the directory do not match or do not exist anymore on the 241 | filesystem. 242 | 243 | =item t 244 | 245 | Toggle dirs before files when sorting. 246 | 247 | =item g 248 | 249 | Toggle between showing percentage, graph, both, or none. Percentage is relative 250 | to the size of the current directory, graph is relative to the largest item in 251 | the current directory. 252 | 253 | =item c 254 | 255 | Toggle display of child item counts. 256 | 257 | =item m 258 | 259 | Toggle display of latest child mtime, or modified time. Requires the -e flag. 260 | 261 | =item e 262 | 263 | Show/hide 'hidden' or 'excluded' files and directories. Please note that even 264 | though you can't see the hidden files and directories, they are still there and 265 | they are still included in the directory sizes. If you suspect that the totals 266 | shown at the bottom of the screen are not correct, make sure you haven't 267 | enabled this option. 268 | 269 | =item i 270 | 271 | Show information about the current selected item. 272 | 273 | =item r 274 | 275 | Refresh/recalculate the current directory. 276 | 277 | =item b 278 | 279 | Spawn shell in current directory. 280 | 281 | Ncdu will determine your preferred shell from the C or C 282 | variable (in that order), or will call C if neither are set. This 283 | allows you to also configure another command to be run when he 'b' key is 284 | pressed. For example, to spawn the L file manager instead of a shell, 285 | run ncdu as follows: 286 | 287 | export NCDU_SHELL=vifm 288 | ncdu 289 | 290 | =item q 291 | 292 | Quit 293 | 294 | =back 295 | 296 | 297 | =head1 FILE FLAGS 298 | 299 | Entries in the browser interface may be prefixed by a one-character flag. These 300 | flags have the following meaning: 301 | 302 | =over 303 | 304 | =item ! 305 | 306 | An error occurred while reading this directory. 307 | 308 | =item . 309 | 310 | An error occurred while reading a subdirectory, so the indicated size may not be 311 | correct. 312 | 313 | =item < 314 | 315 | File or directory is excluded from the statistics by using exclude patterns. 316 | 317 | =item > 318 | 319 | Directory is on another filesystem. 320 | 321 | =item ^ 322 | 323 | Directory is excluded from the statistics due to being a Linux pseudo filesystem. 324 | 325 | =item @ 326 | 327 | This is neither a file nor a folder (symlink, socket, ...). 328 | 329 | =item H 330 | 331 | Same file was already counted (hard link). 332 | 333 | =item e 334 | 335 | Empty directory. 336 | 337 | =back 338 | 339 | 340 | =head1 EXAMPLES 341 | 342 | To scan and browse the directory you're currently in, all you need is a simple: 343 | 344 | ncdu 345 | 346 | If you want to scan a full filesystem, your root filesystem, for example, then 347 | you'll want to use C<-x>: 348 | 349 | ncdu -x / 350 | 351 | Since scanning a large directory may take a while, you can scan a directory and 352 | export the results for later viewing: 353 | 354 | ncdu -1xo- / | gzip >export.gz 355 | # ...some time later: 356 | zcat export.gz | ncdu -f- 357 | 358 | To export from a cron job, make sure to replace C<-1> with C<-0> to suppress 359 | any unnecessary output. 360 | 361 | You can also export a directory and browse it once scanning is done: 362 | 363 | ncdu -o- | tee export.file | ./ncdu -f- 364 | 365 | The same is possible with gzip compression, but is a bit kludgey: 366 | 367 | ncdu -o- | gzip | tee export.gz | gunzip | ./ncdu -f- 368 | 369 | To scan a system remotely, but browse through the files locally: 370 | 371 | ssh -C user@system ncdu -o- / | ./ncdu -f- 372 | 373 | The C<-C> option to ssh enables compression, which will be very useful over 374 | slow links. Remote scanning and local viewing has two major advantages when 375 | compared to running ncdu directly on the remote system: You can browse through 376 | the scanned directory on the local system without any network latency, and ncdu 377 | does not keep the entire directory structure in memory when exporting, so you 378 | won't consume much memory on the remote system. 379 | 380 | 381 | =head1 HARD LINKS 382 | 383 | Every disk usage analysis utility has its own way of (not) counting hard links. 384 | There does not seem to be any universally agreed method of handling hard links, 385 | and it is even inconsistent among different versions of ncdu. This section 386 | explains what each version of ncdu does. 387 | 388 | ncdu 1.5 and below does not support any hard link detection at all: each link 389 | is considered a separate inode and its size is counted for every link. This 390 | means that the displayed directory sizes are incorrect when analyzing 391 | directories which contain hard links. 392 | 393 | ncdu 1.6 has basic hard link detection: When a link to a previously encountered 394 | inode is detected, the link is considered to have a file size of zero bytes. 395 | Its size is not counted again, and the link is indicated in the browser 396 | interface with a 'H' mark. The displayed directory sizes are only correct when 397 | all links to an inode reside within that directory. When this is not the case, 398 | the sizes may or may not be correct, depending on which links were considered 399 | as "duplicate" and which as "original". The indicated size of the topmost 400 | directory (that is, the one specified on the command line upon starting ncdu) 401 | is always correct. 402 | 403 | ncdu 1.7 and later has improved hard link detection. Each file that has more 404 | than two links has the "H" mark visible in the browser interface. Each hard 405 | link is counted exactly once for every directory it appears in. The indicated 406 | size of each directory is therefore, correctly, the sum of the sizes of all 407 | unique inodes that can be found in that directory. Note, however, that this may 408 | not always be same as the space that will be reclaimed after deleting the 409 | directory, as some inodes may still be accessible from hard links outside it. 410 | 411 | 412 | =head1 BUGS 413 | 414 | Directory hard links are not supported. They will not be detected as being hard 415 | links, and will thus be scanned and counted multiple times. 416 | 417 | Some minor glitches may appear when displaying filenames that contain multibyte 418 | or multicolumn characters. 419 | 420 | All sizes are internally represented as a signed 64bit integer. If you have a 421 | directory larger than 8 EiB minus one byte, ncdu will clip its size to 8 EiB 422 | minus one byte. When deleting items in a directory with a clipped size, the 423 | resulting sizes will be incorrect. 424 | 425 | Item counts are stored in a signed 32-bit integer without overflow detection. 426 | If you have a directory with more than 2 billion files, quite literally 427 | anything can happen. 428 | 429 | On macOS 10.15 and later, running ncdu on the root directory without 430 | `--exclude-firmlinks` may cause directories to be scanned and counted multiple 431 | times. Firmlink cycles are currently (1.15.1) not detected, so it may also 432 | cause ncdu to get stuck in an infinite loop and eventually run out of memory. 433 | 434 | Please report any other bugs you may find at the bug tracker, which can be 435 | found on the web site at https://dev.yorhel.nl/ncdu 436 | 437 | 438 | =head1 AUTHOR 439 | 440 | Written by Yoran Heling . 441 | 442 | 443 | =head1 SEE ALSO 444 | 445 | L 446 | -------------------------------------------------------------------------------- /deps/khashl.h: -------------------------------------------------------------------------------- 1 | /* The MIT License 2 | 3 | Copyright (c) 2019 by Attractive Chaos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #ifndef __AC_KHASHL_H 27 | #define __AC_KHASHL_H 28 | 29 | #define AC_VERSION_KHASHL_H "0.1" 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | /************************************ 36 | * Compiler specific configurations * 37 | ************************************/ 38 | 39 | #if UINT_MAX == 0xffffffffu 40 | typedef unsigned int khint32_t; 41 | #elif ULONG_MAX == 0xffffffffu 42 | typedef unsigned long khint32_t; 43 | #endif 44 | 45 | #if ULONG_MAX == ULLONG_MAX 46 | typedef unsigned long khint64_t; 47 | #else 48 | typedef unsigned long long khint64_t; 49 | #endif 50 | 51 | #ifndef kh_inline 52 | #ifdef _MSC_VER 53 | #define kh_inline __inline 54 | #else 55 | #define kh_inline inline 56 | #endif 57 | #endif /* kh_inline */ 58 | 59 | #ifndef klib_unused 60 | #if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) 61 | #define klib_unused __attribute__ ((__unused__)) 62 | #else 63 | #define klib_unused 64 | #endif 65 | #endif /* klib_unused */ 66 | 67 | #define KH_LOCAL static kh_inline klib_unused 68 | 69 | typedef khint32_t khint_t; 70 | 71 | /****************** 72 | * malloc aliases * 73 | ******************/ 74 | 75 | #ifndef kcalloc 76 | #define kcalloc(N,Z) calloc(N,Z) 77 | #endif 78 | #ifndef kmalloc 79 | #define kmalloc(Z) malloc(Z) 80 | #endif 81 | #ifndef krealloc 82 | #define krealloc(P,Z) realloc(P,Z) 83 | #endif 84 | #ifndef kfree 85 | #define kfree(P) free(P) 86 | #endif 87 | 88 | /**************************** 89 | * Simple private functions * 90 | ****************************/ 91 | 92 | #define __kh_used(flag, i) (flag[i>>5] >> (i&0x1fU) & 1U) 93 | #define __kh_set_used(flag, i) (flag[i>>5] |= 1U<<(i&0x1fU)) 94 | #define __kh_set_unused(flag, i) (flag[i>>5] &= ~(1U<<(i&0x1fU))) 95 | 96 | #define __kh_fsize(m) ((m) < 32? 1 : (m)>>5) 97 | 98 | static kh_inline khint_t __kh_h2b(khint_t hash, khint_t bits) { return hash * 2654435769U >> (32 - bits); } 99 | 100 | /******************* 101 | * Hash table base * 102 | *******************/ 103 | 104 | #define __KHASHL_TYPE(HType, khkey_t) \ 105 | typedef struct { \ 106 | khint_t bits, count; \ 107 | khint32_t *used; \ 108 | khkey_t *keys; \ 109 | } HType; 110 | 111 | #define __KHASHL_PROTOTYPES(HType, prefix, khkey_t) \ 112 | extern HType *prefix##_init(void); \ 113 | extern void prefix##_destroy(HType *h); \ 114 | extern void prefix##_clear(HType *h); \ 115 | extern khint_t prefix##_getp(const HType *h, const khkey_t *key); \ 116 | extern int prefix##_resize(HType *h, khint_t new_n_buckets); \ 117 | extern khint_t prefix##_putp(HType *h, const khkey_t *key, int *absent); \ 118 | extern void prefix##_del(HType *h, khint_t k); 119 | 120 | #define __KHASHL_IMPL_BASIC(SCOPE, HType, prefix) \ 121 | SCOPE HType *prefix##_init(void) { \ 122 | return (HType*)kcalloc(1, sizeof(HType)); \ 123 | } \ 124 | SCOPE void prefix##_destroy(HType *h) { \ 125 | if (!h) return; \ 126 | kfree((void *)h->keys); kfree(h->used); \ 127 | kfree(h); \ 128 | } \ 129 | SCOPE void prefix##_clear(HType *h) { \ 130 | if (h && h->used) { \ 131 | uint32_t n_buckets = 1U << h->bits; \ 132 | memset(h->used, 0, __kh_fsize(n_buckets) * sizeof(khint32_t)); \ 133 | h->count = 0; \ 134 | } \ 135 | } 136 | 137 | #define __KHASHL_IMPL_GET(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ 138 | SCOPE khint_t prefix##_getp(const HType *h, const khkey_t *key) { \ 139 | khint_t i, last, n_buckets, mask; \ 140 | if (h->keys == 0) return 0; \ 141 | n_buckets = 1U << h->bits; \ 142 | mask = n_buckets - 1U; \ 143 | i = last = __kh_h2b(__hash_fn(*key), h->bits); \ 144 | while (__kh_used(h->used, i) && !__hash_eq(h->keys[i], *key)) { \ 145 | i = (i + 1U) & mask; \ 146 | if (i == last) return n_buckets; \ 147 | } \ 148 | return !__kh_used(h->used, i)? n_buckets : i; \ 149 | } \ 150 | SCOPE khint_t prefix##_get(const HType *h, khkey_t key) { return prefix##_getp(h, &key); } 151 | 152 | #define __KHASHL_IMPL_RESIZE(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ 153 | SCOPE int prefix##_resize(HType *h, khint_t new_n_buckets) { \ 154 | khint32_t *new_used = 0; \ 155 | khint_t j = 0, x = new_n_buckets, n_buckets, new_bits, new_mask; \ 156 | while ((x >>= 1) != 0) ++j; \ 157 | if (new_n_buckets & (new_n_buckets - 1)) ++j; \ 158 | new_bits = j > 2? j : 2; \ 159 | new_n_buckets = 1U << new_bits; \ 160 | if (h->count > (new_n_buckets>>1) + (new_n_buckets>>2)) return 0; /* requested size is too small */ \ 161 | new_used = (khint32_t*)kmalloc(__kh_fsize(new_n_buckets) * sizeof(khint32_t)); \ 162 | memset(new_used, 0, __kh_fsize(new_n_buckets) * sizeof(khint32_t)); \ 163 | if (!new_used) return -1; /* not enough memory */ \ 164 | n_buckets = h->keys? 1U<bits : 0U; \ 165 | if (n_buckets < new_n_buckets) { /* expand */ \ 166 | khkey_t *new_keys = (khkey_t*)krealloc((void*)h->keys, new_n_buckets * sizeof(khkey_t)); \ 167 | if (!new_keys) { kfree(new_used); return -1; } \ 168 | h->keys = new_keys; \ 169 | } /* otherwise shrink */ \ 170 | new_mask = new_n_buckets - 1; \ 171 | for (j = 0; j != n_buckets; ++j) { \ 172 | khkey_t key; \ 173 | if (!__kh_used(h->used, j)) continue; \ 174 | key = h->keys[j]; \ 175 | __kh_set_unused(h->used, j); \ 176 | while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ 177 | khint_t i; \ 178 | i = __kh_h2b(__hash_fn(key), new_bits); \ 179 | while (__kh_used(new_used, i)) i = (i + 1) & new_mask; \ 180 | __kh_set_used(new_used, i); \ 181 | if (i < n_buckets && __kh_used(h->used, i)) { /* kick out the existing element */ \ 182 | { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ 183 | __kh_set_unused(h->used, i); /* mark it as deleted in the old hash table */ \ 184 | } else { /* write the element and jump out of the loop */ \ 185 | h->keys[i] = key; \ 186 | break; \ 187 | } \ 188 | } \ 189 | } \ 190 | if (n_buckets > new_n_buckets) /* shrink the hash table */ \ 191 | h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ 192 | kfree(h->used); /* free the working space */ \ 193 | h->used = new_used, h->bits = new_bits; \ 194 | return 0; \ 195 | } 196 | 197 | #define __KHASHL_IMPL_PUT(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ 198 | SCOPE khint_t prefix##_putp(HType *h, const khkey_t *key, int *absent) { \ 199 | khint_t n_buckets, i, last, mask; \ 200 | n_buckets = h->keys? 1U<bits : 0U; \ 201 | *absent = -1; \ 202 | if (h->count >= (n_buckets>>1) + (n_buckets>>2)) { /* rehashing */ \ 203 | if (prefix##_resize(h, n_buckets + 1U) < 0) \ 204 | return n_buckets; \ 205 | n_buckets = 1U<bits; \ 206 | } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ 207 | mask = n_buckets - 1; \ 208 | i = last = __kh_h2b(__hash_fn(*key), h->bits); \ 209 | while (__kh_used(h->used, i) && !__hash_eq(h->keys[i], *key)) { \ 210 | i = (i + 1U) & mask; \ 211 | if (i == last) break; \ 212 | } \ 213 | if (!__kh_used(h->used, i)) { /* not present at all */ \ 214 | h->keys[i] = *key; \ 215 | __kh_set_used(h->used, i); \ 216 | ++h->count; \ 217 | *absent = 1; \ 218 | } else *absent = 0; /* Don't touch h->keys[i] if present */ \ 219 | return i; \ 220 | } \ 221 | SCOPE khint_t prefix##_put(HType *h, khkey_t key, int *absent) { return prefix##_putp(h, &key, absent); } 222 | 223 | #define __KHASHL_IMPL_DEL(SCOPE, HType, prefix, khkey_t, __hash_fn) \ 224 | SCOPE int prefix##_del(HType *h, khint_t i) { \ 225 | khint_t j = i, k, mask, n_buckets; \ 226 | if (h->keys == 0) return 0; \ 227 | n_buckets = 1U<bits; \ 228 | mask = n_buckets - 1U; \ 229 | while (1) { \ 230 | j = (j + 1U) & mask; \ 231 | if (j == i || !__kh_used(h->used, j)) break; /* j==i only when the table is completely full */ \ 232 | k = __kh_h2b(__hash_fn(h->keys[j]), h->bits); \ 233 | if ((j > i && (k <= i || k > j)) || (j < i && (k <= i && k > j))) \ 234 | h->keys[i] = h->keys[j], i = j; \ 235 | } \ 236 | __kh_set_unused(h->used, i); \ 237 | --h->count; \ 238 | return 1; \ 239 | } 240 | 241 | #define KHASHL_DECLARE(HType, prefix, khkey_t) \ 242 | __KHASHL_TYPE(HType, khkey_t) \ 243 | __KHASHL_PROTOTYPES(HType, prefix, khkey_t) 244 | 245 | #define KHASHL_INIT(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ 246 | __KHASHL_TYPE(HType, khkey_t) \ 247 | __KHASHL_IMPL_BASIC(SCOPE, HType, prefix) \ 248 | __KHASHL_IMPL_GET(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ 249 | __KHASHL_IMPL_RESIZE(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ 250 | __KHASHL_IMPL_PUT(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ 251 | __KHASHL_IMPL_DEL(SCOPE, HType, prefix, khkey_t, __hash_fn) 252 | 253 | /***************************** 254 | * More convenient interface * 255 | *****************************/ 256 | 257 | #define __kh_packed __attribute__ ((__packed__)) 258 | #define __kh_cached_hash(x) ((x).hash) 259 | 260 | #define KHASHL_SET_INIT(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ 261 | typedef struct { khkey_t key; } __kh_packed HType##_s_bucket_t; \ 262 | static kh_inline khint_t prefix##_s_hash(HType##_s_bucket_t x) { return __hash_fn(x.key); } \ 263 | static kh_inline int prefix##_s_eq(HType##_s_bucket_t x, HType##_s_bucket_t y) { return __hash_eq(x.key, y.key); } \ 264 | KHASHL_INIT(KH_LOCAL, HType, prefix##_s, HType##_s_bucket_t, prefix##_s_hash, prefix##_s_eq) \ 265 | SCOPE HType *prefix##_init(void) { return prefix##_s_init(); } \ 266 | SCOPE void prefix##_destroy(HType *h) { prefix##_s_destroy(h); } \ 267 | SCOPE khint_t prefix##_get(const HType *h, khkey_t key) { HType##_s_bucket_t t; t.key = key; return prefix##_s_getp(h, &t); } \ 268 | SCOPE int prefix##_del(HType *h, khint_t k) { return prefix##_s_del(h, k); } \ 269 | SCOPE khint_t prefix##_put(HType *h, khkey_t key, int *absent) { HType##_s_bucket_t t; t.key = key; return prefix##_s_putp(h, &t, absent); } 270 | 271 | #define KHASHL_MAP_INIT(SCOPE, HType, prefix, khkey_t, kh_val_t, __hash_fn, __hash_eq) \ 272 | typedef struct { khkey_t key; kh_val_t val; } __kh_packed HType##_m_bucket_t; \ 273 | static kh_inline khint_t prefix##_m_hash(HType##_m_bucket_t x) { return __hash_fn(x.key); } \ 274 | static kh_inline int prefix##_m_eq(HType##_m_bucket_t x, HType##_m_bucket_t y) { return __hash_eq(x.key, y.key); } \ 275 | KHASHL_INIT(KH_LOCAL, HType, prefix##_m, HType##_m_bucket_t, prefix##_m_hash, prefix##_m_eq) \ 276 | SCOPE HType *prefix##_init(void) { return prefix##_m_init(); } \ 277 | SCOPE void prefix##_destroy(HType *h) { prefix##_m_destroy(h); } \ 278 | SCOPE khint_t prefix##_get(const HType *h, khkey_t key) { HType##_m_bucket_t t; t.key = key; return prefix##_m_getp(h, &t); } \ 279 | SCOPE int prefix##_del(HType *h, khint_t k) { return prefix##_m_del(h, k); } \ 280 | SCOPE khint_t prefix##_put(HType *h, khkey_t key, int *absent) { HType##_m_bucket_t t; t.key = key; return prefix##_m_putp(h, &t, absent); } 281 | 282 | #define KHASHL_CSET_INIT(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ 283 | typedef struct { khkey_t key; khint_t hash; } __kh_packed HType##_cs_bucket_t; \ 284 | static kh_inline int prefix##_cs_eq(HType##_cs_bucket_t x, HType##_cs_bucket_t y) { return x.hash == y.hash && __hash_eq(x.key, y.key); } \ 285 | KHASHL_INIT(KH_LOCAL, HType, prefix##_cs, HType##_cs_bucket_t, __kh_cached_hash, prefix##_cs_eq) \ 286 | SCOPE HType *prefix##_init(void) { return prefix##_cs_init(); } \ 287 | SCOPE void prefix##_destroy(HType *h) { prefix##_cs_destroy(h); } \ 288 | SCOPE khint_t prefix##_get(const HType *h, khkey_t key) { HType##_cs_bucket_t t; t.key = key; t.hash = __hash_fn(key); return prefix##_cs_getp(h, &t); } \ 289 | SCOPE int prefix##_del(HType *h, khint_t k) { return prefix##_cs_del(h, k); } \ 290 | SCOPE khint_t prefix##_put(HType *h, khkey_t key, int *absent) { HType##_cs_bucket_t t; t.key = key, t.hash = __hash_fn(key); return prefix##_cs_putp(h, &t, absent); } 291 | 292 | #define KHASHL_CMAP_INIT(SCOPE, HType, prefix, khkey_t, kh_val_t, __hash_fn, __hash_eq) \ 293 | typedef struct { khkey_t key; kh_val_t val; khint_t hash; } __kh_packed HType##_cm_bucket_t; \ 294 | static kh_inline int prefix##_cm_eq(HType##_cm_bucket_t x, HType##_cm_bucket_t y) { return x.hash == y.hash && __hash_eq(x.key, y.key); } \ 295 | KHASHL_INIT(KH_LOCAL, HType, prefix##_cm, HType##_cm_bucket_t, __kh_cached_hash, prefix##_cm_eq) \ 296 | SCOPE HType *prefix##_init(void) { return prefix##_cm_init(); } \ 297 | SCOPE void prefix##_destroy(HType *h) { prefix##_cm_destroy(h); } \ 298 | SCOPE khint_t prefix##_get(const HType *h, khkey_t key) { HType##_cm_bucket_t t; t.key = key; t.hash = __hash_fn(key); return prefix##_cm_getp(h, &t); } \ 299 | SCOPE int prefix##_del(HType *h, khint_t k) { return prefix##_cm_del(h, k); } \ 300 | SCOPE khint_t prefix##_put(HType *h, khkey_t key, int *absent) { HType##_cm_bucket_t t; t.key = key, t.hash = __hash_fn(key); return prefix##_cm_putp(h, &t, absent); } 301 | 302 | /************************** 303 | * Public macro functions * 304 | **************************/ 305 | 306 | #define kh_bucket(h, x) ((h)->keys[x]) 307 | #define kh_size(h) ((h)->count) 308 | #define kh_capacity(h) ((h)->keys? 1U<<(h)->bits : 0U) 309 | #define kh_end(h) kh_capacity(h) 310 | 311 | #define kh_key(h, x) ((h)->keys[x].key) 312 | #define kh_val(h, x) ((h)->keys[x].val) 313 | 314 | /************************************** 315 | * Common hash and equality functions * 316 | **************************************/ 317 | 318 | #define kh_eq_generic(a, b) ((a) == (b)) 319 | #define kh_eq_str(a, b) (strcmp((a), (b)) == 0) 320 | #define kh_hash_dummy(x) ((khint_t)(x)) 321 | 322 | static kh_inline khint_t kh_hash_uint32(khint_t key) { 323 | key += ~(key << 15); 324 | key ^= (key >> 10); 325 | key += (key << 3); 326 | key ^= (key >> 6); 327 | key += ~(key << 11); 328 | key ^= (key >> 16); 329 | return key; 330 | } 331 | 332 | static kh_inline khint_t kh_hash_uint64(khint64_t key) { 333 | key = ~key + (key << 21); 334 | key = key ^ key >> 24; 335 | key = (key + (key << 3)) + (key << 8); 336 | key = key ^ key >> 14; 337 | key = (key + (key << 2)) + (key << 4); 338 | key = key ^ key >> 28; 339 | key = key + (key << 31); 340 | return (khint_t)key; 341 | } 342 | 343 | static kh_inline khint_t kh_hash_str(const char *s) { 344 | khint_t h = (khint_t)*s; 345 | if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; 346 | return h; 347 | } 348 | 349 | #endif /* __AC_KHASHL_H */ 350 | -------------------------------------------------------------------------------- /src/browser.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include "global.h" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | 34 | static int graph = 1, show_as = 0, info_show = 0, info_page = 0, info_start = 0, show_items = 0, show_mtime = 0; 35 | static const char *message = NULL; 36 | 37 | 38 | 39 | static void browse_draw_info(struct dir *dr) { 40 | struct dir *t; 41 | struct dir_ext *e = dir_ext_ptr(dr); 42 | char mbuf[46]; 43 | int i; 44 | 45 | nccreate(11, 60, "Item info"); 46 | 47 | if(dr->hlnk) { 48 | nctab(41, info_page == 0, 1, "Info"); 49 | nctab(50, info_page == 1, 2, "Links"); 50 | } 51 | 52 | switch(info_page) { 53 | case 0: 54 | attron(A_BOLD); 55 | ncaddstr(2, 3, "Name:"); 56 | ncaddstr(3, 3, "Path:"); 57 | if(!e) 58 | ncaddstr(4, 3, "Type:"); 59 | else { 60 | ncaddstr(4, 3, "Mode:"); 61 | ncaddstr(4, 21, "UID:"); 62 | ncaddstr(4, 33, "GID:"); 63 | ncaddstr(5, 3, "Last modified:"); 64 | } 65 | ncaddstr(6, 3, " Disk usage:"); 66 | ncaddstr(7, 3, "Apparent size:"); 67 | attroff(A_BOLD); 68 | 69 | ncaddstr(2, 9, cropstr(dr->name, 49)); 70 | ncaddstr(3, 9, cropstr(getpath(dr->parent), 49)); 71 | ncaddstr(4, 9, dr->flags & FF_DIR ? "Directory" : dr->flags & FF_FILE ? "File" : "Other"); 72 | 73 | if(e) { 74 | ncaddstr(4, 9, fmtmode(e->mode)); 75 | ncprint(4, 26, "%d", e->uid); 76 | ncprint(4, 38, "%d", e->gid); 77 | time_t t = (time_t)e->mtime; 78 | strftime(mbuf, sizeof(mbuf), "%Y-%m-%d %H:%M:%S %z", localtime(&t)); 79 | ncaddstr(5, 18, mbuf); 80 | } 81 | 82 | ncmove(6, 18); 83 | printsize(UIC_DEFAULT, dr->size); 84 | addstrc(UIC_DEFAULT, " ("); 85 | addstrc(UIC_NUM, fullsize(dr->size)); 86 | addstrc(UIC_DEFAULT, " B)"); 87 | 88 | ncmove(7, 18); 89 | printsize(UIC_DEFAULT, dr->asize); 90 | addstrc(UIC_DEFAULT, " ("); 91 | addstrc(UIC_NUM, fullsize(dr->asize)); 92 | addstrc(UIC_DEFAULT, " B)"); 93 | break; 94 | 95 | case 1: 96 | for(i=0,t=dr->hlnk; t!=dr; t=t->hlnk,i++) { 97 | if(info_start > i) 98 | continue; 99 | if(i-info_start > 5) 100 | break; 101 | ncaddstr(2+i-info_start, 3, cropstr(getpath(t), 54)); 102 | } 103 | if(t!=dr) 104 | ncaddstr(8, 25, "-- more --"); 105 | break; 106 | } 107 | 108 | ncaddstr(9, 31, "Press "); 109 | addchc(UIC_KEY, 'i'); 110 | addstrc(UIC_DEFAULT, " to hide this window"); 111 | } 112 | 113 | 114 | static void browse_draw_flag(struct dir *n, int *x) { 115 | addchc(n->flags & FF_BSEL ? UIC_FLAG_SEL : UIC_FLAG, 116 | n == dirlist_parent ? ' ' : 117 | n->flags & FF_EXL ? '<' : 118 | n->flags & FF_ERR ? '!' : 119 | n->flags & FF_SERR ? '.' : 120 | n->flags & FF_OTHFS ? '>' : 121 | n->flags & FF_KERNFS ? '^' : 122 | n->flags & FF_FRMLNK ? 'F' : 123 | n->flags & FF_HLNKC ? 'H' : 124 | !(n->flags & FF_FILE 125 | || n->flags & FF_DIR) ? '@' : 126 | n->flags & FF_DIR 127 | && n->sub == NULL ? 'e' : 128 | ' '); 129 | *x += 2; 130 | } 131 | 132 | 133 | static void browse_draw_graph(struct dir *n, int *x) { 134 | float pc = 0.0f; 135 | int o, i, bar_size = wincols/7 > 10 ? wincols/7 : 10; 136 | enum ui_coltype c = n->flags & FF_BSEL ? UIC_SEL : UIC_DEFAULT; 137 | 138 | if(!graph) 139 | return; 140 | 141 | *x += graph == 1 ? (bar_size + 3) : graph == 2 ? 9 : (bar_size + 10); 142 | if(n == dirlist_parent) 143 | return; 144 | 145 | addchc(c, '['); 146 | 147 | /* percentage (6 columns) */ 148 | if(graph == 2 || graph == 3) { 149 | pc = (float)(show_as ? n->parent->asize : n->parent->size); 150 | if(pc < 1) 151 | pc = 1.0f; 152 | uic_set(c == UIC_SEL ? UIC_NUM_SEL : UIC_NUM); 153 | printw("%5.1f", ((float)(show_as ? n->asize : n->size) / pc) * 100.0f); 154 | addchc(c, '%'); 155 | } 156 | 157 | if(graph == 3) 158 | addch(' '); 159 | 160 | /* graph (10+ columns) */ 161 | if(graph == 1 || graph == 3) { 162 | uic_set(c == UIC_SEL ? UIC_GRAPH_SEL : UIC_GRAPH); 163 | o = (int)((float)bar_size*(float)(show_as ? n->asize : n->size) / (float)(show_as ? dirlist_maxa : dirlist_maxs)); 164 | for(i=0; iflags & FF_BSEL ? UIC_SEL : UIC_DEFAULT; 174 | enum ui_coltype cn = c == UIC_SEL ? UIC_NUM_SEL : UIC_NUM; 175 | 176 | if(!show_items) 177 | return; 178 | *x += 7; 179 | 180 | if(!n->items) 181 | return; 182 | else if(n->items < 100*1000) { 183 | uic_set(cn); 184 | printw("%6s", fullsize(n->items)); 185 | } else if(n->items < 1000*1000) { 186 | uic_set(cn); 187 | printw("%5.1f", n->items / 1000.0); 188 | addstrc(c, "k"); 189 | } else if(n->items < 1000*1000*1000) { 190 | uic_set(cn); 191 | printw("%5.1f", n->items / 1e6); 192 | addstrc(c, "M"); 193 | } else { 194 | addstrc(c, " > "); 195 | addstrc(cn, "1"); 196 | addchc(c, 'B'); 197 | } 198 | } 199 | 200 | 201 | static void browse_draw_mtime(struct dir *n, int *x) { 202 | enum ui_coltype c = n->flags & FF_BSEL ? UIC_SEL : UIC_DEFAULT; 203 | char mbuf[26]; 204 | struct dir_ext *e; 205 | time_t t; 206 | 207 | if (n->flags & FF_EXT) { 208 | e = dir_ext_ptr(n); 209 | } else if (!strcmp(n->name, "..") && (n->parent->flags & FF_EXT)) { 210 | e = dir_ext_ptr(n->parent); 211 | } else { 212 | snprintf(mbuf, sizeof(mbuf), "no mtime"); 213 | goto no_mtime; 214 | } 215 | t = (time_t)e->mtime; 216 | 217 | strftime(mbuf, sizeof(mbuf), "%Y-%m-%d %H:%M:%S %z", localtime(&t)); 218 | uic_set(c == UIC_SEL ? UIC_NUM_SEL : UIC_NUM); 219 | no_mtime: 220 | printw("%26s", mbuf); 221 | *x += 27; 222 | } 223 | 224 | 225 | static void browse_draw_item(struct dir *n, int row) { 226 | int x = 0; 227 | 228 | enum ui_coltype c = n->flags & FF_BSEL ? UIC_SEL : UIC_DEFAULT; 229 | uic_set(c); 230 | mvhline(row, 0, ' ', wincols); 231 | move(row, 0); 232 | 233 | browse_draw_flag(n, &x); 234 | move(row, x); 235 | 236 | if(n != dirlist_parent) 237 | printsize(c, show_as ? n->asize : n->size); 238 | x += 10; 239 | move(row, x); 240 | 241 | browse_draw_graph(n, &x); 242 | move(row, x); 243 | 244 | browse_draw_items(n, &x); 245 | move(row, x); 246 | 247 | if (extended_info && show_mtime) { 248 | browse_draw_mtime(n, &x); 249 | move(row, x); 250 | } 251 | 252 | if(n->flags & FF_DIR) 253 | c = c == UIC_SEL ? UIC_DIR_SEL : UIC_DIR; 254 | addchc(c, n->flags & FF_DIR ? '/' : ' '); 255 | addstrc(c, cropstr(n->name, wincols-x-1)); 256 | } 257 | 258 | 259 | void browse_draw() { 260 | struct dir *t; 261 | const char *tmp; 262 | int selected = 0, i; 263 | 264 | erase(); 265 | t = dirlist_get(0); 266 | 267 | /* top line - basic info */ 268 | uic_set(UIC_HD); 269 | mvhline(0, 0, ' ', wincols); 270 | mvprintw(0,0,"%s %s ~ Use the arrow keys to navigate, press ", PACKAGE_NAME, PACKAGE_VERSION); 271 | addchc(UIC_KEY_HD, '?'); 272 | addstrc(UIC_HD, " for help"); 273 | if(dir_import_active) 274 | mvaddstr(0, wincols-10, "[imported]"); 275 | else if(read_only) 276 | mvaddstr(0, wincols-11, "[read-only]"); 277 | 278 | /* second line - the path */ 279 | mvhlinec(UIC_DEFAULT, 1, 0, '-', wincols); 280 | if(dirlist_par) { 281 | mvaddchc(UIC_DEFAULT, 1, 3, ' '); 282 | tmp = getpath(dirlist_par); 283 | mvaddstrc(UIC_DIR, 1, 4, cropstr(tmp, wincols-8)); 284 | mvaddchc(UIC_DEFAULT, 1, 4+((int)strlen(tmp) > wincols-8 ? wincols-8 : (int)strlen(tmp)), ' '); 285 | } 286 | 287 | /* bottom line - stats */ 288 | uic_set(UIC_HD); 289 | mvhline(winrows-1, 0, ' ', wincols); 290 | if(t) { 291 | mvaddstr(winrows-1, 0, " Total disk usage: "); 292 | printsize(UIC_HD, t->parent->size); 293 | addstrc(UIC_HD, " Apparent size: "); 294 | uic_set(UIC_NUM_HD); 295 | printsize(UIC_HD, t->parent->asize); 296 | addstrc(UIC_HD, " Items: "); 297 | uic_set(UIC_NUM_HD); 298 | printw("%d", t->parent->items); 299 | } else 300 | mvaddstr(winrows-1, 0, " No items to display."); 301 | uic_set(UIC_DEFAULT); 302 | 303 | /* nothing to display? stop here. */ 304 | if(!t) 305 | return; 306 | 307 | /* get start position */ 308 | t = dirlist_top(0); 309 | 310 | /* print the list to the screen */ 311 | for(i=0; t && iflags & FF_BSEL) 315 | selected = i; 316 | } 317 | 318 | /* draw message window */ 319 | if(message) { 320 | nccreate(6, 60, "Message"); 321 | ncaddstr(2, 2, message); 322 | ncaddstr(4, 34, "Press any key to continue"); 323 | } 324 | 325 | /* draw information window */ 326 | t = dirlist_get(0); 327 | if(!message && info_show && t != dirlist_parent) 328 | browse_draw_info(t); 329 | 330 | /* move cursor to selected row for accessibility */ 331 | move(selected+2, 0); 332 | } 333 | 334 | 335 | int browse_key(int ch) { 336 | struct dir *t, *sel; 337 | int i, catch = 0; 338 | 339 | /* message window overwrites all keys */ 340 | if(message) { 341 | message = NULL; 342 | return 0; 343 | } 344 | 345 | sel = dirlist_get(0); 346 | 347 | /* info window overwrites a few keys */ 348 | if(info_show && sel) 349 | switch(ch) { 350 | case '1': 351 | info_page = 0; 352 | break; 353 | case '2': 354 | if(sel->hlnk) 355 | info_page = 1; 356 | break; 357 | case KEY_RIGHT: 358 | case 'l': 359 | if(sel->hlnk) { 360 | info_page = 1; 361 | catch++; 362 | } 363 | break; 364 | case KEY_LEFT: 365 | case 'h': 366 | if(sel->hlnk) { 367 | info_page = 0; 368 | catch++; 369 | } 370 | break; 371 | case KEY_UP: 372 | case 'k': 373 | if(sel->hlnk && info_page == 1) { 374 | if(info_start > 0) 375 | info_start--; 376 | catch++; 377 | } 378 | break; 379 | case KEY_DOWN: 380 | case 'j': 381 | case ' ': 382 | if(sel->hlnk && info_page == 1) { 383 | for(i=0,t=sel->hlnk; t!=sel; t=t->hlnk) 384 | i++; 385 | if(i > info_start+6) 386 | info_start++; 387 | catch++; 388 | } 389 | break; 390 | } 391 | 392 | if(!catch) 393 | switch(ch) { 394 | /* selecting items */ 395 | case KEY_UP: 396 | case 'k': 397 | dirlist_select(dirlist_get(-1)); 398 | dirlist_top(-1); 399 | info_start = 0; 400 | break; 401 | case KEY_DOWN: 402 | case 'j': 403 | dirlist_select(dirlist_get(1)); 404 | dirlist_top(1); 405 | info_start = 0; 406 | break; 407 | case KEY_HOME: 408 | dirlist_select(dirlist_next(NULL)); 409 | dirlist_top(2); 410 | info_start = 0; 411 | break; 412 | case KEY_LL: 413 | case KEY_END: 414 | dirlist_select(dirlist_get(1<<30)); 415 | dirlist_top(1); 416 | info_start = 0; 417 | break; 418 | case KEY_PPAGE: 419 | dirlist_select(dirlist_get(-1*(winrows-3))); 420 | dirlist_top(-1); 421 | info_start = 0; 422 | break; 423 | case KEY_NPAGE: 424 | dirlist_select(dirlist_get(winrows-3)); 425 | dirlist_top(1); 426 | info_start = 0; 427 | break; 428 | 429 | /* sorting items */ 430 | case 'n': 431 | dirlist_set_sort(DL_COL_NAME, dirlist_sort_col == DL_COL_NAME ? !dirlist_sort_desc : 0, DL_NOCHANGE); 432 | info_show = 0; 433 | break; 434 | case 's': 435 | i = show_as ? DL_COL_ASIZE : DL_COL_SIZE; 436 | dirlist_set_sort(i, dirlist_sort_col == i ? !dirlist_sort_desc : 1, DL_NOCHANGE); 437 | info_show = 0; 438 | break; 439 | case 'C': 440 | dirlist_set_sort(DL_COL_ITEMS, dirlist_sort_col == DL_COL_ITEMS ? !dirlist_sort_desc : 1, DL_NOCHANGE); 441 | info_show = 0; 442 | break; 443 | case 'M': 444 | if (extended_info) { 445 | dirlist_set_sort(DL_COL_MTIME, dirlist_sort_col == DL_COL_MTIME ? !dirlist_sort_desc : 1, DL_NOCHANGE); 446 | info_show = 0; 447 | } 448 | break; 449 | case 'e': 450 | dirlist_set_hidden(!dirlist_hidden); 451 | info_show = 0; 452 | break; 453 | case 't': 454 | dirlist_set_sort(DL_NOCHANGE, DL_NOCHANGE, !dirlist_sort_df); 455 | info_show = 0; 456 | break; 457 | case 'a': 458 | show_as = !show_as; 459 | if(dirlist_sort_col == DL_COL_ASIZE || dirlist_sort_col == DL_COL_SIZE) 460 | dirlist_set_sort(show_as ? DL_COL_ASIZE : DL_COL_SIZE, DL_NOCHANGE, DL_NOCHANGE); 461 | info_show = 0; 462 | break; 463 | 464 | /* browsing */ 465 | case 10: 466 | case KEY_RIGHT: 467 | case 'l': 468 | if(sel != NULL && sel->flags & FF_DIR) { 469 | dirlist_open(sel == dirlist_parent ? dirlist_par->parent : sel); 470 | dirlist_top(-3); 471 | } 472 | info_show = 0; 473 | break; 474 | case KEY_LEFT: 475 | case KEY_BACKSPACE: 476 | case 'h': 477 | case '<': 478 | if(dirlist_par && dirlist_par->parent != NULL) { 479 | dirlist_open(dirlist_par->parent); 480 | dirlist_top(-3); 481 | } 482 | info_show = 0; 483 | break; 484 | 485 | /* and other stuff */ 486 | case 'r': 487 | if(dir_import_active) { 488 | message = "Directory imported from file, won't refresh."; 489 | break; 490 | } 491 | if(dirlist_par) { 492 | dir_ui = 2; 493 | dir_mem_init(dirlist_par); 494 | dir_scan_init(getpath(dirlist_par)); 495 | } 496 | info_show = 0; 497 | break; 498 | case 'q': 499 | if(info_show) 500 | info_show = 0; 501 | else 502 | if (confirm_quit) 503 | quit_init(); 504 | else return 1; 505 | break; 506 | case 'g': 507 | if(++graph > 3) 508 | graph = 0; 509 | info_show = 0; 510 | break; 511 | case 'c': 512 | show_items = !show_items; 513 | break; 514 | case 'm': 515 | if (extended_info) 516 | show_mtime = !show_mtime; 517 | break; 518 | case 'i': 519 | info_show = !info_show; 520 | break; 521 | case '?': 522 | help_init(); 523 | info_show = 0; 524 | break; 525 | case 'd': 526 | if(read_only >= 1 || dir_import_active) { 527 | message = read_only >= 1 528 | ? "File deletion disabled in read-only mode." 529 | : "File deletion not available for imported directories."; 530 | break; 531 | } 532 | if(sel == NULL || sel == dirlist_parent) 533 | break; 534 | info_show = 0; 535 | if((t = dirlist_get(1)) == sel) 536 | if((t = dirlist_get(-1)) == sel || t == dirlist_parent) 537 | t = NULL; 538 | delete_init(sel, t); 539 | break; 540 | case 'b': 541 | if(read_only >= 2 || dir_import_active) { 542 | message = read_only >= 2 543 | ? "Shell feature disabled in read-only mode." 544 | : "Shell feature not available for imported directories."; 545 | break; 546 | } 547 | shell_init(); 548 | break; 549 | } 550 | 551 | /* make sure the info_* options are correct */ 552 | sel = dirlist_get(0); 553 | if(!info_show || sel == dirlist_parent) 554 | info_show = info_page = info_start = 0; 555 | else if(sel && !sel->hlnk) 556 | info_page = info_start = 0; 557 | 558 | return 0; 559 | } 560 | 561 | 562 | void browse_init(struct dir *par) { 563 | pstate = ST_BROWSE; 564 | message = NULL; 565 | dirlist_open(par); 566 | } 567 | 568 | -------------------------------------------------------------------------------- /src/dir_import.c: -------------------------------------------------------------------------------- 1 | /* ncdu - NCurses Disk Usage 2 | 3 | Copyright (c) 2007-2020 Yoran Heling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | /* This JSON parser has the following limitations: 27 | * - No support for character encodings incompatible with ASCII (e.g. 28 | * UTF-16/32) 29 | * - Doesn't validate UTF-8 correctness (in fact, besides the ASCII part this 30 | * parser doesn't know anything about encoding). 31 | * - Doesn't validate that there are no duplicate keys in JSON objects. 32 | * - Isn't very strict with validating non-integer numbers. 33 | */ 34 | 35 | #include "global.h" 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | 44 | /* Max. length of any JSON string we're interested in. A string may of course 45 | * be larger, we're not going to read more than MAX_VAL in memory. If a string 46 | * we're interested in (e.g. a file name) is longer than this, reading the 47 | * import will results in an error. */ 48 | #define MAX_VAL (32*1024) 49 | 50 | /* Minimum number of bytes we request from fread() */ 51 | #define MIN_READ_SIZE 1024 52 | 53 | /* Read buffer size. Must be at least 2*MIN_READ_SIZE, everything larger 54 | * improves performance. */ 55 | #define READ_BUF_SIZE (32*1024) 56 | 57 | 58 | int dir_import_active = 0; 59 | 60 | 61 | /* Use a struct for easy batch-allocation and deallocation of state data. */ 62 | static struct ctx { 63 | FILE *stream; 64 | 65 | int line; 66 | int byte; 67 | int eof; 68 | int items; 69 | char *buf; /* points into readbuf, always zero-terminated. */ 70 | char *lastfill; /* points into readbuf, location of the zero terminator. */ 71 | 72 | /* scratch space */ 73 | struct dir *buf_dir; 74 | struct dir_ext buf_ext[1]; 75 | 76 | char buf_name[MAX_VAL]; 77 | char val[MAX_VAL]; 78 | char readbuf[READ_BUF_SIZE]; 79 | } *ctx; 80 | 81 | 82 | /* Fills readbuf with data from the stream. *buf will have at least n (< 83 | * READ_BUF_SIZE) bytes available, unless the stream reached EOF or an error 84 | * occurred. If the file data contains a null-type, this is considered an error. 85 | * Returns 0 on success, non-zero on error. */ 86 | static int fill(int n) { 87 | int r; 88 | 89 | if(ctx->eof) 90 | return 0; 91 | 92 | r = READ_BUF_SIZE-(ctx->lastfill - ctx->readbuf); /* number of bytes left in the buffer */ 93 | if(n < r) 94 | n = r-1; 95 | if(n < MIN_READ_SIZE) { 96 | r = ctx->lastfill - ctx->buf; /* number of unread bytes left in the buffer */ 97 | memcpy(ctx->readbuf, ctx->buf, r); 98 | ctx->lastfill = ctx->readbuf + r; 99 | ctx->buf = ctx->readbuf; 100 | n = READ_BUF_SIZE-r-1; 101 | } 102 | 103 | do { 104 | r = fread(ctx->lastfill, 1, n, ctx->stream); 105 | if(r != n) { 106 | if(feof(ctx->stream)) 107 | ctx->eof = 1; 108 | else if(ferror(ctx->stream) && errno != EINTR) { 109 | dir_seterr("Read error: %s", strerror(errno)); 110 | return 1; 111 | } 112 | } 113 | 114 | ctx->lastfill[r] = 0; 115 | if(strlen(ctx->lastfill) != (size_t)r) { 116 | dir_seterr("Zero-byte found in JSON stream"); 117 | return 1; 118 | } 119 | ctx->lastfill += r; 120 | n -= r; 121 | } while(!ctx->eof && n > MIN_READ_SIZE); 122 | 123 | return 0; 124 | } 125 | 126 | 127 | /* Two macros that break function calling behaviour, but are damn convenient */ 128 | #define E(_x, _m) do {\ 129 | if(_x) {\ 130 | if(!dir_fatalerr)\ 131 | dir_seterr("Line %d byte %d: %s", ctx->line, ctx->byte, _m);\ 132 | return 1;\ 133 | }\ 134 | } while(0) 135 | 136 | #define C(_x) do {\ 137 | if(_x)\ 138 | return 1;\ 139 | } while(0) 140 | 141 | 142 | /* Require at least n bytes in the buffer, throw an error on early EOF. 143 | * (Macro to quickly handle the common case) */ 144 | #define rfill1 (!*ctx->buf && _rfill(1)) 145 | #define rfill(_n) ((ctx->lastfill - ctx->buf < (_n)) && _rfill(_n)) 146 | 147 | static int _rfill(int n) { 148 | C(fill(n)); 149 | E(ctx->lastfill - ctx->buf < n, "Unexpected EOF"); 150 | return 0; 151 | } 152 | 153 | 154 | /* Consumes n bytes from the buffer. */ 155 | static inline void con(int n) { 156 | ctx->buf += n; 157 | ctx->byte += n; 158 | } 159 | 160 | 161 | /* Consumes any whitespace. If *ctx->buf == 0 after this function, we've reached EOF. */ 162 | static int cons(void) { 163 | while(1) { 164 | C(!*ctx->buf && fill(1)); 165 | 166 | switch(*ctx->buf) { 167 | case 0x0A: 168 | /* Special-case the newline-character with respect to consuming stuff 169 | * from the buffer. This is the only function which *can* consume the 170 | * newline character, so it's more efficient to handle it in here rather 171 | * than in the more general con(). */ 172 | ctx->buf++; 173 | ctx->line++; 174 | ctx->byte = 0; 175 | break; 176 | case 0x20: 177 | case 0x09: 178 | case 0x0D: 179 | con(1); 180 | break; 181 | default: 182 | return 0; 183 | } 184 | } 185 | } 186 | 187 | 188 | static int rstring_esc(char **dest, int *destlen) { 189 | unsigned int n; 190 | 191 | C(rfill1); 192 | 193 | #define ap(c) if(*destlen > 1) { *((*dest)++) = c; (*destlen)--; } 194 | switch(*ctx->buf) { 195 | case '"': ap('"'); con(1); break; 196 | case '\\': ap('\\'); con(1); break; 197 | case '/': ap('/'); con(1); break; 198 | case 'b': ap(0x08); con(1); break; 199 | case 'f': ap(0x0C); con(1); break; 200 | case 'n': ap(0x0A); con(1); break; 201 | case 'r': ap(0x0D); con(1); break; 202 | case 't': ap(0x09); con(1); break; 203 | case 'u': 204 | C(rfill(5)); 205 | #define hn(n) (n >= '0' && n <= '9' ? n-'0' : n >= 'A' && n <= 'F' ? n-'A'+10 : n >= 'a' && n <= 'f' ? n-'a'+10 : 1<<16) 206 | n = (hn(ctx->buf[1])<<12) + (hn(ctx->buf[2])<<8) + (hn(ctx->buf[3])<<4) + hn(ctx->buf[4]); 207 | #undef hn 208 | if(n <= 0x007F) { 209 | ap(n); 210 | } else if(n <= 0x07FF) { 211 | ap(0xC0 | (n>>6)); 212 | ap(0x80 | (n & 0x3F)); 213 | } else if(n <= 0xFFFF) { 214 | ap(0xE0 | (n>>12)); 215 | ap(0x80 | ((n>>6) & 0x3F)); 216 | ap(0x80 | (n & 0x3F)); 217 | } else /* this happens if there was an invalid character (n >= (1<<16)) */ 218 | E(1, "Invalid character in \\u escape"); 219 | con(5); 220 | break; 221 | default: 222 | E(1, "Invalid escape sequence"); 223 | } 224 | #undef ap 225 | return 0; 226 | } 227 | 228 | 229 | /* Parse a JSON string and write it to *dest (max. destlen). Consumes but 230 | * otherwise ignores any characters if the string is longer than destlen. *dest 231 | * will be null-terminated, dest[destlen-1] = 0 if the string was cut just long 232 | * enough of was cut off. That byte will be left untouched if the string is 233 | * small enough. */ 234 | static int rstring(char *dest, int destlen) { 235 | C(rfill1); 236 | E(*ctx->buf != '"', "Expected string"); 237 | con(1); 238 | 239 | while(1) { 240 | C(rfill1); 241 | if(*ctx->buf == '"') 242 | break; 243 | if(*ctx->buf == '\\') { 244 | con(1); 245 | C(rstring_esc(&dest, &destlen)); 246 | continue; 247 | } 248 | E((unsigned char)*ctx->buf <= 0x1F || (unsigned char)*ctx->buf == 0x7F, "Invalid character"); 249 | if(destlen > 1) { 250 | *(dest++) = *ctx->buf; 251 | destlen--; 252 | } 253 | con(1); 254 | } 255 | con(1); 256 | if(destlen > 0) 257 | *dest = 0; 258 | return 0; 259 | } 260 | 261 | 262 | /* Parse and consume a JSON integer. Throws an error if the value does not fit 263 | * in an uint64_t, is not an integer or is larger than 'max'. */ 264 | static int rint64(uint64_t *val, uint64_t max) { 265 | uint64_t v; 266 | int haschar = 0; 267 | *val = 0; 268 | while(1) { 269 | C(!*ctx->buf && fill(1)); 270 | if(*ctx->buf == '0' && !haschar) { 271 | con(1); 272 | break; 273 | } 274 | if(*ctx->buf >= '0' && *ctx->buf <= '9') { 275 | haschar = 1; 276 | v = (*val)*10 + (*ctx->buf-'0'); 277 | E(v < *val, "Invalid (positive) integer"); 278 | *val = v; 279 | con(1); 280 | continue; 281 | } 282 | E(!haschar, "Invalid (positive) integer"); 283 | break; 284 | } 285 | E(*val > max, "Integer out of range"); 286 | return 0; 287 | } 288 | 289 | 290 | /* Parse and consume a JSON number. The result is discarded. 291 | * TODO: Improve validation. */ 292 | static int rnum(void) { 293 | int haschar = 0; 294 | C(rfill1); 295 | while(1) { 296 | C(!*ctx->buf && fill(1)); 297 | if(*ctx->buf == 'e' || *ctx->buf == 'E' || *ctx->buf == '-' || *ctx->buf == '+' || *ctx->buf == '.' || (*ctx->buf >= '0' && *ctx->buf <= '9')) { 298 | haschar = 1; 299 | con(1); 300 | } else { 301 | E(!haschar, "Invalid JSON value"); 302 | break; 303 | } 304 | } 305 | return 0; 306 | } 307 | 308 | 309 | static int rlit(const char *v, int len) { 310 | C(rfill(len)); 311 | E(strncmp(ctx->buf, v, len) != 0, "Invalid JSON value"); 312 | con(len); 313 | return 0; 314 | } 315 | 316 | 317 | /* Parse the " : " part of an object key. */ 318 | static int rkey(char *dest, int destlen) { 319 | C(cons() || rstring(dest, destlen) || cons()); 320 | E(*ctx->buf != ':', "Expected ':'"); 321 | con(1); 322 | return cons(); 323 | } 324 | 325 | 326 | /* (Recursively) parse and consume any JSON value. The result is discarded. */ 327 | static int rval(void) { 328 | C(rfill1); 329 | switch(*ctx->buf) { 330 | case 't': /* true */ 331 | C(rlit("true", 4)); 332 | break; 333 | case 'f': /* false */ 334 | C(rlit("false", 5)); 335 | break; 336 | case 'n': /* null */ 337 | C(rlit("null", 4)); 338 | break; 339 | case '"': /* string */ 340 | C(rstring(NULL, 0)); 341 | break; 342 | case '{': /* object */ 343 | con(1); 344 | while(1) { 345 | C(cons()); 346 | if(*ctx->buf == '}') 347 | break; 348 | C(rkey(NULL, 0) || rval() || cons()); 349 | if(*ctx->buf == '}') 350 | break; 351 | E(*ctx->buf != ',', "Expected ',' or '}'"); 352 | con(1); 353 | } 354 | con(1); 355 | break; 356 | case '[': /* array */ 357 | con(1); 358 | while(1) { 359 | C(cons()); 360 | if(*ctx->buf == ']') 361 | break; 362 | C(cons() || rval() || cons()); 363 | if(*ctx->buf == ']') 364 | break; 365 | E(*ctx->buf != ',', "Expected ',' or ']'"); 366 | con(1); 367 | } 368 | con(1); 369 | break; 370 | default: /* assume number */ 371 | C(rnum()); 372 | break; 373 | } 374 | 375 | return 0; 376 | } 377 | 378 | 379 | /* Consumes everything up to the root item, and checks that this item is a dir. */ 380 | static int header(void) { 381 | uint64_t v; 382 | 383 | C(cons()); 384 | E(*ctx->buf != '[', "Expected JSON array"); 385 | con(1); 386 | C(cons() || rint64(&v, 10000) || cons()); 387 | E(v != 1, "Incompatible major format version"); 388 | E(*ctx->buf != ',', "Expected ','"); 389 | con(1); 390 | C(cons() || rint64(&v, 10000) || cons()); /* Ignore the minor version for now */ 391 | E(*ctx->buf != ',', "Expected ','"); 392 | con(1); 393 | /* Metadata block is currently ignored */ 394 | C(cons() || rval() || cons()); 395 | E(*ctx->buf != ',', "Expected ','"); 396 | con(1); 397 | 398 | C(cons()); 399 | E(*ctx->buf != '[', "Top-level item must be a directory"); 400 | 401 | return 0; 402 | } 403 | 404 | 405 | static int item(uint64_t); 406 | 407 | /* Read and add dir contents */ 408 | static int itemdir(uint64_t dev) { 409 | while(1) { 410 | C(cons()); 411 | if(*ctx->buf == ']') 412 | break; 413 | E(*ctx->buf != ',', "Expected ',' or ']'"); 414 | con(1); 415 | C(cons() || item(dev)); 416 | } 417 | con(1); 418 | C(cons()); 419 | return 0; 420 | } 421 | 422 | 423 | /* Reads a JSON object representing a struct dir/dir_ext item. Writes to 424 | * ctx->buf_dir, ctx->buf_ext and ctx->buf_name. */ 425 | static int iteminfo(void) { 426 | uint64_t iv; 427 | 428 | E(*ctx->buf != '{', "Expected JSON object"); 429 | con(1); 430 | 431 | while(1) { 432 | C(rkey(ctx->val, MAX_VAL)); 433 | /* TODO: strcmp() in this fashion isn't very fast. */ 434 | if(strcmp(ctx->val, "name") == 0) { /* name */ 435 | ctx->val[MAX_VAL-1] = 1; 436 | C(rstring(ctx->val, MAX_VAL)); 437 | E(ctx->val[MAX_VAL-1] != 1, "Too large string value"); 438 | strcpy(ctx->buf_name, ctx->val); 439 | } else if(strcmp(ctx->val, "asize") == 0) { /* asize */ 440 | C(rint64(&iv, INT64_MAX)); 441 | ctx->buf_dir->asize = iv; 442 | } else if(strcmp(ctx->val, "dsize") == 0) { /* dsize */ 443 | C(rint64(&iv, INT64_MAX)); 444 | ctx->buf_dir->size = iv; 445 | } else if(strcmp(ctx->val, "dev") == 0) { /* dev */ 446 | C(rint64(&iv, UINT64_MAX)); 447 | ctx->buf_dir->dev = iv; 448 | } else if(strcmp(ctx->val, "ino") == 0) { /* ino */ 449 | C(rint64(&iv, UINT64_MAX)); 450 | ctx->buf_dir->ino = iv; 451 | } else if(strcmp(ctx->val, "uid") == 0) { /* uid */ 452 | C(rint64(&iv, INT32_MAX)); 453 | ctx->buf_dir->flags |= FF_EXT; 454 | ctx->buf_ext->uid = iv; 455 | } else if(strcmp(ctx->val, "gid") == 0) { /* gid */ 456 | C(rint64(&iv, INT32_MAX)); 457 | ctx->buf_dir->flags |= FF_EXT; 458 | ctx->buf_ext->gid = iv; 459 | } else if(strcmp(ctx->val, "mode") == 0) { /* mode */ 460 | C(rint64(&iv, UINT16_MAX)); 461 | ctx->buf_dir->flags |= FF_EXT; 462 | ctx->buf_ext->mode = iv; 463 | } else if(strcmp(ctx->val, "mtime") == 0) { /* mtime */ 464 | C(rint64(&iv, UINT64_MAX)); 465 | ctx->buf_dir->flags |= FF_EXT; 466 | ctx->buf_ext->mtime = iv; 467 | } else if(strcmp(ctx->val, "hlnkc") == 0) { /* hlnkc */ 468 | if(*ctx->buf == 't') { 469 | C(rlit("true", 4)); 470 | ctx->buf_dir->flags |= FF_HLNKC; 471 | } else 472 | C(rlit("false", 5)); 473 | } else if(strcmp(ctx->val, "read_error") == 0) { /* read_error */ 474 | if(*ctx->buf == 't') { 475 | C(rlit("true", 4)); 476 | ctx->buf_dir->flags |= FF_ERR; 477 | } else 478 | C(rlit("false", 5)); 479 | } else if(strcmp(ctx->val, "excluded") == 0) { /* excluded */ 480 | C(rstring(ctx->val, 8)); 481 | if(strcmp(ctx->val, "otherfs") == 0) 482 | ctx->buf_dir->flags |= FF_OTHFS; 483 | else if(strcmp(ctx->val, "kernfs") == 0) 484 | ctx->buf_dir->flags |= FF_KERNFS; 485 | else if(strcmp(ctx->val, "frmlnk") == 0) 486 | ctx->buf_dir->flags |= FF_FRMLNK; 487 | else 488 | ctx->buf_dir->flags |= FF_EXL; 489 | } else if(strcmp(ctx->val, "notreg") == 0) { /* notreg */ 490 | if(*ctx->buf == 't') { 491 | C(rlit("true", 4)); 492 | ctx->buf_dir->flags &= ~FF_FILE; 493 | } else 494 | C(rlit("false", 5)); 495 | } else 496 | C(rval()); 497 | 498 | C(cons()); 499 | if(*ctx->buf == '}') 500 | break; 501 | E(*ctx->buf != ',', "Expected ',' or '}'"); 502 | con(1); 503 | } 504 | con(1); 505 | 506 | E(!*ctx->buf_name, "No name field present in item information object"); 507 | ctx->items++; 508 | /* Only call input_handle() once for every 32 items. Importing items is so 509 | * fast that the time spent in input_handle() dominates when called every 510 | * time. Don't set this value too high, either, as feedback should still be 511 | * somewhat responsive when our import data comes from a slow-ish source. */ 512 | return !(ctx->items & 31) ? input_handle(1) : 0; 513 | } 514 | 515 | 516 | /* Recursively reads a file or directory item */ 517 | static int item(uint64_t dev) { 518 | int isdir = 0; 519 | int isroot = ctx->items == 0; 520 | 521 | if(*ctx->buf == '[') { 522 | isdir = 1; 523 | con(1); 524 | C(cons()); 525 | } 526 | 527 | memset(ctx->buf_dir, 0, offsetof(struct dir, name)); 528 | memset(ctx->buf_ext, 0, sizeof(struct dir_ext)); 529 | *ctx->buf_name = 0; 530 | ctx->buf_dir->flags |= isdir ? FF_DIR : FF_FILE; 531 | ctx->buf_dir->dev = dev; 532 | 533 | C(iteminfo()); 534 | dev = ctx->buf_dir->dev; 535 | 536 | if(isroot) 537 | dir_curpath_set(ctx->buf_name); 538 | else 539 | dir_curpath_enter(ctx->buf_name); 540 | 541 | if(isdir) { 542 | if(dir_output.item(ctx->buf_dir, ctx->buf_name, ctx->buf_ext)) { 543 | dir_seterr("Output error: %s", strerror(errno)); 544 | return 1; 545 | } 546 | C(itemdir(dev)); 547 | if(dir_output.item(NULL, 0, NULL)) { 548 | dir_seterr("Output error: %s", strerror(errno)); 549 | return 1; 550 | } 551 | } else if(dir_output.item(ctx->buf_dir, ctx->buf_name, ctx->buf_ext)) { 552 | dir_seterr("Output error: %s", strerror(errno)); 553 | return 1; 554 | } 555 | 556 | if(!isroot) 557 | dir_curpath_leave(); 558 | 559 | return 0; 560 | } 561 | 562 | 563 | static int footer(void) { 564 | C(cons()); 565 | E(*ctx->buf != ']', "Expected ']'"); 566 | con(1); 567 | C(cons()); 568 | E(*ctx->buf, "Trailing garbage"); 569 | return 0; 570 | } 571 | 572 | 573 | static int process(void) { 574 | int fail = 0; 575 | 576 | header(); 577 | 578 | if(!dir_fatalerr) 579 | fail = item(0); 580 | 581 | if(!dir_fatalerr && !fail) 582 | footer(); 583 | 584 | if(fclose(ctx->stream) && !dir_fatalerr && !fail) 585 | dir_seterr("Error closing file: %s", strerror(errno)); 586 | free(ctx->buf_dir); 587 | free(ctx); 588 | 589 | while(dir_fatalerr && !input_handle(0)) 590 | ; 591 | return dir_output.final(dir_fatalerr || fail); 592 | } 593 | 594 | 595 | int dir_import_init(const char *fn) { 596 | FILE *stream; 597 | if(strcmp(fn, "-") == 0) 598 | stream = stdin; 599 | else if((stream = fopen(fn, "r")) == NULL) 600 | return 1; 601 | 602 | ctx = xmalloc(sizeof(struct ctx)); 603 | ctx->stream = stream; 604 | ctx->line = 1; 605 | ctx->byte = ctx->eof = ctx->items = 0; 606 | ctx->buf = ctx->lastfill = ctx->readbuf; 607 | ctx->buf_dir = xmalloc(dir_memsize("")); 608 | ctx->readbuf[0] = 0; 609 | 610 | dir_curpath_set(fn); 611 | dir_process = process; 612 | dir_import_active = 1; 613 | return 0; 614 | } 615 | 616 | --------------------------------------------------------------------------------