summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS3
-rw-r--r--TODO2
-rw-r--r--doc/coreutils.texi7
-rw-r--r--src/df.c81
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/check.mk1
-rwxr-xr-xtests/df/total42
-rwxr-xr-xtests/df/total-awk78
8 files changed, 208 insertions, 8 deletions
diff --git a/NEWS b/NEWS
index 4979dd54a..a774d1ad9 100644
--- a/NEWS
+++ b/NEWS
@@ -21,6 +21,9 @@ GNU coreutils NEWS -*- outline -*-
With this new option, after a short read, dd repeatedly calls read,
until it fills the incomplete block, reaches EOF, or encounters an error.
+ df accepts a new option --total, which produces a grand total of all
+ arguments after all arguments have been processed.
+
If the GNU MP library is available at configure time, factor and
expr support arbitrarily large numbers. Pollard's rho algorithm is
used to factor large numbers.
diff --git a/TODO b/TODO
index 0321768af..da5e0ac33 100644
--- a/TODO
+++ b/TODO
@@ -72,8 +72,6 @@ Should printf '\0123' print "\n3"?
printf: consider adapting builtins/printf.def from bash
-df: add `--total' option, suggested here http://bugs.debian.org/186007
-
tail: don't use xlseek; it *exits*.
Instead, maybe use a macro and return nonzero.
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 3a04176c7..be59ae9aa 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -9692,6 +9692,13 @@ pseudo-file-systems, such as automounter entries.
Scale sizes by @var{size} before printing them (@pxref{Block size}).
For example, @option{-BG} prints sizes in units of 1,073,741,824 bytes.
+@itemx --total
+@opindex --total
+@cindex grand total of disk size, usage and available space
+Print a grand total of all arguments after all arguments have
+been processed. This can be used to find out the total disk size, usage
+and available space of all listed devices.
+
@optHumanReadable
@item -H
diff --git a/src/df.c b/src/df.c
index 0769a1eb5..0bb3b1e5c 100644
--- a/src/df.c
+++ b/src/df.c
@@ -108,6 +108,12 @@ static struct mount_entry *mount_list;
/* If true, print file system type as well. */
static bool print_type;
+/* If true, print a grand total at the end. */
+static bool print_grand_total;
+
+/* Grand total data. */
+static struct fs_usage grand_fsu;
+
/* For long options that have no equivalent short option, use a
non-character as a pseudo short option, starting with CHAR_MAX + 1. */
enum
@@ -129,6 +135,7 @@ static struct option const long_options[] =
{"print-type", no_argument, NULL, 'T'},
{"sync", no_argument, NULL, SYNC_OPTION},
{"no-sync", no_argument, NULL, NO_SYNC_OPTION},
+ {"total", no_argument, NULL, 'c'},
{"type", required_argument, NULL, 't'},
{"exclude-type", required_argument, NULL, 'x'},
{GETOPT_HELP_OPTION_DECL},
@@ -247,6 +254,41 @@ df_readable (bool negative, uintmax_t n, char *buf,
}
}
+/* Logical equivalence */
+#define LOG_EQ(a, b) (!(a) == !(b))
+
+/* Add integral value while using uintmax_t for value part and separate
+ negation flag. It adds value of SRC and SRC_NEG to DEST and DEST_NEG.
+ The result will be in DEST and DEST_NEG. See df_readable to understand
+ how the negation flag is used. */
+static void
+add_uint_with_neg_flag (uintmax_t *dest, bool *dest_neg,
+ uintmax_t src, bool src_neg)
+{
+ if (LOG_EQ (*dest_neg, src_neg))
+ {
+ *dest += src;
+ return;
+ }
+
+ if (*dest_neg)
+ *dest = -*dest;
+
+ if (src_neg)
+ src = -src;
+
+ if (src < *dest)
+ *dest -= src;
+ else
+ {
+ *dest = src - *dest;
+ *dest_neg = src_neg;
+ }
+
+ if (*dest_neg)
+ *dest = -*dest;
+}
+
/* Display a space listing for the disk device with absolute file name DISK.
If MOUNT_POINT is non-NULL, it is the name of the root of the
file system on DISK.
@@ -263,7 +305,8 @@ df_readable (bool negative, uintmax_t n, char *buf,
static void
show_dev (char const *disk, char const *mount_point,
char const *stat_file, char const *fstype,
- bool me_dummy, bool me_remote)
+ bool me_dummy, bool me_remote,
+ const struct fs_usage *force_fsu)
{
struct fs_usage fsu;
char buf[3][LONGEST_HUMAN_READABLE + 2];
@@ -296,7 +339,9 @@ show_dev (char const *disk, char const *mount_point,
if (!stat_file)
stat_file = mount_point ? mount_point : disk;
- if (get_fs_usage (stat_file, disk, &fsu))
+ if (force_fsu)
+ fsu = *force_fsu;
+ else if (get_fs_usage (stat_file, disk, &fsu))
{
error (0, errno, "%s", quote (stat_file));
exit_status = EXIT_FAILURE;
@@ -347,6 +392,9 @@ show_dev (char const *disk, char const *mount_point,
available = fsu.fsu_ffree;
negate_available = false;
available_to_root = available;
+
+ grand_fsu.fsu_files += total;
+ grand_fsu.fsu_ffree += available;
}
else
{
@@ -373,6 +421,12 @@ show_dev (char const *disk, char const *mount_point,
negate_available = (fsu.fsu_bavail_top_bit_set
& (available != UINTMAX_MAX));
available_to_root = fsu.fsu_bfree;
+
+ grand_fsu.fsu_blocks += input_units * total;
+ grand_fsu.fsu_bfree += input_units * available_to_root;
+ add_uint_with_neg_flag (&grand_fsu.fsu_bavail,
+ &grand_fsu.fsu_bavail_top_bit_set,
+ input_units * available, negate_available);
}
used = UINTMAX_MAX;
@@ -550,7 +604,7 @@ show_disk (char const *disk)
{
show_dev (best_match->me_devname, best_match->me_mountdir, NULL,
best_match->me_type, best_match->me_dummy,
- best_match->me_remote);
+ best_match->me_remote, NULL);
return true;
}
@@ -654,7 +708,8 @@ show_point (const char *point, const struct stat *statp)
if (best_match)
show_dev (best_match->me_devname, best_match->me_mountdir, point,
- best_match->me_type, best_match->me_dummy, best_match->me_remote);
+ best_match->me_type, best_match->me_dummy, best_match->me_remote,
+ NULL);
else
{
/* We couldn't find the mount entry corresponding to POINT. Go ahead and
@@ -665,7 +720,7 @@ show_point (const char *point, const struct stat *statp)
char *mp = find_mount_point (point, statp);
if (mp)
{
- show_dev (NULL, mp, NULL, NULL, false, false);
+ show_dev (NULL, mp, NULL, NULL, false, false, NULL);
free (mp);
}
}
@@ -694,7 +749,7 @@ show_all_entries (void)
for (me = mount_list; me; me = me->me_next)
show_dev (me->me_devname, me->me_mountdir, NULL, me->me_type,
- me->me_dummy, me->me_remote);
+ me->me_dummy, me->me_remote, NULL);
}
/* Add FSTYPE to the list of file system types to display. */
@@ -743,6 +798,7 @@ Mandatory arguments to long options are mandatory for short options too.\n\
fputs (_("\
-a, --all include dummy file systems\n\
-B, --block-size=SIZE use SIZE-byte blocks\n\
+ --total produce a grand total\n\
-h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)\n\
-H, --si likewise, but use powers of 1000 not 1024\n\
"), stdout);
@@ -794,6 +850,8 @@ main (int argc, char **argv)
file_systems_processed = false;
posix_format = false;
exit_status = EXIT_SUCCESS;
+ print_grand_total = false;
+ grand_fsu.fsu_blocksize = 1;
for (;;)
{
@@ -864,6 +922,10 @@ main (int argc, char **argv)
add_excluded_fs_type (optarg);
break;
+ case 'c':
+ print_grand_total = true;
+ break;
+
case_GETOPT_HELP_CHAR;
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
@@ -959,6 +1021,13 @@ main (int argc, char **argv)
else
show_all_entries ();
+ if (print_grand_total)
+ {
+ if (inode_format)
+ grand_fsu.fsu_blocks = 1;
+ show_dev ("total", NULL, NULL, NULL, false, false, &grand_fsu);
+ }
+
if (! file_systems_processed)
error (EXIT_FAILURE, 0, _("no file systems processed"));
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 5a57ca993..59d1e489c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -284,6 +284,8 @@ TESTS = \
dd/skip-seek \
dd/skip-seek2 \
dd/unblock-sync \
+ df/total \
+ df/total-awk \
du/2g \
du/8gb \
du/basic \
diff --git a/tests/check.mk b/tests/check.mk
index 4fca28352..03b89dc3f 100644
--- a/tests/check.mk
+++ b/tests/check.mk
@@ -79,6 +79,7 @@ TESTS_ENVIRONMENT = \
top_srcdir='$(top_srcdir)' \
CONFIG_HEADER='$(abs_top_builddir)/lib/config.h' \
CU_TEST_NAME=`basename '$(abs_srcdir)'`,$$tst \
+ AWK='$(AWK)' \
EGREP='$(EGREP)' \
EXEEXT='$(EXEEXT)' \
MAKE=$(MAKE) \
diff --git a/tests/df/total b/tests/df/total
new file mode 100755
index 000000000..186bf8de3
--- /dev/null
+++ b/tests/df/total
@@ -0,0 +1,42 @@
+#!/bin/sh
+# Ensure "df --total" produces /^total.../ line
+
+# Copyright (C) 2008 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 <http://www.gnu.org/licenses/>.
+
+if test "$VERBOSE" = yes; then
+ set -x
+ ls --version
+fi
+
+. $srcdir/test-lib.sh
+
+fail=0
+
+# Don't let a different umask perturb the results.
+umask 22
+
+RE_TOTAL='^total( +(-?[0-9]+|-)){3} +-?[0-9]+%$'
+
+df > tmp || fail=1
+$EGREP "$RE_TOTAL" tmp && fail=1
+
+df -i > tmp || fail=1
+$EGREP "$RE_TOTAL" tmp && fail=1
+
+df --total | $EGREP "$RE_TOTAL" || fail=1
+df -i --total | $EGREP "$RE_TOTAL" || fail=1
+
+(exit $fail); exit $fail
diff --git a/tests/df/total-awk b/tests/df/total-awk
new file mode 100755
index 000000000..632e945a7
--- /dev/null
+++ b/tests/df/total-awk
@@ -0,0 +1,78 @@
+#!/bin/sh
+# Ensure "df --total" computes well summary statistics
+
+# Copyright (C) 2008 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 <http://www.gnu.org/licenses/>.
+
+if test "$VERBOSE" = yes; then
+ set -x
+ ls --version
+fi
+
+. $srcdir/test-lib.sh
+
+fail=0
+
+# Don't let a different umask perturb the results.
+umask 22
+
+echo '
+BEGIN {
+ total = 0
+ used = 0
+ available = 0
+}
+{
+ if (NR==1 || $0==$1 || $0~/^total +(-?[0-9]+|-) +(-?[0-9]+|-) +(-?[0-9]+|-) +-?[0-9]+%$/)
+ next
+ if ($1~/^[0-9]/)
+ {
+ total += $1
+ used += $2
+ available += $3
+ }
+ else
+ {
+ total += $2
+ used += $3
+ available += $4
+ }
+}
+END {
+ print total
+ print used
+ print available
+}
+' > compute_sum.awk || fail=1
+
+echo '
+/^total +(-?[0-9]+|-) +(-?[0-9]+|-) +(-?[0-9]+|-) +-?[0-9]+%$/ {
+ print $2;
+ print $3;
+ print $4
+}
+' > parse_total.awk || fail=1
+
+df --total > tmp || fail=1
+$AWK -f compute_sum.awk tmp > out1 || fail=1
+$AWK -f parse_total.awk tmp > out2 || fail=1
+compare out1 out2 || fail=1
+
+df -i --total > tmp || fail=1
+$AWK -f compute_sum.awk tmp > out1 || fail=1
+$AWK -f parse_total.awk tmp > out2 || fail=1
+compare out1 out2 || fail=1
+
+(exit $fail); exit $fail