|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <config.h> |
|
|
#include <getopt.h> |
|
|
#include <sys/types.h> |
|
|
#include "argmatch.h" |
|
|
#include "system.h" |
|
|
#include "argv-iter.h" |
|
|
#include "assure.h" |
|
|
#include "di-set.h" |
|
|
#include "exclude.h" |
|
|
#include "human.h" |
|
|
#include "mountlist.h" |
|
|
#include "quote.h" |
|
|
#include "show-date.h" |
|
|
#include "stat-size.h" |
|
|
#include "stat-time.h" |
|
|
#include "stdio--.h" |
|
|
#include "xfts.h" |
|
|
#include "xstrtol.h" |
|
|
#include "xstrtol-error.h" |
|
|
|
|
|
|
|
|
#define PROGRAM_NAME "du" |
|
|
|
|
|
#define AUTHORS \ |
|
|
proper_name_lite ("Torbjorn Granlund", "Torbj\303\266rn Granlund"), \ |
|
|
proper_name ("David MacKenzie"), \ |
|
|
proper_name ("Paul Eggert"), \ |
|
|
proper_name ("Jim Meyering") |
|
|
|
|
|
|
|
|
|
|
|
static struct di_set *di_files; |
|
|
|
|
|
|
|
|
static struct di_set *di_mnt; |
|
|
|
|
|
|
|
|
|
|
|
static idx_t prev_level; |
|
|
|
|
|
|
|
|
struct duinfo |
|
|
{ |
|
|
|
|
|
uintmax_t size; |
|
|
|
|
|
|
|
|
uintmax_t inodes; |
|
|
|
|
|
|
|
|
|
|
|
struct timespec tmax; |
|
|
}; |
|
|
|
|
|
|
|
|
static inline void |
|
|
duinfo_init (struct duinfo *a) |
|
|
{ |
|
|
a->size = 0; |
|
|
a->inodes = 0; |
|
|
a->tmax.tv_sec = TYPE_MINIMUM (time_t); |
|
|
a->tmax.tv_nsec = -1; |
|
|
} |
|
|
|
|
|
|
|
|
static inline void |
|
|
duinfo_set (struct duinfo *a, uintmax_t size, struct timespec tmax) |
|
|
{ |
|
|
a->size = size; |
|
|
a->inodes = 1; |
|
|
a->tmax = tmax; |
|
|
} |
|
|
|
|
|
|
|
|
static inline void |
|
|
duinfo_add (struct duinfo *a, struct duinfo const *b) |
|
|
{ |
|
|
uintmax_t sum = a->size + b->size; |
|
|
a->size = a->size <= sum ? sum : UINTMAX_MAX; |
|
|
a->inodes = a->inodes + b->inodes; |
|
|
if (timespec_cmp (a->tmax, b->tmax) < 0) |
|
|
a->tmax = b->tmax; |
|
|
} |
|
|
|
|
|
|
|
|
struct dulevel |
|
|
{ |
|
|
|
|
|
struct duinfo ent; |
|
|
|
|
|
|
|
|
struct duinfo subdir; |
|
|
}; |
|
|
|
|
|
|
|
|
static bool opt_all = false; |
|
|
|
|
|
|
|
|
|
|
|
static bool apparent_size = false; |
|
|
|
|
|
|
|
|
static bool opt_count_all = false; |
|
|
|
|
|
|
|
|
static bool hash_all; |
|
|
|
|
|
|
|
|
static bool opt_nul_terminate_output = false; |
|
|
|
|
|
|
|
|
static bool print_grand_total = false; |
|
|
|
|
|
|
|
|
static bool opt_separate_dirs = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static idx_t max_depth = IDX_MAX; |
|
|
|
|
|
|
|
|
|
|
|
static intmax_t opt_threshold = 0; |
|
|
|
|
|
|
|
|
static int human_output_opts; |
|
|
|
|
|
|
|
|
static bool opt_inodes = false; |
|
|
|
|
|
|
|
|
static bool opt_time = false; |
|
|
|
|
|
|
|
|
|
|
|
enum time_type |
|
|
{ |
|
|
time_mtime, |
|
|
time_ctime, |
|
|
time_atime |
|
|
}; |
|
|
|
|
|
static enum time_type time_type = time_mtime; |
|
|
|
|
|
|
|
|
static char const *time_style = nullptr; |
|
|
|
|
|
|
|
|
static char const *time_format = nullptr; |
|
|
|
|
|
|
|
|
static timezone_t localtz; |
|
|
|
|
|
|
|
|
static uintmax_t output_block_size; |
|
|
|
|
|
|
|
|
static struct exclude *exclude; |
|
|
|
|
|
|
|
|
static struct duinfo tot_dui; |
|
|
|
|
|
#define IS_DIR_TYPE(Type) \ |
|
|
((Type) == FTS_DP \ |
|
|
|| (Type) == FTS_DNR) |
|
|
|
|
|
|
|
|
|
|
|
enum |
|
|
{ |
|
|
APPARENT_SIZE_OPTION = CHAR_MAX + 1, |
|
|
EXCLUDE_OPTION, |
|
|
FILES0_FROM_OPTION, |
|
|
HUMAN_SI_OPTION, |
|
|
#if GNULIB_FTS_DEBUG |
|
|
FTS_DEBUG, |
|
|
#endif |
|
|
TIME_OPTION, |
|
|
TIME_STYLE_OPTION, |
|
|
INODES_OPTION |
|
|
}; |
|
|
|
|
|
static struct option const long_options[] = |
|
|
{ |
|
|
{"all", no_argument, nullptr, 'a'}, |
|
|
{"apparent-size", no_argument, nullptr, APPARENT_SIZE_OPTION}, |
|
|
{"block-size", required_argument, nullptr, 'B'}, |
|
|
{"bytes", no_argument, nullptr, 'b'}, |
|
|
{"count-links", no_argument, nullptr, 'l'}, |
|
|
#if GNULIB_FTS_DEBUG |
|
|
{"-debug", no_argument, nullptr, FTS_DEBUG}, |
|
|
#endif |
|
|
{"dereference", no_argument, nullptr, 'L'}, |
|
|
{"dereference-args", no_argument, nullptr, 'D'}, |
|
|
{"exclude", required_argument, nullptr, EXCLUDE_OPTION}, |
|
|
{"exclude-from", required_argument, nullptr, 'X'}, |
|
|
{"files0-from", required_argument, nullptr, FILES0_FROM_OPTION}, |
|
|
{"human-readable", no_argument, nullptr, 'h'}, |
|
|
{"inodes", no_argument, nullptr, INODES_OPTION}, |
|
|
{"si", no_argument, nullptr, HUMAN_SI_OPTION}, |
|
|
{"max-depth", required_argument, nullptr, 'd'}, |
|
|
{"null", no_argument, nullptr, '0'}, |
|
|
{"no-dereference", no_argument, nullptr, 'P'}, |
|
|
{"one-file-system", no_argument, nullptr, 'x'}, |
|
|
{"separate-dirs", no_argument, nullptr, 'S'}, |
|
|
{"summarize", no_argument, nullptr, 's'}, |
|
|
{"total", no_argument, nullptr, 'c'}, |
|
|
{"threshold", required_argument, nullptr, 't'}, |
|
|
{"time", optional_argument, nullptr, TIME_OPTION}, |
|
|
{"time-style", required_argument, nullptr, TIME_STYLE_OPTION}, |
|
|
{GETOPT_HELP_OPTION_DECL}, |
|
|
{GETOPT_VERSION_OPTION_DECL}, |
|
|
{nullptr, 0, nullptr, 0} |
|
|
}; |
|
|
|
|
|
static char const *const time_args[] = |
|
|
{ |
|
|
"atime", "access", "use", "ctime", "status", nullptr |
|
|
}; |
|
|
static enum time_type const time_types[] = |
|
|
{ |
|
|
time_atime, time_atime, time_atime, time_ctime, time_ctime |
|
|
}; |
|
|
ARGMATCH_VERIFY (time_args, time_types); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum time_style |
|
|
{ |
|
|
full_iso_time_style, |
|
|
long_iso_time_style, |
|
|
iso_time_style |
|
|
}; |
|
|
|
|
|
static char const *const time_style_args[] = |
|
|
{ |
|
|
"full-iso", "long-iso", "iso", nullptr |
|
|
}; |
|
|
static enum time_style const time_style_types[] = |
|
|
{ |
|
|
full_iso_time_style, long_iso_time_style, iso_time_style |
|
|
}; |
|
|
ARGMATCH_VERIFY (time_style_args, time_style_types); |
|
|
|
|
|
void |
|
|
usage (int status) |
|
|
{ |
|
|
if (status != EXIT_SUCCESS) |
|
|
emit_try_help (); |
|
|
else |
|
|
{ |
|
|
printf (_("\ |
|
|
Usage: %s [OPTION]... [FILE]...\n\ |
|
|
or: %s [OPTION]... --files0-from=F\n\ |
|
|
"), program_name, program_name); |
|
|
fputs (_("\ |
|
|
Summarize device usage of the set of FILEs, recursively for directories.\n\ |
|
|
"), stdout); |
|
|
|
|
|
emit_mandatory_arg_note (); |
|
|
|
|
|
fputs (_("\ |
|
|
-0, --null end each output line with NUL, not newline\n\ |
|
|
-a, --all write counts for all files, not just directories\n\ |
|
|
--apparent-size print apparent sizes rather than device usage; although\ |
|
|
\n\ |
|
|
the apparent size is usually smaller, it may be\n\ |
|
|
larger due to holes in ('sparse') files, internal\n\ |
|
|
fragmentation, indirect blocks, and the like\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
-B, --block-size=SIZE scale sizes by SIZE before printing them; e.g.,\n\ |
|
|
'-BM' prints sizes in units of 1,048,576 bytes;\n\ |
|
|
see SIZE format below\n\ |
|
|
-b, --bytes equivalent to '--apparent-size --block-size=1'\n\ |
|
|
-c, --total produce a grand total\n\ |
|
|
-D, --dereference-args dereference only symlinks that are listed on the\n\ |
|
|
command line\n\ |
|
|
-d, --max-depth=N print the total for a directory (or file, with --all)\n\ |
|
|
only if it is N or fewer levels below the command\n\ |
|
|
line argument; --max-depth=0 is the same as\n\ |
|
|
--summarize\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
--files0-from=F summarize device usage of the\n\ |
|
|
NUL-terminated file names specified in file F;\n\ |
|
|
if F is -, then read names from standard input\n\ |
|
|
-H equivalent to --dereference-args (-D)\n\ |
|
|
-h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)\ |
|
|
\n\ |
|
|
--inodes list inode usage information instead of block usage\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
-k like --block-size=1K\n\ |
|
|
-L, --dereference dereference all symbolic links\n\ |
|
|
-l, --count-links count sizes many times if hard linked\n\ |
|
|
-m like --block-size=1M\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
-P, --no-dereference don't follow any symbolic links (this is the default)\n\ |
|
|
-S, --separate-dirs for directories do not include size of subdirectories\n\ |
|
|
--si like -h, but use powers of 1000 not 1024\n\ |
|
|
-s, --summarize display only a total for each argument\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
-t, --threshold=SIZE exclude entries smaller than SIZE if positive,\n\ |
|
|
or entries greater than SIZE if negative\n\ |
|
|
--time show time of the last modification of any file in the\n\ |
|
|
directory, or any of its subdirectories\n\ |
|
|
--time=WORD show time as WORD instead of modification time:\n\ |
|
|
atime, access, use, ctime or status\n\ |
|
|
--time-style=STYLE show times using STYLE, which can be:\n\ |
|
|
full-iso, long-iso, iso, or +FORMAT;\n\ |
|
|
FORMAT is interpreted like in 'date'\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
-X, --exclude-from=FILE exclude files that match any pattern in FILE\n\ |
|
|
--exclude=PATTERN exclude files that match PATTERN\n\ |
|
|
-x, --one-file-system skip directories on different file systems\n\ |
|
|
"), stdout); |
|
|
fputs (HELP_OPTION_DESCRIPTION, stdout); |
|
|
fputs (VERSION_OPTION_DESCRIPTION, stdout); |
|
|
emit_blocksize_note ("DU"); |
|
|
emit_size_note (); |
|
|
emit_ancillary_info (PROGRAM_NAME); |
|
|
} |
|
|
exit (status); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
hash_ins (struct di_set *di_set, ino_t ino, dev_t dev) |
|
|
{ |
|
|
int inserted = di_set_insert (di_set, dev, ino); |
|
|
if (inserted < 0) |
|
|
xalloc_die (); |
|
|
return inserted; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
print_only_size (uintmax_t n_bytes) |
|
|
{ |
|
|
char buf[LONGEST_HUMAN_READABLE + 1]; |
|
|
fputs ((n_bytes == UINTMAX_MAX |
|
|
? _("Infinity") |
|
|
: human_readable (n_bytes, buf, human_output_opts, |
|
|
1, output_block_size)), |
|
|
stdout); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
print_size (const struct duinfo *pdui, char const *string) |
|
|
{ |
|
|
print_only_size (opt_inodes |
|
|
? pdui->inodes |
|
|
: pdui->size); |
|
|
|
|
|
if (opt_time) |
|
|
{ |
|
|
putchar ('\t'); |
|
|
bool ok = show_date (time_format, pdui->tmax, localtz); |
|
|
if (!ok) |
|
|
{ |
|
|
|
|
|
char buf[INT_BUFSIZE_BOUND (intmax_t)]; |
|
|
fputs (timetostr (pdui->tmax.tv_sec, buf), stdout); |
|
|
} |
|
|
} |
|
|
printf ("\t%s%c", string, opt_nul_terminate_output ? '\0' : '\n'); |
|
|
fflush (stdout); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
fill_mount_table (void) |
|
|
{ |
|
|
struct mount_entry *mnt_ent = read_file_system_list (false); |
|
|
while (mnt_ent) |
|
|
{ |
|
|
struct mount_entry *mnt_free; |
|
|
if (!mnt_ent->me_remote && !mnt_ent->me_dummy) |
|
|
{ |
|
|
struct stat buf; |
|
|
if (!stat (mnt_ent->me_mountdir, &buf)) |
|
|
hash_ins (di_mnt, buf.st_ino, buf.st_dev); |
|
|
else |
|
|
{ |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
mnt_free = mnt_ent; |
|
|
mnt_ent = mnt_ent->me_next; |
|
|
free_mount_entry (mnt_free); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
mount_point_in_fts_cycle (FTSENT const *ent) |
|
|
{ |
|
|
FTSENT const *cycle_ent = ent->fts_cycle; |
|
|
|
|
|
if (!di_mnt) |
|
|
{ |
|
|
|
|
|
di_mnt = di_set_alloc (); |
|
|
if (!di_mnt) |
|
|
xalloc_die (); |
|
|
|
|
|
fill_mount_table (); |
|
|
} |
|
|
|
|
|
while (ent && ent != cycle_ent) |
|
|
{ |
|
|
if (di_set_lookup (di_mnt, ent->fts_statp->st_dev, |
|
|
ent->fts_statp->st_ino) > 0) |
|
|
{ |
|
|
return true; |
|
|
} |
|
|
ent = ent->fts_parent; |
|
|
} |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
process_file (FTS *fts, FTSENT *ent) |
|
|
{ |
|
|
bool ok = true; |
|
|
struct duinfo dui; |
|
|
struct duinfo dui_to_print; |
|
|
static idx_t n_alloc; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static struct dulevel *dulvl; |
|
|
|
|
|
char const *file = ent->fts_path; |
|
|
const struct stat *sb = ent->fts_statp; |
|
|
int info = ent->fts_info; |
|
|
|
|
|
if (info == FTS_DNR) |
|
|
{ |
|
|
|
|
|
error (0, ent->fts_errno, _("cannot read directory %s"), quoteaf (file)); |
|
|
ok = false; |
|
|
} |
|
|
else if (info != FTS_DP) |
|
|
{ |
|
|
bool excluded = excluded_file_name (exclude, file); |
|
|
if (! excluded) |
|
|
{ |
|
|
|
|
|
|
|
|
if (info == FTS_NSOK) |
|
|
{ |
|
|
fts_set (fts, ent, FTS_AGAIN); |
|
|
MAYBE_UNUSED FTSENT const *e = fts_read (fts); |
|
|
affirm (e == ent); |
|
|
info = ent->fts_info; |
|
|
} |
|
|
|
|
|
if (info == FTS_NS || info == FTS_SLNONE) |
|
|
{ |
|
|
error (0, ent->fts_errno, _("cannot access %s"), quoteaf (file)); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (fts->fts_options & FTS_XDEV |
|
|
&& FTS_ROOTLEVEL < ent->fts_level |
|
|
&& fts->fts_dev != sb->st_dev) |
|
|
excluded = true; |
|
|
} |
|
|
|
|
|
if (excluded |
|
|
|| (! opt_count_all |
|
|
&& (hash_all || (! S_ISDIR (sb->st_mode) && 1 < sb->st_nlink)) |
|
|
&& ! hash_ins (di_files, sb->st_ino, sb->st_dev))) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
if (info == FTS_D) |
|
|
{ |
|
|
fts_set (fts, ent, FTS_SKIP); |
|
|
MAYBE_UNUSED FTSENT const *e = fts_read (fts); |
|
|
affirm (e == ent); |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
switch (info) |
|
|
{ |
|
|
case FTS_D: |
|
|
return true; |
|
|
|
|
|
case FTS_ERR: |
|
|
|
|
|
error (0, ent->fts_errno, "%s", quotef (file)); |
|
|
ok = false; |
|
|
break; |
|
|
|
|
|
case FTS_DC: |
|
|
|
|
|
if (cycle_warning_required (fts, ent) |
|
|
&& ! mount_point_in_fts_cycle (ent)) |
|
|
{ |
|
|
emit_cycle_warning (file); |
|
|
return false; |
|
|
} |
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
duinfo_set (&dui, |
|
|
(apparent_size |
|
|
? (usable_st_size (sb) ? MAX (0, sb->st_size) : 0) |
|
|
: (uintmax_t) STP_NBLOCKS (sb) * ST_NBLOCKSIZE), |
|
|
(time_type == time_mtime ? get_stat_mtime (sb) |
|
|
: time_type == time_atime ? get_stat_atime (sb) |
|
|
: get_stat_ctime (sb))); |
|
|
|
|
|
idx_t level = ent->fts_level; |
|
|
dui_to_print = dui; |
|
|
|
|
|
if (n_alloc == 0) |
|
|
{ |
|
|
n_alloc = level + 10; |
|
|
dulvl = xcalloc (n_alloc, sizeof *dulvl); |
|
|
} |
|
|
else |
|
|
{ |
|
|
if (level == prev_level) |
|
|
{ |
|
|
|
|
|
} |
|
|
else if (level > prev_level) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (n_alloc <= level) |
|
|
dulvl = xpalloc (dulvl, &n_alloc, level - n_alloc + 1, -1, |
|
|
sizeof *dulvl); |
|
|
|
|
|
for (idx_t i = prev_level + 1; i <= level; i++) |
|
|
{ |
|
|
duinfo_init (&dulvl[i].ent); |
|
|
duinfo_init (&dulvl[i].subdir); |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
affirm (level == prev_level - 1); |
|
|
duinfo_add (&dui_to_print, &dulvl[prev_level].ent); |
|
|
if (!opt_separate_dirs) |
|
|
duinfo_add (&dui_to_print, &dulvl[prev_level].subdir); |
|
|
duinfo_add (&dulvl[level].subdir, &dulvl[prev_level].ent); |
|
|
duinfo_add (&dulvl[level].subdir, &dulvl[prev_level].subdir); |
|
|
} |
|
|
} |
|
|
|
|
|
prev_level = level; |
|
|
|
|
|
|
|
|
|
|
|
if (! (opt_separate_dirs && IS_DIR_TYPE (info))) |
|
|
duinfo_add (&dulvl[level].ent, &dui); |
|
|
|
|
|
|
|
|
|
|
|
duinfo_add (&tot_dui, &dui); |
|
|
|
|
|
if ((IS_DIR_TYPE (info) && level <= max_depth) |
|
|
|| (opt_all && level <= max_depth) |
|
|
|| level == 0) |
|
|
{ |
|
|
|
|
|
uintmax_t v = opt_inodes ? dui_to_print.inodes : dui_to_print.size; |
|
|
if (opt_threshold < 0 |
|
|
? v <= -opt_threshold |
|
|
: v >= opt_threshold) |
|
|
print_size (&dui_to_print, file); |
|
|
} |
|
|
|
|
|
return ok; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
du_files (char **files, int bit_flags) |
|
|
{ |
|
|
bool ok = true; |
|
|
|
|
|
if (*files) |
|
|
{ |
|
|
FTS *fts = xfts_open (files, bit_flags, nullptr); |
|
|
|
|
|
while (true) |
|
|
{ |
|
|
FTSENT *ent; |
|
|
|
|
|
ent = fts_read (fts); |
|
|
if (ent == nullptr) |
|
|
{ |
|
|
if (errno != 0) |
|
|
{ |
|
|
error (0, errno, _("fts_read failed: %s"), |
|
|
quotef (fts->fts_path)); |
|
|
ok = false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
prev_level = 0; |
|
|
break; |
|
|
} |
|
|
|
|
|
#if GNULIB_FTS_DEBUG |
|
|
if (fts_debug) |
|
|
fts_cross_check (fts); |
|
|
#endif |
|
|
|
|
|
ok &= process_file (fts, ent); |
|
|
} |
|
|
|
|
|
if (fts_close (fts) != 0) |
|
|
{ |
|
|
error (0, errno, _("fts_close failed")); |
|
|
ok = false; |
|
|
} |
|
|
} |
|
|
|
|
|
return ok; |
|
|
} |
|
|
|
|
|
int |
|
|
main (int argc, char **argv) |
|
|
{ |
|
|
char *cwd_only[2]; |
|
|
bool max_depth_specified = false; |
|
|
bool ok = true; |
|
|
char *files_from = nullptr; |
|
|
|
|
|
|
|
|
int bit_flags = FTS_NOSTAT; |
|
|
|
|
|
|
|
|
|
|
|
int symlink_deref_bits = FTS_PHYSICAL; |
|
|
|
|
|
|
|
|
bool opt_summarize_only = false; |
|
|
|
|
|
cwd_only[0] = bad_cast ("."); |
|
|
cwd_only[1] = nullptr; |
|
|
|
|
|
initialize_main (&argc, &argv); |
|
|
set_program_name (argv[0]); |
|
|
setlocale (LC_ALL, ""); |
|
|
bindtextdomain (PACKAGE, LOCALEDIR); |
|
|
textdomain (PACKAGE); |
|
|
|
|
|
atexit (close_stdout); |
|
|
|
|
|
exclude = new_exclude (); |
|
|
|
|
|
human_options (getenv ("DU_BLOCK_SIZE"), |
|
|
&human_output_opts, &output_block_size); |
|
|
|
|
|
while (true) |
|
|
{ |
|
|
int oi = -1; |
|
|
int c = getopt_long (argc, argv, "0abd:chHklmst:xB:DLPSX:", |
|
|
long_options, &oi); |
|
|
if (c == -1) |
|
|
break; |
|
|
|
|
|
switch (c) |
|
|
{ |
|
|
#if GNULIB_FTS_DEBUG |
|
|
case FTS_DEBUG: |
|
|
fts_debug = true; |
|
|
break; |
|
|
#endif |
|
|
|
|
|
case '0': |
|
|
opt_nul_terminate_output = true; |
|
|
break; |
|
|
|
|
|
case 'a': |
|
|
opt_all = true; |
|
|
break; |
|
|
|
|
|
case APPARENT_SIZE_OPTION: |
|
|
apparent_size = true; |
|
|
break; |
|
|
|
|
|
case 'b': |
|
|
apparent_size = true; |
|
|
human_output_opts = 0; |
|
|
output_block_size = 1; |
|
|
break; |
|
|
|
|
|
case 'c': |
|
|
print_grand_total = true; |
|
|
break; |
|
|
|
|
|
case 'h': |
|
|
human_output_opts = human_autoscale | human_SI | human_base_1024; |
|
|
output_block_size = 1; |
|
|
break; |
|
|
|
|
|
case HUMAN_SI_OPTION: |
|
|
human_output_opts = human_autoscale | human_SI; |
|
|
output_block_size = 1; |
|
|
break; |
|
|
|
|
|
case 'k': |
|
|
human_output_opts = 0; |
|
|
output_block_size = 1024; |
|
|
break; |
|
|
|
|
|
case 'd': |
|
|
{ |
|
|
intmax_t tmp; |
|
|
if (xstrtoimax (optarg, nullptr, 0, &tmp, "") == LONGINT_OK |
|
|
&& tmp <= IDX_MAX) |
|
|
{ |
|
|
max_depth_specified = true; |
|
|
max_depth = tmp; |
|
|
} |
|
|
else |
|
|
{ |
|
|
error (0, 0, _("invalid maximum depth %s"), |
|
|
quote (optarg)); |
|
|
ok = false; |
|
|
} |
|
|
} |
|
|
break; |
|
|
|
|
|
case 'm': |
|
|
human_output_opts = 0; |
|
|
output_block_size = 1024 * 1024; |
|
|
break; |
|
|
|
|
|
case 'l': |
|
|
opt_count_all = true; |
|
|
break; |
|
|
|
|
|
case 's': |
|
|
opt_summarize_only = true; |
|
|
break; |
|
|
|
|
|
case 't': |
|
|
{ |
|
|
enum strtol_error e; |
|
|
e = xstrtoimax (optarg, nullptr, 0, &opt_threshold, |
|
|
"kKmMGTPEZYRQ0"); |
|
|
if (e != LONGINT_OK) |
|
|
xstrtol_fatal (e, oi, c, long_options, optarg); |
|
|
if (opt_threshold == 0 && *optarg == '-') |
|
|
{ |
|
|
|
|
|
error (EXIT_FAILURE, 0, _("invalid --threshold argument '-0'")); |
|
|
} |
|
|
} |
|
|
break; |
|
|
|
|
|
case 'x': |
|
|
bit_flags |= FTS_XDEV; |
|
|
break; |
|
|
|
|
|
case 'B': |
|
|
{ |
|
|
enum strtol_error e = human_options (optarg, &human_output_opts, |
|
|
&output_block_size); |
|
|
if (e != LONGINT_OK) |
|
|
xstrtol_fatal (e, oi, c, long_options, optarg); |
|
|
} |
|
|
break; |
|
|
|
|
|
case 'H': |
|
|
case 'D': |
|
|
symlink_deref_bits = FTS_COMFOLLOW | FTS_PHYSICAL; |
|
|
break; |
|
|
|
|
|
case 'L': |
|
|
symlink_deref_bits = FTS_LOGICAL; |
|
|
break; |
|
|
|
|
|
case 'P': |
|
|
symlink_deref_bits = FTS_PHYSICAL; |
|
|
break; |
|
|
|
|
|
case 'S': |
|
|
opt_separate_dirs = true; |
|
|
break; |
|
|
|
|
|
case 'X': |
|
|
if (add_exclude_file (add_exclude, exclude, optarg, |
|
|
EXCLUDE_WILDCARDS, '\n')) |
|
|
{ |
|
|
error (0, errno, "%s", quotef (optarg)); |
|
|
ok = false; |
|
|
} |
|
|
break; |
|
|
|
|
|
case FILES0_FROM_OPTION: |
|
|
files_from = optarg; |
|
|
break; |
|
|
|
|
|
case EXCLUDE_OPTION: |
|
|
add_exclude (exclude, optarg, EXCLUDE_WILDCARDS); |
|
|
break; |
|
|
|
|
|
case INODES_OPTION: |
|
|
opt_inodes = true; |
|
|
break; |
|
|
|
|
|
case TIME_OPTION: |
|
|
opt_time = true; |
|
|
time_type = |
|
|
(optarg |
|
|
? XARGMATCH ("--time", optarg, time_args, time_types) |
|
|
: time_mtime); |
|
|
localtz = tzalloc (getenv ("TZ")); |
|
|
break; |
|
|
|
|
|
case TIME_STYLE_OPTION: |
|
|
time_style = optarg; |
|
|
break; |
|
|
|
|
|
case_GETOPT_HELP_CHAR; |
|
|
|
|
|
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); |
|
|
|
|
|
default: |
|
|
ok = false; |
|
|
} |
|
|
} |
|
|
|
|
|
if (!ok) |
|
|
usage (EXIT_FAILURE); |
|
|
|
|
|
if (opt_all && opt_summarize_only) |
|
|
{ |
|
|
error (0, 0, _("cannot both summarize and show all entries")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (opt_summarize_only && max_depth_specified && max_depth == 0) |
|
|
{ |
|
|
error (0, 0, |
|
|
_("warning: summarizing is the same as using --max-depth=0")); |
|
|
} |
|
|
|
|
|
if (opt_summarize_only && max_depth_specified && max_depth != 0) |
|
|
{ |
|
|
error (0, 0, _("warning: summarizing conflicts with --max-depth=%td"), |
|
|
max_depth); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (opt_summarize_only) |
|
|
max_depth = 0; |
|
|
|
|
|
if (opt_inodes) |
|
|
{ |
|
|
if (apparent_size) |
|
|
{ |
|
|
error (0, 0, _("warning: options --apparent-size and -b are " |
|
|
"ineffective with --inodes")); |
|
|
} |
|
|
output_block_size = 1; |
|
|
} |
|
|
|
|
|
|
|
|
if (opt_time) |
|
|
{ |
|
|
if (! time_style) |
|
|
{ |
|
|
time_style = getenv ("TIME_STYLE"); |
|
|
|
|
|
|
|
|
if (! time_style || streq (time_style, "locale")) |
|
|
time_style = "long-iso"; |
|
|
else if (*time_style == '+') |
|
|
{ |
|
|
|
|
|
|
|
|
char *p = strchr (time_style, '\n'); |
|
|
if (p) |
|
|
*p = '\0'; |
|
|
} |
|
|
else |
|
|
{ |
|
|
|
|
|
static char const posix_prefix[] = "posix-"; |
|
|
static const size_t prefix_len = sizeof posix_prefix - 1; |
|
|
while (STREQ_LEN (time_style, posix_prefix, prefix_len)) |
|
|
time_style += prefix_len; |
|
|
} |
|
|
} |
|
|
|
|
|
if (*time_style == '+') |
|
|
time_format = time_style + 1; |
|
|
else |
|
|
{ |
|
|
switch (x_timestyle_match (time_style, false, |
|
|
time_style_args, |
|
|
(char const *) time_style_types, |
|
|
sizeof (*time_style_types), EXIT_FAILURE)) |
|
|
{ |
|
|
case full_iso_time_style: |
|
|
time_format = "%Y-%m-%d %H:%M:%S.%N %z"; |
|
|
break; |
|
|
|
|
|
case long_iso_time_style: |
|
|
time_format = "%Y-%m-%d %H:%M"; |
|
|
break; |
|
|
|
|
|
case iso_time_style: |
|
|
time_format = "%Y-%m-%d"; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
struct argv_iterator *ai; |
|
|
if (files_from) |
|
|
{ |
|
|
|
|
|
|
|
|
if (optind < argc) |
|
|
{ |
|
|
error (0, 0, _("extra operand %s"), quote (argv[optind])); |
|
|
fprintf (stderr, "%s\n", |
|
|
_("file operands cannot be combined with --files0-from")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (! (streq (files_from, "-") || freopen (files_from, "r", stdin))) |
|
|
error (EXIT_FAILURE, errno, _("cannot open %s for reading"), |
|
|
quoteaf (files_from)); |
|
|
|
|
|
ai = argv_iter_init_stream (stdin); |
|
|
|
|
|
|
|
|
|
|
|
hash_all = true; |
|
|
} |
|
|
else |
|
|
{ |
|
|
char **files = (optind < argc ? argv + optind : cwd_only); |
|
|
ai = argv_iter_init_argv (files); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hash_all = (optind + 1 < argc || symlink_deref_bits == FTS_LOGICAL); |
|
|
} |
|
|
|
|
|
if (!ai) |
|
|
xalloc_die (); |
|
|
|
|
|
|
|
|
di_files = di_set_alloc (); |
|
|
if (!di_files) |
|
|
xalloc_die (); |
|
|
|
|
|
|
|
|
|
|
|
if (opt_count_all || ! hash_all) |
|
|
bit_flags |= FTS_TIGHT_CYCLE_CHECK; |
|
|
|
|
|
bit_flags |= symlink_deref_bits; |
|
|
static char *temp_argv[] = { nullptr, nullptr }; |
|
|
|
|
|
while (true) |
|
|
{ |
|
|
bool skip_file = false; |
|
|
enum argv_iter_err ai_err; |
|
|
char *file_name = argv_iter (ai, &ai_err); |
|
|
if (!file_name) |
|
|
{ |
|
|
switch (ai_err) |
|
|
{ |
|
|
case AI_ERR_EOF: |
|
|
goto argv_iter_done; |
|
|
case AI_ERR_READ: |
|
|
error (0, errno, _("%s: read error"), |
|
|
quotef (files_from)); |
|
|
ok = false; |
|
|
goto argv_iter_done; |
|
|
case AI_ERR_MEM: |
|
|
xalloc_die (); |
|
|
case AI_ERR_OK: default: |
|
|
affirm (!"unexpected error code from argv_iter"); |
|
|
} |
|
|
} |
|
|
if (files_from && streq (files_from, "-") && streq (file_name, "-")) |
|
|
{ |
|
|
|
|
|
|
|
|
error (0, 0, _("when reading file names from standard input, " |
|
|
"no file name of %s allowed"), |
|
|
quoteaf (file_name)); |
|
|
skip_file = true; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!file_name[0]) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (files_from == nullptr) |
|
|
error (0, 0, "%s", _("invalid zero-length file name")); |
|
|
else |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
idx_t file_number = argv_iter_n_args (ai); |
|
|
error (0, 0, "%s:%td: %s", quotef (files_from), |
|
|
file_number, _("invalid zero-length file name")); |
|
|
} |
|
|
skip_file = true; |
|
|
} |
|
|
|
|
|
if (skip_file) |
|
|
ok = false; |
|
|
else |
|
|
{ |
|
|
temp_argv[0] = file_name; |
|
|
ok &= du_files (temp_argv, bit_flags); |
|
|
} |
|
|
} |
|
|
argv_iter_done: |
|
|
|
|
|
argv_iter_free (ai); |
|
|
di_set_free (di_files); |
|
|
if (di_mnt) |
|
|
di_set_free (di_mnt); |
|
|
|
|
|
if (files_from && (ferror (stdin) || fclose (stdin) != 0) && ok) |
|
|
error (EXIT_FAILURE, 0, _("error reading %s"), quoteaf (files_from)); |
|
|
|
|
|
if (print_grand_total) |
|
|
print_size (&tot_dui, _("total")); |
|
|
|
|
|
return ok ? EXIT_SUCCESS : EXIT_FAILURE; |
|
|
} |
|
|
|