summaryrefslogtreecommitdiff
path: root/src/du.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/du.c')
-rw-r--r--src/du.c138
1 files changed, 108 insertions, 30 deletions
diff --git a/src/du.c b/src/du.c
index 73339412..a80a1770 100644
--- a/src/du.c
+++ b/src/du.c
@@ -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));