diff options
author | Yorhel <git@yorhel.nl> | 2021-04-29 12:48:45 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2021-04-29 12:48:52 +0200 |
commit | 0783d357937e5e705321c0f92f6c4043f317b909 (patch) | |
tree | 93d2da37d87d4011928488a601bbcaa55a265973 | |
parent | 9337cdc99e8c205e6e75de25de29a5be53ea858c (diff) |
WIP: Experimenting with a rewrite to Zig & a new data model
The new data model is supposed to solve a few problems with ncdu 1.x's
'struct dir':
- Reduce memory overhead,
- Fix extremely slow counting of hard links in some scenarios
(issue #121)
- Add support for counting 'shared' data with other directories
(issue #36)
Quick memory usage comparison of my root directory with ~3.5 million
files (normal / extended mode):
ncdu 1.15.1: 379M / 451M
new (unaligned): 145M / 178M
new (aligned): 155M / 200M
There's still a /lot/ of to-do's left before this is usable, however,
and there's a bunch of issues I haven't really decided on yet, such as
which TUI library to use.
Backporting this data model to the C version of ncdu is also possible,
but somewhat painful. Let's first see how far I get with Zig.
-rw-r--r-- | .gitignore | 19 | ||||
-rw-r--r-- | Makefile.am | 47 | ||||
-rw-r--r-- | build.zig | 20 | ||||
-rw-r--r-- | configure.ac | 75 | ||||
-rw-r--r-- | deps/khashl.h | 349 | ||||
-rw-r--r-- | deps/yopt.h | 198 | ||||
-rw-r--r-- | src/browser.c | 567 | ||||
-rw-r--r-- | src/browser.h | 37 | ||||
-rw-r--r-- | src/delete.c | 253 | ||||
-rw-r--r-- | src/delete.h | 37 | ||||
-rw-r--r-- | src/dir.h | 141 | ||||
-rw-r--r-- | src/dir_common.c | 232 | ||||
-rw-r--r-- | src/dir_export.c | 194 | ||||
-rw-r--r-- | src/dir_import.c | 615 | ||||
-rw-r--r-- | src/dir_mem.c | 215 | ||||
-rw-r--r-- | src/dir_scan.c | 405 | ||||
-rw-r--r-- | src/dirlist.c | 398 | ||||
-rw-r--r-- | src/dirlist.h | 86 | ||||
-rw-r--r-- | src/exclude.c | 139 | ||||
-rw-r--r-- | src/exclude.h | 35 | ||||
-rw-r--r-- | src/global.h | 132 | ||||
-rw-r--r-- | src/help.c | 212 | ||||
-rw-r--r-- | src/help.h | 37 | ||||
-rw-r--r-- | src/main.c | 359 | ||||
-rw-r--r-- | src/main.zig | 72 | ||||
-rw-r--r-- | src/model.zig | 350 | ||||
-rw-r--r-- | src/path.c | 246 | ||||
-rw-r--r-- | src/path.h | 47 | ||||
-rw-r--r-- | src/quit.c | 50 | ||||
-rw-r--r-- | src/quit.h | 37 | ||||
-rw-r--r-- | src/scan.zig | 138 | ||||
-rw-r--r-- | src/shell.c | 82 | ||||
-rw-r--r-- | src/shell.h | 35 | ||||
-rw-r--r-- | src/util.c | 434 | ||||
-rw-r--r-- | src/util.h | 195 | ||||
-rwxr-xr-x | static/build.sh | 130 |
36 files changed, 581 insertions, 6037 deletions
@@ -1,21 +1,4 @@ -Makefile -Makefile.in -aclocal.m4 -autom4te.cache/ -compile -config.h -config.h.in -config.log -config.status -configure -depcomp -install-sh -missing -.deps/ -.dirstamp -*.o -stamp-h1 -ncdu +zig-cache/ ncdu.1 *~ *.swp diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index 01a79c4..0000000 --- a/Makefile.am +++ /dev/null @@ -1,47 +0,0 @@ -AM_CPPFLAGS=-I$(srcdir)/deps -bin_PROGRAMS=ncdu - -ncdu_SOURCES=\ - src/browser.c\ - src/delete.c\ - src/dirlist.c\ - src/dir_common.c\ - src/dir_export.c\ - src/dir_import.c\ - src/dir_mem.c\ - src/dir_scan.c\ - src/exclude.c\ - src/help.c\ - src/shell.c\ - src/quit.c\ - src/main.c\ - src/path.c\ - src/util.c - -noinst_HEADERS=\ - deps/yopt.h\ - deps/khashl.h\ - src/browser.h\ - src/delete.h\ - src/dir.h\ - src/dirlist.h\ - src/exclude.h\ - src/global.h\ - src/help.h\ - src/shell.h\ - src/quit.h\ - src/path.h\ - src/util.h - - -man_MANS=ncdu.1 -EXTRA_DIST=ncdu.1 doc/ncdu.pod - -# Don't "clean" ncdu.1, it should be in the tarball so that pod2man isn't a -# build dependency for those who use the tarball. -ncdu.1: $(srcdir)/doc/ncdu.pod - pod2man --center "ncdu manual" --release "@PACKAGE@-@VERSION@" "$(srcdir)/doc/ncdu.pod" >ncdu.1 - -update-deps: - wget -q https://raw.github.com/attractivechaos/klib/master/khashl.h -O "$(srcdir)/deps/khashl.h" - wget -q http://g.blicky.net/ylib.git/plain/yopt.h -O "$(srcdir)/deps/yopt.h" diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..1f693d3 --- /dev/null +++ b/build.zig @@ -0,0 +1,20 @@ +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("ncdu", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} diff --git a/configure.ac b/configure.ac deleted file mode 100644 index a5d1cd4..0000000 --- a/configure.ac +++ /dev/null @@ -1,75 +0,0 @@ - -AC_INIT([ncdu],[1.15.1],[projects@yorhel.nl]) -AC_CONFIG_SRCDIR([src/global.h]) -AC_CONFIG_HEADER([config.h]) -AM_INIT_AUTOMAKE([foreign std-options subdir-objects]) - -# Check for programs. -AC_PROG_CC -AC_PROG_INSTALL -AC_PROG_RANLIB -PKG_PROG_PKG_CONFIG - -# Check for header files. -AC_CHECK_HEADERS( - [limits.h sys/time.h sys/types.h sys/stat.h dirent.h unistd.h fnmatch.h ncurses.h],[], - AC_MSG_ERROR([required header file not found])) - -AC_CHECK_HEADERS([locale.h sys/statfs.h linux/magic.h]) - -# Check for typedefs, structures, and compiler characteristics. -AC_TYPE_INT64_T -AC_TYPE_UINT64_T -AC_SYS_LARGEFILE -AC_STRUCT_ST_BLOCKS - -# Check for library functions. -AC_CHECK_FUNCS( - [getcwd gettimeofday fnmatch chdir rmdir unlink lstat system getenv],[], - AC_MSG_ERROR([required function missing])) - -AC_CHECK_FUNCS(statfs) - -AC_CHECK_HEADERS([sys/attr.h]) - -AC_CHECK_FUNCS([getattrlist]) - -AC_CHECK_DECLS([ATTR_CMNEXT_NOFIRMLINKPATH], [], [], [[#include <sys/attr.h>]]) - -# Look for ncurses library to link to -ncurses=auto -AC_ARG_WITH([ncurses], - [AS_HELP_STRING([--with-ncurses], [compile/link with ncurses library] )], - [ncurses=ncurses]) -AC_ARG_WITH([ncursesw], - [AS_HELP_STRING([--with-ncursesw], [compile/link with wide-char ncurses library @<:@default@:>@])], - [ncurses=ncursesw]) -if test "$ncurses" = "auto" -o "$ncurses" = "ncursesw"; then - PKG_CHECK_MODULES([NCURSES], [ncursesw], [LIBS="$LIBS $NCURSES_LIBS"; ncurses=ncursesw], - [AC_CHECK_LIB([ncursesw], - [initscr], - [LIBS="$LIBS -lncursesw"; ncurses=ncursesw], - [ncurses=ncurses]) - ]) -fi -if test "$ncurses" = "ncurses"; then - PKG_CHECK_MODULES([NCURSES], [ncurses], [LIBS="$LIBS $NCURSES_LIBS"], - [AC_CHECK_LIB([ncurses], - [initscr], - [LIBS="$LIBS -lncurses"], - [AC_MSG_ERROR(ncurses library is required)]) - ]) -fi - -# Configure default shell for spawning shell when $SHELL is not set -AC_ARG_WITH([shell], - [AS_HELP_STRING([--with-shell], - [used interpreter as default shell (default is /bin/sh)])], - [DEFAULT_SHELL=$withval], - [DEFAULT_SHELL=/bin/sh]) -AC_MSG_NOTICE([Using $DEFAULT_SHELL as the default shell if \$SHELL is not set]) -AC_DEFINE_UNQUOTED(DEFAULT_SHELL, "$DEFAULT_SHELL", [Used default shell interpreter]) - - -AC_CONFIG_FILES([Makefile]) -AC_OUTPUT diff --git a/deps/khashl.h b/deps/khashl.h deleted file mode 100644 index 951feda..0000000 --- a/deps/khashl.h +++ /dev/null @@ -1,349 +0,0 @@ -/* The MIT License - - Copyright (c) 2019 by Attractive Chaos <attractor@live.co.uk> - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - -#ifndef __AC_KHASHL_H -#define __AC_KHASHL_H - -#define AC_VERSION_KHASHL_H "0.1" - -#include <stdlib.h> -#include <string.h> -#include <limits.h> - -/************************************ - * Compiler specific configurations * - ************************************/ - -#if UINT_MAX == 0xffffffffu -typedef unsigned int khint32_t; -#elif ULONG_MAX == 0xffffffffu -typedef unsigned long khint32_t; -#endif - -#if ULONG_MAX == ULLONG_MAX -typedef unsigned long khint64_t; -#else -typedef unsigned long long khint64_t; -#endif - -#ifndef kh_inline -#ifdef _MSC_VER -#define kh_inline __inline -#else -#define kh_inline inline -#endif -#endif /* kh_inline */ - -#ifndef klib_unused -#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) -#define klib_unused __attribute__ ((__unused__)) -#else -#define klib_unused -#endif -#endif /* klib_unused */ - -#define KH_LOCAL static kh_inline klib_unused - -typedef khint32_t khint_t; - -/****************** - * malloc aliases * - ******************/ - -#ifndef kcalloc -#define kcalloc(N,Z) calloc(N,Z) -#endif -#ifndef kmalloc -#define kmalloc(Z) malloc(Z) -#endif -#ifndef krealloc -#define krealloc(P,Z) realloc(P,Z) -#endif -#ifndef kfree -#define kfree(P) free(P) -#endif - -/**************************** - * Simple private functions * - ****************************/ - -#define __kh_used(flag, i) (flag[i>>5] >> (i&0x1fU) & 1U) -#define __kh_set_used(flag, i) (flag[i>>5] |= 1U<<(i&0x1fU)) -#define __kh_set_unused(flag, i) (flag[i>>5] &= ~(1U<<(i&0x1fU))) - -#define __kh_fsize(m) ((m) < 32? 1 : (m)>>5) - -static kh_inline khint_t __kh_h2b(khint_t hash, khint_t bits) { return hash * 2654435769U >> (32 - bits); } - -/******************* - * Hash table base * - *******************/ - -#define __KHASHL_TYPE(HType, khkey_t) \ - typedef struct { \ - khint_t bits, count; \ - khint32_t *used; \ - khkey_t *keys; \ - } HType; - -#define __KHASHL_PROTOTYPES(HType, prefix, khkey_t) \ - extern HType *prefix##_init(void); \ - extern void prefix##_destroy(HType *h); \ - extern void prefix##_clear(HType *h); \ - extern khint_t prefix##_getp(const HType *h, const khkey_t *key); \ - extern int prefix##_resize(HType *h, khint_t new_n_buckets); \ - extern khint_t prefix##_putp(HType *h, const khkey_t *key, int *absent); \ - extern void prefix##_del(HType *h, khint_t k); - -#define __KHASHL_IMPL_BASIC(SCOPE, HType, prefix) \ - SCOPE HType *prefix##_init(void) { \ - return (HType*)kcalloc(1, sizeof(HType)); \ - } \ - SCOPE void prefix##_destroy(HType *h) { \ - if (!h) return; \ - kfree((void *)h->keys); kfree(h->used); \ - kfree(h); \ - } \ - SCOPE void prefix##_clear(HType *h) { \ - if (h && h->used) { \ - uint32_t n_buckets = 1U << h->bits; \ - memset(h->used, 0, __kh_fsize(n_buckets) * sizeof(khint32_t)); \ - h->count = 0; \ - } \ - } - -#define __KHASHL_IMPL_GET(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ - SCOPE khint_t prefix##_getp(const HType *h, const khkey_t *key) { \ - khint_t i, last, n_buckets, mask; \ - if (h->keys == 0) return 0; \ - n_buckets = 1U << h->bits; \ - mask = n_buckets - 1U; \ - i = last = __kh_h2b(__hash_fn(*key), h->bits); \ - while (__kh_used(h->used, i) && !__hash_eq(h->keys[i], *key)) { \ - i = (i + 1U) & mask; \ - if (i == last) return n_buckets; \ - } \ - return !__kh_used(h->used, i)? n_buckets : i; \ - } \ - SCOPE khint_t prefix##_get(const HType *h, khkey_t key) { return prefix##_getp(h, &key); } - -#define __KHASHL_IMPL_RESIZE(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ - SCOPE int prefix##_resize(HType *h, khint_t new_n_buckets) { \ - khint32_t *new_used = 0; \ - khint_t j = 0, x = new_n_buckets, n_buckets, new_bits, new_mask; \ - while ((x >>= 1) != 0) ++j; \ - if (new_n_buckets & (new_n_buckets - 1)) ++j; \ - new_bits = j > 2? j : 2; \ - new_n_buckets = 1U << new_bits; \ - if (h->count > (new_n_buckets>>1) + (new_n_buckets>>2)) return 0; /* requested size is too small */ \ - new_used = (khint32_t*)kmalloc(__kh_fsize(new_n_buckets) * sizeof(khint32_t)); \ - memset(new_used, 0, __kh_fsize(new_n_buckets) * sizeof(khint32_t)); \ - if (!new_used) return -1; /* not enough memory */ \ - n_buckets = h->keys? 1U<<h->bits : 0U; \ - if (n_buckets < new_n_buckets) { /* expand */ \ - khkey_t *new_keys = (khkey_t*)krealloc((void*)h->keys, new_n_buckets * sizeof(khkey_t)); \ - if (!new_keys) { kfree(new_used); return -1; } \ - h->keys = new_keys; \ - } /* otherwise shrink */ \ - new_mask = new_n_buckets - 1; \ - for (j = 0; j != n_buckets; ++j) { \ - khkey_t key; \ - if (!__kh_used(h->used, j)) continue; \ - key = h->keys[j]; \ - __kh_set_unused(h->used, j); \ - while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ - khint_t i; \ - i = __kh_h2b(__hash_fn(key), new_bits); \ - while (__kh_used(new_used, i)) i = (i + 1) & new_mask; \ - __kh_set_used(new_used, i); \ - if (i < n_buckets && __kh_used(h->used, i)) { /* kick out the existing element */ \ - { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ - __kh_set_unused(h->used, i); /* mark it as deleted in the old hash table */ \ - } else { /* write the element and jump out of the loop */ \ - h->keys[i] = key; \ - break; \ - } \ - } \ - } \ - if (n_buckets > new_n_buckets) /* shrink the hash table */ \ - h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ - kfree(h->used); /* free the working space */ \ - h->used = new_used, h->bits = new_bits; \ - return 0; \ - } - -#define __KHASHL_IMPL_PUT(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ - SCOPE khint_t prefix##_putp(HType *h, const khkey_t *key, int *absent) { \ - khint_t n_buckets, i, last, mask; \ - n_buckets = h->keys? 1U<<h->bits : 0U; \ - *absent = -1; \ - if (h->count >= (n_buckets>>1) + (n_buckets>>2)) { /* rehashing */ \ - if (prefix##_resize(h, n_buckets + 1U) < 0) \ - return n_buckets; \ - n_buckets = 1U<<h->bits; \ - } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ - mask = n_buckets - 1; \ - i = last = __kh_h2b(__hash_fn(*key), h->bits); \ - while (__kh_used(h->used, i) && !__hash_eq(h->keys[i], *key)) { \ - i = (i + 1U) & mask; \ - if (i == last) break; \ - } \ - if (!__kh_used(h->used, i)) { /* not present at all */ \ - h->keys[i] = *key; \ - __kh_set_used(h->used, i); \ - ++h->count; \ - *absent = 1; \ - } else *absent = 0; /* Don't touch h->keys[i] if present */ \ - return i; \ - } \ - SCOPE khint_t prefix##_put(HType *h, khkey_t key, int *absent) { return prefix##_putp(h, &key, absent); } - -#define __KHASHL_IMPL_DEL(SCOPE, HType, prefix, khkey_t, __hash_fn) \ - SCOPE int prefix##_del(HType *h, khint_t i) { \ - khint_t j = i, k, mask, n_buckets; \ - if (h->keys == 0) return 0; \ - n_buckets = 1U<<h->bits; \ - mask = n_buckets - 1U; \ - while (1) { \ - j = (j + 1U) & mask; \ - if (j == i || !__kh_used(h->used, j)) break; /* j==i only when the table is completely full */ \ - k = __kh_h2b(__hash_fn(h->keys[j]), h->bits); \ - if ((j > i && (k <= i || k > j)) || (j < i && (k <= i && k > j))) \ - h->keys[i] = h->keys[j], i = j; \ - } \ - __kh_set_unused(h->used, i); \ - --h->count; \ - return 1; \ - } - -#define KHASHL_DECLARE(HType, prefix, khkey_t) \ - __KHASHL_TYPE(HType, khkey_t) \ - __KHASHL_PROTOTYPES(HType, prefix, khkey_t) - -#define KHASHL_INIT(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ - __KHASHL_TYPE(HType, khkey_t) \ - __KHASHL_IMPL_BASIC(SCOPE, HType, prefix) \ - __KHASHL_IMPL_GET(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ - __KHASHL_IMPL_RESIZE(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ - __KHASHL_IMPL_PUT(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ - __KHASHL_IMPL_DEL(SCOPE, HType, prefix, khkey_t, __hash_fn) - -/***************************** - * More convenient interface * - *****************************/ - -#define __kh_packed __attribute__ ((__packed__)) -#define __kh_cached_hash(x) ((x).hash) - -#define KHASHL_SET_INIT(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ - typedef struct { khkey_t key; } __kh_packed HType##_s_bucket_t; \ - static kh_inline khint_t prefix##_s_hash(HType##_s_bucket_t x) { return __hash_fn(x.key); } \ - static kh_inline int prefix##_s_eq(HType##_s_bucket_t x, HType##_s_bucket_t y) { return __hash_eq(x.key, y.key); } \ - KHASHL_INIT(KH_LOCAL, HType, prefix##_s, HType##_s_bucket_t, prefix##_s_hash, prefix##_s_eq) \ - SCOPE HType *prefix##_init(void) { return prefix##_s_init(); } \ - SCOPE void prefix##_destroy(HType *h) { prefix##_s_destroy(h); } \ - 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); } \ - SCOPE int prefix##_del(HType *h, khint_t k) { return prefix##_s_del(h, k); } \ - 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); } - -#define KHASHL_MAP_INIT(SCOPE, HType, prefix, khkey_t, kh_val_t, __hash_fn, __hash_eq) \ - typedef struct { khkey_t key; kh_val_t val; } __kh_packed HType##_m_bucket_t; \ - static kh_inline khint_t prefix##_m_hash(HType##_m_bucket_t x) { return __hash_fn(x.key); } \ - static kh_inline int prefix##_m_eq(HType##_m_bucket_t x, HType##_m_bucket_t y) { return __hash_eq(x.key, y.key); } \ - KHASHL_INIT(KH_LOCAL, HType, prefix##_m, HType##_m_bucket_t, prefix##_m_hash, prefix##_m_eq) \ - SCOPE HType *prefix##_init(void) { return prefix##_m_init(); } \ - SCOPE void prefix##_destroy(HType *h) { prefix##_m_destroy(h); } \ - 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); } \ - SCOPE int prefix##_del(HType *h, khint_t k) { return prefix##_m_del(h, k); } \ - 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); } - -#define KHASHL_CSET_INIT(SCOPE, HType, prefix, khkey_t, __hash_fn, __hash_eq) \ - typedef struct { khkey_t key; khint_t hash; } __kh_packed HType##_cs_bucket_t; \ - 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); } \ - KHASHL_INIT(KH_LOCAL, HType, prefix##_cs, HType##_cs_bucket_t, __kh_cached_hash, prefix##_cs_eq) \ - SCOPE HType *prefix##_init(void) { return prefix##_cs_init(); } \ - SCOPE void prefix##_destroy(HType *h) { prefix##_cs_destroy(h); } \ - 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); } \ - SCOPE int prefix##_del(HType *h, khint_t k) { return prefix##_cs_del(h, k); } \ - 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); } - -#define KHASHL_CMAP_INIT(SCOPE, HType, prefix, khkey_t, kh_val_t, __hash_fn, __hash_eq) \ - typedef struct { khkey_t key; kh_val_t val; khint_t hash; } __kh_packed HType##_cm_bucket_t; \ - 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); } \ - KHASHL_INIT(KH_LOCAL, HType, prefix##_cm, HType##_cm_bucket_t, __kh_cached_hash, prefix##_cm_eq) \ - SCOPE HType *prefix##_init(void) { return prefix##_cm_init(); } \ - SCOPE void prefix##_destroy(HType *h) { prefix##_cm_destroy(h); } \ - 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); } \ - SCOPE int prefix##_del(HType *h, khint_t k) { return prefix##_cm_del(h, k); } \ - 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); } - -/************************** - * Public macro functions * - **************************/ - -#define kh_bucket(h, x) ((h)->keys[x]) -#define kh_size(h) ((h)->count) -#define kh_capacity(h) ((h)->keys? 1U<<(h)->bits : 0U) -#define kh_end(h) kh_capacity(h) - -#define kh_key(h, x) ((h)->keys[x].key) -#define kh_val(h, x) ((h)->keys[x].val) - -/************************************** - * Common hash and equality functions * - **************************************/ - -#define kh_eq_generic(a, b) ((a) == (b)) -#define kh_eq_str(a, b) (strcmp((a), (b)) == 0) -#define kh_hash_dummy(x) ((khint_t)(x)) - -static kh_inline khint_t kh_hash_uint32(khint_t key) { - key += ~(key << 15); - key ^= (key >> 10); - key += (key << 3); - key ^= (key >> 6); - key += ~(key << 11); - key ^= (key >> 16); - return key; -} - -static kh_inline khint_t kh_hash_uint64(khint64_t key) { - key = ~key + (key << 21); - key = key ^ key >> 24; - key = (key + (key << 3)) + (key << 8); - key = key ^ key >> 14; - key = (key + (key << 2)) + (key << 4); - key = key ^ key >> 28; - key = key + (key << 31); - return (khint_t)key; -} - -static kh_inline khint_t kh_hash_str(const char *s) { - khint_t h = (khint_t)*s; - if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; - return h; -} - -#endif /* __AC_KHASHL_H */ diff --git a/deps/yopt.h b/deps/yopt.h deleted file mode 100644 index f1a3b7e..0000000 --- a/deps/yopt.h +++ /dev/null @@ -1,198 +0,0 @@ -/* Copyright (c) 2012-2013 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* This is a simple command-line option parser. Operation is similar to - * getopt_long(), except with a cleaner API. - * - * This is implemented in a single header file, as it's pretty small and you - * generally only use an option parser in a single .c file in your program. - * - * Supports (examples from GNU tar(1)): - * "--gzip" - * "--file <arg>" - * "--file=<arg>" - * "-z" - * "-f <arg>" - * "-f<arg>" - * "-zf <arg>" - * "-zf<arg>" - * "--" (To stop looking for further options) - * "<arg>" (Non-option arguments) - * - * Issues/non-features: - * - An option either requires an argument or it doesn't. - * - No way to specify how often an option can/should be used. - * - No way to specify the type of an argument (filename/integer/enum/whatever) - */ - - -#ifndef YOPT_H -#define YOPT_H - - -#include <string.h> -#include <stdarg.h> -#include <stdlib.h> -#include <stdio.h> - - -typedef struct { - /* Value yopt_next() will return for this option */ - int val; - /* Whether this option needs an argument */ - int needarg; - /* Name(s) of this option, prefixed with '-' or '--' and separated by a - * comma. E.g. "-z", "--gzip", "-z,--gzip". - * An option can have any number of aliases. - */ - const char *name; -} yopt_opt_t; - - -typedef struct { - int argc; - int cur; - int argsep; /* '--' found */ - char **argv; - char *sh; - const yopt_opt_t *opts; - char errbuf[128]; -} yopt_t; - - -/* opts must be an array of options, terminated with an option with val=0 */ -static inline void yopt_init(yopt_t *o, int argc, char **argv, const yopt_opt_t *opts) { - o->argc = argc; - o->argv = argv; - o->opts = opts; - o->cur = 0; - o->argsep = 0; - o->sh = NULL; -} - - -static inline const yopt_opt_t *_yopt_find(const yopt_opt_t *o, const char *v) { - const char *tn, *tv; - - for(; o->val; o++) { - tn = o->name; - while(*tn) { - tv = v; - while(*tn && *tn != ',' && *tv && *tv != '=' && *tn == *tv) { - tn++; - tv++; - } - if(!(*tn && *tn != ',') && !(*tv && *tv != '=')) - return o; - while(*tn && *tn != ',') - tn++; - while(*tn == ',') - tn++; - } - } - - return NULL; -} - - -static inline int _yopt_err(yopt_t *o, char **val, const char *fmt, ...) { - va_list va; - va_start(va, fmt); - vsnprintf(o->errbuf, sizeof(o->errbuf), fmt, va); - va_end(va); - *val = o->errbuf; - return -2; -} - - -/* Return values: - * 0 -> Non-option argument, val is its value - * -1 -> Last argument has been processed - * -2 -> Error, val will contain the error message. - * x -> Option with val = x found. If the option requires an argument, its - * value will be in val. - */ -static inline int yopt_next(yopt_t *o, char **val) { - const yopt_opt_t *opt; - char sh[3]; - - *val = NULL; - if(o->sh) - goto inshort; - - if(++o->cur >= o->argc) - return -1; - - if(!o->argsep && o->argv[o->cur][0] == '-' && o->argv[o->cur][1] == '-' && o->argv[o->cur][2] == 0) { - o->argsep = 1; - if(++o->cur >= o->argc) - return -1; - } - - if(o->argsep || *o->argv[o->cur] != '-') { - *val = o->argv[o->cur]; - return 0; - } - - if(o->argv[o->cur][1] != '-') { - o->sh = o->argv[o->cur]+1; - goto inshort; - } - - /* Now we're supposed to have a long option */ - if(!(opt = _yopt_find(o->opts, o->argv[o->cur]))) - return _yopt_err(o, val, "Unknown option '%s'", o->argv[o->cur]); - if((*val = strchr(o->argv[o->cur], '=')) != NULL) - (*val)++; - if(!opt->needarg && *val) - return _yopt_err(o, val, "Option '%s' does not accept an argument", o->argv[o->cur]); - if(opt->needarg && !*val) { - if(o->cur+1 >= o->argc) - return _yopt_err(o, val, "Option '%s' requires an argument", o->argv[o->cur]); - *val = o->argv[++o->cur]; - } - return opt->val; - - /* And here we're supposed to have a short option */ -inshort: - sh[0] = '-'; - sh[1] = *o->sh; - sh[2] = 0; - if(!(opt = _yopt_find(o->opts, sh))) - return _yopt_err(o, val, "Unknown option '%s'", sh); - o->sh++; - if(opt->needarg && *o->sh) - *val = o->sh; - else if(opt->needarg) { - if(++o->cur >= o->argc) - return _yopt_err(o, val, "Option '%s' requires an argument", sh); - *val = o->argv[o->cur]; - } - if(!*o->sh || opt->needarg) - o->sh = NULL; - return opt->val; -} - - -#endif - -/* vim: set noet sw=4 ts=4: */ diff --git a/src/browser.c b/src/browser.c deleted file mode 100644 index a223b84..0000000 --- a/src/browser.c +++ /dev/null @@ -1,567 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "global.h" - -#include <string.h> -#include <stdlib.h> -#include <ncurses.h> -#include <time.h> - - -static int graph = 1, show_as = 0, info_show = 0, info_page = 0, info_start = 0, show_items = 0, show_mtime = 0; -static const char *message = NULL; - - - -static void browse_draw_info(struct dir *dr) { - struct dir *t; - struct dir_ext *e = dir_ext_ptr(dr); - char mbuf[46]; - int i; - - nccreate(11, 60, "Item info"); - - if(dr->hlnk) { - nctab(41, info_page == 0, 1, "Info"); - nctab(50, info_page == 1, 2, "Links"); - } - - switch(info_page) { - case 0: - attron(A_BOLD); - ncaddstr(2, 3, "Name:"); - ncaddstr(3, 3, "Path:"); - if(!e) - ncaddstr(4, 3, "Type:"); - else { - ncaddstr(4, 3, "Mode:"); - ncaddstr(4, 21, "UID:"); - ncaddstr(4, 33, "GID:"); - ncaddstr(5, 3, "Last modified:"); - } - ncaddstr(6, 3, " Disk usage:"); - ncaddstr(7, 3, "Apparent size:"); - attroff(A_BOLD); - - ncaddstr(2, 9, cropstr(dr->name, 49)); - ncaddstr(3, 9, cropstr(getpath(dr->parent), 49)); - ncaddstr(4, 9, dr->flags & FF_DIR ? "Directory" : dr->flags & FF_FILE ? "File" : "Other"); - - if(e) { - ncaddstr(4, 9, fmtmode(e->mode)); - ncprint(4, 26, "%d", e->uid); - ncprint(4, 38, "%d", e->gid); - time_t t = (time_t)e->mtime; - strftime(mbuf, sizeof(mbuf), "%Y-%m-%d %H:%M:%S %z", localtime(&t)); - ncaddstr(5, 18, mbuf); - } - - ncmove(6, 18); - printsize(UIC_DEFAULT, dr->size); - addstrc(UIC_DEFAULT, " ("); - addstrc(UIC_NUM, fullsize(dr->size)); - addstrc(UIC_DEFAULT, " B)"); - - ncmove(7, 18); - printsize(UIC_DEFAULT, dr->asize); - addstrc(UIC_DEFAULT, " ("); - addstrc(UIC_NUM, fullsize(dr->asize)); - addstrc(UIC_DEFAULT, " B)"); - break; - - case 1: - for(i=0,t=dr->hlnk; t!=dr; t=t->hlnk,i++) { - if(info_start > i) - continue; - if(i-info_start > 5) - break; - ncaddstr(2+i-info_start, 3, cropstr(getpath(t), 54)); - } - if(t!=dr) - ncaddstr(8, 25, "-- more --"); - break; - } - - ncaddstr(9, 31, "Press "); - addchc(UIC_KEY, 'i'); - addstrc(UIC_DEFAULT, " to hide this window"); -} - - -static void browse_draw_flag(struct dir *n, int *x) { - addchc(n->flags & FF_BSEL ? UIC_FLAG_SEL : UIC_FLAG, - n == dirlist_parent ? ' ' : - n->flags & FF_EXL ? '<' : - n->flags & FF_ERR ? '!' : - n->flags & FF_SERR ? '.' : - n->flags & FF_OTHFS ? '>' : - n->flags & FF_KERNFS ? '^' : - n->flags & FF_FRMLNK ? 'F' : - n->flags & FF_HLNKC ? 'H' : - !(n->flags & FF_FILE - || n->flags & FF_DIR) ? '@' : - n->flags & FF_DIR - && n->sub == NULL ? 'e' : - ' '); - *x += 2; -} - - -static void browse_draw_graph(struct dir *n, int *x) { - float pc = 0.0f; - int o, i, bar_size = wincols/7 > 10 ? wincols/7 : 10; - enum ui_coltype c = n->flags & FF_BSEL ? UIC_SEL : UIC_DEFAULT; - - if(!graph) - return; - - *x += graph == 1 ? (bar_size + 3) : graph == 2 ? 9 : (bar_size + 10); - if(n == dirlist_parent) - return; - - addchc(c, '['); - - /* percentage (6 columns) */ - if(graph == 2 || graph == 3) { - pc = (float)(show_as ? n->parent->asize : n->parent->size); - if(pc < 1) - pc = 1.0f; - uic_set(c == UIC_SEL ? UIC_NUM_SEL : UIC_NUM); - printw("%5.1f", ((float)(show_as ? n->asize : n->size) / pc) * 100.0f); - addchc(c, '%'); - } - - if(graph == 3) - addch(' '); - - /* graph (10+ columns) */ - if(graph == 1 || graph == 3) { - uic_set(c == UIC_SEL ? UIC_GRAPH_SEL : UIC_GRAPH); - o = (int)((float)bar_size*(float)(show_as ? n->asize : n->size) / (float)(show_as ? dirlist_maxa : dirlist_maxs)); - for(i=0; i<bar_size; i++) - addch(i < o ? '#' : ' '); - } - - addchc(c, ']'); -} - - -static void browse_draw_items(struct dir *n, int *x) { - enum ui_coltype c = n->flags & FF_BSEL ? UIC_SEL : UIC_DEFAULT; - enum ui_coltype cn = c == UIC_SEL ? UIC_NUM_SEL : UIC_NUM; - - if(!show_items) - return; - *x += 7; - - if(!n->items) - return; - else if(n->items < 100*1000) { - uic_set(cn); - printw("%6s", fullsize(n->items)); - } else if(n->items < 1000*1000) { - uic_set(cn); - printw("%5.1f", n->items / 1000.0); - addstrc(c, "k"); - } else if(n->items < 1000*1000*1000) { - uic_set(cn); - printw("%5.1f", n->items / 1e6); - addstrc(c, "M"); - } else { - addstrc(c, " > "); - addstrc(cn, "1"); - addchc(c, 'B'); - } -} - - -static void browse_draw_mtime(struct dir *n, int *x) { - enum ui_coltype c = n->flags & FF_BSEL ? UIC_SEL : UIC_DEFAULT; - char mbuf[26]; - struct dir_ext *e; - time_t t; - - if (n->flags & FF_EXT) { - e = dir_ext_ptr(n); - } else if (!strcmp(n->name, "..") && (n->parent->flags & FF_EXT)) { - e = dir_ext_ptr(n->parent); - } else { - snprintf(mbuf, sizeof(mbuf), "no mtime"); - goto no_mtime; - } - t = (time_t)e->mtime; - - strftime(mbuf, sizeof(mbuf), "%Y-%m-%d %H:%M:%S %z", localtime(&t)); - uic_set(c == UIC_SEL ? UIC_NUM_SEL : UIC_NUM); -no_mtime: - printw("%26s", mbuf); - *x += 27; -} - - -static void browse_draw_item(struct dir *n, int row) { - int x = 0; - - enum ui_coltype c = n->flags & FF_BSEL ? UIC_SEL : UIC_DEFAULT; - uic_set(c); - mvhline(row, 0, ' ', wincols); - move(row, 0); - - browse_draw_flag(n, &x); - move(row, x); - - if(n != dirlist_parent) - printsize(c, show_as ? n->asize : n->size); - x += 10; - move(row, x); - - browse_draw_graph(n, &x); - move(row, x); - - browse_draw_items(n, &x); - move(row, x); - - if (extended_info && show_mtime) { - browse_draw_mtime(n, &x); - move(row, x); - } - - if(n->flags & FF_DIR) - c = c == UIC_SEL ? UIC_DIR_SEL : UIC_DIR; - addchc(c, n->flags & FF_DIR ? '/' : ' '); - addstrc(c, cropstr(n->name, wincols-x-1)); -} - - -void browse_draw() { - struct dir *t; - const char *tmp; - int selected = 0, i; - - erase(); - t = dirlist_get(0); - - /* top line - basic info */ - uic_set(UIC_HD); - mvhline(0, 0, ' ', wincols); - mvprintw(0,0,"%s %s ~ Use the arrow keys to navigate, press ", PACKAGE_NAME, PACKAGE_VERSION); - addchc(UIC_KEY_HD, '?'); - addstrc(UIC_HD, " for help"); - if(dir_import_active) - mvaddstr(0, wincols-10, "[imported]"); - else if(read_only) - mvaddstr(0, wincols-11, "[read-only]"); - - /* second line - the path */ - mvhlinec(UIC_DEFAULT, 1, 0, '-', wincols); - if(dirlist_par) { - mvaddchc(UIC_DEFAULT, 1, 3, ' '); - tmp = getpath(dirlist_par); - mvaddstrc(UIC_DIR, 1, 4, cropstr(tmp, wincols-8)); - mvaddchc(UIC_DEFAULT, 1, 4+((int)strlen(tmp) > wincols-8 ? wincols-8 : (int)strlen(tmp)), ' '); - } - - /* bottom line - stats */ - uic_set(UIC_HD); - mvhline(winrows-1, 0, ' ', wincols); - if(t) { - mvaddstr(winrows-1, 0, " Total disk usage: "); - printsize(UIC_HD, t->parent->size); - addstrc(UIC_HD, " Apparent size: "); - uic_set(UIC_NUM_HD); - printsize(UIC_HD, t->parent->asize); - addstrc(UIC_HD, " Items: "); - uic_set(UIC_NUM_HD); - printw("%d", t->parent->items); - } else - mvaddstr(winrows-1, 0, " No items to display."); - uic_set(UIC_DEFAULT); - - /* nothing to display? stop here. */ - if(!t) - return; - - /* get start position */ - t = dirlist_top(0); - - /* print the list to the screen */ - for(i=0; t && i<winrows-3; t=dirlist_next(t),i++) { - browse_draw_item(t, 2+i); - /* save the selected row number for later */ - if(t->flags & FF_BSEL) - selected = i; - } - - /* draw message window */ - if(message) { - nccreate(6, 60, "Message"); - ncaddstr(2, 2, message); - ncaddstr(4, 34, "Press any key to continue"); - } - - /* draw information window */ - t = dirlist_get(0); - if(!message && info_show && t != dirlist_parent) - browse_draw_info(t); - - /* move cursor to selected row for accessibility */ - move(selected+2, 0); -} - - -int browse_key(int ch) { - struct dir *t, *sel; - int i, catch = 0; - - /* message window overwrites all keys */ - if(message) { - message = NULL; - return 0; - } - - sel = dirlist_get(0); - - /* info window overwrites a few keys */ - if(info_show && sel) - switch(ch) { - case '1': - info_page = 0; - break; - case '2': - if(sel->hlnk) - info_page = 1; - break; - case KEY_RIGHT: - case 'l': - if(sel->hlnk) { - info_page = 1; - catch++; - } - break; - case KEY_LEFT: - case 'h': - if(sel->hlnk) { - info_page = 0; - catch++; - } - break; - case KEY_UP: - case 'k': - if(sel->hlnk && info_page == 1) { - if(info_start > 0) - info_start--; - catch++; - } - break; - case KEY_DOWN: - case 'j': - case ' ': - if(sel->hlnk && info_page == 1) { - for(i=0,t=sel->hlnk; t!=sel; t=t->hlnk) - i++; - if(i > info_start+6) - info_start++; - catch++; - } - break; - } - - if(!catch) - switch(ch) { - /* selecting items */ - case KEY_UP: - case 'k': - dirlist_select(dirlist_get(-1)); - dirlist_top(-1); - info_start = 0; - break; - case KEY_DOWN: - case 'j': - dirlist_select(dirlist_get(1)); - dirlist_top(1); - info_start = 0; - break; - case KEY_HOME: - dirlist_select(dirlist_next(NULL)); - dirlist_top(2); - info_start = 0; - break; - case KEY_LL: - case KEY_END: - dirlist_select(dirlist_get(1<<30)); - dirlist_top(1); - info_start = 0; - break; - case KEY_PPAGE: - dirlist_select(dirlist_get(-1*(winrows-3))); - dirlist_top(-1); - info_start = 0; - break; - case KEY_NPAGE: - dirlist_select(dirlist_get(winrows-3)); - dirlist_top(1); - info_start = 0; - break; - - /* sorting items */ - case 'n': - dirlist_set_sort(DL_COL_NAME, dirlist_sort_col == DL_COL_NAME ? !dirlist_sort_desc : 0, DL_NOCHANGE); - info_show = 0; - break; - case 's': - i = show_as ? DL_COL_ASIZE : DL_COL_SIZE; - dirlist_set_sort(i, dirlist_sort_col == i ? !dirlist_sort_desc : 1, DL_NOCHANGE); - info_show = 0; - break; - case 'C': - dirlist_set_sort(DL_COL_ITEMS, dirlist_sort_col == DL_COL_ITEMS ? !dirlist_sort_desc : 1, DL_NOCHANGE); - info_show = 0; - break; - case 'M': - if (extended_info) { - dirlist_set_sort(DL_COL_MTIME, dirlist_sort_col == DL_COL_MTIME ? !dirlist_sort_desc : 1, DL_NOCHANGE); - info_show = 0; - } - break; - case 'e': - dirlist_set_hidden(!dirlist_hidden); - info_show = 0; - break; - case 't': - dirlist_set_sort(DL_NOCHANGE, DL_NOCHANGE, !dirlist_sort_df); - info_show = 0; - break; - case 'a': - show_as = !show_as; - if(dirlist_sort_col == DL_COL_ASIZE || dirlist_sort_col == DL_COL_SIZE) - dirlist_set_sort(show_as ? DL_COL_ASIZE : DL_COL_SIZE, DL_NOCHANGE, DL_NOCHANGE); - info_show = 0; - break; - - /* browsing */ - case 10: - case KEY_RIGHT: - case 'l': - if(sel != NULL && sel->flags & FF_DIR) { - dirlist_open(sel == dirlist_parent ? dirlist_par->parent : sel); - dirlist_top(-3); - } - info_show = 0; - break; - case KEY_LEFT: - case KEY_BACKSPACE: - case 'h': - case '<': - if(dirlist_par && dirlist_par->parent != NULL) { - dirlist_open(dirlist_par->parent); - dirlist_top(-3); - } - info_show = 0; - break; - - /* and other stuff */ - case 'r': - if(dir_import_active) { - message = "Directory imported from file, won't refresh."; - break; - } - if(dirlist_par) { - dir_ui = 2; - dir_mem_init(dirlist_par); - dir_scan_init(getpath(dirlist_par)); - } - info_show = 0; - break; - case 'q': - if(info_show) - info_show = 0; - else - if (confirm_quit) - quit_init(); - else return 1; - break; - case 'g': - if(++graph > 3) - graph = 0; - info_show = 0; - break; - case 'c': - show_items = !show_items; - break; - case 'm': - if (extended_info) - show_mtime = !show_mtime; - break; - case 'i': - info_show = !info_show; - break; - case '?': - help_init(); - info_show = 0; - break; - case 'd': - if(read_only >= 1 || dir_import_active) { - message = read_only >= 1 - ? "File deletion disabled in read-only mode." - : "File deletion not available for imported directories."; - break; - } - if(sel == NULL || sel == dirlist_parent) - break; - info_show = 0; - if((t = dirlist_get(1)) == sel) - if((t = dirlist_get(-1)) == sel || t == dirlist_parent) - t = NULL; - delete_init(sel, t); - break; - case 'b': - if(read_only >= 2 || dir_import_active) { - message = read_only >= 2 - ? "Shell feature disabled in read-only mode." - : "Shell feature not available for imported directories."; - break; - } - shell_init(); - break; - } - - /* make sure the info_* options are correct */ - sel = dirlist_get(0); - if(!info_show || sel == dirlist_parent) - info_show = info_page = info_start = 0; - else if(sel && !sel->hlnk) - info_page = info_start = 0; - - return 0; -} - - -void browse_init(struct dir *par) { - pstate = ST_BROWSE; - message = NULL; - dirlist_open(par); -} - diff --git a/src/browser.h b/src/browser.h deleted file mode 100644 index 9f1579c..0000000 --- a/src/browser.h +++ /dev/null @@ -1,37 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#ifndef _browser_h -#define _browser_h - -#include "global.h" - -int browse_key(int); -void browse_draw(void); -void browse_init(struct dir *); - - -#endif - diff --git a/src/delete.c b/src/delete.c deleted file mode 100644 index d60ee6c..0000000 --- a/src/delete.c +++ /dev/null @@ -1,253 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - - -#include "global.h" - -#include <string.h> -#include <errno.h> -#include <unistd.h> - - -#define DS_CONFIRM 0 -#define DS_PROGRESS 1 -#define DS_FAILED 2 - - -static struct dir *root, *nextsel, *curdir; -static char noconfirm = 0, ignoreerr = 0, state; -static signed char seloption; -static int lasterrno; - - -static void delete_draw_confirm(void) { - nccreate(6, 60, "Confirm delete"); - - ncprint(1, 2, "Are you sure you want to delete \"%s\"%c", - cropstr(root->name, 21), root->flags & FF_DIR ? ' ' : '?'); - if(root->flags & FF_DIR && root->sub != NULL) - ncprint(2, 18, "and all of its contents?"); - - if(seloption == 0) - attron(A_REVERSE); - ncaddstr(4, 15, "yes"); - attroff(A_REVERSE); - if(seloption == 1) - attron(A_REVERSE); - ncaddstr(4, 24, "no"); - attroff(A_REVERSE); - if(seloption == 2) - attron(A_REVERSE); - ncaddstr(4, 31, "don't ask me again"); - attroff(A_REVERSE); - - ncmove(4, seloption == 0 ? 15 : seloption == 1 ? 24 : 31); -} - - -static void delete_draw_progress(void) { - nccreate(6, 60, "Deleting..."); - - ncaddstr(1, 2, cropstr(getpath(curdir), 47)); - ncaddstr(4, 41, "Press "); - addchc(UIC_KEY, 'q'); - addstrc(UIC_DEFAULT, " to abort"); -} - - -static void delete_draw_error(void) { - nccreate(6, 60, "Error!"); - - ncprint(1, 2, "Can't delete %s:", cropstr(getpath(curdir), 42)); - ncaddstr(2, 4, strerror(lasterrno)); - - if(seloption == 0) - attron(A_REVERSE); - ncaddstr(4, 14, "abort"); - attroff(A_REVERSE); - if(seloption == 1) - attron(A_REVERSE); - ncaddstr(4, 23, "ignore"); - attroff(A_REVERSE); - if(seloption == 2) - attron(A_REVERSE); - ncaddstr(4, 33, "ignore all"); - attroff(A_REVERSE); -} - - -void delete_draw() { - browse_draw(); - switch(state) { - case DS_CONFIRM: delete_draw_confirm(); break; - case DS_PROGRESS: delete_draw_progress(); break; - case DS_FAILED: delete_draw_error(); break; - } -} - - -int delete_key(int ch) { - /* confirm */ - if(state == DS_CONFIRM) - switch(ch) { - case KEY_LEFT: - case 'h': - if(--seloption < 0) - seloption = 0; - break; - case KEY_RIGHT: - case 'l': - if(++seloption > 2) - seloption = 2; - break; - case '\n': - if(seloption == 1) - return 1; - if(seloption == 2) - noconfirm++; - state = DS_PROGRESS; - break; - case 'q': - return 1; - } - /* processing deletion */ - else if(state == DS_PROGRESS) - switch(ch) { - case 'q': - return 1; - } - /* error */ - else if(state == DS_FAILED) - switch(ch) { - case KEY_LEFT: - case 'h': - if(--seloption < 0) - seloption = 0; - break; - case KEY_RIGHT: - case 'l': - if(++seloption > 2) - seloption = 2; - break; - case 10: - if(seloption == 0) - return 1; - if(seloption == 2) - ignoreerr++; - state = DS_PROGRESS; - break; - case 'q': - return 1; - } - - return 0; -} - - -static int delete_dir(struct dir *dr) { - struct dir *nxt, *cur; - int r; - - /* check for input or screen resizes */ - curdir = dr; - if(input_handle(1)) - return 1; - - /* do the actual deleting */ - if(dr->flags & FF_DIR) { - if((r = chdir(dr->name)) < 0) - goto delete_nxt; - if(dr->sub != NULL) { - nxt = dr->sub; - while(nxt != NULL) { - cur = nxt; - nxt = cur->next; - if(delete_dir(cur)) - return 1; - } - } - if((r = chdir("..")) < 0) - goto delete_nxt; - r = dr->sub == NULL ? rmdir(dr->name) : 0; - } else - r = unlink(dr->name); - -delete_nxt: - /* error occurred, ask user what to do */ - if(r == -1 && !ignoreerr) { - state = DS_FAILED; - lasterrno = errno; - curdir = dr; - while(state == DS_FAILED) - if(input_handle(0)) - return 1; - } else if(!(dr->flags & FF_DIR && dr->sub != NULL)) { - freedir(dr); - return 0; - } - return root == dr ? 1 : 0; -} - - -void delete_process() { - struct dir *par; - - /* confirm */ - seloption = 1; - while(state == DS_CONFIRM && !noconfirm) - if(input_handle(0)) { - browse_init(root->parent); - return; - } - - /* chdir */ - if(path_chdir(getpath(root->parent)) < 0) { - state = DS_FAILED; - lasterrno = errno; - while(state == DS_FAILED) - if(input_handle(0)) - return; - } - - /* delete */ - seloption = 0; - state = DS_PROGRESS; - par = root->parent; - delete_dir(root); - if(nextsel) - nextsel->flags |= FF_BSEL; - browse_init(par); - if(nextsel) - dirlist_top(-4); -} - - -void delete_init(struct dir *dr, struct dir *s) { - state = DS_CONFIRM; - root = curdir = dr; - pstate = ST_DEL; - nextsel = s; -} - diff --git a/src/delete.h b/src/delete.h deleted file mode 100644 index 6abb431..0000000 --- a/src/delete.h +++ /dev/null @@ -1,37 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#ifndef _delete_h -#define _delete_h - -#include "global.h" - -void delete_process(void); -int delete_key(int); -void delete_draw(void); -void delete_init(struct dir *, struct dir *); - - -#endif diff --git a/src/dir.h b/src/dir.h deleted file mode 100644 index fc33eb0..0000000 --- a/src/dir.h +++ /dev/null @@ -1,141 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#ifndef _dir_h -#define _dir_h - -/* The dir_* functions and files implement the SCAN state and are organized as - * follows: - * - * Input: - * Responsible for getting a directory structure into ncdu. Will call the - * Output functions for data and the UI functions for feedback. Currently - * there is only one input implementation: dir_scan.c - * Output: - * Called by the Input handling code when there's some new file/directory - * information. The Output code is responsible for doing something with it - * and determines what action should follow after the Input is done. - * Currently there is only one output implementation: dir_mem.c. - * Common: - * Utility functions and UI code for use by the Input handling code to draw - * progress/error information on the screen, handle any user input and misc. - * stuff. - */ - - -/* "Interface" that Input code should call and Output code should implement. */ -struct dir_output { - /* Called when there is new file/dir info. Call stack for an example - * directory structure: - * / item('/') - * /subdir item('subdir') - * /subdir/f item('f') - * .. item(NULL) - * /abc item('abc') - * .. item(NULL) - * Every opened dir is followed by a call to NULL. There is only one top-level - * dir item. The name of the top-level dir item is the absolute path to the - * scanned directory. - * - * The *item struct has the following fields set when item() is called: - * size, asize, ino, dev, flags (only DIR,FILE,ERR,OTHFS,EXL,HLNKC). - * All other fields/flags should be initialized to NULL or 0. - * The name and dir_ext fields are given separately. - * All pointers may be overwritten or freed in subsequent calls, so this - * function should make a copy if necessary. - * - * The function should return non-zero on error, at which point errno is - * assumed to be set to something sensible. - */ - int (*item)(struct dir *, const char *, struct dir_ext *); - - /* Finalizes the output to go to the next program state or exit ncdu. Called - * after item(NULL) has been called for the root item or before any item() - * has been called at all. - * Argument indicates success (0) or failure (1). - * Failure happens when the root directory couldn't be opened, chdir, lstat, - * read, when it is empty, or when the user aborted the operation. - * Return value should be 0 to continue running ncdu, 1 to exit. - */ - int (*final)(int); - - /* The output code is responsible for updating these stats. Can be 0 when not - * available. */ - int64_t size; - int items; -}; - - -/* Initializes the SCAN state and dir_output for immediate browsing. - * On success: - * If a dir item is given, overwrites it with the new dir struct. - * Then calls browse_init(new_dir_struct->sub). - * On failure: - * If a dir item is given, will just call browse_init(orig). - * Otherwise, will exit ncdu. - */ -void dir_mem_init(struct dir *); - -/* Initializes the SCAN state and dir_output for exporting to a file. */ -int dir_export_init(const char *fn); - - -/* Function set by input code. Returns dir_output.final(). */ -extern int (*dir_process)(void); - -/* Scanning a live directory */ -extern int dir_scan_smfs; -void dir_scan_init(const char *path); - -/* Importing a file */ -extern int dir_import_active; -int dir_import_init(const char *fn); - -#if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS -extern int exclude_kernfs; -#endif - - -/* The currently configured output functions. */ -extern struct dir_output dir_output; - -/* Current path that we're working with. These are defined in dir_common.c. */ -extern char *dir_curpath; -void dir_curpath_set(const char *); -void dir_curpath_enter(const char *); -void dir_curpath_leave(void); - -/* Sets the path where the last error occurred, or reset on NULL. */ -void dir_setlasterr(const char *); - -/* Error message on fatal error, or NULL if there hasn't been a fatal error yet. */ -extern char *dir_fatalerr; -void dir_seterr(const char *, ...); - -extern int dir_ui; -int dir_key(int); -void dir_draw(void); - -#endif diff --git a/src/dir_common.c b/src/dir_common.c deleted file mode 100644 index 9eb06e8..0000000 --- a/src/dir_common.c +++ /dev/null @@ -1,232 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "global.h" - -#include <string.h> -#include <stdlib.h> -#include <stdio.h> -#include <stdarg.h> - - -int (*dir_process)(void); -char *dir_curpath; /* Full path of the last seen item. */ -struct dir_output dir_output; -char *dir_fatalerr; /* Error message on a fatal error. (NULL if there was no fatal error) */ -int dir_ui; /* User interface to use */ -static int confirm_quit_while_scanning_stage_1_passed; /* Additional check before quitting */ -static char *lasterr; /* Path where the last error occurred. */ -static int curpathl; /* Allocated length of dir_curpath */ -static int lasterrl; /* ^ of lasterr */ - - -static void curpath_resize(int s) { - if(curpathl < s) { - curpathl = s < 128 ? 128 : s < curpathl*2 ? curpathl*2 : s; - dir_curpath = xrealloc(dir_curpath, curpathl); - } -} - - -void dir_curpath_set(const char *path) { - curpath_resize(strlen(path)+1); - strcpy(dir_curpath, path); -} - - -void dir_curpath_enter(const char *name) { - curpath_resize(strlen(dir_curpath)+strlen(name)+2); - if(dir_curpath[1]) - strcat(dir_curpath, "/"); - strcat(dir_curpath, name); -} - - -/* removes last component from dir_curpath */ -void dir_curpath_leave() { - char *tmp; - if((tmp = strrchr(dir_curpath, '/')) == NULL) - strcpy(dir_curpath, "/"); - else if(tmp != dir_curpath) - tmp[0] = 0; - else - tmp[1] = 0; -} - - -void dir_setlasterr(const char *path) { - if(!path) { - free(lasterr); - lasterr = NULL; - lasterrl = 0; - return; - } - int req = strlen(path)+1; - if(lasterrl < req) { - lasterrl = req; - lasterr = xrealloc(lasterr, lasterrl); - } - strcpy(lasterr, path); -} - - -void dir_seterr(const char *fmt, ...) { - free(dir_fatalerr); - dir_fatalerr = NULL; - if(!fmt) - return; - - va_list va; - va_start(va, fmt); - dir_fatalerr = xmalloc(1024); /* Should be enough for everything... */ - vsnprintf(dir_fatalerr, 1023, fmt, va); - dir_fatalerr[1023] = 0; - va_end(va); -} - - -static void draw_progress(void) { - static const char scantext[] = "Scanning..."; - static const char loadtext[] = "Loading..."; - static size_t anpos = 0; - const char *antext = dir_import_active ? loadtext : scantext; - char ani[16] = {0}; - size_t i; - int width = wincols-5; - - nccreate(10, width, antext); - - ncaddstr(2, 2, "Total items: "); - uic_set(UIC_NUM); - printw("%-9d", dir_output.items); - - if(dir_output.size) { - ncaddstrc(UIC_DEFAULT, 2, 24, "size: "); - printsize(UIC_DEFAULT, dir_output.size); - } - - uic_set(UIC_DEFAULT); - ncprint(3, 2, "Current item: %s", cropstr(dir_curpath, width-18)); - if(confirm_quit_while_scanning_stage_1_passed) { - ncaddstr(8, width-26, "Press "); - addchc(UIC_KEY, 'y'); - addstrc(UIC_DEFAULT, " to confirm abort"); - } else { - ncaddstr(8, width-18, "Press "); - addchc(UIC_KEY, 'q'); - addstrc(UIC_DEFAULT, " to abort"); - } - - /* show warning if we couldn't open a dir */ - if(lasterr) { - attron(A_BOLD); - ncaddstr(5, 2, "Warning:"); - attroff(A_BOLD); - ncprint(5, 11, "error scanning %-32s", cropstr(lasterr, width-28)); - ncaddstr(6, 3, "some directory sizes may not be correct"); - } - - /* animation - but only if the screen refreshes more than or once every second */ - if(update_delay <= 1000) { - if(++anpos == strlen(antext)*2) - anpos = 0; - memset(ani, ' ', strlen(antext)); - if(anpos < strlen(antext)) - for(i=0; i<=anpos; i++) - ani[i] = antext[i]; - else - for(i=strlen(antext)-1; i>anpos-strlen(antext); i--) - ani[i] = antext[i]; - } else - strcpy(ani, antext); - ncaddstr(8, 3, ani); -} - - -static void draw_error(char *cur, char *msg) { - int width = wincols-5; - nccreate(7, width, "Error!"); - - attron(A_BOLD); - ncaddstr(2, 2, "Error:"); - attroff(A_BOLD); - - ncprint(2, 9, "could not open %s", cropstr(cur, width-26)); - ncprint(3, 4, "%s", cropstr(msg, width-8)); - ncaddstr(5, width-30, "press any key to continue..."); -} - - -void dir_draw() { - float f; - const char *unit; - - switch(dir_ui) { - case 0: - if(dir_fatalerr) - fprintf(stderr, "%s.\n", dir_fatalerr); - break; - case 1: - if(dir_fatalerr) - fprintf(stderr, "\r%s.\n", dir_fatalerr); - else if(dir_output.size) { - f = formatsize(dir_output.size, &unit); - fprintf(stderr, "\r%-55s %8d files /%5.1f %s", - cropstr(dir_curpath, 55), dir_output.items, f, unit); - } else - fprintf(stderr, "\r%-65s %8d files", cropstr(dir_curpath, 65), dir_output.items); - break; - case 2: - browse_draw(); - if(dir_fatalerr) - draw_error(dir_curpath, dir_fatalerr); - else - draw_progress(); - break; - } -} - - -/* This function can't be called unless dir_ui == 2 - * (Doesn't really matter either way). */ -int dir_key(int ch) { - if(dir_fatalerr) - return 1; - if(confirm_quit && confirm_quit_while_scanning_stage_1_passed) { - if (ch == 'y'|| ch == 'Y') { - return 1; - } else { - confirm_quit_while_scanning_stage_1_passed = 0; - return 0; - } - } else if(ch == 'q') { - if(confirm_quit) { - confirm_quit_while_scanning_stage_1_passed = 1; - return 0; - } else - return 1; - } - return 0; -} diff --git a/src/dir_export.c b/src/dir_export.c deleted file mode 100644 index dd96721..0000000 --- a/src/dir_export.c +++ /dev/null @@ -1,194 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "global.h" - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <time.h> - - -static FILE *stream; - -/* Stack of device IDs, also used to keep track of the level of nesting */ -static struct stack { - uint64_t *list; - int size, top; -} stack; - - -static void output_string(const char *str) { - for(; *str; str++) { - switch(*str) { - case '\n': fputs("\\n", stream); break; - case '\r': fputs("\\r", stream); break; - case '\b': fputs("\\b", stream); break; - case '\t': fputs("\\t", stream); break; - case '\f': fputs("\\f", stream); break; - case '\\': fputs("\\\\", stream); break; - case '"': fputs("\\\"", stream); break; - default: - if((unsigned char)*str <= 31 || (unsigned char)*str == 127) - fprintf(stream, "\\u00%02x", *str); - else - fputc(*str, stream); - break; - } - } -} - - -static void output_int(uint64_t n) { - char tmp[20]; - int i = 0; - - do - tmp[i++] = n % 10; - while((n /= 10) > 0); - - while(i--) - fputc(tmp[i]+'0', stream); -} - - -static void output_info(struct dir *d, const char *name, struct dir_ext *e) { - if(!extended_info || !(d->flags & FF_EXT)) - e = NULL; - - fputs("{\"name\":\"", stream); - output_string(name); - fputc('"', stream); - - /* No need for asize/dsize if they're 0 (which happens with excluded or failed-to-stat files) */ - if(d->asize) { - fputs(",\"asize\":", stream); - output_int((uint64_t)d->asize); - } - if(d->size) { - fputs(",\"dsize\":", stream); - output_int((uint64_t)d->size); - } - - if(d->dev != nstack_top(&stack, 0)) { - fputs(",\"dev\":", stream); - output_int(d->dev); - } - fputs(",\"ino\":", stream); - output_int(d->ino); - - if(e) { - fputs(",\"uid\":", stream); - output_int(e->uid); - fputs(",\"gid\":", stream); - output_int(e->gid); - fputs(",\"mode\":", stream); - output_int(e->mode); - fputs(",\"mtime\":", stream); - output_int(e->mtime); - } - - /* TODO: Including the actual number of links would be nicer. */ - if(d->flags & FF_HLNKC) - fputs(",\"hlnkc\":true", stream); - if(d->flags & FF_ERR) - fputs(",\"read_error\":true", stream); - /* excluded/error'd files are "unknown" with respect to the "notreg" field. */ - if(!(d->flags & (FF_DIR|FF_FILE|FF_ERR|FF_EXL|FF_OTHFS|FF_KERNFS|FF_FRMLNK))) - fputs(",\"notreg\":true", stream); - if(d->flags & FF_EXL) - fputs(",\"excluded\":\"pattern\"", stream); - else if(d->flags & FF_OTHFS) - fputs(",\"excluded\":\"othfs\"", stream); - else if(d->flags & FF_KERNFS) - fputs(",\"excluded\":\"kernfs\"", stream); - else if(d->flags & FF_FRMLNK) - fputs(",\"excluded\":\"frmlnk\"", stream); - - fputc('}', stream); -} - - -/* Note on error handling: For convenience, we just keep writing to *stream - * without checking the return values of the functions. Only at the and of each - * item() call do we check for ferror(). This greatly simplifies the code, but - * assumes that calls to fwrite()/fput./etc don't do any weird stuff when - * called with a stream that's in an error state. */ -static int item(struct dir *item, const char *name, struct dir_ext *ext) { - if(!item) { - nstack_pop(&stack); - if(!stack.top) { /* closing of the root item */ - fputs("]]", stream); - return fclose(stream); - } else /* closing of a regular directory item */ - fputs("]", stream); - return ferror(stream); - } - - dir_output.items++; - - /* File header. - * TODO: Add scan options? */ - if(!stack.top) { - fputs("[1,1,{\"progname\":\""PACKAGE"\",\"progver\":\""PACKAGE_VERSION"\",\"timestamp\":", stream); - output_int((uint64_t)time(NULL)); - fputc('}', stream); - } - - fputs(",\n", stream); - if(item->flags & FF_DIR) - fputc('[', stream); - - output_info(item, name, ext); - - if(item->flags & FF_DIR) - nstack_push(&stack, item->dev); - - return ferror(stream); -} - - -static int final(int fail) { - nstack_free(&stack); - return fail ? 1 : 1; /* Silences -Wunused-parameter */ -} - - -int dir_export_init(const char *fn) { - if(strcmp(fn, "-") == 0) - stream = stdout; - else if((stream = fopen(fn, "w")) == NULL) - return 1; - - nstack_init(&stack); - - pstate = ST_CALC; - dir_output.item = item; - dir_output.final = final; - dir_output.size = 0; - dir_output.items = 0; - return 0; -} - diff --git a/src/dir_import.c b/src/dir_import.c deleted file mode 100644 index bde822c..0000000 --- a/src/dir_import.c +++ /dev/null @@ -1,615 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -/* This JSON parser has the following limitations: - * - No support for character encodings incompatible with ASCII (e.g. - * UTF-16/32) - * - Doesn't validate UTF-8 correctness (in fact, besides the ASCII part this - * parser doesn't know anything about encoding). - * - Doesn't validate that there are no duplicate keys in JSON objects. - * - Isn't very strict with validating non-integer numbers. - */ - -#include "global.h" - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <errno.h> -#include <limits.h> - - -/* Max. length of any JSON string we're interested in. A string may of course - * be larger, we're not going to read more than MAX_VAL in memory. If a string - * we're interested in (e.g. a file name) is longer than this, reading the - * import will results in an error. */ -#define MAX_VAL (32*1024) - -/* Minimum number of bytes we request from fread() */ -#define MIN_READ_SIZE 1024 - -/* Read buffer size. Must be at least 2*MIN_READ_SIZE, everything larger - * improves performance. */ -#define READ_BUF_SIZE (32*1024) - - -int dir_import_active = 0; - - -/* Use a struct for easy batch-allocation and deallocation of state data. */ -static struct ctx { - FILE *stream; - - int line; - int byte; - int eof; - int items; - char *buf; /* points into readbuf, always zero-terminated. */ - char *lastfill; /* points into readbuf, location of the zero terminator. */ - - /* scratch space */ - struct dir *buf_dir; - struct dir_ext buf_ext[1]; - - char buf_name[MAX_VAL]; - char val[MAX_VAL]; - char readbuf[READ_BUF_SIZE]; -} *ctx; - - -/* Fills readbuf with data from the stream. *buf will have at least n (< - * READ_BUF_SIZE) bytes available, unless the stream reached EOF or an error - * occurred. If the file data contains a null-type, this is considered an error. - * Returns 0 on success, non-zero on error. */ -static int fill(int n) { - int r; - - if(ctx->eof) - return 0; - - r = READ_BUF_SIZE-(ctx->lastfill - ctx->readbuf); /* number of bytes left in the buffer */ - if(n < r) - n = r-1; - if(n < MIN_READ_SIZE) { - r = ctx->lastfill - ctx->buf; /* number of unread bytes left in the buffer */ - memcpy(ctx->readbuf, ctx->buf, r); - ctx->lastfill = ctx->readbuf + r; - ctx->buf = ctx->readbuf; - n = READ_BUF_SIZE-r-1; - } - - do { - r = fread(ctx->lastfill, 1, n, ctx->stream); - if(r != n) { - if(feof(ctx->stream)) - ctx->eof = 1; - else if(ferror(ctx->stream) && errno != EINTR) { - dir_seterr("Read error: %s", strerror(errno)); - return 1; - } - } - - ctx->lastfill[r] = 0; - if(strlen(ctx->lastfill) != (size_t)r) { - dir_seterr("Zero-byte found in JSON stream"); - return 1; - } - ctx->lastfill += r; - n -= r; - } while(!ctx->eof && n > MIN_READ_SIZE); - - return 0; -} - - -/* Two macros that break function calling behaviour, but are damn convenient */ -#define E(_x, _m) do {\ - if(_x) {\ - if(!dir_fatalerr)\ - dir_seterr("Line %d byte %d: %s", ctx->line, ctx->byte, _m);\ - return 1;\ - }\ - } while(0) - -#define C(_x) do {\ - if(_x)\ - return 1;\ - } while(0) - - -/* Require at least n bytes in the buffer, throw an error on early EOF. - * (Macro to quickly handle the common case) */ -#define rfill1 (!*ctx->buf && _rfill(1)) -#define rfill(_n) ((ctx->lastfill - ctx->buf < (_n)) && _rfill(_n)) - -static int _rfill(int n) { - C(fill(n)); - E(ctx->lastfill - ctx->buf < n, "Unexpected EOF"); - return 0; -} - - -/* Consumes n bytes from the buffer. */ -static inline void con(int n) { - ctx->buf += n; - ctx->byte += n; -} - - -/* Consumes any whitespace. If *ctx->buf == 0 after this function, we've reached EOF. */ -static int cons(void) { - while(1) { - C(!*ctx->buf && fill(1)); - - switch(*ctx->buf) { - case 0x0A: - /* Special-case the newline-character with respect to consuming stuff - * from the buffer. This is the only function which *can* consume the - * newline character, so it's more efficient to handle it in here rather - * than in the more general con(). */ - ctx->buf++; - ctx->line++; - ctx->byte = 0; - break; - case 0x20: - case 0x09: - case 0x0D: - con(1); - break; - default: - return 0; - } - } -} - - -static int rstring_esc(char **dest, int *destlen) { - unsigned int n; - - C(rfill1); - -#define ap(c) if(*destlen > 1) { *((*dest)++) = c; (*destlen)--; } - switch(*ctx->buf) { - case '"': ap('"'); con(1); break; - case '\\': ap('\\'); con(1); break; - case '/': ap('/'); con(1); break; - case 'b': ap(0x08); con(1); break; - case 'f': ap(0x0C); con(1); break; - case 'n': ap(0x0A); con(1); break; - case 'r': ap(0x0D); con(1); break; - case 't': ap(0x09); con(1); break; - case 'u': - C(rfill(5)); -#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) - n = (hn(ctx->buf[1])<<12) + (hn(ctx->buf[2])<<8) + (hn(ctx->buf[3])<<4) + hn(ctx->buf[4]); -#undef hn - if(n <= 0x007F) { - ap(n); - } else if(n <= 0x07FF) { - ap(0xC0 | (n>>6)); - ap(0x80 | (n & 0x3F)); - } else if(n <= 0xFFFF) { - ap(0xE0 | (n>>12)); - ap(0x80 | ((n>>6) & 0x3F)); - ap(0x80 | (n & 0x3F)); - } else /* this happens if there was an invalid character (n >= (1<<16)) */ - E(1, "Invalid character in \\u escape"); - con(5); - break; - default: - E(1, "Invalid escape sequence"); - } -#undef ap - return 0; -} - - -/* Parse a JSON string and write it to *dest (max. destlen). Consumes but - * otherwise ignores any characters if the string is longer than destlen. *dest - * will be null-terminated, dest[destlen-1] = 0 if the string was cut just long - * enough of was cut off. That byte will be left untouched if the string is - * small enough. */ -static int rstring(char *dest, int destlen) { - C(rfill1); - E(*ctx->buf != '"', "Expected string"); - con(1); - - while(1) { - C(rfill1); - if(*ctx->buf == '"') - break; - if(*ctx->buf == '\\') { - con(1); - C(rstring_esc(&dest, &destlen)); - continue; - } - E((unsigned char)*ctx->buf <= 0x1F || (unsigned char)*ctx->buf == 0x7F, "Invalid character"); - if(destlen > 1) { - *(dest++) = *ctx->buf; - destlen--; - } - con(1); - } - con(1); - if(destlen > 0) - *dest = 0; - return 0; -} - - -/* Parse and consume a JSON integer. Throws an error if the value does not fit - * in an uint64_t, is not an integer or is larger than 'max'. */ -static int rint64(uint64_t *val, uint64_t max) { - uint64_t v; - int haschar = 0; - *val = 0; - while(1) { - C(!*ctx->buf && fill(1)); - if(*ctx->buf == '0' && !haschar) { - con(1); - break; - } - if(*ctx->buf >= '0' && *ctx->buf <= '9') { - haschar = 1; - v = (*val)*10 + (*ctx->buf-'0'); - E(v < *val, "Invalid (positive) integer"); - *val = v; - con(1); - continue; - } - E(!haschar, "Invalid (positive) integer"); - break; - } - E(*val > max, "Integer out of range"); - return 0; -} - - -/* Parse and consume a JSON number. The result is discarded. - * TODO: Improve validation. */ -static int rnum(void) { - int haschar = 0; - C(rfill1); - while(1) { - C(!*ctx->buf && fill(1)); - if(*ctx->buf == 'e' || *ctx->buf == 'E' || *ctx->buf == '-' || *ctx->buf == '+' || *ctx->buf == '.' || (*ctx->buf >= '0' && *ctx->buf <= '9')) { - haschar = 1; - con(1); - } else { - E(!haschar, "Invalid JSON value"); - break; - } - } - return 0; -} - - -static int rlit(const char *v, int len) { - C(rfill(len)); - E(strncmp(ctx->buf, v, len) != 0, "Invalid JSON value"); - con(len); - return 0; -} - - -/* Parse the "<space> <string> <space> : <space>" part of an object key. */ -static int rkey(char *dest, int destlen) { - C(cons() || rstring(dest, destlen) || cons()); - E(*ctx->buf != ':', "Expected ':'"); - con(1); - return cons(); -} - - -/* (Recursively) parse and consume any JSON value. The result is discarded. */ -static int rval(void) { - C(rfill1); - switch(*ctx->buf) { - case 't': /* true */ - C(rlit("true", 4)); - break; - case 'f': /* false */ - C(rlit("false", 5)); - break; - case 'n': /* null */ - C(rlit("null", 4)); - break; - case '"': /* string */ - C(rstring(NULL, 0)); - break; - case '{': /* object */ - con(1); - while(1) { - C(cons()); - if(*ctx->buf == '}') - break; - C(rkey(NULL, 0) || rval() || cons()); - if(*ctx->buf == '}') - break; - E(*ctx->buf != ',', "Expected ',' or '}'"); - con(1); - } - con(1); - break; - case '[': /* array */ - con(1); - while(1) { - C(cons()); - if(*ctx->buf == ']') - break; - C(cons() || rval() || cons()); - if(*ctx->buf == ']') - break; - E(*ctx->buf != ',', "Expected ',' or ']'"); - con(1); - } - con(1); - break; - default: /* assume number */ - C(rnum()); - break; - } - - return 0; -} - - -/* Consumes everything up to the root item, and checks that this item is a dir. */ -static int header(void) { - uint64_t v; - - C(cons()); - E(*ctx->buf != '[', "Expected JSON array"); - con(1); - C(cons() || rint64(&v, 10000) || cons()); - E(v != 1, "Incompatible major format version"); - E(*ctx->buf != ',', "Expected ','"); - con(1); - C(cons() || rint64(&v, 10000) || cons()); /* Ignore the minor version for now */ - E(*ctx->buf != ',', "Expected ','"); - con(1); - /* Metadata block is currently ignored */ - C(cons() || rval() || cons()); - E(*ctx->buf != ',', "Expected ','"); - con(1); - - C(cons()); - E(*ctx->buf != '[', "Top-level item must be a directory"); - - return 0; -} - - -static int item(uint64_t); - -/* Read and add dir contents */ -static int itemdir(uint64_t dev) { - while(1) { - C(cons()); - if(*ctx->buf == ']') - break; - E(*ctx->buf != ',', "Expected ',' or ']'"); - con(1); - C(cons() || item(dev)); - } - con(1); - C(cons()); - return 0; -} - - -/* Reads a JSON object representing a struct dir/dir_ext item. Writes to - * ctx->buf_dir, ctx->buf_ext and ctx->buf_name. */ -static int iteminfo(void) { - uint64_t iv; - - E(*ctx->buf != '{', "Expected JSON object"); - con(1); - - while(1) { - C(rkey(ctx->val, MAX_VAL)); - /* TODO: strcmp() in this fashion isn't very fast. */ - if(strcmp(ctx->val, "name") == 0) { /* name */ - ctx->val[MAX_VAL-1] = 1; - C(rstring(ctx->val, MAX_VAL)); - E(ctx->val[MAX_VAL-1] != 1, "Too large string value"); - strcpy(ctx->buf_name, ctx->val); - } else if(strcmp(ctx->val, "asize") == 0) { /* asize */ - C(rint64(&iv, INT64_MAX)); - ctx->buf_dir->asize = iv; - } else if(strcmp(ctx->val, "dsize") == 0) { /* dsize */ - C(rint64(&iv, INT64_MAX)); - ctx->buf_dir->size = iv; - } else if(strcmp(ctx->val, "dev") == 0) { /* dev */ - C(rint64(&iv, UINT64_MAX)); - ctx->buf_dir->dev = iv; - } else if(strcmp(ctx->val, "ino") == 0) { /* ino */ - C(rint64(&iv, UINT64_MAX)); - ctx->buf_dir->ino = iv; - } else if(strcmp(ctx->val, "uid") == 0) { /* uid */ - C(rint64(&iv, INT32_MAX)); - ctx->buf_dir->flags |= FF_EXT; - ctx->buf_ext->uid = iv; - } else if(strcmp(ctx->val, "gid") == 0) { /* gid */ - C(rint64(&iv, INT32_MAX)); - ctx->buf_dir->flags |= FF_EXT; - ctx->buf_ext->gid = iv; - } else if(strcmp(ctx->val, "mode") == 0) { /* mode */ - C(rint64(&iv, UINT16_MAX)); - ctx->buf_dir->flags |= FF_EXT; - ctx->buf_ext->mode = iv; - } else if(strcmp(ctx->val, "mtime") == 0) { /* mtime */ - C(rint64(&iv, UINT64_MAX)); - ctx->buf_dir->flags |= FF_EXT; - ctx->buf_ext->mtime = iv; - } else if(strcmp(ctx->val, "hlnkc") == 0) { /* hlnkc */ - if(*ctx->buf == 't') { - C(rlit("true", 4)); - ctx->buf_dir->flags |= FF_HLNKC; - } else - C(rlit("false", 5)); - } else if(strcmp(ctx->val, "read_error") == 0) { /* read_error */ - if(*ctx->buf == 't') { - C(rlit("true", 4)); - ctx->buf_dir->flags |= FF_ERR; - } else - C(rlit("false", 5)); - } else if(strcmp(ctx->val, "excluded") == 0) { /* excluded */ - C(rstring(ctx->val, 8)); - if(strcmp(ctx->val, "otherfs") == 0) - ctx->buf_dir->flags |= FF_OTHFS; - else if(strcmp(ctx->val, "kernfs") == 0) - ctx->buf_dir->flags |= FF_KERNFS; - else if(strcmp(ctx->val, "frmlnk") == 0) - ctx->buf_dir->flags |= FF_FRMLNK; - else - ctx->buf_dir->flags |= FF_EXL; - } else if(strcmp(ctx->val, "notreg") == 0) { /* notreg */ - if(*ctx->buf == 't') { - C(rlit("true", 4)); - ctx->buf_dir->flags &= ~FF_FILE; - } else - C(rlit("false", 5)); - } else - C(rval()); - - C(cons()); - if(*ctx->buf == '}') - break; - E(*ctx->buf != ',', "Expected ',' or '}'"); - con(1); - } - con(1); - - E(!*ctx->buf_name, "No name field present in item information object"); - ctx->items++; - /* Only call input_handle() once for every 32 items. Importing items is so - * fast that the time spent in input_handle() dominates when called every - * time. Don't set this value too high, either, as feedback should still be - * somewhat responsive when our import data comes from a slow-ish source. */ - return !(ctx->items & 31) ? input_handle(1) : 0; -} - - -/* Recursively reads a file or directory item */ -static int item(uint64_t dev) { - int isdir = 0; - int isroot = ctx->items == 0; - - if(*ctx->buf == '[') { - isdir = 1; - con(1); - C(cons()); - } - - memset(ctx->buf_dir, 0, offsetof(struct dir, name)); - memset(ctx->buf_ext, 0, sizeof(struct dir_ext)); - *ctx->buf_name = 0; - ctx->buf_dir->flags |= isdir ? FF_DIR : FF_FILE; - ctx->buf_dir->dev = dev; - - C(iteminfo()); - dev = ctx->buf_dir->dev; - - if(isroot) - dir_curpath_set(ctx->buf_name); - else - dir_curpath_enter(ctx->buf_name); - - if(isdir) { - if(dir_output.item(ctx->buf_dir, ctx->buf_name, ctx->buf_ext)) { - dir_seterr("Output error: %s", strerror(errno)); - return 1; - } - C(itemdir(dev)); - if(dir_output.item(NULL, 0, NULL)) { - dir_seterr("Output error: %s", strerror(errno)); - return 1; - } - } else if(dir_output.item(ctx->buf_dir, ctx->buf_name, ctx->buf_ext)) { - dir_seterr("Output error: %s", strerror(errno)); - return 1; - } - - if(!isroot) - dir_curpath_leave(); - - return 0; -} - - -static int footer(void) { - C(cons()); - E(*ctx->buf != ']', "Expected ']'"); - con(1); - C(cons()); - E(*ctx->buf, "Trailing garbage"); - return 0; -} - - -static int process(void) { - int fail = 0; - - header(); - - if(!dir_fatalerr) - fail = item(0); - - if(!dir_fatalerr && !fail) - footer(); - - if(fclose(ctx->stream) && !dir_fatalerr && !fail) - dir_seterr("Error closing file: %s", strerror(errno)); - free(ctx->buf_dir); - free(ctx); - - while(dir_fatalerr && !input_handle(0)) - ; - return dir_output.final(dir_fatalerr || fail); -} - - -int dir_import_init(const char *fn) { - FILE *stream; - if(strcmp(fn, "-") == 0) - stream = stdin; - else if((stream = fopen(fn, "r")) == NULL) - return 1; - - ctx = xmalloc(sizeof(struct ctx)); - ctx->stream = stream; - ctx->line = 1; - ctx->byte = ctx->eof = ctx->items = 0; - ctx->buf = ctx->lastfill = ctx->readbuf; - ctx->buf_dir = xmalloc(dir_memsize("")); - ctx->readbuf[0] = 0; - - dir_curpath_set(fn); - dir_process = process; - dir_import_active = 1; - return 0; -} - diff --git a/src/dir_mem.c b/src/dir_mem.c deleted file mode 100644 index d1dce8d..0000000 --- a/src/dir_mem.c +++ /dev/null @@ -1,215 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "global.h" - -#include <string.h> -#include <stdlib.h> - -#include <khashl.h> - - -static struct dir *root; /* root directory struct we're scanning */ -static struct dir *curdir; /* directory item that we're currently adding items to */ -static struct dir *orig; /* original directory, when refreshing an already scanned dir */ - -/* Table of struct dir items with more than one link (in order to detect hard links) */ -#define hlink_hash(d) (kh_hash_uint64((khint64_t)d->dev) ^ kh_hash_uint64((khint64_t)d->ino)) -#define hlink_equal(a, b) ((a)->dev == (b)->dev && (a)->ino == (b)->ino) -KHASHL_SET_INIT(KH_LOCAL, hl_t, hl, struct dir *, hlink_hash, hlink_equal) -static hl_t *links = NULL; - - -/* recursively checks a dir structure for hard links and fills the lookup array */ -static void hlink_init(struct dir *d) { - struct dir *t; - - for(t=d->sub; t!=NULL; t=t->next) - hlink_init(t); - - if(!(d->flags & FF_HLNKC)) - return; - int r; - hl_put(links, d, &r); -} - - -/* checks an individual file for hard links and updates its cicrular linked - * list, also updates the sizes of the parent dirs */ -static void hlink_check(struct dir *d) { - struct dir *t, *pt, *par; - int i; - - /* add to links table */ - khint_t k = hl_put(links, d, &i); - - /* found in the table? update hlnk */ - if(!i) { - t = kh_key(links, k); - d->hlnk = t->hlnk == NULL ? t : t->hlnk; - t->hlnk = d; - } - - /* now update the sizes of the parent directories, - * This works by only counting this file in the parent directories where this - * file hasn't been counted yet, which can be determined from the hlnk list. - * XXX: This may not be the most efficient algorithm to do this */ - for(i=1,par=d->parent; i&∥ par=par->parent) { - if(d->hlnk) - for(t=d->hlnk; i&&t!=d; t=t->hlnk) - for(pt=t->parent; i&&pt; pt=pt->parent) - if(pt==par) - i=0; - if(i) { - par->size = adds64(par->size, d->size); - par->asize = adds64(par->asize, d->asize); - } - } -} - - -/* Add item to the correct place in the memory structure */ -static void item_add(struct dir *item) { - if(!root) { - root = item; - /* Make sure that the *root appears to be part of the same dir structure as - * *orig, otherwise the directory size calculation will be incorrect in the - * case of hard links. */ - if(orig) - root->parent = orig->parent; - } else { - item->parent = curdir; - item->next = curdir->sub; - if(item->next) - item->next->prev = item; - curdir->sub = item; - } -} - - -static int item(struct dir *dir, const char *name, struct dir_ext *ext) { - struct dir *t, *item; - - /* Go back to parent dir */ - if(!dir) { - curdir = curdir->parent; - return 0; - } - - if(!root && orig) - name = orig->name; - - if(!extended_info) - dir->flags &= ~FF_EXT; - item = xmalloc(dir->flags & FF_EXT ? dir_ext_memsize(name) : dir_memsize(name)); - memcpy(item, dir, offsetof(struct dir, name)); - strcpy(item->name, name); - if(dir->flags & FF_EXT) - memcpy(dir_ext_ptr(item), ext, sizeof(struct dir_ext)); - - item_add(item); - - /* Ensure that any next items will go to this directory */ - if(item->flags & FF_DIR) - curdir = item; - - /* Special-case the name of the root item to be empty instead of "/". This is - * what getpath() expects. */ - if(item == root && strcmp(item->name, "/") == 0) - item->name[0] = 0; - - /* Update stats of parents. Don't update the size/asize fields if this is a - * possible hard link, because hlnk_check() will take care of it in that - * case. */ - if(item->flags & FF_HLNKC) { - addparentstats(item->parent, 0, 0, 0, 1); - hlink_check(item); - } else if(item->flags & FF_EXT) { - addparentstats(item->parent, item->size, item->asize, dir_ext_ptr(item)->mtime, 1); - } else { - addparentstats(item->parent, item->size, item->asize, 0, 1); - } - - /* propagate ERR and SERR back up to the root */ - if(item->flags & FF_SERR || item->flags & FF_ERR) - for(t=item->parent; t; t=t->parent) - t->flags |= FF_SERR; - - dir_output.size = root->size; - dir_output.items = root->items; - - return 0; -} - - -static int final(int fail) { - hl_destroy(links); - links = NULL; - - if(fail) { - freedir(root); - if(orig) { - browse_init(orig); - return 0; - } else - return 1; - } - - /* success, update references and free original item */ - if(orig) { - root->next = orig->next; - root->prev = orig->prev; - if(root->parent && root->parent->sub == orig) - root->parent->sub = root; - if(root->prev) - root->prev->next = root; - if(root->next) - root->next->prev = root; - orig->next = orig->prev = NULL; - freedir(orig); - } - - browse_init(root); - dirlist_top(-3); - return 0; -} - - -void dir_mem_init(struct dir *_orig) { - orig = _orig; - root = curdir = NULL; - pstate = ST_CALC; - - dir_output.item = item; - dir_output.final = final; - dir_output.size = 0; - dir_output.items = 0; - - /* Init hash table for hard link detection */ - links = hl_init(); - if(orig) - hlink_init(getroot(orig)); -} - diff --git a/src/dir_scan.c b/src/dir_scan.c deleted file mode 100644 index 03a582b..0000000 --- a/src/dir_scan.c +++ /dev/null @@ -1,405 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "global.h" - -#include <string.h> -#include <stdlib.h> -#include <errno.h> - -#include <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <dirent.h> - -#if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH -#include <sys/attr.h> -#endif - -#if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS -#include <sys/statfs.h> -#include <linux/magic.h> -#endif - - -/* set S_BLKSIZE if not defined already in sys/stat.h */ -#ifndef S_BLKSIZE -# define S_BLKSIZE 512 -#endif - - -int dir_scan_smfs; /* Stay on the same filesystem */ - -static uint64_t curdev; /* current device we're scanning on */ - -/* scratch space */ -static struct dir *buf_dir; -static struct dir_ext buf_ext[1]; - - -#if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS -int exclude_kernfs; /* Exclude Linux pseudo filesystems */ - -static int is_kernfs(unsigned long type) { - if( -#ifdef BINFMTFS_MAGIC - type == BINFMTFS_MAGIC || -#endif -#ifdef BPF_FS_MAGIC - type == BPF_FS_MAGIC || -#endif -#ifdef CGROUP_SUPER_MAGIC - type == CGROUP_SUPER_MAGIC || -#endif -#ifdef CGROUP2_SUPER_MAGIC - type == CGROUP2_SUPER_MAGIC|| -#endif -#ifdef DEBUGFS_MAGIC - type == DEBUGFS_MAGIC || -#endif -#ifdef DEVPTS_SUPER_MAGIC - type == DEVPTS_SUPER_MAGIC || -#endif -#ifdef PROC_SUPER_MAGIC - type == PROC_SUPER_MAGIC || -#endif -#ifdef PSTOREFS_MAGIC - type == PSTOREFS_MAGIC || -#endif -#ifdef SECURITYFS_MAGIC - type == SECURITYFS_MAGIC || -#endif -#ifdef SELINUX_MAGIC - type == SELINUX_MAGIC || -#endif -#ifdef SYSFS_MAGIC - type == SYSFS_MAGIC || -#endif -#ifdef TRACEFS_MAGIC - type == TRACEFS_MAGIC || -#endif - 0 - ) - return 1; - - return 0; -} -#endif - -/* Populates the buf_dir and buf_ext with information from the stat struct. - * Sets everything necessary for output_dir.item() except FF_ERR and FF_EXL. */ -static void stat_to_dir(struct stat *fs) { - buf_dir->flags |= FF_EXT; /* We always read extended data because it doesn't have an additional cost */ - buf_dir->ino = (uint64_t)fs->st_ino; - buf_dir->dev = (uint64_t)fs->st_dev; - - if(S_ISREG(fs->st_mode)) - buf_dir->flags |= FF_FILE; - else if(S_ISDIR(fs->st_mode)) - buf_dir->flags |= FF_DIR; - - if(!S_ISDIR(fs->st_mode) && fs->st_nlink > 1) - buf_dir->flags |= FF_HLNKC; - - if(dir_scan_smfs && curdev != buf_dir->dev) - buf_dir->flags |= FF_OTHFS; - - if(!(buf_dir->flags & (FF_OTHFS|FF_EXL|FF_KERNFS))) { - buf_dir->size = fs->st_blocks * S_BLKSIZE; - buf_dir->asize = fs->st_size; - } - - buf_ext->mode = fs->st_mode; - buf_ext->mtime = fs->st_mtime; - buf_ext->uid = (int)fs->st_uid; - buf_ext->gid = (int)fs->st_gid; -} - - -/* Reads all filenames in the currently chdir'ed directory and stores it as a - * nul-separated list of filenames. The list ends with an empty filename (i.e. - * two nuls). . and .. are not included. Returned memory should be freed. *err - * is set to 1 if some error occurred. Returns NULL if that error was fatal. - * The reason for reading everything in memory first and then walking through - * the list is to avoid eating too many file descriptors in a deeply recursive - * directory. */ -static char *dir_read(int *err) { - DIR *dir; - struct dirent *item; - char *buf = NULL; - size_t buflen = 512; - size_t off = 0; - - if((dir = opendir(".")) == NULL) { - *err = 1; - return NULL; - } - - buf = xmalloc(buflen); - errno = 0; - - while((item = readdir(dir)) != NULL) { - if(item->d_name[0] == '.' && (item->d_name[1] == 0 || (item->d_name[1] == '.' && item->d_name[2] == 0))) - continue; - size_t req = off+3+strlen(item->d_name); - if(req > buflen) { - buflen = req < buflen*2 ? buflen*2 : req; - buf = xrealloc(buf, buflen); - } - strcpy(buf+off, item->d_name); - off += strlen(item->d_name)+1; - } - if(errno) - *err = 1; - if(closedir(dir) < 0) - *err = 1; - - buf[off] = 0; - buf[off+1] = 0; - return buf; -} - - -static int dir_walk(char *); - - -/* Tries to recurse into the current directory item (buf_dir is assumed to be the current dir) */ -static int dir_scan_recurse(const char *name) { - int fail = 0; - char *dir; - - if(chdir(name)) { - dir_setlasterr(dir_curpath); - buf_dir->flags |= FF_ERR; - if(dir_output.item(buf_dir, name, buf_ext) || dir_output.item(NULL, 0, NULL)) { - dir_seterr("Output error: %s", strerror(errno)); - return 1; - } - return 0; - } - - if((dir = dir_read(&fail)) == NULL) { - dir_setlasterr(dir_curpath); - buf_dir->flags |= FF_ERR; - if(dir_output.item(buf_dir, name, buf_ext) || dir_output.item(NULL, 0, NULL)) { - dir_seterr("Output error: %s", strerror(errno)); - return 1; - } - if(chdir("..")) { - dir_seterr("Error going back to parent directory: %s", strerror(errno)); - return 1; - } else - return 0; - } - - /* readdir() failed halfway, not fatal. */ - if(fail) - buf_dir->flags |= FF_ERR; - - if(dir_output.item(buf_dir, name, buf_ext)) { - dir_seterr("Output error: %s", strerror(errno)); - return 1; - } - fail = dir_walk(dir); - if(dir_output.item(NULL, 0, NULL)) { - dir_seterr("Output error: %s", strerror(errno)); - return 1; - } - - /* Not being able to chdir back is fatal */ - if(!fail && chdir("..")) { - dir_seterr("Error going back to parent directory: %s", strerror(errno)); - return 1; - } - - return fail; -} - - -/* Scans and adds a single item. Recurses into dir_walk() again if this is a - * directory. Assumes we're chdir'ed in the directory in which this item - * resides. */ -static int dir_scan_item(const char *name) { - static struct stat st, stl; - int fail = 0; - -#ifdef __CYGWIN__ - /* /proc/registry names may contain slashes */ - if(strchr(name, '/') || strchr(name, '\\')) { - buf_dir->flags |= FF_ERR; - dir_setlasterr(dir_curpath); - } -#endif - - if(exclude_match(dir_curpath)) - buf_dir->flags |= FF_EXL; - - if(!(buf_dir->flags & (FF_ERR|FF_EXL)) && lstat(name, &st)) { - buf_dir->flags |= FF_ERR; - dir_setlasterr(dir_curpath); - } - -#if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS - if(exclude_kernfs && !(buf_dir->flags & (FF_ERR|FF_EXL)) && S_ISDIR(st.st_mode)) { - struct statfs fst; - if(statfs(name, &fst)) { - buf_dir->flags |= FF_ERR; - dir_setlasterr(dir_curpath); - } else if(is_kernfs(fst.f_type)) - buf_dir->flags |= FF_KERNFS; - } -#endif - -#if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH - if(!follow_firmlinks) { - struct attrlist list = { - .bitmapcount = ATTR_BIT_MAP_COUNT, - .forkattr = ATTR_CMNEXT_NOFIRMLINKPATH, - }; - struct { - uint32_t length; - attrreference_t reference; - char extra[PATH_MAX]; - } __attribute__((aligned(4), packed)) attributes; - if (getattrlist(name, &list, &attributes, sizeof(attributes), FSOPT_ATTR_CMN_EXTENDED) == -1) { - buf_dir->flags |= FF_ERR; - dir_setlasterr(dir_curpath); - } else if (strcmp(dir_curpath, (char *)&attributes.reference + attributes.reference.attr_dataoffset)) - buf_dir->flags |= FF_FRMLNK; - } -#endif - - if(!(buf_dir->flags & (FF_ERR|FF_EXL))) { - if(follow_symlinks && S_ISLNK(st.st_mode) && !stat(name, &stl) && !S_ISDIR(stl.st_mode)) - stat_to_dir(&stl); - else - stat_to_dir(&st); - } - - if(cachedir_tags && (buf_dir->flags & FF_DIR) && !(buf_dir->flags & (FF_ERR|FF_EXL|FF_OTHFS|FF_KERNFS|FF_FRMLNK))) - if(has_cachedir_tag(name)) { - buf_dir->flags |= FF_EXL; - buf_dir->size = buf_dir->asize = 0; - } - - /* Recurse into the dir or output the item */ - if(buf_dir->flags & FF_DIR && !(buf_dir->flags & (FF_ERR|FF_EXL|FF_OTHFS|FF_KERNFS|FF_FRMLNK))) - fail = dir_scan_recurse(name); - else if(buf_dir->flags & FF_DIR) { - if(dir_output.item(buf_dir, name, buf_ext) || dir_output.item(NULL, 0, NULL)) { - dir_seterr("Output error: %s", strerror(errno)); - fail = 1; - } - } else if(dir_output.item(buf_dir, name, buf_ext)) { - dir_seterr("Output error: %s", strerror(errno)); - fail = 1; - } - - return fail || input_handle(1); -} - - -/* Walks through the directory that we're currently chdir'ed to. *dir contains - * the filenames as returned by dir_read(), and will be freed automatically by - * this function. */ -static int dir_walk(char *dir) { - int fail = 0; - char *cur; - - fail = 0; - for(cur=dir; !fail&&cur&&*cur; cur+=strlen(cur)+1) { - dir_curpath_enter(cur); - memset(buf_dir, 0, offsetof(struct dir, name)); - memset(buf_ext, 0, sizeof(struct dir_ext)); - fail = dir_scan_item(cur); - dir_curpath_leave(); - } - - free(dir); - return fail; -} - - -static int process(void) { - char *path; - char *dir; - int fail = 0; - struct stat fs; - - memset(buf_dir, 0, offsetof(struct dir, name)); - memset(buf_ext, 0, sizeof(struct dir_ext)); - - if((path = path_real(dir_curpath)) == NULL) - dir_seterr("Error obtaining full path: %s", strerror(errno)); - else { - dir_curpath_set(path); - free(path); - } - - if(!dir_fatalerr && path_chdir(dir_curpath) < 0) - dir_seterr("Error changing directory: %s", strerror(errno)); - - /* Can these even fail after a chdir? */ - if(!dir_fatalerr && lstat(".", &fs) != 0) - dir_seterr("Error obtaining directory information: %s", strerror(errno)); - if(!dir_fatalerr && !S_ISDIR(fs.st_mode)) - dir_seterr("Not a directory"); - - if(!dir_fatalerr && !(dir = dir_read(&fail))) - dir_seterr("Error reading directory: %s", strerror(errno)); - - if(!dir_fatalerr) { - curdev = (uint64_t)fs.st_dev; - if(fail) - buf_dir->flags |= FF_ERR; - stat_to_dir(&fs); - - if(dir_output.item(buf_dir, dir_curpath, buf_ext)) { - dir_seterr("Output error: %s", strerror(errno)); - fail = 1; - } - if(!fail) - fail = dir_walk(dir); - if(!fail && dir_output.item(NULL, 0, NULL)) { - dir_seterr("Output error: %s", strerror(errno)); - fail = 1; - } - } - - while(dir_fatalerr && !input_handle(0)) - ; - return dir_output.final(dir_fatalerr || fail); -} - - -void dir_scan_init(const char *path) { - dir_curpath_set(path); - dir_setlasterr(NULL); - dir_seterr(NULL); - dir_process = process; - if (!buf_dir) - buf_dir = xmalloc(dir_memsize("")); - pstate = ST_CALC; -} diff --git a/src/dirlist.c b/src/dirlist.c deleted file mode 100644 index bf2ea01..0000000 --- a/src/dirlist.c +++ /dev/null @@ -1,398 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "global.h" - -#include <string.h> -#include <stdlib.h> - - -/* public variables */ -struct dir *dirlist_parent = NULL, - *dirlist_par = NULL; -int64_t dirlist_maxs = 0, - dirlist_maxa = 0; - -int dirlist_sort_desc = 1, - dirlist_sort_col = DL_COL_SIZE, - dirlist_sort_df = 0, - dirlist_hidden = 0; - -/* private state vars */ -static struct dir *parent_alloc, *head, *head_real, *selected, *top = NULL; - - - -#define ISHIDDEN(d) (dirlist_hidden && (d) != dirlist_parent && (\ - (d)->flags & FF_EXL || (d)->name[0] == '.' || (d)->name[strlen((d)->name)-1] == '~'\ - )) - - -static inline int cmp_mtime(struct dir *x, struct dir*y) { - int64_t x_mtime = 0, y_mtime = 0; - if (x->flags & FF_EXT) - x_mtime = dir_ext_ptr(x)->mtime; - if (y->flags & FF_EXT) - y_mtime = dir_ext_ptr(y)->mtime; - return (x_mtime > y_mtime ? 1 : (x_mtime == y_mtime ? 0 : -1)); -} - -static int dirlist_cmp(struct dir *x, struct dir *y) { - int r; - - /* dirs are always before files when that option is set */ - if(dirlist_sort_df) { - if(y->flags & FF_DIR && !(x->flags & FF_DIR)) - return 1; - else if(!(y->flags & FF_DIR) && x->flags & FF_DIR) - return -1; - } - - /* sort columns: - * 1 -> 2 -> 3 -> 4 - * NAME: name -> size -> asize -> items - * SIZE: size -> asize -> name -> items - * ASIZE: asize -> size -> name -> items - * ITEMS: items -> size -> asize -> name - * - * Note that the method used below is supposed to be fast, not readable :-) - */ -#define CMP_NAME strcmp(x->name, y->name) -#define CMP_SIZE (x->size > y->size ? 1 : (x->size == y->size ? 0 : -1)) -#define CMP_ASIZE (x->asize > y->asize ? 1 : (x->asize == y->asize ? 0 : -1)) -#define CMP_ITEMS (x->items > y->items ? 1 : (x->items == y->items ? 0 : -1)) - - /* try 1 */ - r = dirlist_sort_col == DL_COL_NAME ? CMP_NAME : - dirlist_sort_col == DL_COL_SIZE ? CMP_SIZE : - dirlist_sort_col == DL_COL_ASIZE ? CMP_ASIZE : - dirlist_sort_col == DL_COL_ITEMS ? CMP_ITEMS : - cmp_mtime(x, y); - /* try 2 */ - if(!r) - r = dirlist_sort_col == DL_COL_SIZE ? CMP_ASIZE : CMP_SIZE; - /* try 3 */ - if(!r) - r = (dirlist_sort_col == DL_COL_NAME || dirlist_sort_col == DL_COL_ITEMS) ? - CMP_ASIZE : CMP_NAME; - /* try 4 */ - if(!r) - r = dirlist_sort_col == DL_COL_ITEMS ? CMP_NAME : CMP_ITEMS; - - /* reverse when sorting in descending order */ - if(dirlist_sort_desc && r != 0) - r = r < 0 ? 1 : -1; - - return r; -} - - -static struct dir *dirlist_sort(struct dir *list) { - struct dir *p, *q, *e, *tail; - int insize, nmerges, psize, qsize, i; - - insize = 1; - while(1) { - p = list; - list = NULL; - tail = NULL; - nmerges = 0; - while(p) { - nmerges++; - q = p; - psize = 0; - for(i=0; i<insize; i++) { - psize++; - q = q->next; - if(!q) break; - } - qsize = insize; - while(psize > 0 || (qsize > 0 && q)) { - if(psize == 0) { - e = q; q = q->next; qsize--; - } else if(qsize == 0 || !q) { - e = p; p = p->next; psize--; - } else if(dirlist_cmp(p,q) <= 0) { - e = p; p = p->next; psize--; - } else { - e = q; q = q->next; qsize--; - } - if(tail) tail->next = e; - else list = e; - e->prev = tail; - tail = e; - } - p = q; - } - tail->next = NULL; - if(nmerges <= 1) { - if(list->parent) - list->parent->sub = list; - return list; - } - insize *= 2; - } -} - - -/* passes through the dir listing once and: - * - makes sure one, and only one, visible item is selected - * - updates the dirlist_(maxs|maxa) values - * - makes sure that the FF_BSEL bits are correct */ -static void dirlist_fixup(void) { - struct dir *t; - - /* we're going to determine the selected items from the list itself, so reset this one */ - selected = NULL; - - for(t=head; t; t=t->next) { - /* not visible? not selected! */ - if(ISHIDDEN(t)) - t->flags &= ~FF_BSEL; - else { - /* visible and selected? make sure only one item is selected */ - if(t->flags & FF_BSEL) { - if(!selected) - selected = t; - else - t->flags &= ~FF_BSEL; - } - } - - /* update dirlist_(maxs|maxa) */ - if(t->size > dirlist_maxs) - dirlist_maxs = t->size; - if(t->asize > dirlist_maxa) - dirlist_maxa = t->asize; - } - - /* no selected items found after one pass? select the first visible item */ - if(!selected) - if((selected = dirlist_next(NULL))) - selected->flags |= FF_BSEL; -} - - -void dirlist_open(struct dir *d) { - dirlist_par = d; - - /* set the head of the list */ - head_real = head = d == NULL ? NULL : d->sub; - - /* reset internal status */ - dirlist_maxs = dirlist_maxa = 0; - - /* stop if this is not a directory list we can work with */ - if(d == NULL) { - dirlist_parent = NULL; - return; - } - - /* sort the dir listing */ - if(head) - head_real = head = dirlist_sort(head); - - /* set the reference to the parent dir */ - if(d->parent) { - if(!parent_alloc) - parent_alloc = xcalloc(1, dir_memsize("..")); - dirlist_parent = parent_alloc; - strcpy(dirlist_parent->name, ".."); - dirlist_parent->next = head; - dirlist_parent->parent = d; - dirlist_parent->sub = d; - dirlist_parent->flags = FF_DIR; - head = dirlist_parent; - } else - dirlist_parent = NULL; - - dirlist_fixup(); -} - - -struct dir *dirlist_next(struct dir *d) { - if(!head) - return NULL; - if(!d) { - if(!ISHIDDEN(head)) - return head; - else - d = head; - } - while((d = d->next)) { - if(!ISHIDDEN(d)) - return d; - } - return NULL; -} - - -static struct dir *dirlist_prev(struct dir *d) { - if(!head || !d) - return NULL; - while((d = d->prev)) { - if(!ISHIDDEN(d)) - return d; - } - if(dirlist_parent) - return dirlist_parent; - return NULL; -} - - -struct dir *dirlist_get(int i) { - struct dir *t = selected, *d; - - if(!head) - return NULL; - - if(ISHIDDEN(selected)) { - selected = dirlist_next(NULL); - return selected; - } - - /* i == 0? return the selected item */ - if(!i) - return selected; - - /* positive number? simply move forward */ - while(i > 0) { - d = dirlist_next(t); - if(!d) - return t; - t = d; - if(!--i) - return t; - } - - /* otherwise, backward */ - while(1) { - d = dirlist_prev(t); - if(!d) - return t; - t = d; - if(!++i) - return t; - } -} - - -void dirlist_select(struct dir *d) { - if(!d || !head || ISHIDDEN(d) || d->parent != head->parent) - return; - - selected->flags &= ~FF_BSEL; - selected = d; - selected->flags |= FF_BSEL; -} - - - -/* We need a hint in order to figure out which item should be on top: - * 0 = only get the current top, don't set anything - * 1 = selected has moved down - * -1 = selected has moved up - * -2 = selected = first item in the list (faster version of '1') - * -3 = top should be considered as invalid (after sorting or opening another dir) - * -4 = an item has been deleted - * -5 = hidden flag has been changed - * - * Actions: - * hint = -1 or -4 -> top = selected_is_visible ? top : selected - * hint = -2 or -3 -> top = selected-(winrows-3)/2 - * hint = 1 -> top = selected_is_visible ? top : selected-(winrows-4) - * hint = 0 or -5 -> top = selected_is_visible ? top : selected-(winrows-3)/2 - * - * Regardless of the hint, the returned top will always be chosen such that the - * selected item is visible. - */ -struct dir *dirlist_top(int hint) { - struct dir *t; - int i, visible = 0; - - if(hint == -2 || hint == -3) - top = NULL; - - /* check whether the current selected item is within the visible window */ - if(top) { - i = winrows-3; - t = dirlist_get(0); - while(t && i--) { - if(t == top) { - visible++; - break; - } - t = dirlist_prev(t); - } - } - - /* otherwise, get a new top */ - if(!visible) - top = hint == -1 || hint == -4 ? dirlist_get(0) : - hint == 1 ? dirlist_get(-1*(winrows-4)) : - dirlist_get(-1*(winrows-3)/2); - - /* also make sure that if the list is longer than the window and the last - * item is visible, that this last item is also the last on the window */ - t = top; - i = winrows-3; - while(t && i--) - t = dirlist_next(t); - t = top; - do { - top = t; - t = dirlist_prev(t); - } while(t && i-- > 0); - - return top; -} - - -void dirlist_set_sort(int col, int desc, int df) { - /* update config */ - if(col != DL_NOCHANGE) - dirlist_sort_col = col; - if(desc != DL_NOCHANGE) - dirlist_sort_desc = desc; - if(df != DL_NOCHANGE) - dirlist_sort_df = df; - - /* sort the list (excluding the parent, which is always on top) */ - if(head_real) - head_real = dirlist_sort(head_real); - if(dirlist_parent) - dirlist_parent->next = head_real; - else - head = head_real; - dirlist_top(-3); -} - - -void dirlist_set_hidden(int hidden) { - dirlist_hidden = hidden; - dirlist_fixup(); - dirlist_top(-5); -} - diff --git a/src/dirlist.h b/src/dirlist.h deleted file mode 100644 index e833b01..0000000 --- a/src/dirlist.h +++ /dev/null @@ -1,86 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -/* Note: all functions below include a 'reference to parent dir' node at the - * top of the list. */ - -#ifndef _dirlist_h -#define _dirlist_h - -#include "global.h" - - -#define DL_NOCHANGE -1 -#define DL_COL_NAME 0 -#define DL_COL_SIZE 1 -#define DL_COL_ASIZE 2 -#define DL_COL_ITEMS 3 -#define DL_COL_MTIME 4 - - -void dirlist_open(struct dir *); - -/* Get the next non-hidden item, - * NULL = get first non-hidden item */ -struct dir *dirlist_next(struct dir *); - -/* Get the struct dir item relative to the selected item, or the item nearest to the requested item - * i = 0 get selected item - * hidden items aren't considered */ -struct dir *dirlist_get(int i); - -/* Get/set the first visible item in the list on the screen */ -struct dir *dirlist_top(int hint); - -/* Set selected dir (must be in the currently opened directory, obviously) */ -void dirlist_select(struct dir *); - -/* Change sort column (arguments should have a NO_CHANGE option) */ -void dirlist_set_sort(int column, int desc, int df); - -/* Set the hidden thingy */ -void dirlist_set_hidden(int hidden); - - -/* DO NOT WRITE TO ANY OF THE BELOW VARIABLES FROM OUTSIDE OF dirlist.c! */ - -/* The 'reference to parent dir' */ -extern struct dir *dirlist_parent; - -/* The actual parent dir */ -extern struct dir *dirlist_par; - -/* current sorting configuration (set with dirlist_set_sort()) */ -extern int dirlist_sort_desc, dirlist_sort_col, dirlist_sort_df; - -/* set with dirlist_set_hidden() */ -extern int dirlist_hidden; - -/* maximum size of an item in the opened dir */ -extern int64_t dirlist_maxs, dirlist_maxa; - - -#endif - diff --git a/src/exclude.c b/src/exclude.c deleted file mode 100644 index 572335f..0000000 --- a/src/exclude.c +++ /dev/null @@ -1,139 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "global.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <fnmatch.h> - - -static struct exclude { - char *pattern; - struct exclude *next; -} *excludes = NULL; - - - -void exclude_add(char *pat) { - struct exclude **n; - - n = &excludes; - while(*n != NULL) - n = &((*n)->next); - - *n = (struct exclude *) xcalloc(1, sizeof(struct exclude)); - (*n)->pattern = (char *) xmalloc(strlen(pat)+1); - strcpy((*n)->pattern, pat); -} - - -int exclude_addfile(char *file) { - FILE *f; - char buf[256]; - int len; - - if((f = fopen(file, "r")) == NULL) - return 1; - - while(fgets(buf, 256, f) != NULL) { - len = strlen(buf)-1; - while(len >=0 && (buf[len] == '\r' || buf[len] == '\n')) - buf[len--] = '\0'; - if(len < 0) - continue; - exclude_add(buf); - } - - len = ferror(f); - fclose(f); - return len; -} - - -int exclude_match(char *path) { - struct exclude *n; - char *c; - - for(n=excludes; n!=NULL; n=n->next) { - if(!fnmatch(n->pattern, path, 0)) - return 1; - for(c = path; *c; c++) - if(*c == '/' && c[1] != '/' && !fnmatch(n->pattern, c+1, 0)) - return 1; - } - return 0; -} - - -void exclude_clear() { - struct exclude *n, *l; - - for(n=excludes; n!=NULL; n=l) { - l = n->next; - free(n->pattern); - free(n); - } - excludes = NULL; -} - - -/* - * Exclusion of directories that contain only cached information. - * See http://www.brynosaurus.com/cachedir/ - */ -#define CACHEDIR_TAG_FILENAME "CACHEDIR.TAG" -#define CACHEDIR_TAG_SIGNATURE "Signature: 8a477f597d28d172789f06886806bc55" - -int has_cachedir_tag(const char *name) { - static int path_l = 1024; - static char *path = NULL; - int l; - char buf[sizeof CACHEDIR_TAG_SIGNATURE - 1]; - FILE *f; - int match = 0; - - /* Compute the required length for `path`. */ - l = strlen(name) + sizeof CACHEDIR_TAG_FILENAME + 2; - if(l > path_l || path == NULL) { - path_l = path_l * 2; - if(path_l < l) - path_l = l; - /* We don't need to copy the content of `path`, so it's more efficient to - * use `free` + `malloc`. */ - free(path); - path = xmalloc(path_l); - } - snprintf(path, path_l, "%s/%s", name, CACHEDIR_TAG_FILENAME); - f = fopen(path, "rb"); - - if(f != NULL) { - match = ((fread(buf, 1, sizeof buf, f) == sizeof buf) && - !memcmp(buf, CACHEDIR_TAG_SIGNATURE, sizeof buf)); - fclose(f); - } - return match; -} diff --git a/src/exclude.h b/src/exclude.h deleted file mode 100644 index 72eff50..0000000 --- a/src/exclude.h +++ /dev/null @@ -1,35 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#ifndef _exclude_h -#define _exclude_h - -void exclude_add(char *); -int exclude_addfile(char *); -int exclude_match(char *); -void exclude_clear(void); -int has_cachedir_tag(const char *name); - -#endif diff --git a/src/global.h b/src/global.h deleted file mode 100644 index afb9c5e..0000000 --- a/src/global.h +++ /dev/null @@ -1,132 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#ifndef _global_h -#define _global_h - -#include "config.h" -#include <stdio.h> -#include <stddef.h> -#include <limits.h> -#include <string.h> -#include <sys/types.h> -#include <sys/stat.h> - -#ifdef HAVE_INTTYPES_H -# include <inttypes.h> -#endif -#ifdef HAVE_STDINT_H -# include <stdint.h> -#endif - -/* File Flags (struct dir -> flags) */ -#define FF_DIR 0x01 -#define FF_FILE 0x02 -#define FF_ERR 0x04 /* error while reading this item */ -#define FF_OTHFS 0x08 /* excluded because it was another filesystem */ -#define FF_EXL 0x10 /* excluded using exclude patterns */ -#define FF_SERR 0x20 /* error in subdirectory */ -#define FF_HLNKC 0x40 /* hard link candidate (file with st_nlink > 1) */ -#define FF_BSEL 0x80 /* selected */ -#define FF_EXT 0x100 /* extended struct available */ -#define FF_KERNFS 0x200 /* excluded because it was a Linux pseudo filesystem */ -#define FF_FRMLNK 0x400 /* excluded because it was a firmlink */ - -/* Program states */ -#define ST_CALC 0 -#define ST_BROWSE 1 -#define ST_DEL 2 -#define ST_HELP 3 -#define ST_SHELL 4 -#define ST_QUIT 5 - - -/* structure representing a file or directory */ -struct dir { - int64_t size, asize; - uint64_t ino, dev; - struct dir *parent, *next, *prev, *sub, *hlnk; - int items; - unsigned short flags; - char name[]; -}; - -/* A note on the ino and dev fields above: ino is usually represented as ino_t, - * which POSIX specifies to be an unsigned integer. dev is usually represented - * as dev_t, which may be either a signed or unsigned integer, and in practice - * both are used. dev represents an index / identifier of a device or - * filesystem, and I'm unsure whether a negative value has any meaning in that - * context. Hence my choice of using an unsigned integer. Negative values, if - * we encounter them, will just get typecasted into a positive value. No - * information is lost in this conversion, and the semantics remain the same. - */ - -/* Extended information for a struct dir. This struct is stored in the same - * memory region as struct dir, placed after the name field. See util.h for - * macros to help manage this. */ -struct dir_ext { - uint64_t mtime; - int uid, gid; - unsigned short mode; -}; - - -/* program state */ -extern int pstate; -/* read-only flag, 1+ = disable deletion, 2+ = also disable shell */ -extern int read_only; -/* minimum screen update interval when calculating, in ms */ -extern long update_delay; -/* filter directories with CACHEDIR.TAG */ -extern int cachedir_tags; -/* flag if we should ask for confirmation when quitting */ -extern int confirm_quit; -/* flag whether we want to enable use of struct dir_ext */ -extern int extended_info; -/* flag whether we want to follow symlinks */ -extern int follow_symlinks; -/* flag whether we want to follow firmlinks */ -extern int follow_firmlinks; - -/* handle input from keyboard and update display */ -int input_handle(int); - -/* de-initialize ncurses */ -void close_nc(void); - - -/* import all other global functions and variables */ -#include "browser.h" -#include "delete.h" -#include "dir.h" -#include "dirlist.h" -#include "exclude.h" -#include "help.h" -#include "path.h" -#include "util.h" -#include "shell.h" -#include "quit.h" - -#endif diff --git a/src/help.c b/src/help.c deleted file mode 100644 index 4ceeebc..0000000 --- a/src/help.c +++ /dev/null @@ -1,212 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "global.h" - -#include <ncurses.h> -#include <string.h> - - -static int page, start; - - -#define KEYS 19 -static const char *keys[KEYS*2] = { -/*|----key----| |----------------description----------------|*/ - "up, k", "Move cursor up", - "down, j", "Move cursor down", - "right/enter", "Open selected directory", - "left, <, h", "Open parent directory", - "n", "Sort by name (ascending/descending)", - "s", "Sort by size (ascending/descending)", - "C", "Sort by items (ascending/descending)", - "M", "Sort by mtime (-e flag)", - "d", "Delete selected file or directory", - "t", "Toggle dirs before files when sorting", - "g", "Show percentage and/or graph", - "a", "Toggle between apparent size and disk usage", - "c", "Toggle display of child item counts", - "m", "Toggle display of latest mtime (-e flag)", - "e", "Show/hide hidden or excluded files", - "i", "Show information about selected item", - "r", "Recalculate the current directory", - "b", "Spawn shell in current directory", - "q", "Quit ncdu" -}; - - -#define FLAGS 9 -static const char *flags[FLAGS*2] = { - "!", "An error occurred while reading this directory", - ".", "An error occurred while reading a subdirectory", - "<", "File or directory is excluded from the statistics", - "e", "Empty directory", - ">", "Directory was on another filesystem", - "@", "This is not a file nor a dir (symlink, socket, ...)", - "^", "Excluded Linux pseudo-filesystem", - "H", "Same file was already counted (hard link)", - "F", "Excluded firmlink", -}; - -void help_draw() { - int i, line; - - browse_draw(); - - nccreate(15, 60, "ncdu help"); - ncaddstr(13, 42, "Press "); - uic_set(UIC_KEY); - addch('q'); - uic_set(UIC_DEFAULT); - addstr(" to close"); - - nctab(30, page == 1, 1, "Keys"); - nctab(39, page == 2, 2, "Format"); - nctab(50, page == 3, 3, "About"); - - switch(page) { - case 1: - line = 1; - for(i=start*2; i<start*2+20; i+=2) { - uic_set(UIC_KEY); - ncaddstr(++line, 13-strlen(keys[i]), keys[i]); - uic_set(UIC_DEFAULT); - ncaddstr(line, 15, keys[i+1]); - } - if(start != KEYS-10) - ncaddstr(12, 25, "-- more --"); - break; - case 2: - attron(A_BOLD); - ncaddstr(2, 3, "X [size] [graph] [file or directory]"); - attroff(A_BOLD); - ncaddstr(3, 4, "The X is only present in the following cases:"); - line = 4; - for(i=start*2; i<start*2+14; i+=2) { - uic_set(UIC_FLAG); - ncaddstr(++line, 4, flags[i]); - uic_set(UIC_DEFAULT); - ncaddstr(line, 7, flags[i+1]); - } - if(start != FLAGS-7) - ncaddstr(12, 25, "-- more --"); - break; - case 3: - /* Indeed, too much spare time */ - attron(A_REVERSE); -#define x 12 -#define y 3 - /* N */ - ncaddstr(y+0, x+0, " "); - ncaddstr(y+1, x+0, " "); - ncaddstr(y+2, x+0, " "); - ncaddstr(y+3, x+0, " "); - ncaddstr(y+4, x+0, " "); - ncaddstr(y+1, x+4, " "); - ncaddstr(y+2, x+4, " "); - ncaddstr(y+3, x+4, " "); - ncaddstr(y+4, x+4, " "); - /* C */ - ncaddstr(y+0, x+8, " "); - ncaddstr(y+1, x+8, " "); - ncaddstr(y+2, x+8, " "); - ncaddstr(y+3, x+8, " "); - ncaddstr(y+4, x+8, " "); - /* D */ - ncaddstr(y+0, x+19, " "); - ncaddstr(y+1, x+19, " "); - ncaddstr(y+2, x+15, " "); - ncaddstr(y+3, x+15, " "); - ncaddstr(y+3, x+19, " "); - ncaddstr(y+4, x+15, " "); - /* U */ - ncaddstr(y+0, x+23, " "); - ncaddstr(y+1, x+23, " "); - ncaddstr(y+2, x+23, " "); - ncaddstr(y+3, x+23, " "); - ncaddstr(y+0, x+27, " "); - ncaddstr(y+1, x+27, " "); - ncaddstr(y+2, x+27, " "); - ncaddstr(y+3, x+27, " "); - ncaddstr(y+4, x+23, " "); - attroff(A_REVERSE); - ncaddstr(y+0, x+30, "NCurses"); - ncaddstr(y+1, x+30, "Disk"); - ncaddstr(y+2, x+30, "Usage"); - ncprint( y+4, x+30, "%s", PACKAGE_VERSION); - ncaddstr( 9, 7, "Written by Yoran Heling <projects@yorhel.nl>"); - ncaddstr(10, 16, "https://dev.yorhel.nl/ncdu/"); - break; - } -} - - -int help_key(int ch) { - switch(ch) { - case '1': - case '2': - case '3': - page = ch-'0'; - start = 0; - break; - case KEY_RIGHT: - case KEY_NPAGE: - case 'l': - if(++page > 3) - page = 3; - start = 0; - break; - case KEY_LEFT: - case KEY_PPAGE: - case 'h': - if(--page < 1) - page = 1; - start = 0; - break; - case KEY_DOWN: - case ' ': - case 'j': - if((page == 1 && start < KEYS-10) || (page == 2 && start < FLAGS-7)) - start++; - break; - case KEY_UP: - case 'k': - if(start > 0) - start--; - break; - default: - pstate = ST_BROWSE; - } - return 0; -} - - -void help_init() { - page = 1; - start = 0; - pstate = ST_HELP; -} - - diff --git a/src/help.h b/src/help.h deleted file mode 100644 index 09fd553..0000000 --- a/src/help.h +++ /dev/null @@ -1,37 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#ifndef _help_h -#define _help_h - -#include "global.h" - -int help_key(int); -void help_draw(void); -void help_init(void); - - -#endif - diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 6e85c52..0000000 --- a/src/main.c +++ /dev/null @@ -1,359 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "global.h" - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <errno.h> - -#include <unistd.h> -#include <sys/time.h> - -#include <yopt.h> - - -int pstate; -int read_only = 0; -long update_delay = 100; -int cachedir_tags = 0; -int extended_info = 0; -int follow_symlinks = 0; -int follow_firmlinks = 1; -int confirm_quit = 0; - -static int min_rows = 17, min_cols = 60; -static int ncurses_init = 0; -static int ncurses_tty = 0; /* Explicitly open /dev/tty instead of using stdio */ -static long lastupdate = 999; - - -static void screen_draw(void) { - switch(pstate) { - case ST_CALC: dir_draw(); break; - case ST_BROWSE: browse_draw(); break; - case ST_HELP: help_draw(); break; - case ST_SHELL: shell_draw(); break; - case ST_DEL: delete_draw(); break; - case ST_QUIT: quit_draw(); break; - } -} - - -/* wait: - * -1: non-blocking, always draw screen - * 0: blocking wait for input and always draw screen - * 1: non-blocking, draw screen only if a configured delay has passed or after keypress - */ -int input_handle(int wait) { - int ch; - struct timeval tv; - - if(wait != 1) - screen_draw(); - else { - gettimeofday(&tv, NULL); - tv.tv_usec = (1000*(tv.tv_sec % 1000) + (tv.tv_usec / 1000)) / update_delay; - if(lastupdate != tv.tv_usec) { - screen_draw(); - lastupdate = tv.tv_usec; - } - } - - /* No actual input handling is done if ncurses hasn't been initialized yet. */ - if(!ncurses_init) - return wait == 0 ? 1 : 0; - - nodelay(stdscr, wait?1:0); - errno = 0; - while((ch = getch()) != ERR) { - if(ch == KEY_RESIZE) { - if(ncresize(min_rows, min_cols)) - min_rows = min_cols = 0; - /* ncresize() may change nodelay state, make sure to revert it. */ - nodelay(stdscr, wait?1:0); - screen_draw(); - continue; - } - switch(pstate) { - case ST_CALC: return dir_key(ch); - case ST_BROWSE: return browse_key(ch); - case ST_HELP: return help_key(ch); - case ST_DEL: return delete_key(ch); - case ST_QUIT: return quit_key(ch); - } - screen_draw(); - } - if(errno == EPIPE || errno == EBADF || errno == EIO) - return 1; - return 0; -} - - -/* parse command line */ -static void argv_parse(int argc, char **argv) { - yopt_t yopt; - int v; - char *val; - char *export = NULL; - char *import = NULL; - char *dir = NULL; - - static yopt_opt_t opts[] = { - { 'h', 0, "-h,-?,--help" }, - { 'q', 0, "-q" }, - { 'v', 0, "-v,-V,--version" }, - { 'x', 0, "-x" }, - { 'e', 0, "-e" }, - { 'r', 0, "-r" }, - { 'o', 1, "-o" }, - { 'f', 1, "-f" }, - { '0', 0, "-0" }, - { '1', 0, "-1" }, - { '2', 0, "-2" }, - { 1, 1, "--exclude" }, - { 'X', 1, "-X,--exclude-from" }, - { 'L', 0, "-L,--follow-symlinks" }, - { 'C', 0, "--exclude-caches" }, - { 2, 0, "--exclude-kernfs" }, - { 3, 0, "--follow-firmlinks" }, /* undocumented, this behavior is the current default */ - { 4, 0, "--exclude-firmlinks" }, - { 's', 0, "--si" }, - { 'Q', 0, "--confirm-quit" }, - { 'c', 1, "--color" }, - {0,0,NULL} - }; - - dir_ui = -1; - si = 0; - - yopt_init(&yopt, argc, argv, opts); - while((v = yopt_next(&yopt, &val)) != -1) { - switch(v) { - case 0 : dir = val; break; - case 'h': - printf("ncdu <options> <directory>\n\n"); - printf(" -h,--help This help message\n"); - printf(" -q Quiet mode, refresh interval 2 seconds\n"); - printf(" -v,-V,--version Print version\n"); - printf(" -x Same filesystem\n"); - printf(" -e Enable extended information\n"); - printf(" -r Read only\n"); - printf(" -o FILE Export scanned directory to FILE\n"); - printf(" -f FILE Import scanned directory from FILE\n"); - printf(" -0,-1,-2 UI to use when scanning (0=none,2=full ncurses)\n"); - printf(" --si Use base 10 (SI) prefixes instead of base 2\n"); - printf(" --exclude PATTERN Exclude files that match PATTERN\n"); - printf(" -X, --exclude-from FILE Exclude files that match any pattern in FILE\n"); - printf(" -L, --follow-symlinks Follow symbolic links (excluding directories)\n"); - printf(" --exclude-caches Exclude directories containing CACHEDIR.TAG\n"); -#if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS - printf(" --exclude-kernfs Exclude Linux pseudo filesystems (procfs,sysfs,cgroup,...)\n"); -#endif -#if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH - printf(" --exclude-firmlinks Exclude firmlinks on macOS\n"); -#endif - printf(" --confirm-quit Confirm quitting ncdu\n"); - printf(" --color SCHEME Set color scheme (off/dark)\n"); - exit(0); - case 'q': update_delay = 2000; break; - case 'v': - printf("ncdu %s\n", PACKAGE_VERSION); - exit(0); - case 'x': dir_scan_smfs = 1; break; - case 'e': extended_info = 1; break; - case 'r': read_only++; break; - case 's': si = 1; break; - case 'o': export = val; break; - case 'f': import = val; break; - case '0': dir_ui = 0; break; - case '1': dir_ui = 1; break; - case '2': dir_ui = 2; break; - case 'Q': confirm_quit = 1; break; - case 1 : exclude_add(val); break; /* --exclude */ - case 'X': - if(exclude_addfile(val)) { - fprintf(stderr, "Can't open %s: %s\n", val, strerror(errno)); - exit(1); - } - break; - case 'L': follow_symlinks = 1; break; - case 'C': - cachedir_tags = 1; - break; - - case 2 : /* --exclude-kernfs */ -#if HAVE_LINUX_MAGIC_H && HAVE_SYS_STATFS_H && HAVE_STATFS - exclude_kernfs = 1; break; -#else - fprintf(stderr, "This feature is not supported on your platform\n"); - exit(1); -#endif - case 3 : /* --follow-firmlinks */ -#if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH - follow_firmlinks = 1; break; -#else - fprintf(stderr, "This feature is not supported on your platform\n"); - exit(1); -#endif - case 4 : /* --exclude-firmlinks */ -#if HAVE_SYS_ATTR_H && HAVE_GETATTRLIST && HAVE_DECL_ATTR_CMNEXT_NOFIRMLINKPATH - follow_firmlinks = 0; break; -#else - fprintf(stderr, "This feature is not supported on your platform\n"); - exit(1); -#endif - case 'c': - if(strcmp(val, "off") == 0) { uic_theme = 0; } - else if(strcmp(val, "dark") == 0) { uic_theme = 1; } - else { - fprintf(stderr, "Unknown --color option: %s\n", val); - exit(1); - } - break; - case -2: - fprintf(stderr, "ncdu: %s.\n", val); - exit(1); - } - } - - if(export) { - if(dir_export_init(export)) { - fprintf(stderr, "Can't open %s: %s\n", export, strerror(errno)); - exit(1); - } - if(strcmp(export, "-") == 0) - ncurses_tty = 1; - } else - dir_mem_init(NULL); - - if(import) { - if(dir_import_init(import)) { - fprintf(stderr, "Can't open %s: %s\n", import, strerror(errno)); - exit(1); - } - if(strcmp(import, "-") == 0) - ncurses_tty = 1; - } else - dir_scan_init(dir ? dir : "."); - - /* Use the single-line scan feedback by default when exporting to file, no - * feedback when exporting to stdout. */ - if(dir_ui == -1) - dir_ui = export && strcmp(export, "-") == 0 ? 0 : export ? 1 : 2; -} - - -/* Initializes ncurses only when not done yet. */ -static void init_nc(void) { - int ok = 0; - FILE *tty; - SCREEN *term; - - if(ncurses_init) - return; - ncurses_init = 1; - - if(ncurses_tty) { - tty = fopen("/dev/tty", "r+"); - if(!tty) { - fprintf(stderr, "Error opening /dev/tty: %s\n", strerror(errno)); - exit(1); - } - term = newterm(NULL, tty, tty); - if(term) - set_term(term); - ok = !!term; - } else { - /* Make sure the user doesn't accidentally pipe in data to ncdu's standard - * input without using "-f -". An annoying input sequence could result in - * the deletion of your files, which we want to prevent at all costs. */ - if(!isatty(0)) { - fprintf(stderr, "Standard input is not a TTY. Did you mean to import a file using '-f -'?\n"); - exit(1); - } - ok = !!initscr(); - } - - if(!ok) { - fprintf(stderr, "Error while initializing ncurses.\n"); - exit(1); - } - - uic_init(); - cbreak(); - noecho(); - curs_set(0); - keypad(stdscr, TRUE); - if(ncresize(min_rows, min_cols)) - min_rows = min_cols = 0; -} - - -void close_nc() { - if(ncurses_init) { - erase(); - refresh(); - endwin(); - } -} - - -/* main program */ -int main(int argc, char **argv) { - read_locale(); - argv_parse(argc, argv); - - if(dir_ui == 2) - init_nc(); - - while(1) { - /* We may need to initialize/clean up the screen when switching from the - * (sometimes non-ncurses) CALC state to something else. */ - if(pstate != ST_CALC) { - if(dir_ui == 1) - fputc('\n', stderr); - init_nc(); - } - - if(pstate == ST_CALC) { - if(dir_process()) { - if(dir_ui == 1) - fputc('\n', stderr); - break; - } - } else if(pstate == ST_DEL) - delete_process(); - else if(input_handle(0)) - break; - } - - close_nc(); - exclude_clear(); - - return 0; -} - diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..65ab30d --- /dev/null +++ b/src/main.zig @@ -0,0 +1,72 @@ +const std = @import("std"); +const model = @import("model.zig"); +const scan = @import("scan.zig"); + +var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; +pub const allocator = &general_purpose_allocator.allocator; + +pub const Config = struct { + same_fs: bool = true, + extended: bool = false, + exclude_caches: bool = false, + follow_symlinks: bool = false, + exclude_kernfs: bool = false, + // TODO: exclude patterns + + update_delay: u32 = 100, + si: bool = false, + // TODO: color scheme + + read_only: bool = false, + can_shell: bool = true, + confirm_quit: bool = false, +}; + +pub var config = Config{}; + +// For debugging +fn writeTree(out: anytype, e: *model.Entry, indent: u32) @TypeOf(out).Error!void { + var i: u32 = 0; + while (i<indent) { + try out.writeByte(' '); + i += 1; + } + try out.print("{s} blocks={d} size={d}", .{ e.name(), e.blocks, e.size }); + + if (e.dir()) |d| { + try out.print(" blocks={d}-{d} size={d}-{d} items={d}-{d} dev={x}", .{ + d.total_blocks, d.shared_blocks, + d.total_size, d.shared_size, + d.total_items, d.shared_items, d.dev + }); + if (d.err) try out.writeAll(" err"); + if (d.suberr) try out.writeAll(" suberr"); + } else if (e.file()) |f| { + if (f.err) try out.writeAll(" err"); + if (f.excluded) try out.writeAll(" excluded"); + if (f.other_fs) try out.writeAll(" other_fs"); + if (f.kernfs) try out.writeAll(" kernfs"); + if (f.notreg) try out.writeAll(" notreg"); + } else if (e.link()) |l| { + try out.print(" ino={x} nlinks={d}", .{ l.ino, l.nlink }); + } + + try out.writeByte('\n'); + if (e.dir()) |d| { + var s = d.sub; + while (s) |sub| { + try writeTree(out, sub, indent+4); + s = sub.next; + } + } +} + +pub fn main() anyerror!void { + std.log.info("align={}, Entry={}, Dir={}, Link={}, File={}.", + .{@alignOf(model.Dir), @sizeOf(model.Entry), @sizeOf(model.Dir), @sizeOf(model.Link), @sizeOf(model.File)}); + try scan.scanRoot("/"); + + //var out = std.io.bufferedWriter(std.io.getStdOut().writer()); + //try writeTree(out.writer(), &model.root.entry, 0); + //try out.flush(); +} diff --git a/src/model.zig b/src/model.zig new file mode 100644 index 0000000..9782c4f --- /dev/null +++ b/src/model.zig @@ -0,0 +1,350 @@ +const std = @import("std"); +const main = @import("main.zig"); + +// While an arena allocator is optimimal for almost all scenarios in which ncdu +// is used, it doesn't allow for re-using deleted nodes after doing a delete or +// refresh operation, so a long-running ncdu session with regular refreshes +// will leak memory, but I'd say that's worth the efficiency gains. +// (TODO: Measure, though. Might as well use a general purpose allocator if the +// memory overhead turns out to be insignificant.) +var allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); + +pub const EType = packed enum(u2) { dir, link, file }; + +// Memory layout: +// Dir + name (+ alignment + Ext) +// or: Link + name (+ alignment + Ext) +// or: File + name (+ alignment + Ext) +// +// Entry is always the first part of Dir, Link and File, so a pointer cast to +// *Entry is always safe and an *Entry can be casted to the full type. +// (TODO: What are the aliassing rules for Zig? There is a 'noalias' keyword, +// but does that mean all unmarked pointers are allowed to alias?) +// (TODO: The 'alignment' in the layout above is a lie, none of these structs +// or fields have any sort of alignment. This is great for saving memory but +// perhaps not very great for code size or performance. Might want to +// experiment with setting some alignment and measure the impact) +// (TODO: Putting Ext before the Entry pointer may be a little faster; removes +// the need to iterate over the name) +pub const Entry = packed struct { + etype: EType, + isext: bool, + blocks: u61, // 512-byte blocks + size: u64, + next: ?*Entry, + + const Self = @This(); + + pub fn dir(self: *Self) ?*Dir { + return if (self.etype == .dir) @ptrCast(*Dir, self) else null; + } + + pub fn link(self: *Self) ?*Link { + return if (self.etype == .link) @ptrCast(*Link, self) else null; + } + + pub fn file(self: *Self) ?*File { + return if (self.etype == .file) @ptrCast(*File, self) else null; + } + + fn name_offset(etype: EType) usize { + return switch (etype) { + .dir => @byteOffsetOf(Dir, "name"), + .link => @byteOffsetOf(Link, "name"), + .file => @byteOffsetOf(File, "name"), + }; + } + + pub fn name(self: *const Self) [:0]const u8 { + const ptr = @intToPtr([*:0]u8, @ptrToInt(self) + name_offset(self.etype)); + return ptr[0..std.mem.lenZ(ptr) :0]; + } + + pub fn ext(self: *Self) ?*Ext { + if (!self.isext) return null; + const n = self.name(); + return @intToPtr(*Ext, std.mem.alignForward(@ptrToInt(self) + name_offset(self.etype) + n.len + 1, @alignOf(Ext))); + } + + pub fn create(etype: EType, isext: bool, ename: []const u8) !*Entry { + const base_size = name_offset(etype) + ename.len + 1; + const size = (if (isext) std.mem.alignForward(base_size, @alignOf(Ext))+@sizeOf(Ext) else base_size); + var ptr = try allocator.allocator.allocWithOptions(u8, size, @alignOf(Entry), null); + std.mem.set(u8, ptr, 0); // kind of ugly, but does the trick + var e = @ptrCast(*Entry, ptr); + e.etype = etype; + e.isext = isext; + var name_ptr = @intToPtr([*]u8, @ptrToInt(e) + name_offset(etype)); + std.mem.copy(u8, name_ptr[0..ename.len], ename); + //std.debug.warn("{any}\n", .{ @ptrCast([*]u8, e)[0..size] }); + return e; + } + + // Set the 'err' flag on Dirs and Files, propagating 'suberr' to parents. + pub fn set_err(self: *Self, parents: *const Parents) void { + if (self.dir()) |d| d.err = true + else if (self.file()) |f| f.err = true + else unreachable; + var it = parents.iter(); + if (&parents.top().entry == self) _ = it.next(); + while (it.next()) |p| { + if (p.suberr) break; + p.suberr = true; + } + } + + // Insert this entry into the tree at the given directory, updating parent sizes and item counts. + // (TODO: This function creates an unrecoverable mess on OOM, need to do something better) + pub fn insert(self: *Entry, parents: *const Parents) !void { + self.next = parents.top().sub; + parents.top().sub = self; + if (self.dir()) |d| std.debug.assert(d.sub == null); + + const dev = parents.top().dev; + // Set if this is the first time we've found this hardlink in the bottom-most directory of the given dev. + // Means we should count it for other-dev parent dirs, too. + var new_hl = false; + + // TODO: Saturating add/substract + var it = parents.iter(); + while(it.next()) |p| { + var add_total = false; + + // Hardlink in a subdirectory with a different device, only count it the first time. + if (self.link() != null and dev != p.dev) { + add_total = new_hl; + + } else if (self.link()) |l| { + const n = HardlinkNode{ .ino = l.ino, .dir = p, .num_files = 1 }; + var d = try devices.items[dev].hardlinks.getOrPut(n); + new_hl = !d.found_existing; + if (d.found_existing) d.entry.key.num_files += 1; + // First time we encounter this file in this dir, count it. + if (d.entry.key.num_files == 1) { + add_total = true; + p.shared_size += self.size; + p.shared_blocks += self.blocks; + p.shared_items += 1; + // Encountered this file in this dir the same number of times as its link count, meaning it's not shared with other dirs. + } else if(d.entry.key.num_files == l.nlink) { + p.shared_size -= self.size; + p.shared_blocks -= self.blocks; + p.shared_items -= 1; + } + } else { + add_total = true; + } + if(add_total) { + p.total_size += self.size; + p.total_blocks += self.blocks; + p.total_items += 1; + } + } + } +}; + +const DevId = u30; // Can be reduced to make room for more flags in Dir. + +pub const Dir = packed struct { + entry: Entry, + + sub: ?*Entry, + + // total_*: Total size of all unique files + dirs. Non-shared hardlinks are counted only once. + // (i.e. the space you'll need if you created a filesystem with only this dir) + // shared_*: Unique hardlinks that still have references outside of this directory. + // (i.e. the space you won't reclaim by deleting this dir) + // (space reclaimed by deleting a dir =~ total_ - shared_) + total_blocks: u64, + shared_blocks: u64, + total_size: u64, + shared_size: u64, + total_items: u32, + shared_items: u32, + // TODO: ncdu1 only keeps track of a total item count including duplicate hardlinks. + // That number seems useful, too. Include it somehow? + + // Indexes into the global 'devices' array + dev: DevId, + + err: bool, + suberr: bool, + + // Only used to find the @byteOffsetOff, the name is written at this point as a 0-terminated string. + // (Old C habits die hard) + name: u8, +}; + +// File that's been hardlinked (i.e. nlink > 1) +pub const Link = packed struct { + entry: Entry, + ino: u64, + // dev is inherited from the parent Dir + nlink: u32, + name: u8, +}; + +// Anything that's not an (indexed) directory or hardlink. Excluded directories are also "Files". +pub const File = packed struct { + entry: Entry, + + err: bool, + excluded: bool, + other_fs: bool, + kernfs: bool, + notreg: bool, + _pad: u3, + + name: u8, +}; + +pub const Ext = packed struct { + mtime: u64, + uid: i32, + gid: i32, + mode: u16, +}; + + +// Hardlink handling: +// +// Global lookup table of dev -> (ino,*Dir) -> num_files +// +// num_files is how many times the file has been found in the particular dir. +// num_links is the file's st_nlink count. +// +// Adding a hardlink: O(parents) +// +// for dir in file.parents: +// add to dir.total_* if it's not yet in the lookup table +// add to num_files in the lookup table +// add to dir.shared_* where num_files == 1 +// +// Removing a hardlink: O(parents) +// +// for dir in file.parents: +// subtract from num_files in the lookup table +// subtract from dir.total_* if num_files == 0 +// subtract from dir.shared_* if num_files == num_links-1 +// remove from lookup table if num_files == 0 +// +// Re-calculating full hardlink stats (only possible when also storing sizes): +// +// reset total_* and shared_* for all dirs +// for (file,dir) in lookup_table: +// dir.total_* += file +// if file.num_links != dir.num_files: +// dir.shared_* += file +// +// Problem: num_links is not available in ncdu JSON dumps, will have to assume +// that there are no shared hardlinks outside of the given dump. +// +// Problem: This data structure does not provide a way to easily list all paths +// with the same dev,ino. ncdu provides this list in the info window. Doesn't +// seem too commonly used, can still be provided by a slow full scan of the +// tree. + + +// 20 bytes per hardlink/Dir entry, everything in a single allocation. +// (Should really be aligned to 8 bytes and hence take up 24 bytes, but let's see how this works out) +// +// getEntry() allows modification of the key without re-insertion (this is unsafe in the general case, but works fine for modifying num_files) +// +// Potential problem: HashMap uses a 32bit item counter, which may be exceeded in extreme scenarios. +// (ncdu itself doesn't support more than 31bit-counted files, but this table is hardlink_count*parent_dirs and may grow a bit) + +const HardlinkNode = packed struct { + ino: u64, + dir: *Dir, + num_files: u32, + + const Self = @This(); + + // hash() assumes a struct layout, hence the 'packed struct' + fn hash(self: Self) u64 { return std.hash.Wyhash.hash(0, @ptrCast([*]const u8, &self)[0..@byteOffsetOf(Self, "dir")+@sizeOf(*Dir)]); } + fn eql(a: Self, b: Self) bool { return a.ino == b.ino and a.dir == b.dir; } +}; + +// Device entry, this is used for two reasons: +// 1. st_dev ids are 64-bit, but in a typical filesystem there's only a few +// unique ids, hence we can save RAM by only storing smaller DevId's in Dir +// entries and using that as an index to a lookup table. +// 2. Keeping track of hardlink counts for each dir and inode, as described above. +// +// (Device entries are never deallocated) +const Device = struct { + dev: u64, + hardlinks: Hardlinks = Hardlinks.init(main.allocator), + + const Hardlinks = std.HashMap(HardlinkNode, void, HardlinkNode.hash, HardlinkNode.eql, 80); +}; + +var devices: std.ArrayList(Device) = std.ArrayList(Device).init(main.allocator); +var dev_lookup: std.AutoHashMap(u64, DevId) = std.AutoHashMap(u64, DevId).init(main.allocator); + +pub fn getDevId(dev: u64) !DevId { + var d = try dev_lookup.getOrPut(dev); + if (!d.found_existing) { + errdefer dev_lookup.removeAssertDiscard(dev); + d.entry.value = @intCast(DevId, devices.items.len); + try devices.append(.{ .dev = dev }); + } + return d.entry.value; +} + +pub fn getDev(id: DevId) u64 { + return devices.items[id].dev; +} + +pub var root: *Dir = undefined; + +// Stack of parent directories, convenient helper when constructing and traversing the tree. +// The 'root' node is always implicitely at the bottom of the stack. +pub const Parents = struct { + stack: std.ArrayList(*Dir) = std.ArrayList(*Dir).init(main.allocator), + + const Self = @This(); + + pub fn push(self: *Self, dir: *Dir) !void { + return self.stack.append(dir); + } + + // Attempting to remove the root node is considered a bug. + pub fn pop(self: *Self) void { + _ = self.stack.pop(); + } + + pub fn top(self: *const Self) *Dir { + return if (self.stack.items.len == 0) root else self.stack.items[self.stack.items.len-1]; + } + + pub const Iterator = struct { + lst: *const Self, + index: usize = 0, // 0 = top of the stack, counts upwards to go down + + pub fn next(it: *Iterator) ?*Dir { + const len = it.lst.stack.items.len; + if (it.index > len) return null; + it.index += 1; + return if (it.index > len) root else it.lst.stack.items[len-it.index]; + } + }; + + // Iterate from top to bottom of the stack. + pub fn iter(self: *const Self) Iterator { + return .{ .lst = self }; + } +}; + +test "name offsets" { + std.testing.expectEqual(@bitOffsetOf(Dir, "name") % 8, 0); + std.testing.expectEqual(@bitOffsetOf(Link, "name") % 8, 0); + std.testing.expectEqual(@bitOffsetOf(File, "name") % 8, 0); +} + +test "entry" { + var e = Entry.create(.file, false, "hello") catch unreachable; + std.debug.assert(e.etype == .file); + std.debug.assert(!e.isext); + std.testing.expectEqualStrings(e.name(), "hello"); +} diff --git a/src/path.c b/src/path.c deleted file mode 100644 index 8ccdfa9..0000000 --- a/src/path.c +++ /dev/null @@ -1,246 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "global.h" - -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <errno.h> -#include <unistd.h> -#include <limits.h> - -#ifndef LINK_MAX -# ifdef _POSIX_LINK_MAX -# define LINK_MAX _POSIX_LINK_MAX -# else -# define LINK_MAX 32 -# endif -#endif - - -#define RPATH_CNKSZ 256 - - -/* splits a path into components and does a bit of cannonicalization. - a pointer to a reversed array of components is stored in res and the - number of components is returned. - cur is modified, and res has to be free()d after use */ -static int path_split(char *cur, char ***res) { - char **old; - int i, j, n; - - cur += strspn(cur, "/"); - n = strlen(cur); - - /* replace slashes with zeros */ - for(i=j=0; i<n; i++) - if(cur[i] == '/') { - cur[i] = 0; - if(cur[i-1] != 0) - j++; - } - - /* create array of the components */ - old = xmalloc((j+1)*sizeof(char *)); - *res = xmalloc((j+1)*sizeof(char *)); - for(i=j=0; i<n; i++) - if(i == 0 || (cur[i-1] == 0 && cur[i] != 0)) - old[j++] = cur+i; - - /* re-order and remove parts */ - for(i=n=0; --j>=0; ) { - if(!strcmp(old[j], "..")) { - n++; - continue; - } - if(!strcmp(old[j], ".")) - continue; - if(n) { - n--; - continue; - } - (*res)[i++] = old[j]; - } - free(old); - return i; -} - - -/* copies path and prepends cwd if needed, to ensure an absolute path - return value has to be free()'d manually */ -static char *path_absolute(const char *path) { - int i, n; - char *ret; - - /* not an absolute path? prepend cwd */ - if(path[0] != '/') { - n = RPATH_CNKSZ; - ret = xmalloc(n); - errno = 0; - while(!getcwd(ret, n) && errno == ERANGE) { - n += RPATH_CNKSZ; - ret = xrealloc(ret, n); - errno = 0; - } - if(errno) { - free(ret); - return NULL; - } - - i = strlen(path) + strlen(ret) + 2; - if(i > n) - ret = xrealloc(ret, i); - strcat(ret, "/"); - strcat(ret, path); - /* otherwise, just make a copy */ - } else { - ret = xmalloc(strlen(path)+1); - strcpy(ret, path); - } - return ret; -} - - -/* NOTE: cwd and the memory cur points to are unreliable after calling this - * function. - * TODO: This code is rather fragile and inefficient. A rewrite is in order. - */ -static char *path_real_rec(char *cur, int *links) { - int i, n, tmpl, lnkl = 0; - char **arr, *tmp, *lnk = NULL, *ret = NULL; - - tmpl = strlen(cur)+1; - tmp = xmalloc(tmpl); - - /* split path */ - i = path_split(cur, &arr); - - /* re-create full path */ - strcpy(tmp, "/"); - if(i > 0) { - lnkl = RPATH_CNKSZ; - lnk = xmalloc(lnkl); - if(chdir("/") < 0) - goto path_real_done; - } - - while(--i>=0) { - if(arr[i][0] == 0) - continue; - /* check for symlink */ - while((n = readlink(arr[i], lnk, lnkl)) == lnkl || (n < 0 && errno == ERANGE)) { - lnkl += RPATH_CNKSZ; - lnk = xrealloc(lnk, lnkl); - } - if(n < 0 && errno != EINVAL) - goto path_real_done; - if(n > 0) { - if(++*links > LINK_MAX) { - errno = ELOOP; - goto path_real_done; - } - lnk[n++] = 0; - /* create new path */ - if(lnk[0] != '/') - n += strlen(tmp); - if(tmpl < n) { - tmpl = n; - tmp = xrealloc(tmp, tmpl); - } - if(lnk[0] != '/') - strcat(tmp, lnk); - else - strcpy(tmp, lnk); - /* append remaining directories */ - while(--i>=0) { - n += strlen(arr[i])+1; - if(tmpl < n) { - tmpl = n; - tmp = xrealloc(tmp, tmpl); - } - strcat(tmp, "/"); - strcat(tmp, arr[i]); - } - /* call path_real_rec() with the new path */ - ret = path_real_rec(tmp, links); - goto path_real_done; - } - /* not a symlink, append component and go to the next part */ - strcat(tmp, arr[i]); - if(i) { - if(chdir(arr[i]) < 0) - goto path_real_done; - strcat(tmp, "/"); - } - } - ret = tmp; - -path_real_done: - if(ret != tmp) - free(tmp); - if(lnkl > 0) - free(lnk); - free(arr); - return ret; -} - - -char *path_real(const char *orig) { - int links = 0; - char *tmp, *ret; - - if(orig == NULL) - return NULL; - - if((tmp = path_absolute(orig)) == NULL) - return NULL; - ret = path_real_rec(tmp, &links); - free(tmp); - return ret; -} - - -int path_chdir(const char *path) { - char **arr, *cur; - int i, r = -1; - - if((cur = path_absolute(path)) == NULL) - return -1; - - i = path_split(cur, &arr); - if(chdir("/") < 0) - goto path_chdir_done; - while(--i >= 0) - if(chdir(arr[i]) < 0) - goto path_chdir_done; - r = 0; - -path_chdir_done: - free(cur); - free(arr); - return r; -} - diff --git a/src/path.h b/src/path.h deleted file mode 100644 index 773da9d..0000000 --- a/src/path.h +++ /dev/null @@ -1,47 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ -/* - path.c reimplements realpath() and chdir(), both functions accept - arbitrary long path names not limited by PATH_MAX. - - Caveats/bugs: - - path_real uses chdir(), so it's not thread safe - - Process requires +x access for all directory components - - Potentionally slow - - path_real doesn't check for the existence of the last component - - cwd is unreliable after path_real -*/ - -#ifndef _path_h -#define _path_h - -/* path_real reimplements realpath(). The returned string is allocated - by malloc() and should be manually free()d by the programmer. */ -extern char *path_real(const char *); - -/* works exactly the same as chdir() */ -extern int path_chdir(const char *); - -#endif diff --git a/src/quit.c b/src/quit.c deleted file mode 100644 index 154e319..0000000 --- a/src/quit.c +++ /dev/null @@ -1,50 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2015-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "global.h" - -#include <ncurses.h> - -int quit_key(int ch) { - switch(ch) { - case 'y': - case 'Y': - return 1; - default: - pstate = ST_BROWSE; - } - return 0; -} - -void quit_draw() { - browse_draw(); - - nccreate(4,30, "ncdu confirm quit"); - ncaddstr(2,2, "Really quit? (y/N)"); -} - -void quit_init() { - pstate = ST_QUIT; -} diff --git a/src/quit.h b/src/quit.h deleted file mode 100644 index 6c50d5e..0000000 --- a/src/quit.h +++ /dev/null @@ -1,37 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2015-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#ifndef _quit_h -#define _quit_h - -#include "global.h" - -int quit_key(int); -void quit_draw(void); -void quit_init(void); - - -#endif - diff --git a/src/scan.zig b/src/scan.zig new file mode 100644 index 0000000..dacb7ff --- /dev/null +++ b/src/scan.zig @@ -0,0 +1,138 @@ +const std = @import("std"); +const main = @import("main.zig"); +const model = @import("model.zig"); + + +// Concise stat struct for fields we're interested in, with the types used by the model. +const Stat = struct { + blocks: u61, + size: u64, + dev: u64, + ino: u64, + nlink: u32, + dir: bool, + reg: bool, + ext: model.Ext, +}; + +// Cast any integer type to the target type, clamping the +// value to the supported maximum if necessary. +fn castClamp(comptime T: type, x: anytype) T { + // (adapted from std.math.cast) + if (std.math.maxInt(@TypeOf(x)) > std.math.maxInt(T) and x > std.math.maxInt(T)) { + return std.math.maxInt(T); + } else if (std.math.minInt(@TypeOf(x)) < std.math.minInt(T) and x < std.math.minInt(T)) { + return std.math.minInt(T); + } else { + return @intCast(T, x); + } +} + +// Cast any integer type to the unsigned target type, wrapping/truncating as necessary. +fn castWrap(comptime T: type, x: anytype) T { + return @intCast(T, x); // TODO +} + +fn clamp(comptime T: type, comptime field: anytype, x: anytype) std.meta.fieldInfo(T, field).field_type { + return castClamp(std.meta.fieldInfo(T, field).field_type, x); +} + +fn wrap(comptime T: type, comptime field: anytype, x: anytype) std.meta.fieldInfo(T, field).field_type { + return castWrap(std.meta.fieldInfo(T, field).field_type, x); +} + +fn readStat(parent: std.fs.Dir, name: [:0]const u8) !Stat { + const stat = try std.os.fstatatZ(parent.fd, name, 0); + return Stat{ + .blocks = clamp(Stat, .blocks, stat.blocks), + .size = clamp(Stat, .size, stat.size), + .dev = wrap(Stat, .dev, stat.dev), + .ino = wrap(Stat, .ino, stat.ino), + .nlink = clamp(Stat, .nlink, stat.nlink), + .dir = std.os.system.S_ISDIR(stat.mode), + .reg = std.os.system.S_ISREG(stat.mode), + .ext = .{ + .mtime = clamp(model.Ext, .mtime, stat.mtime().tv_sec), + .uid = wrap(model.Ext, .uid, stat.uid), + .gid = wrap(model.Ext, .gid, stat.gid), + .mode = clamp(model.Ext, .mode, stat.mode & 0xffff), + }, + }; +} + +// Read and index entries of the dir identified by parent/parents.top(). +// (TODO: shouldn't error on OOM but instead call a function that waits or something) +fn scanDir(parents: *model.Parents, parent: std.fs.Dir) std.mem.Allocator.Error!void { + var dir = parent.openDirZ(parents.top().entry.name(), .{ .access_sub_paths = true, .iterate = true, .no_follow = true }) catch { + parents.top().entry.set_err(parents); + return; + }; + defer dir.close(); + + var it = dir.iterate(); + while(true) { + const entry = it.next() catch { + parents.top().entry.set_err(parents); + return; + } orelse break; + + // TODO: Check for exclude patterns + + // XXX: Surely the name already has a trailing \0 in the buffer received by the OS? + const name_z = std.os.toPosixPath(entry.name) catch undefined; + const stat = readStat(dir, &name_z) catch { + var e = try model.Entry.create(.file, false, entry.name); + e.insert(parents) catch unreachable; + e.set_err(parents); + continue; + }; + + if (main.config.same_fs and stat.dev != model.getDev(parents.top().dev)) { + var e = try model.Entry.create(.file, false, entry.name); + e.file().?.other_fs = true; + e.insert(parents) catch unreachable; + continue; + } + + // TODO Check for kernfs + // TODO Follow symlink if that option is enabled + // TODO Check for CACHEDIR.TAG if that option is enabled and this is a dir + + const etype = if (stat.dir) model.EType.dir else if (stat.nlink > 1) model.EType.link else model.EType.file; + var e = try model.Entry.create(etype, main.config.extended, entry.name); + e.blocks = stat.blocks; + e.size = stat.size; + if (e.dir()) |d| { + d.dev = try model.getDevId(stat.dev); + // The dir entry itself also counts. + d.total_blocks = stat.blocks; + d.total_size = stat.size; + d.total_items = 1; + } + if (e.ext()) |ext| ext.* = stat.ext; + if (e.link()) |l| { + l.ino = stat.ino; + l.nlink = stat.nlink; + } + try e.insert(parents); + + if (e.dir()) |d| { + try parents.push(d); + try scanDir(parents, dir); + parents.pop(); + } + } +} + +pub fn scanRoot(path: [:0]const u8) !void { + const stat = try readStat(std.fs.cwd(), path); + if (!stat.dir) return error.NotADirectory; + model.root = (try model.Entry.create(.dir, false, path)).dir().?; + model.root.entry.blocks = stat.blocks; + model.root.entry.size = stat.size; + model.root.dev = try model.getDevId(stat.dev); + if (model.root.entry.ext()) |ext| ext.* = stat.ext; + + var parents = model.Parents{}; + try scanDir(&parents, std.fs.cwd()); +} diff --git a/src/shell.c b/src/shell.c deleted file mode 100644 index fd589e6..0000000 --- a/src/shell.c +++ /dev/null @@ -1,82 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - Shell support: Copyright (c) 2014 Thomas Jarosch - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "config.h" -#include "global.h" -#include "dirlist.h" -#include "util.h" - -#include <ncurses.h> -#include <stdlib.h> -#include <unistd.h> -#include <sys/wait.h> - -void shell_draw() { - const char *full_path; - int res; - - /* suspend ncurses mode */ - def_prog_mode(); - endwin(); - - full_path = getpath(dirlist_par); - res = chdir(full_path); - if (res != 0) { - reset_prog_mode(); - clear(); - printw("ERROR: Can't change directory: %s (errcode: %d)\n" - "\n" - "Press any key to continue.", - full_path, res); - } else { - const char *shell = getenv("NCDU_SHELL"); - if (shell == NULL) { - shell = getenv("SHELL"); - if (shell == NULL) - shell = DEFAULT_SHELL; - } - - res = system(shell); - - /* resume ncurses mode */ - reset_prog_mode(); - - if (res == -1 || !WIFEXITED(res) || WEXITSTATUS(res) == 127) { - clear(); - printw("ERROR: Can't execute shell interpreter: %s\n" - "\n" - "Press any key to continue.", - shell); - } - } - - refresh(); - pstate = ST_BROWSE; -} - -void shell_init() { - pstate = ST_SHELL; -} diff --git a/src/shell.h b/src/shell.h deleted file mode 100644 index b0e53b7..0000000 --- a/src/shell.h +++ /dev/null @@ -1,35 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - Shell support: Copyright (c) 2014 Thomas Jarosch - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#ifndef _shell_h -#define _shell_h - -#include "global.h" - -void shell_draw(void); -void shell_init(void); - -#endif diff --git a/src/util.c b/src/util.c deleted file mode 100644 index 2d92718..0000000 --- a/src/util.c +++ /dev/null @@ -1,434 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "util.h" - -#include <string.h> -#include <stdlib.h> -#include <ncurses.h> -#include <stdarg.h> -#include <unistd.h> -#ifdef HAVE_LOCALE_H -#include <locale.h> -#endif - -int uic_theme; -int winrows, wincols; -int subwinr, subwinc; -int si; -static char thou_sep; - - -char *cropstr(const char *from, int s) { - static char dat[4096]; - int i, j, o = strlen(from); - if(o < s) { - strcpy(dat, from); - return dat; - } - j=s/2-3; - for(i=0; i<j; i++) - dat[i] = from[i]; - dat[i] = '.'; - dat[++i] = '.'; - dat[++i] = '.'; - j=o-s; - while(++i<s) - dat[i] = from[j+i]; - dat[s] = '\0'; - return dat; -} - - -float formatsize(int64_t from, const char **unit) { - float r = from; - if (si) { - if(r < 1000.0f) { *unit = " B"; } - else if(r < 1e6f) { *unit = "KB"; r/=1e3f; } - else if(r < 1e9f) { *unit = "MB"; r/=1e6f; } - else if(r < 1e12f){ *unit = "GB"; r/=1e9f; } - else if(r < 1e15f){ *unit = "TB"; r/=1e12f; } - else if(r < 1e18f){ *unit = "PB"; r/=1e15f; } - else { *unit = "EB"; r/=1e18f; } - } - else { - if(r < 1000.0f) { *unit = " B"; } - else if(r < 1023e3f) { *unit = "KiB"; r/=1024.0f; } - else if(r < 1023e6f) { *unit = "MiB"; r/=1048576.0f; } - else if(r < 1023e9f) { *unit = "GiB"; r/=1073741824.0f; } - else if(r < 1023e12f){ *unit = "TiB"; r/=1099511627776.0f; } - else if(r < 1023e15f){ *unit = "PiB"; r/=1125899906842624.0f; } - else { *unit = "EiB"; r/=1152921504606846976.0f; } - } - return r; -} - - -void printsize(enum ui_coltype t, int64_t from) { - const char *unit; - float r = formatsize(from, &unit); - uic_set(t == UIC_HD ? UIC_NUM_HD : t == UIC_SEL ? UIC_NUM_SEL : UIC_NUM); - printw("%5.1f", r); - addchc(t, ' '); - addstrc(t, unit); -} - - -char *fullsize(int64_t from) { - static char dat[26]; /* max: 9.223.372.036.854.775.807 (= 2^63-1) */ - char tmp[26]; - int64_t n = from; - int i, j; - - /* the K&R method - more portable than sprintf with %lld */ - i = 0; - do { - tmp[i++] = n % 10 + '0'; - } while((n /= 10) > 0); - tmp[i] = '\0'; - - /* reverse and add thousand separators */ - j = 0; - while(i--) { - dat[j++] = tmp[i]; - if(i != 0 && i%3 == 0) - dat[j++] = thou_sep; - } - dat[j] = '\0'; - - return dat; -} - - -char *fmtmode(unsigned short mode) { - static char buf[11]; - unsigned short ft = mode & S_IFMT; - buf[0] = ft == S_IFDIR ? 'd' - : ft == S_IFREG ? '-' - : ft == S_IFLNK ? 'l' - : ft == S_IFIFO ? 'p' - : ft == S_IFSOCK ? 's' - : ft == S_IFCHR ? 'c' - : ft == S_IFBLK ? 'b' : '?'; - buf[1] = mode & 0400 ? 'r' : '-'; - buf[2] = mode & 0200 ? 'w' : '-'; - buf[3] = mode & 0100 ? 'x' : '-'; - buf[4] = mode & 0040 ? 'r' : '-'; - buf[5] = mode & 0020 ? 'w' : '-'; - buf[6] = mode & 0010 ? 'x' : '-'; - buf[7] = mode & 0004 ? 'r' : '-'; - buf[8] = mode & 0002 ? 'w' : '-'; - buf[9] = mode & 0001 ? 'x' : '-'; - buf[10] = 0; - return buf; -} - - -void read_locale() { - thou_sep = '.'; -#ifdef HAVE_LOCALE_H - setlocale(LC_ALL, ""); - char *locale_thou_sep = localeconv()->thousands_sep; - if(locale_thou_sep && 1 == strlen(locale_thou_sep)) - thou_sep = locale_thou_sep[0]; -#endif -} - - -int ncresize(int minrows, int mincols) { - int ch; - - getmaxyx(stdscr, winrows, wincols); - while((minrows && winrows < minrows) || (mincols && wincols < mincols)) { - erase(); - mvaddstr(0, 0, "Warning: terminal too small,"); - mvaddstr(1, 1, "please either resize your terminal,"); - mvaddstr(2, 1, "press i to ignore, or press q to quit."); - refresh(); - nodelay(stdscr, 0); - ch = getch(); - getmaxyx(stdscr, winrows, wincols); - if(ch == 'q') { - erase(); - refresh(); - endwin(); - exit(0); - } - if(ch == 'i') - return 1; - } - erase(); - return 0; -} - - -void nccreate(int height, int width, const char *title) { - int i; - - uic_set(UIC_DEFAULT); - subwinr = winrows/2-height/2; - subwinc = wincols/2-width/2; - - /* clear window */ - for(i=0; i<height; i++) - mvhline(subwinr+i, subwinc, ' ', width); - - /* box() only works around curses windows, so create our own */ - move(subwinr, subwinc); - addch(ACS_ULCORNER); - for(i=0; i<width-2; i++) - addch(ACS_HLINE); - addch(ACS_URCORNER); - - move(subwinr+height-1, subwinc); - addch(ACS_LLCORNER); - for(i=0; i<width-2; i++) - addch(ACS_HLINE); - addch(ACS_LRCORNER); - - mvvline(subwinr+1, subwinc, ACS_VLINE, height-2); - mvvline(subwinr+1, subwinc+width-1, ACS_VLINE, height-2); - - /* title */ - uic_set(UIC_BOX_TITLE); - mvaddstr(subwinr, subwinc+4, title); - uic_set(UIC_DEFAULT); -} - - -void ncprint(int r, int c, const char *fmt, ...) { - va_list arg; - va_start(arg, fmt); - move(subwinr+r, subwinc+c); - vw_printw(stdscr, fmt, arg); - va_end(arg); -} - - -void nctab(int c, int sel, int num, const char *str) { - uic_set(sel ? UIC_KEY_HD : UIC_KEY); - ncprint(0, c, "%d", num); - uic_set(sel ? UIC_HD : UIC_DEFAULT); - addch(':'); - addstr(str); - uic_set(UIC_DEFAULT); -} - - -static int colors[] = { -#define C(name, ...) 0, - UI_COLORS -#undef C - 0 -}; -static int lastcolor = 0; - - -static const struct { - short fg, bg; - int attr; -} color_defs[] = { -#define C(name, off_fg, off_bg, off_a, dark_fg, dark_bg, dark_a) \ - {off_fg, off_bg, off_a}, \ - {dark_fg, dark_bg, dark_a}, - UI_COLORS -#undef C - {0,0,0} -}; - -void uic_init() { - size_t i, j; - - start_color(); - use_default_colors(); - for(i=0; i<sizeof(colors)/sizeof(*colors)-1; i++) { - j = i*2 + uic_theme; - init_pair(i+1, color_defs[j].fg, color_defs[j].bg); - colors[i] = color_defs[j].attr | COLOR_PAIR(i+1); - } -} - -void uic_set(enum ui_coltype c) { - attroff(lastcolor); - lastcolor = colors[(int)c]; - attron(lastcolor); -} - - - -/* removes item from the hlnk circular linked list and size counts of the parents */ -static void freedir_hlnk(struct dir *d) { - struct dir *t, *par, *pt; - int i; - - if(!(d->flags & FF_HLNKC)) - return; - - /* remove size from parents. - * This works the same as with adding: only the parents in which THIS is the - * only occurrence of the hard link will be modified, if the same file still - * exists within the parent it shouldn't get removed from the count. - * XXX: Same note as for dir_mem.c / hlink_check(): - * this is probably not the most efficient algorithm */ - for(i=1,par=d->parent; i&∥ par=par->parent) { - if(d->hlnk) - for(t=d->hlnk; i&&t!=d; t=t->hlnk) - for(pt=t->parent; i&&pt; pt=pt->parent) - if(pt==par) - i=0; - if(i) { - par->size = adds64(par->size, -d->size); - par->asize = adds64(par->size, -d->asize); - } - } - - /* remove from hlnk */ - if(d->hlnk) { - for(t=d->hlnk; t->hlnk!=d; t=t->hlnk) - ; - t->hlnk = d->hlnk; - } -} - - -static void freedir_rec(struct dir *dr) { - struct dir *tmp, *tmp2; - tmp2 = dr; - while((tmp = tmp2) != NULL) { - freedir_hlnk(tmp); - /* remove item */ - if(tmp->sub) freedir_rec(tmp->sub); - tmp2 = tmp->next; - free(tmp); - } -} - - -void freedir(struct dir *dr) { - if(!dr) - return; - - /* free dr->sub recursively */ - if(dr->sub) - freedir_rec(dr->sub); - - /* update references */ - if(dr->parent && dr->parent->sub == dr) - dr->parent->sub = dr->next; - if(dr->prev) - dr->prev->next = dr->next; - if(dr->next) - dr->next->prev = dr->prev; - - freedir_hlnk(dr); - - /* update sizes of parent directories if this isn't a hard link. - * If this is a hard link, freedir_hlnk() would have done so already - * - * mtime is 0 here because recalculating the maximum at every parent - * dir is expensive, but might be good feature to add later if desired */ - addparentstats(dr->parent, dr->flags & FF_HLNKC ? 0 : -dr->size, dr->flags & FF_HLNKC ? 0 : -dr->asize, 0, -(dr->items+1)); - - free(dr); -} - - -const char *getpath(struct dir *cur) { - static char *dat; - static int datl = 0; - struct dir *d, **list; - int c, i; - - if(!cur->name[0]) - return "/"; - - c = i = 1; - for(d=cur; d!=NULL; d=d->parent) { - i += strlen(d->name)+1; - c++; - } - - if(datl == 0) { - datl = i; - dat = xmalloc(i); - } else if(datl < i) { - datl = i; - dat = xrealloc(dat, i); - } - list = xmalloc(c*sizeof(struct dir *)); - - c = 0; - for(d=cur; d!=NULL; d=d->parent) - list[c++] = d; - - dat[0] = '\0'; - while(c--) { - if(list[c]->parent) - strcat(dat, "/"); - strcat(dat, list[c]->name); - } - free(list); - return dat; -} - - -struct dir *getroot(struct dir *d) { - while(d && d->parent) - d = d->parent; - return d; -} - - -void addparentstats(struct dir *d, int64_t size, int64_t asize, uint64_t mtime, int items) { - struct dir_ext *e; - while(d) { - d->size = adds64(d->size, size); - d->asize = adds64(d->asize, asize); - d->items += items; - if (d->flags & FF_EXT) { - e = dir_ext_ptr(d); - e->mtime = (e->mtime > mtime) ? e->mtime : mtime; - } - d = d->parent; - } -} - - -/* Apparently we can just resume drawing after endwin() and ncurses will pick - * up where it left. Probably not very portable... */ -#define oom_msg "\nOut of memory, press enter to try again or Ctrl-C to give up.\n" -#define wrap_oom(f) \ - void *ptr;\ - char buf[128];\ - while((ptr = f) == NULL) {\ - close_nc();\ - write(2, oom_msg, sizeof(oom_msg));\ - read(0, buf, sizeof(buf));\ - }\ - return ptr; - -void *xmalloc(size_t size) { wrap_oom(malloc(size)) } -void *xcalloc(size_t n, size_t size) { wrap_oom(calloc(n, size)) } -void *xrealloc(void *mem, size_t size) { wrap_oom(realloc(mem, size)) } diff --git a/src/util.h b/src/util.h deleted file mode 100644 index 2c3e5ed..0000000 --- a/src/util.h +++ /dev/null @@ -1,195 +0,0 @@ -/* ncdu - NCurses Disk Usage - - Copyright (c) 2007-2020 Yoran Heling - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#ifndef _util_h -#define _util_h - -#include "global.h" -#include <ncurses.h> - - -/* UI colors: (foreground, background, attrs) - * NAME OFF DARK - */ -#define UI_COLORS \ - C(DEFAULT, -1,-1,0 , -1, -1, 0 )\ - C(BOX_TITLE, -1,-1,A_BOLD , COLOR_BLUE, -1, A_BOLD)\ - C(HD, -1,-1,A_REVERSE , COLOR_BLACK, COLOR_CYAN, 0 ) /* header & footer */\ - C(SEL, -1,-1,A_REVERSE , COLOR_WHITE, COLOR_GREEN,A_BOLD)\ - C(NUM, -1,-1,0 , COLOR_YELLOW, -1, A_BOLD)\ - C(NUM_HD, -1,-1,A_REVERSE , COLOR_YELLOW, COLOR_CYAN, A_BOLD)\ - C(NUM_SEL, -1,-1,A_REVERSE , COLOR_YELLOW, COLOR_GREEN,A_BOLD)\ - C(KEY, -1,-1,A_BOLD , COLOR_YELLOW, -1, A_BOLD)\ - C(KEY_HD, -1,-1,A_BOLD|A_REVERSE, COLOR_YELLOW, COLOR_CYAN, A_BOLD)\ - C(DIR, -1,-1,0 , COLOR_BLUE, -1, A_BOLD)\ - C(DIR_SEL, -1,-1,A_REVERSE , COLOR_BLUE, COLOR_GREEN,A_BOLD)\ - C(FLAG, -1,-1,0 , COLOR_RED, -1, 0 )\ - C(FLAG_SEL, -1,-1,A_REVERSE , COLOR_RED, COLOR_GREEN,0 )\ - C(GRAPH, -1,-1,0 , COLOR_MAGENTA,-1, 0 )\ - C(GRAPH_SEL, -1,-1,A_REVERSE , COLOR_MAGENTA,COLOR_GREEN,0 ) - -enum ui_coltype { -#define C(name, ...) UIC_##name, - UI_COLORS -#undef C - UIC_NONE -}; - -/* Color & attribute manipulation */ -extern int uic_theme; - -void uic_init(void); -void uic_set(enum ui_coltype); - - -/* updated when window is resized */ -extern int winrows, wincols; - -/* used by the nc* functions and macros */ -extern int subwinr, subwinc; - -/* used by formatsize to choose between base 2 or 10 prefixes */ -extern int si; - - -/* Macros/functions for managing struct dir and struct dir_ext */ - -#define dir_memsize(n) (offsetof(struct dir, name)+1+strlen(n)) -#define dir_ext_offset(n) ((dir_memsize(n) + 7) & ~7) -#define dir_ext_memsize(n) (dir_ext_offset(n) + sizeof(struct dir_ext)) - -static inline struct dir_ext *dir_ext_ptr(struct dir *d) { - return d->flags & FF_EXT - ? (struct dir_ext *) ( ((char *)d) + dir_ext_offset(d->name) ) - : NULL; -} - - -/* Instead of using several ncurses windows, we only draw to stdscr. - * the functions nccreate, ncprint and the macros ncaddstr and ncaddch - * mimic the behaviour of ncurses windows. - * This works better than using ncurses windows when all windows are - * created in the correct order: it paints directly on stdscr, so - * wrefresh, wnoutrefresh and other window-specific functions are not - * necessary. - * Also, this method doesn't require any window objects, as you can - * only create one window at a time. -*/ - -/* updates winrows, wincols, and displays a warning when the terminal - * is smaller than the specified minimum size. */ -int ncresize(int, int); - -/* creates a new centered window with border */ -void nccreate(int, int, const char *); - -/* printf something somewhere in the last created window */ -void ncprint(int, int, const char *, ...); - -/* Add a "tab" to a window */ -void nctab(int, int, int, const char *); - -/* same as the w* functions of ncurses, with a color */ -#define ncaddstr(r, c, s) mvaddstr(subwinr+(r), subwinc+(c), s) -#define ncaddch(r, c, s) mvaddch(subwinr+(r), subwinc+(c), s) -#define ncmove(r, c) move(subwinr+(r), subwinc+(c)) - -/* add stuff with a color */ -#define mvaddstrc(t, r, c, s) do { uic_set(t); mvaddstr(r, c, s); } while(0) -#define mvaddchc(t, r, c, s) do { uic_set(t); mvaddch(r, c, s); } while(0) -#define addstrc(t, s) do { uic_set(t); addstr( s); } while(0) -#define addchc(t, s) do { uic_set(t); addch( s); } while(0) -#define ncaddstrc(t, r, c, s) do { uic_set(t); ncaddstr(r, c, s); } while(0) -#define ncaddchc(t, r, c, s) do { uic_set(t); ncaddch(r, c, s); } while(0) -#define mvhlinec(t, r, c, s, n) do { uic_set(t); mvhline(r, c, s, n); } while(0) - -/* crops a string into the specified length */ -char *cropstr(const char *, int); - -/* Converts the given size in bytes into a float (0 <= f < 1000) and a unit string */ -float formatsize(int64_t, const char **); - -/* print size in the form of xxx.x XB */ -void printsize(enum ui_coltype, int64_t); - -/* int2string with thousand separators */ -char *fullsize(int64_t); - -/* format's a file mode as a ls -l string */ -char *fmtmode(unsigned short); - -/* read locale information from the environment */ -void read_locale(void); - -/* recursively free()s a directory tree */ -void freedir(struct dir *); - -/* generates full path from a dir item, - returned pointer will be overwritten with a subsequent call */ -const char *getpath(struct dir *); - -/* returns the root element of the given dir struct */ -struct dir *getroot(struct dir *); - -/* Add two signed 64-bit integers. Returns INT64_MAX if the result would - * overflow, or 0 if it would be negative. At least one of the integers must be - * positive. - * I use uint64_t's to detect the overflow, as (a + b < 0) relies on undefined - * behaviour, and (INT64_MAX - b >= a) didn't work for some reason. */ -#define adds64(a, b) ((a) > 0 && (b) > 0\ - ? ((uint64_t)(a) + (uint64_t)(b) > (uint64_t)INT64_MAX ? INT64_MAX : (a)+(b))\ - : (a)+(b) < 0 ? 0 : (a)+(b)) - -/* Adds a value to the size, asize and items fields of *d and its parents */ -void addparentstats(struct dir *, int64_t, int64_t, uint64_t, int); - - -/* A simple stack implemented in macros */ -#define nstack_init(_s) do {\ - (_s)->size = 10;\ - (_s)->top = 0;\ - (_s)->list = xmalloc(10*sizeof(*(_s)->list));\ - } while(0) - -#define nstack_push(_s, _v) do {\ - if((_s)->size <= (_s)->top) {\ - (_s)->size *= 2;\ - (_s)->list = xrealloc((_s)->list, (_s)->size*sizeof(*(_s)->list));\ - }\ - (_s)->list[(_s)->top++] = _v;\ - } while(0) - -#define nstack_pop(_s) (_s)->top-- -#define nstack_top(_s, _d) ((_s)->top > 0 ? (_s)->list[(_s)->top-1] : (_d)) -#define nstack_free(_s) free((_s)->list) - - -/* Malloc wrappers that exit on OOM */ -void *xmalloc(size_t); -void *xcalloc(size_t, size_t); -void *xrealloc(void *, size_t); - -#endif - diff --git a/static/build.sh b/static/build.sh deleted file mode 100755 index 37cfabf..0000000 --- a/static/build.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/bin/sh - -# This script is based on static/build.sh from the ncdc git repo. -# Only i486 and arm arches are supported. i486 should perform well enough, so -# x86_64 isn't really necessary. I can't test any other arches. -# -# This script assumes that you have the musl-cross cross compilers installed in -# $MUSL_CROSS_PATH. -# -# Usage: -# ./build.sh $arch -# where $arch = 'arm', 'i486' or 'x86_64' - -MUSL_CROSS_PATH=/opt/cross -NCURSES_VERSION=6.0 - -export CFLAGS="-O3 -g -static" - -# (The variables below are automatically set by the functions, they're defined -# here to make sure they have global scope and for documentation purposes.) - -# This is the arch we're compiling for, e.g. arm/mipsel. -TARGET= -# This is the name of the toolchain we're using, and thus the value we should -# pass to autoconf's --host argument. -HOST= -# Installation prefix. -PREFIX= -# Path of the extracted source code of the package we're currently building. -srcdir= - -mkdir -p tarballs - - -# "Fetch, Extract, Move" -fem() { # base-url name targerdir extractdir - echo "====== Fetching and extracting $1 $2" - cd tarballs - if [ -n "$4" ]; then - EDIR="$4" - else - EDIR=$(basename $(basename $(basename $2 .tar.bz2) .tar.gz) .tar.xz) - fi - if [ ! -e "$2" ]; then - wget "$1$2" || exit - fi - if [ ! -d "$3" ]; then - tar -xvf "$2" || exit - mv "$EDIR" "$3" - fi - cd .. -} - - -prebuild() { # dirname - if [ -e "$TARGET/$1/_built" ]; then - echo "====== Skipping build for $TARGET/$1 (assumed to be done)" - return 1 - fi - echo "====== Starting build for $TARGET/$1" - rm -rf "$TARGET/$1" - mkdir -p "$TARGET/$1" - cd "$TARGET/$1" - srcdir="../../tarballs/$1" - return 0 -} - - -postbuild() { - touch _built - cd ../.. -} - - -getncurses() { - fem http://ftp.gnu.org/pub/gnu/ncurses/ ncurses-$NCURSES_VERSION.tar.gz ncurses - prebuild ncurses || return - $srcdir/configure --prefix=$PREFIX\ - --without-cxx --without-cxx-binding --without-ada --without-manpages --without-progs\ - --without-tests --without-curses-h --without-pkg-config --without-shared --without-debug\ - --without-gpm --without-sysmouse --enable-widec --with-default-terminfo-dir=/usr/share/terminfo\ - --with-terminfo-dirs=/usr/share/terminfo:/lib/terminfo:/usr/local/share/terminfo\ - --with-fallbacks="screen linux vt100 xterm xterm-256color" --host=$HOST\ - CPPFLAGS=-D_GNU_SOURCE || exit - make || exit - make install.libs || exit - postbuild -} - - -getncdu() { - prebuild ncdu || return - srcdir=../../.. - $srcdir/configure --host=$HOST --with-ncursesw PKG_CONFIG=false\ - CPPFLAGS="-I$PREFIX/include -I$PREFIX/include/ncursesw"\ - LDFLAGS="-static -L$PREFIX/lib -lncursesw" CFLAGS="$CFLAGS -Wall -Wextra" || exit - make || exit - - VER=`cd '../../..' && git describe --abbrev=5 --dirty= | sed s/^v//` - tar -czf ../../ncdu-linux-$TARGET-$VER-unstripped.tar.gz ncdu - $HOST-strip ncdu - tar -czf ../../ncdu-linux-$TARGET-$VER.tar.gz ncdu - echo "====== ncdu-linux-$TARGET-$VER.tar.gz and -unstripped created." - - postbuild -} - - -buildarch() { - TARGET=$1 - case $TARGET in - arm) HOST=arm-linux-musleabi DIR=arm-linux-musleabi ;; - aarch64)HOST=aarch64-linux-musl DIR=aarch64-linux-musl ;; - i486) HOST=i486-linux-musl DIR=i486-linux-musl ;; - x86_64) HOST=x86_64-linux-musl DIR=x86_64-linux-musl ;; - *) echo "Unknown target: $TARGET" ;; - esac - PREFIX="`pwd`/$TARGET/inst" - mkdir -p $TARGET $PREFIX - ln -s lib $PREFIX/lib64 - - OLDPATH="$PATH" - export PATH="$PATH:$MUSL_CROSS_PATH/$DIR/bin" - getncurses - getncdu - PATH="$OLDPATH" -} - - -buildarch $1 |