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.
This commit is contained in:
Yorhel 2021-04-29 12:48:45 +02:00
parent 9337cdc99e
commit 0783d35793
36 changed files with 581 additions and 6037 deletions

19
.gitignore vendored
View file

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

View file

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

20
build.zig Normal file
View file

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

View file

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

349
deps/khashl.h vendored
View file

@ -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 */

198
deps/yopt.h vendored
View file

@ -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: */

View file

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

View file

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

View file

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

View file

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

141
src/dir.h
View file

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

View file

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

View file

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

View file

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

View file

@ -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=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));
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

72
src/main.zig Normal file
View file

@ -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();
}

350
src/model.zig Normal file
View file

@ -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");
}

View file

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

View file

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

View file

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

View file

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

138
src/scan.zig Normal file
View file

@ -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());
}

View file

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

View file

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

View file

@ -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=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)) }

View file

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

View file

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