From 333dc83d52e014a0b532e316ea8cd93b048f1ac6 Mon Sep 17 00:00:00 2001 From: Bernhard Voelker Date: Sat, 27 Jul 2013 14:25:28 +0200 Subject: [PATCH] du: add --inodes option This new option can be used to find directories with a huge amount of files. The GNU find utility has the printf format "%h" which prints the number of entries in a directory, but this is non-cumulative and doesn't handle hard links. * src/du.c (struct duinfo): Add new member for counting inodes. (duinfo_init): Initialize inodes member with Zero. (duinfo_set): Set inodes counter to 1. (duinfo_add): Sum up the 2 given inodes counters. (opt_inodes): Add new boolean flag to remember if the --inodes option has been specified. (INODES_OPTION): Add new enum value to be used ... (long_options): ... here. (usage): Add description of the new option. (print_size): Pass inodes counter or size to print_only_size, depending on the inodes mode. (process_file): Adapt threshold handling: with --inodes, print or elide the entries according to the struct member inodes. (main): Add a case for accepting the new INODES_OPTION. Print a warning diagnostic when --inodes is used together with the option --apparent-size or -b. Reset the output_block_size to 1 ... and thus ignoring the options -m and -k. * tests/du/inodes.sh: Add a new test. * tests/local.mk (all_tests): Mention it. * doc/coreutils.texi (du invocation): Document the new option. * NEWS: Mention the new option. --- NEWS | 3 ++ doc/coreutils.texi | 27 ++++++++++- src/du.c | 38 +++++++++++++-- tests/du/inodes.sh | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/local.mk | 1 + 5 files changed, 197 insertions(+), 5 deletions(-) create mode 100755 tests/du/inodes.sh diff --git a/NEWS b/NEWS index 3d0fb18b1..4a78617c7 100644 --- a/NEWS +++ b/NEWS @@ -37,6 +37,9 @@ GNU coreutils NEWS -*- outline -*- ** New features + du accepts a new option: --inodes to show the number of inodes instead + of the blocks used. + id and ls with -Z report the SMACK security context where available. mkdir, mkfifo and mknod with -Z set the SMACK context where available. diff --git a/doc/coreutils.texi b/doc/coreutils.texi index b8d40b4f5..40bff7ae9 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -11424,6 +11424,18 @@ Equivalent to @option{--dereference-args} (@option{-D}). @optHumanReadable +@itemx --inodes +@opindex --inodes +@cindex inode usage, dereferencing in @command{du} +List inode usage information instead of block usage. +This option is useful for finding directories which contain many files, and +therefore eat up most of the inodes space of a file system (see @command{df}, +option @option{--inodes}). +It can well be combined with the options @option{-a}, @option{-c}, +@option{-h}, @option{-l}, @option{-s}, @option{-S}, @option{-t} and +@option{-x}; however, passing other options regarding the block size, for +example @option{-b}, @option{-m} and @option{--apparent-size}, is ignored. + @item -k @opindex -k @cindex kibibytes for file sizes @@ -11485,7 +11497,9 @@ Display only a total for each argument. @itemx --threshold=@var{size} @opindex -t @opindex --threshold -Exclude entries based on a given @var{size} (@pxref{Block size}). +Exclude entries based on a given @var{size}. The @var{size} refers to used +blocks in normal mode (@pxref{Block size}), or inodes count in conjunction +with the @option{--inodes} option. If @var{size} is positive, then @command{du} will only print entries with a size greater than or equal to that. @@ -11501,6 +11515,10 @@ Please note that the @option{--threshold} option can be combined with the @option{--apparent-size} option, and in this case would elide entries based on its apparent size. +Please note that the @option{--threshold} option can be combined with the +@option{--inodes} option, and in this case would elide entries based on +its inodes count. + Here's how you would use @option{--threshold} to find directories with a size greater than or equal to 200 megabytes: @@ -11515,6 +11533,13 @@ note the @option{-a} - with an apparent size smaller than or equal to 500 bytes: du -a -t -500 --apparent-size @end example +Here's how you would use @option{--threshold} to find directories on the root +file system with more than 20000 inodes used in the directory tree below: + +@example +du --inodes -x --threshold=20000 / +@end example + @item --time @opindex --time diff --git a/src/du.c b/src/du.c index a6fa16b29..9f1f98c5c 100644 --- a/src/du.c +++ b/src/du.c @@ -78,6 +78,9 @@ struct duinfo /* Size of files in directory. */ uintmax_t size; + /* Number of inodes in directory. */ + uintmax_t inodes; + /* Latest time stamp found. If tmax.tv_sec == TYPE_MINIMUM (time_t) && tmax.tv_nsec < 0, no time stamp has been found. */ struct timespec tmax; @@ -88,6 +91,7 @@ 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; } @@ -97,6 +101,7 @@ static inline void duinfo_set (struct duinfo *a, uintmax_t size, struct timespec tmax) { a->size = size; + a->inodes = 1; a->tmax = tmax; } @@ -106,6 +111,7 @@ 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; } @@ -154,6 +160,9 @@ static intmax_t opt_threshold = 0; /* Human-readable options for output. */ static int human_output_opts; +/* Output inodes count instead of blocks used. */ +static bool opt_inodes = false; + /* If true, print most recently modified date, using the specified format. */ static bool opt_time = false; @@ -197,7 +206,8 @@ enum HUMAN_SI_OPTION, FTS_DEBUG, TIME_OPTION, - TIME_STYLE_OPTION + TIME_STYLE_OPTION, + INODES_OPTION }; static struct option const long_options[] = @@ -214,6 +224,7 @@ static struct option const long_options[] = {"exclude-from", required_argument, NULL, 'X'}, {"files0-from", required_argument, NULL, FILES0_FROM_OPTION}, {"human-readable", no_argument, NULL, 'h'}, + {"inodes", no_argument, NULL, INODES_OPTION}, {"si", no_argument, NULL, HUMAN_SI_OPTION}, {"max-depth", required_argument, NULL, 'd'}, {"null", no_argument, NULL, '0'}, @@ -306,6 +317,7 @@ Summarize disk usage of each FILE, recursively for directories.\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\ @@ -394,7 +406,10 @@ print_only_size (uintmax_t n_bytes) static void print_size (const struct duinfo *pdui, const char *string) { - print_only_size (pdui->size); + print_only_size (opt_inodes + ? pdui->inodes + : pdui->size); + if (opt_time) { putchar ('\t'); @@ -589,9 +604,10 @@ process_file (FTS *fts, FTSENT *ent) || level == 0) { /* Print or elide this entry according to the --threshold option. */ + uintmax_t v = opt_inodes ? dui_to_print.inodes : dui_to_print.size; if (opt_threshold < 0 - ? dui_to_print.size <= -opt_threshold - : dui_to_print.size >= opt_threshold) + ? v <= -opt_threshold + : v >= opt_threshold) print_size (&dui_to_print, file); } @@ -853,6 +869,10 @@ main (int argc, char **argv) add_exclude (exclude, optarg, EXCLUDE_WILDCARDS); break; + case INODES_OPTION: + opt_inodes = true; + break; + case TIME_OPTION: opt_time = true; time_type = @@ -899,6 +919,16 @@ main (int argc, char **argv) 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; + } + /* Process time style if printing last times. */ if (opt_time) { diff --git a/tests/du/inodes.sh b/tests/du/inodes.sh new file mode 100755 index 000000000..2069e2b2b --- /dev/null +++ b/tests/du/inodes.sh @@ -0,0 +1,133 @@ +#!/bin/sh +# exercise du's --inodes option + +# Copyright (C) 2010-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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ du + +# An empty directory uses only 1 inode. +mkdir d || framework_failure_ +printf '1\td\n' > exp || framework_failure_ + +du --inodes d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Add a regular file: 2 inodes used. +touch d/f || framework_failure_ +printf '2\td\n' > exp || framework_failure_ + +du --inodes d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Add a hardlink to the file: still only 2 inodes used. +ln -v d/f d/h || framework_failure_ +du --inodes d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Now count also hardlinks (-l,--count-links): 3 inodes. +printf '3\td\n' > exp || framework_failure_ +du --inodes -l d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Create a directory and summarize: 3 inodes. +mkdir d/d || framework_failure_ +du --inodes -s d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Count inodes separated: 1-2. +printf '1\td/d\n2\td\n' > exp || framework_failure_ +du --inodes -S d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Count inodes cumulative (default): 1-3. +printf '1\td/d\n3\td\n' > exp || framework_failure_ +du --inodes d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Count all items: 1-1-3. +printf '1\td/d\n1\td/h\n3\td\n' > exp || framework_failure_ +du --inodes -a d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Count all items and hardlinks again: 1-1-1-4 +printf '1\td/d\n1\td/h\n1\td/f\n4\td\n' > exp || framework_failure_ +du --inodes -al d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Run with total (-c) line: 1-3-3 +printf '1\td/d\n3\td\n3\ttotal\n' > exp || framework_failure_ +du --inodes -c d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Create another file in the subdirectory: 2-4 +touch d/d/f || framework_failure_ +printf '2\td/d\n4\td\n' > exp || framework_failure_ +du --inodes d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Ensure human output (-h, --si) works. +rm -rf d || framework_failure_ +mkdir d || framework_failure_ +seq --format="d/file%g" 1023 | xargs touch || framework_failure_ +printf '1.0K\td\n' > exp || framework_failure_ +du --inodes -h d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +printf '1.1k\td\n' > exp || framework_failure_ +du --inodes --si d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Verify --inodes ignores -B. +printf '1024\td\n' > exp || framework_failure_ +du --inodes -B10 d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Verify --inodes works with --threshold. +printf '1024\td\n' > exp || framework_failure_ +du --inodes --threshold=1000 d > out 2>err || fail=1 +compare exp out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +du --inodes --threshold=-1000 d > out 2>err || fail=1 +compare /dev/null out || { cat out; fail=1; } +compare /dev/null err || fail=1 + +# Verify --inodes raises a warning for --apparent-size and -b. +du --inodes -b d > out 2>err || fail=1 +grep ' ineffective ' err >/dev/null || { fail=1; cat out err; } + +du --inodes --apparent-size d > out 2>err || fail=1 +grep ' ineffective ' err >/dev/null || { fail=1; cat out err; } + +# Ensure that --inodes is mentioned in the usage. +du --help > out || fail=1 +grep ' --inodes ' out >/dev/null || { fail=1; cat out; } +Exit $fail diff --git a/tests/local.mk b/tests/local.mk index 58b7958c2..b00ff5958 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -494,6 +494,7 @@ all_tests = \ tests/du/inacc-dest.sh \ tests/du/inacc-dir.sh \ tests/du/inaccessible-cwd.sh \ + tests/du/inodes.sh \ tests/du/long-from-unreadable.sh \ tests/du/long-sloop.sh \ tests/du/max-depth.sh \ -- 2.11.4.GIT