mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-13 01:08:41 -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
|
||||
Makefile.in
|
||||
aclocal.m4
|
||||
autom4te.cache/
|
||||
compile
|
||||
config.h
|
||||
config.h.in
|
||||
config.log
|
||||
config.status
|
||||
configure
|
||||
depcomp
|
||||
install-sh
|
||||
missing
|
||||
.deps/
|
||||
.dirstamp
|
||||
*.o
|
||||
stamp-h1
|
||||
ncdu
|
||||
zig-cache/
|
||||
ncdu.1
|
||||
*~
|
||||
*.swp
|
||||
|
|
|
|||
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