mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-17 19:18:40 -09:00
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:
parent
9337cdc99e
commit
0783d35793
36 changed files with 581 additions and 6037 deletions
19
.gitignore
vendored
19
.gitignore
vendored
|
|
@ -1,21 +1,4 @@
|
||||||
Makefile
|
zig-cache/
|
||||||
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
|
|
||||||
ncdu.1
|
ncdu.1
|
||||||
*~
|
*~
|
||||||
*.swp
|
*.swp
|
||||||
|
|
|
||||||
47
Makefile.am
47
Makefile.am
|
|
@ -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
20
build.zig
Normal 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);
|
||||||
|
}
|
||||||
75
configure.ac
75
configure.ac
|
|
@ -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
349
deps/khashl.h
vendored
|
|
@ -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
198
deps/yopt.h
vendored
|
|
@ -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: */
|
|
||||||
567
src/browser.c
567
src/browser.c
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
||||||
253
src/delete.c
253
src/delete.c
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
37
src/delete.h
37
src/delete.h
|
|
@ -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
141
src/dir.h
|
|
@ -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
|
|
||||||
232
src/dir_common.c
232
src/dir_common.c
|
|
@ -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;
|
|
||||||
}
|
|
||||||
194
src/dir_export.c
194
src/dir_export.c
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
615
src/dir_import.c
615
src/dir_import.c
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
215
src/dir_mem.c
215
src/dir_mem.c
|
|
@ -1,215 +0,0 @@
|
||||||
/* ncdu - NCurses Disk Usage
|
|
||||||
|
|
||||||
Copyright (c) 2007-2020 Yoran Heling
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included
|
|
||||||
in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "global.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include <khashl.h>
|
|
||||||
|
|
||||||
|
|
||||||
static struct dir *root; /* root directory struct we're scanning */
|
|
||||||
static struct dir *curdir; /* directory item that we're currently adding items to */
|
|
||||||
static struct dir *orig; /* original directory, when refreshing an already scanned dir */
|
|
||||||
|
|
||||||
/* Table of struct dir items with more than one link (in order to detect hard links) */
|
|
||||||
#define hlink_hash(d) (kh_hash_uint64((khint64_t)d->dev) ^ kh_hash_uint64((khint64_t)d->ino))
|
|
||||||
#define hlink_equal(a, b) ((a)->dev == (b)->dev && (a)->ino == (b)->ino)
|
|
||||||
KHASHL_SET_INIT(KH_LOCAL, hl_t, hl, struct dir *, hlink_hash, hlink_equal)
|
|
||||||
static hl_t *links = NULL;
|
|
||||||
|
|
||||||
|
|
||||||
/* recursively checks a dir structure for hard links and fills the lookup array */
|
|
||||||
static void hlink_init(struct dir *d) {
|
|
||||||
struct dir *t;
|
|
||||||
|
|
||||||
for(t=d->sub; t!=NULL; t=t->next)
|
|
||||||
hlink_init(t);
|
|
||||||
|
|
||||||
if(!(d->flags & FF_HLNKC))
|
|
||||||
return;
|
|
||||||
int r;
|
|
||||||
hl_put(links, d, &r);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* checks an individual file for hard links and updates its cicrular linked
|
|
||||||
* list, also updates the sizes of the parent dirs */
|
|
||||||
static void hlink_check(struct dir *d) {
|
|
||||||
struct dir *t, *pt, *par;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/* add to links table */
|
|
||||||
khint_t k = hl_put(links, d, &i);
|
|
||||||
|
|
||||||
/* found in the table? update hlnk */
|
|
||||||
if(!i) {
|
|
||||||
t = kh_key(links, k);
|
|
||||||
d->hlnk = t->hlnk == NULL ? t : t->hlnk;
|
|
||||||
t->hlnk = d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* now update the sizes of the parent directories,
|
|
||||||
* This works by only counting this file in the parent directories where this
|
|
||||||
* file hasn't been counted yet, which can be determined from the hlnk list.
|
|
||||||
* XXX: This may not be the most efficient algorithm to do this */
|
|
||||||
for(i=1,par=d->parent; i&∥ par=par->parent) {
|
|
||||||
if(d->hlnk)
|
|
||||||
for(t=d->hlnk; i&&t!=d; t=t->hlnk)
|
|
||||||
for(pt=t->parent; i&&pt; pt=pt->parent)
|
|
||||||
if(pt==par)
|
|
||||||
i=0;
|
|
||||||
if(i) {
|
|
||||||
par->size = adds64(par->size, d->size);
|
|
||||||
par->asize = adds64(par->asize, d->asize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Add item to the correct place in the memory structure */
|
|
||||||
static void item_add(struct dir *item) {
|
|
||||||
if(!root) {
|
|
||||||
root = item;
|
|
||||||
/* Make sure that the *root appears to be part of the same dir structure as
|
|
||||||
* *orig, otherwise the directory size calculation will be incorrect in the
|
|
||||||
* case of hard links. */
|
|
||||||
if(orig)
|
|
||||||
root->parent = orig->parent;
|
|
||||||
} else {
|
|
||||||
item->parent = curdir;
|
|
||||||
item->next = curdir->sub;
|
|
||||||
if(item->next)
|
|
||||||
item->next->prev = item;
|
|
||||||
curdir->sub = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int item(struct dir *dir, const char *name, struct dir_ext *ext) {
|
|
||||||
struct dir *t, *item;
|
|
||||||
|
|
||||||
/* Go back to parent dir */
|
|
||||||
if(!dir) {
|
|
||||||
curdir = curdir->parent;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!root && orig)
|
|
||||||
name = orig->name;
|
|
||||||
|
|
||||||
if(!extended_info)
|
|
||||||
dir->flags &= ~FF_EXT;
|
|
||||||
item = xmalloc(dir->flags & FF_EXT ? dir_ext_memsize(name) : dir_memsize(name));
|
|
||||||
memcpy(item, dir, offsetof(struct dir, name));
|
|
||||||
strcpy(item->name, name);
|
|
||||||
if(dir->flags & FF_EXT)
|
|
||||||
memcpy(dir_ext_ptr(item), ext, sizeof(struct dir_ext));
|
|
||||||
|
|
||||||
item_add(item);
|
|
||||||
|
|
||||||
/* Ensure that any next items will go to this directory */
|
|
||||||
if(item->flags & FF_DIR)
|
|
||||||
curdir = item;
|
|
||||||
|
|
||||||
/* Special-case the name of the root item to be empty instead of "/". This is
|
|
||||||
* what getpath() expects. */
|
|
||||||
if(item == root && strcmp(item->name, "/") == 0)
|
|
||||||
item->name[0] = 0;
|
|
||||||
|
|
||||||
/* Update stats of parents. Don't update the size/asize fields if this is a
|
|
||||||
* possible hard link, because hlnk_check() will take care of it in that
|
|
||||||
* case. */
|
|
||||||
if(item->flags & FF_HLNKC) {
|
|
||||||
addparentstats(item->parent, 0, 0, 0, 1);
|
|
||||||
hlink_check(item);
|
|
||||||
} else if(item->flags & FF_EXT) {
|
|
||||||
addparentstats(item->parent, item->size, item->asize, dir_ext_ptr(item)->mtime, 1);
|
|
||||||
} else {
|
|
||||||
addparentstats(item->parent, item->size, item->asize, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* propagate ERR and SERR back up to the root */
|
|
||||||
if(item->flags & FF_SERR || item->flags & FF_ERR)
|
|
||||||
for(t=item->parent; t; t=t->parent)
|
|
||||||
t->flags |= FF_SERR;
|
|
||||||
|
|
||||||
dir_output.size = root->size;
|
|
||||||
dir_output.items = root->items;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int final(int fail) {
|
|
||||||
hl_destroy(links);
|
|
||||||
links = NULL;
|
|
||||||
|
|
||||||
if(fail) {
|
|
||||||
freedir(root);
|
|
||||||
if(orig) {
|
|
||||||
browse_init(orig);
|
|
||||||
return 0;
|
|
||||||
} else
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* success, update references and free original item */
|
|
||||||
if(orig) {
|
|
||||||
root->next = orig->next;
|
|
||||||
root->prev = orig->prev;
|
|
||||||
if(root->parent && root->parent->sub == orig)
|
|
||||||
root->parent->sub = root;
|
|
||||||
if(root->prev)
|
|
||||||
root->prev->next = root;
|
|
||||||
if(root->next)
|
|
||||||
root->next->prev = root;
|
|
||||||
orig->next = orig->prev = NULL;
|
|
||||||
freedir(orig);
|
|
||||||
}
|
|
||||||
|
|
||||||
browse_init(root);
|
|
||||||
dirlist_top(-3);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void dir_mem_init(struct dir *_orig) {
|
|
||||||
orig = _orig;
|
|
||||||
root = curdir = NULL;
|
|
||||||
pstate = ST_CALC;
|
|
||||||
|
|
||||||
dir_output.item = item;
|
|
||||||
dir_output.final = final;
|
|
||||||
dir_output.size = 0;
|
|
||||||
dir_output.items = 0;
|
|
||||||
|
|
||||||
/* Init hash table for hard link detection */
|
|
||||||
links = hl_init();
|
|
||||||
if(orig)
|
|
||||||
hlink_init(getroot(orig));
|
|
||||||
}
|
|
||||||
|
|
||||||
405
src/dir_scan.c
405
src/dir_scan.c
|
|
@ -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;
|
|
||||||
}
|
|
||||||
398
src/dirlist.c
398
src/dirlist.c
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
||||||
139
src/exclude.c
139
src/exclude.c
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
132
src/global.h
132
src/global.h
|
|
@ -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
|
|
||||||
212
src/help.c
212
src/help.c
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
37
src/help.h
37
src/help.h
|
|
@ -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
|
|
||||||
|
|
||||||
359
src/main.c
359
src/main.c
|
|
@ -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
72
src/main.zig
Normal 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
350
src/model.zig
Normal 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");
|
||||||
|
}
|
||||||
246
src/path.c
246
src/path.c
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
47
src/path.h
47
src/path.h
|
|
@ -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
|
|
||||||
50
src/quit.c
50
src/quit.c
|
|
@ -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;
|
|
||||||
}
|
|
||||||
37
src/quit.h
37
src/quit.h
|
|
@ -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
138
src/scan.zig
Normal 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());
|
||||||
|
}
|
||||||
82
src/shell.c
82
src/shell.c
|
|
@ -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;
|
|
||||||
}
|
|
||||||
35
src/shell.h
35
src/shell.h
|
|
@ -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
|
|
||||||
434
src/util.c
434
src/util.c
|
|
@ -1,434 +0,0 @@
|
||||||
/* ncdu - NCurses Disk Usage
|
|
||||||
|
|
||||||
Copyright (c) 2007-2020 Yoran Heling
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included
|
|
||||||
in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <ncurses.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#ifdef HAVE_LOCALE_H
|
|
||||||
#include <locale.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int uic_theme;
|
|
||||||
int winrows, wincols;
|
|
||||||
int subwinr, subwinc;
|
|
||||||
int si;
|
|
||||||
static char thou_sep;
|
|
||||||
|
|
||||||
|
|
||||||
char *cropstr(const char *from, int s) {
|
|
||||||
static char dat[4096];
|
|
||||||
int i, j, o = strlen(from);
|
|
||||||
if(o < s) {
|
|
||||||
strcpy(dat, from);
|
|
||||||
return dat;
|
|
||||||
}
|
|
||||||
j=s/2-3;
|
|
||||||
for(i=0; i<j; i++)
|
|
||||||
dat[i] = from[i];
|
|
||||||
dat[i] = '.';
|
|
||||||
dat[++i] = '.';
|
|
||||||
dat[++i] = '.';
|
|
||||||
j=o-s;
|
|
||||||
while(++i<s)
|
|
||||||
dat[i] = from[j+i];
|
|
||||||
dat[s] = '\0';
|
|
||||||
return dat;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
float formatsize(int64_t from, const char **unit) {
|
|
||||||
float r = from;
|
|
||||||
if (si) {
|
|
||||||
if(r < 1000.0f) { *unit = " B"; }
|
|
||||||
else if(r < 1e6f) { *unit = "KB"; r/=1e3f; }
|
|
||||||
else if(r < 1e9f) { *unit = "MB"; r/=1e6f; }
|
|
||||||
else if(r < 1e12f){ *unit = "GB"; r/=1e9f; }
|
|
||||||
else if(r < 1e15f){ *unit = "TB"; r/=1e12f; }
|
|
||||||
else if(r < 1e18f){ *unit = "PB"; r/=1e15f; }
|
|
||||||
else { *unit = "EB"; r/=1e18f; }
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if(r < 1000.0f) { *unit = " B"; }
|
|
||||||
else if(r < 1023e3f) { *unit = "KiB"; r/=1024.0f; }
|
|
||||||
else if(r < 1023e6f) { *unit = "MiB"; r/=1048576.0f; }
|
|
||||||
else if(r < 1023e9f) { *unit = "GiB"; r/=1073741824.0f; }
|
|
||||||
else if(r < 1023e12f){ *unit = "TiB"; r/=1099511627776.0f; }
|
|
||||||
else if(r < 1023e15f){ *unit = "PiB"; r/=1125899906842624.0f; }
|
|
||||||
else { *unit = "EiB"; r/=1152921504606846976.0f; }
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void printsize(enum ui_coltype t, int64_t from) {
|
|
||||||
const char *unit;
|
|
||||||
float r = formatsize(from, &unit);
|
|
||||||
uic_set(t == UIC_HD ? UIC_NUM_HD : t == UIC_SEL ? UIC_NUM_SEL : UIC_NUM);
|
|
||||||
printw("%5.1f", r);
|
|
||||||
addchc(t, ' ');
|
|
||||||
addstrc(t, unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
char *fullsize(int64_t from) {
|
|
||||||
static char dat[26]; /* max: 9.223.372.036.854.775.807 (= 2^63-1) */
|
|
||||||
char tmp[26];
|
|
||||||
int64_t n = from;
|
|
||||||
int i, j;
|
|
||||||
|
|
||||||
/* the K&R method - more portable than sprintf with %lld */
|
|
||||||
i = 0;
|
|
||||||
do {
|
|
||||||
tmp[i++] = n % 10 + '0';
|
|
||||||
} while((n /= 10) > 0);
|
|
||||||
tmp[i] = '\0';
|
|
||||||
|
|
||||||
/* reverse and add thousand separators */
|
|
||||||
j = 0;
|
|
||||||
while(i--) {
|
|
||||||
dat[j++] = tmp[i];
|
|
||||||
if(i != 0 && i%3 == 0)
|
|
||||||
dat[j++] = thou_sep;
|
|
||||||
}
|
|
||||||
dat[j] = '\0';
|
|
||||||
|
|
||||||
return dat;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
char *fmtmode(unsigned short mode) {
|
|
||||||
static char buf[11];
|
|
||||||
unsigned short ft = mode & S_IFMT;
|
|
||||||
buf[0] = ft == S_IFDIR ? 'd'
|
|
||||||
: ft == S_IFREG ? '-'
|
|
||||||
: ft == S_IFLNK ? 'l'
|
|
||||||
: ft == S_IFIFO ? 'p'
|
|
||||||
: ft == S_IFSOCK ? 's'
|
|
||||||
: ft == S_IFCHR ? 'c'
|
|
||||||
: ft == S_IFBLK ? 'b' : '?';
|
|
||||||
buf[1] = mode & 0400 ? 'r' : '-';
|
|
||||||
buf[2] = mode & 0200 ? 'w' : '-';
|
|
||||||
buf[3] = mode & 0100 ? 'x' : '-';
|
|
||||||
buf[4] = mode & 0040 ? 'r' : '-';
|
|
||||||
buf[5] = mode & 0020 ? 'w' : '-';
|
|
||||||
buf[6] = mode & 0010 ? 'x' : '-';
|
|
||||||
buf[7] = mode & 0004 ? 'r' : '-';
|
|
||||||
buf[8] = mode & 0002 ? 'w' : '-';
|
|
||||||
buf[9] = mode & 0001 ? 'x' : '-';
|
|
||||||
buf[10] = 0;
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void read_locale() {
|
|
||||||
thou_sep = '.';
|
|
||||||
#ifdef HAVE_LOCALE_H
|
|
||||||
setlocale(LC_ALL, "");
|
|
||||||
char *locale_thou_sep = localeconv()->thousands_sep;
|
|
||||||
if(locale_thou_sep && 1 == strlen(locale_thou_sep))
|
|
||||||
thou_sep = locale_thou_sep[0];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int ncresize(int minrows, int mincols) {
|
|
||||||
int ch;
|
|
||||||
|
|
||||||
getmaxyx(stdscr, winrows, wincols);
|
|
||||||
while((minrows && winrows < minrows) || (mincols && wincols < mincols)) {
|
|
||||||
erase();
|
|
||||||
mvaddstr(0, 0, "Warning: terminal too small,");
|
|
||||||
mvaddstr(1, 1, "please either resize your terminal,");
|
|
||||||
mvaddstr(2, 1, "press i to ignore, or press q to quit.");
|
|
||||||
refresh();
|
|
||||||
nodelay(stdscr, 0);
|
|
||||||
ch = getch();
|
|
||||||
getmaxyx(stdscr, winrows, wincols);
|
|
||||||
if(ch == 'q') {
|
|
||||||
erase();
|
|
||||||
refresh();
|
|
||||||
endwin();
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
if(ch == 'i')
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
erase();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void nccreate(int height, int width, const char *title) {
|
|
||||||
int i;
|
|
||||||
|
|
||||||
uic_set(UIC_DEFAULT);
|
|
||||||
subwinr = winrows/2-height/2;
|
|
||||||
subwinc = wincols/2-width/2;
|
|
||||||
|
|
||||||
/* clear window */
|
|
||||||
for(i=0; i<height; i++)
|
|
||||||
mvhline(subwinr+i, subwinc, ' ', width);
|
|
||||||
|
|
||||||
/* box() only works around curses windows, so create our own */
|
|
||||||
move(subwinr, subwinc);
|
|
||||||
addch(ACS_ULCORNER);
|
|
||||||
for(i=0; i<width-2; i++)
|
|
||||||
addch(ACS_HLINE);
|
|
||||||
addch(ACS_URCORNER);
|
|
||||||
|
|
||||||
move(subwinr+height-1, subwinc);
|
|
||||||
addch(ACS_LLCORNER);
|
|
||||||
for(i=0; i<width-2; i++)
|
|
||||||
addch(ACS_HLINE);
|
|
||||||
addch(ACS_LRCORNER);
|
|
||||||
|
|
||||||
mvvline(subwinr+1, subwinc, ACS_VLINE, height-2);
|
|
||||||
mvvline(subwinr+1, subwinc+width-1, ACS_VLINE, height-2);
|
|
||||||
|
|
||||||
/* title */
|
|
||||||
uic_set(UIC_BOX_TITLE);
|
|
||||||
mvaddstr(subwinr, subwinc+4, title);
|
|
||||||
uic_set(UIC_DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ncprint(int r, int c, const char *fmt, ...) {
|
|
||||||
va_list arg;
|
|
||||||
va_start(arg, fmt);
|
|
||||||
move(subwinr+r, subwinc+c);
|
|
||||||
vw_printw(stdscr, fmt, arg);
|
|
||||||
va_end(arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void nctab(int c, int sel, int num, const char *str) {
|
|
||||||
uic_set(sel ? UIC_KEY_HD : UIC_KEY);
|
|
||||||
ncprint(0, c, "%d", num);
|
|
||||||
uic_set(sel ? UIC_HD : UIC_DEFAULT);
|
|
||||||
addch(':');
|
|
||||||
addstr(str);
|
|
||||||
uic_set(UIC_DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int colors[] = {
|
|
||||||
#define C(name, ...) 0,
|
|
||||||
UI_COLORS
|
|
||||||
#undef C
|
|
||||||
0
|
|
||||||
};
|
|
||||||
static int lastcolor = 0;
|
|
||||||
|
|
||||||
|
|
||||||
static const struct {
|
|
||||||
short fg, bg;
|
|
||||||
int attr;
|
|
||||||
} color_defs[] = {
|
|
||||||
#define C(name, off_fg, off_bg, off_a, dark_fg, dark_bg, dark_a) \
|
|
||||||
{off_fg, off_bg, off_a}, \
|
|
||||||
{dark_fg, dark_bg, dark_a},
|
|
||||||
UI_COLORS
|
|
||||||
#undef C
|
|
||||||
{0,0,0}
|
|
||||||
};
|
|
||||||
|
|
||||||
void uic_init() {
|
|
||||||
size_t i, j;
|
|
||||||
|
|
||||||
start_color();
|
|
||||||
use_default_colors();
|
|
||||||
for(i=0; i<sizeof(colors)/sizeof(*colors)-1; i++) {
|
|
||||||
j = i*2 + uic_theme;
|
|
||||||
init_pair(i+1, color_defs[j].fg, color_defs[j].bg);
|
|
||||||
colors[i] = color_defs[j].attr | COLOR_PAIR(i+1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void uic_set(enum ui_coltype c) {
|
|
||||||
attroff(lastcolor);
|
|
||||||
lastcolor = colors[(int)c];
|
|
||||||
attron(lastcolor);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* removes item from the hlnk circular linked list and size counts of the parents */
|
|
||||||
static void freedir_hlnk(struct dir *d) {
|
|
||||||
struct dir *t, *par, *pt;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if(!(d->flags & FF_HLNKC))
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* remove size from parents.
|
|
||||||
* This works the same as with adding: only the parents in which THIS is the
|
|
||||||
* only occurrence of the hard link will be modified, if the same file still
|
|
||||||
* exists within the parent it shouldn't get removed from the count.
|
|
||||||
* XXX: Same note as for dir_mem.c / hlink_check():
|
|
||||||
* this is probably not the most efficient algorithm */
|
|
||||||
for(i=1,par=d->parent; i&∥ par=par->parent) {
|
|
||||||
if(d->hlnk)
|
|
||||||
for(t=d->hlnk; i&&t!=d; t=t->hlnk)
|
|
||||||
for(pt=t->parent; i&&pt; pt=pt->parent)
|
|
||||||
if(pt==par)
|
|
||||||
i=0;
|
|
||||||
if(i) {
|
|
||||||
par->size = adds64(par->size, -d->size);
|
|
||||||
par->asize = adds64(par->size, -d->asize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* remove from hlnk */
|
|
||||||
if(d->hlnk) {
|
|
||||||
for(t=d->hlnk; t->hlnk!=d; t=t->hlnk)
|
|
||||||
;
|
|
||||||
t->hlnk = d->hlnk;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void freedir_rec(struct dir *dr) {
|
|
||||||
struct dir *tmp, *tmp2;
|
|
||||||
tmp2 = dr;
|
|
||||||
while((tmp = tmp2) != NULL) {
|
|
||||||
freedir_hlnk(tmp);
|
|
||||||
/* remove item */
|
|
||||||
if(tmp->sub) freedir_rec(tmp->sub);
|
|
||||||
tmp2 = tmp->next;
|
|
||||||
free(tmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void freedir(struct dir *dr) {
|
|
||||||
if(!dr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* free dr->sub recursively */
|
|
||||||
if(dr->sub)
|
|
||||||
freedir_rec(dr->sub);
|
|
||||||
|
|
||||||
/* update references */
|
|
||||||
if(dr->parent && dr->parent->sub == dr)
|
|
||||||
dr->parent->sub = dr->next;
|
|
||||||
if(dr->prev)
|
|
||||||
dr->prev->next = dr->next;
|
|
||||||
if(dr->next)
|
|
||||||
dr->next->prev = dr->prev;
|
|
||||||
|
|
||||||
freedir_hlnk(dr);
|
|
||||||
|
|
||||||
/* update sizes of parent directories if this isn't a hard link.
|
|
||||||
* If this is a hard link, freedir_hlnk() would have done so already
|
|
||||||
*
|
|
||||||
* mtime is 0 here because recalculating the maximum at every parent
|
|
||||||
* dir is expensive, but might be good feature to add later if desired */
|
|
||||||
addparentstats(dr->parent, dr->flags & FF_HLNKC ? 0 : -dr->size, dr->flags & FF_HLNKC ? 0 : -dr->asize, 0, -(dr->items+1));
|
|
||||||
|
|
||||||
free(dr);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const char *getpath(struct dir *cur) {
|
|
||||||
static char *dat;
|
|
||||||
static int datl = 0;
|
|
||||||
struct dir *d, **list;
|
|
||||||
int c, i;
|
|
||||||
|
|
||||||
if(!cur->name[0])
|
|
||||||
return "/";
|
|
||||||
|
|
||||||
c = i = 1;
|
|
||||||
for(d=cur; d!=NULL; d=d->parent) {
|
|
||||||
i += strlen(d->name)+1;
|
|
||||||
c++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(datl == 0) {
|
|
||||||
datl = i;
|
|
||||||
dat = xmalloc(i);
|
|
||||||
} else if(datl < i) {
|
|
||||||
datl = i;
|
|
||||||
dat = xrealloc(dat, i);
|
|
||||||
}
|
|
||||||
list = xmalloc(c*sizeof(struct dir *));
|
|
||||||
|
|
||||||
c = 0;
|
|
||||||
for(d=cur; d!=NULL; d=d->parent)
|
|
||||||
list[c++] = d;
|
|
||||||
|
|
||||||
dat[0] = '\0';
|
|
||||||
while(c--) {
|
|
||||||
if(list[c]->parent)
|
|
||||||
strcat(dat, "/");
|
|
||||||
strcat(dat, list[c]->name);
|
|
||||||
}
|
|
||||||
free(list);
|
|
||||||
return dat;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct dir *getroot(struct dir *d) {
|
|
||||||
while(d && d->parent)
|
|
||||||
d = d->parent;
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void addparentstats(struct dir *d, int64_t size, int64_t asize, uint64_t mtime, int items) {
|
|
||||||
struct dir_ext *e;
|
|
||||||
while(d) {
|
|
||||||
d->size = adds64(d->size, size);
|
|
||||||
d->asize = adds64(d->asize, asize);
|
|
||||||
d->items += items;
|
|
||||||
if (d->flags & FF_EXT) {
|
|
||||||
e = dir_ext_ptr(d);
|
|
||||||
e->mtime = (e->mtime > mtime) ? e->mtime : mtime;
|
|
||||||
}
|
|
||||||
d = d->parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Apparently we can just resume drawing after endwin() and ncurses will pick
|
|
||||||
* up where it left. Probably not very portable... */
|
|
||||||
#define oom_msg "\nOut of memory, press enter to try again or Ctrl-C to give up.\n"
|
|
||||||
#define wrap_oom(f) \
|
|
||||||
void *ptr;\
|
|
||||||
char buf[128];\
|
|
||||||
while((ptr = f) == NULL) {\
|
|
||||||
close_nc();\
|
|
||||||
write(2, oom_msg, sizeof(oom_msg));\
|
|
||||||
read(0, buf, sizeof(buf));\
|
|
||||||
}\
|
|
||||||
return ptr;
|
|
||||||
|
|
||||||
void *xmalloc(size_t size) { wrap_oom(malloc(size)) }
|
|
||||||
void *xcalloc(size_t n, size_t size) { wrap_oom(calloc(n, size)) }
|
|
||||||
void *xrealloc(void *mem, size_t size) { wrap_oom(realloc(mem, size)) }
|
|
||||||
195
src/util.h
195
src/util.h
|
|
@ -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
|
|
||||||
|
|
||||||
130
static/build.sh
130
static/build.sh
|
|
@ -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
|
|
||||||
Loading…
Reference in a new issue