diff options
Diffstat (limited to 'src/du.c')
-rw-r--r-- | src/du.c | 138 |
1 files changed, 108 insertions, 30 deletions
@@ -1,5 +1,5 @@ /* du -- summarize disk usage - Copyright (C) 1988-2012 Free Software Foundation, Inc. + Copyright (C) 1988-2013 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,6 +35,7 @@ #include "exclude.h" #include "fprintftime.h" #include "human.h" +#include "mountlist.h" #include "quote.h" #include "quotearg.h" #include "stat-size.h" @@ -60,8 +61,12 @@ extern bool fts_debug; # define FTS_CROSS_CHECK(Fts) #endif -/* A set of dev/ino pairs. */ -static struct di_set *di_set; +/* A set of dev/ino pairs to help identify files and directories + whose sizes have already been counted. */ +static struct di_set *di_files; + +/* A set containing a dev/ino pair for each local mount point directory. */ +static struct di_set *di_mnt; /* Keep track of the preceding "level" (depth in hierarchy) from one call of process_file to the next. */ @@ -142,6 +147,10 @@ static bool opt_separate_dirs = false; is at level 0, so 'du --max-depth=0' is equivalent to 'du -s'. */ static size_t max_depth = SIZE_MAX; +/* Only output entries with at least this SIZE if positive, + or at most if negative. See --threshold option. */ +static intmax_t opt_threshold = 0; + /* Human-readable options for output. */ static int human_output_opts; @@ -213,6 +222,7 @@ static struct option const long_options[] = {"separate-dirs", no_argument, NULL, 'S'}, {"summarize", no_argument, NULL, 's'}, {"total", no_argument, NULL, 'c'}, + {"threshold", required_argument, NULL, 't'}, {"time", optional_argument, NULL, TIME_OPTION}, {"time-style", required_argument, NULL, TIME_STYLE_OPTION}, {GETOPT_HELP_OPTION_DECL}, @@ -263,12 +273,12 @@ Usage: %s [OPTION]... [FILE]...\n\ "), program_name, program_name); fputs (_("\ Summarize disk usage of each FILE, recursively for directories.\n\ -\n\ -"), stdout); - fputs (_("\ -Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); + + emit_mandatory_arg_note (); + fputs (_("\ + -0, --null end each output line with 0 byte rather than newline\n\ -a, --all write counts for all files, not just directories\n\ --apparent-size print apparent sizes, rather than disk usage; although\ \n\ @@ -284,6 +294,10 @@ Mandatory arguments to long options are mandatory for short options too.\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 disk usage of the NUL-terminated file\n\ @@ -292,30 +306,22 @@ Mandatory arguments to long options are mandatory for short options too.\n\ -H equivalent to --dereference-args (-D)\n\ -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)\ \n\ - --si like -h, but use powers of 1000 not 1024\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 (_("\ - -L, --dereference dereference all symbolic links\n\ -P, --no-dereference don't follow any symbolic links (this is the default)\n\ - -0, --null end each output line with 0 byte rather than newline\n\ -S, --separate-dirs 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 (_("\ - -x, --one-file-system skip directories on different file systems\n\ - -X, --exclude-from=FILE exclude files that match any pattern in FILE\n\ - --exclude=PATTERN exclude files that match PATTERN\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 (_("\ + -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\ @@ -324,6 +330,11 @@ Mandatory arguments to long options are mandatory for short options too.\n\ full-iso, long-iso, iso, +FORMAT\n\ FORMAT is interpreted like '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"); @@ -333,11 +344,11 @@ Mandatory arguments to long options are mandatory for short options too.\n\ exit (status); } -/* Try to insert the INO/DEV pair into the global table, HTAB. +/* Try to insert the INO/DEV pair into DI_SET. Return true if the pair is successfully inserted, - false if the pair is already in the table. */ + false if the pair was already there. */ static bool -hash_ins (ino_t ino, dev_t dev) +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) @@ -461,7 +472,7 @@ process_file (FTS *fts, FTSENT *ent) if (excluded || (! opt_count_all && (hash_all || (! S_ISDIR (sb->st_mode) && 1 < sb->st_nlink)) - && ! hash_ins (sb->st_ino, sb->st_dev))) + && ! hash_ins (di_files, sb->st_ino, sb->st_dev))) { /* If ignoring a directory in preorder, skip its children. Ignore the next fts_read output too, as it's a postorder @@ -490,7 +501,13 @@ process_file (FTS *fts, FTSENT *ent) case FTS_DC: if (cycle_warning_required (fts, ent)) { - emit_cycle_warning (file); + /* If this is a mount point, then diagnose it and avoid + the cycle. */ + if (di_set_lookup (di_mnt, sb->st_dev, sb->st_ino)) + error (0, 0, _("mount point %s already traversed"), + quote (file)); + else + emit_cycle_warning (file); return false; } return true; @@ -568,8 +585,15 @@ process_file (FTS *fts, FTSENT *ent) duinfo_add (&tot_dui, &dui); if ((IS_DIR_TYPE (info) && level <= max_depth) - || ((opt_all && level <= max_depth) || level == 0)) - print_size (&dui_to_print, file); + || (opt_all && level <= max_depth) + || level == 0) + { + /* Print or elide this entry according to the --threshold option. */ + if (opt_threshold < 0 + ? dui_to_print.size <= -opt_threshold + : dui_to_print.size >= opt_threshold) + print_size (&dui_to_print, file); + } return ok; } @@ -623,6 +647,38 @@ du_files (char **files, int bit_flags) return ok; } +/* Fill the di_mnt set with local mount point dev/ino pairs. */ + +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 + { + /* Ignore stat failure. False positives are too common. + E.g., "Permission denied" on /run/user/<name>/gvfs. */ + } + } + + mnt_free = mnt_ent; + mnt_ent = mnt_ent->me_next; + + free (mnt_free->me_devname); + free (mnt_free->me_mountdir); + if (mnt_free->me_type_malloced) + free (mnt_free->me_type); + free (mnt_free); + } +} + int main (int argc, char **argv) { @@ -660,7 +716,7 @@ main (int argc, char **argv) while (true) { int oi = -1; - int c = getopt_long (argc, argv, "0abd:chHklmsxB:DLPSX:", + int c = getopt_long (argc, argv, "0abd:chHklmst:xB:DLPSX:", long_options, &oi); if (c == -1) break; @@ -741,6 +797,20 @@ main (int argc, char **argv) opt_summarize_only = true; break; + case 't': + { + enum strtol_error e; + e = xstrtoimax (optarg, NULL, 0, &opt_threshold, "kKmMGTPEZY0"); + if (e != LONGINT_OK) + xstrtol_fatal (e, oi, c, long_options, optarg); + if (opt_threshold == 0 && *optarg == '-') + { + /* Do not allow -0, as this wouldn't make sense anyway. */ + error (EXIT_FAILURE, 0, _("invalid --threshold argument '-0'")); + } + } + break; + case 'x': bit_flags |= FTS_XDEV; break; @@ -922,8 +992,15 @@ main (int argc, char **argv) xalloc_die (); /* Initialize the set of dev,inode pairs. */ - di_set = di_set_alloc (); - if (!di_set) + + di_mnt = di_set_alloc (); + if (!di_mnt) + xalloc_die (); + + fill_mount_table (); + + di_files = di_set_alloc (); + if (!di_files) xalloc_die (); /* If not hashing everything, process_file won't find cycles on its @@ -1001,7 +1078,8 @@ main (int argc, char **argv) argv_iter_done: argv_iter_free (ai); - di_set_free (di_set); + di_set_free (di_files); + di_set_free (di_mnt); if (files_from && (ferror (stdin) || fclose (stdin) != 0) && ok) error (EXIT_FAILURE, 0, _("error reading %s"), quote (files_from)); |