sort: better -Wmissing-variable-declarations
[coreutils.git] / src / du.c
blob81ec1bec8979c80d931a500a04674e7f259c8924
1 /* du -- summarize device usage
2 Copyright (C) 1988-2024 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Differences from the Unix du:
18 * Doesn't simply ignore the names of regular files given as arguments
19 when -a is given.
21 By tege@sics.se, Torbjörn Granlund,
22 and djm@ai.mit.edu, David MacKenzie.
23 Variable blocks added by lm@sgi.com and eggert@twinsun.com.
24 Rewritten to use nftw, then to use fts by Jim Meyering. */
26 #include <config.h>
27 #include <getopt.h>
28 #include <sys/types.h>
29 #include "system.h"
30 #include "argmatch.h"
31 #include "argv-iter.h"
32 #include "assure.h"
33 #include "di-set.h"
34 #include "exclude.h"
35 #include "human.h"
36 #include "mountlist.h"
37 #include "quote.h"
38 #include "show-date.h"
39 #include "stat-size.h"
40 #include "stat-time.h"
41 #include "stdio--.h"
42 #include "xfts.h"
43 #include "xstrtol.h"
44 #include "xstrtol-error.h"
46 extern bool fts_debug;
48 /* The official name of this program (e.g., no 'g' prefix). */
49 #define PROGRAM_NAME "du"
51 #define AUTHORS \
52 proper_name_lite ("Torbjorn Granlund", "Torbj\303\266rn Granlund"), \
53 proper_name ("David MacKenzie"), \
54 proper_name ("Paul Eggert"), \
55 proper_name ("Jim Meyering")
57 #if DU_DEBUG
58 # define FTS_CROSS_CHECK(Fts) fts_cross_check (Fts)
59 #else
60 # define FTS_CROSS_CHECK(Fts)
61 #endif
63 /* A set of dev/ino pairs to help identify files and directories
64 whose sizes have already been counted. */
65 static struct di_set *di_files;
67 /* A set containing a dev/ino pair for each local mount point directory. */
68 static struct di_set *di_mnt;
70 /* Keep track of the preceding "level" (depth in hierarchy)
71 from one call of process_file to the next. */
72 static size_t prev_level;
74 /* Define a class for collecting directory information. */
75 struct duinfo
77 /* Size of files in directory. */
78 uintmax_t size;
80 /* Number of inodes in directory. */
81 uintmax_t inodes;
83 /* Latest timestamp found. If tmax.tv_sec == TYPE_MINIMUM (time_t)
84 && tmax.tv_nsec < 0, no timestamp has been found. */
85 struct timespec tmax;
88 /* Initialize directory data. */
89 static inline void
90 duinfo_init (struct duinfo *a)
92 a->size = 0;
93 a->inodes = 0;
94 a->tmax.tv_sec = TYPE_MINIMUM (time_t);
95 a->tmax.tv_nsec = -1;
98 /* Set directory data. */
99 static inline void
100 duinfo_set (struct duinfo *a, uintmax_t size, struct timespec tmax)
102 a->size = size;
103 a->inodes = 1;
104 a->tmax = tmax;
107 /* Accumulate directory data. */
108 static inline void
109 duinfo_add (struct duinfo *a, struct duinfo const *b)
111 uintmax_t sum = a->size + b->size;
112 a->size = a->size <= sum ? sum : UINTMAX_MAX;
113 a->inodes = a->inodes + b->inodes;
114 if (timespec_cmp (a->tmax, b->tmax) < 0)
115 a->tmax = b->tmax;
118 /* A structure for per-directory level information. */
119 struct dulevel
121 /* Entries in this directory. */
122 struct duinfo ent;
124 /* Total for subdirectories. */
125 struct duinfo subdir;
128 /* If true, display counts for all files, not just directories. */
129 static bool opt_all = false;
131 /* If true, rather than using the device usage of each file,
132 use the apparent size (stat.st_size if usable, 0 otherwise). */
133 static bool apparent_size = false;
135 /* If true, count each hard link of files with multiple links. */
136 static bool opt_count_all = false;
138 /* If true, hash all files to look for hard links. */
139 static bool hash_all;
141 /* If true, output the NUL byte instead of a newline at the end of each line. */
142 static bool opt_nul_terminate_output = false;
144 /* If true, print a grand total at the end. */
145 static bool print_grand_total = false;
147 /* If nonzero, do not add sizes of subdirectories. */
148 static bool opt_separate_dirs = false;
150 /* Show the total for each directory (and file if --all) that is at
151 most MAX_DEPTH levels down from the root of the hierarchy. The root
152 is at level 0, so 'du --max-depth=0' is equivalent to 'du -s'. */
153 static idx_t max_depth = IDX_MAX;
155 /* Only output entries with at least this SIZE if positive,
156 or at most if negative. See --threshold option. */
157 static intmax_t opt_threshold = 0;
159 /* Human-readable options for output. */
160 static int human_output_opts;
162 /* Output inodes count instead of blocks used. */
163 static bool opt_inodes = false;
165 /* If true, print most recently modified date, using the specified format. */
166 static bool opt_time = false;
168 /* Type of time to display. controlled by --time. */
170 enum time_type
172 time_mtime, /* default */
173 time_ctime,
174 time_atime
177 static enum time_type time_type = time_mtime;
179 /* User specified date / time style */
180 static char const *time_style = nullptr;
182 /* Format used to display date / time. Controlled by --time-style */
183 static char const *time_format = nullptr;
185 /* The local time zone rules, as per the TZ environment variable. */
186 static timezone_t localtz;
188 /* The units to use when printing sizes. */
189 static uintmax_t output_block_size;
191 /* File name patterns to exclude. */
192 static struct exclude *exclude;
194 /* Grand total size of all args, in bytes. Also latest modified date. */
195 static struct duinfo tot_dui;
197 #define IS_DIR_TYPE(Type) \
198 ((Type) == FTS_DP \
199 || (Type) == FTS_DNR)
201 /* For long options that have no equivalent short option, use a
202 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
203 enum
205 APPARENT_SIZE_OPTION = CHAR_MAX + 1,
206 EXCLUDE_OPTION,
207 FILES0_FROM_OPTION,
208 HUMAN_SI_OPTION,
209 FTS_DEBUG,
210 TIME_OPTION,
211 TIME_STYLE_OPTION,
212 INODES_OPTION
215 static struct option const long_options[] =
217 {"all", no_argument, nullptr, 'a'},
218 {"apparent-size", no_argument, nullptr, APPARENT_SIZE_OPTION},
219 {"block-size", required_argument, nullptr, 'B'},
220 {"bytes", no_argument, nullptr, 'b'},
221 {"count-links", no_argument, nullptr, 'l'},
222 /* {"-debug", no_argument, nullptr, FTS_DEBUG}, */
223 {"dereference", no_argument, nullptr, 'L'},
224 {"dereference-args", no_argument, nullptr, 'D'},
225 {"exclude", required_argument, nullptr, EXCLUDE_OPTION},
226 {"exclude-from", required_argument, nullptr, 'X'},
227 {"files0-from", required_argument, nullptr, FILES0_FROM_OPTION},
228 {"human-readable", no_argument, nullptr, 'h'},
229 {"inodes", no_argument, nullptr, INODES_OPTION},
230 {"si", no_argument, nullptr, HUMAN_SI_OPTION},
231 {"max-depth", required_argument, nullptr, 'd'},
232 {"null", no_argument, nullptr, '0'},
233 {"no-dereference", no_argument, nullptr, 'P'},
234 {"one-file-system", no_argument, nullptr, 'x'},
235 {"separate-dirs", no_argument, nullptr, 'S'},
236 {"summarize", no_argument, nullptr, 's'},
237 {"total", no_argument, nullptr, 'c'},
238 {"threshold", required_argument, nullptr, 't'},
239 {"time", optional_argument, nullptr, TIME_OPTION},
240 {"time-style", required_argument, nullptr, TIME_STYLE_OPTION},
241 {GETOPT_HELP_OPTION_DECL},
242 {GETOPT_VERSION_OPTION_DECL},
243 {nullptr, 0, nullptr, 0}
246 static char const *const time_args[] =
248 "atime", "access", "use", "ctime", "status", nullptr
250 static enum time_type const time_types[] =
252 time_atime, time_atime, time_atime, time_ctime, time_ctime
254 ARGMATCH_VERIFY (time_args, time_types);
256 /* 'full-iso' uses full ISO-style dates and times. 'long-iso' uses longer
257 ISO-style timestamps, though shorter than 'full-iso'. 'iso' uses shorter
258 ISO-style timestamps. */
259 enum time_style
261 full_iso_time_style, /* --time-style=full-iso */
262 long_iso_time_style, /* --time-style=long-iso */
263 iso_time_style /* --time-style=iso */
266 static char const *const time_style_args[] =
268 "full-iso", "long-iso", "iso", nullptr
270 static enum time_style const time_style_types[] =
272 full_iso_time_style, long_iso_time_style, iso_time_style
274 ARGMATCH_VERIFY (time_style_args, time_style_types);
276 void
277 usage (int status)
279 if (status != EXIT_SUCCESS)
280 emit_try_help ();
281 else
283 printf (_("\
284 Usage: %s [OPTION]... [FILE]...\n\
285 or: %s [OPTION]... --files0-from=F\n\
286 "), program_name, program_name);
287 fputs (_("\
288 Summarize device usage of the set of FILEs, recursively for directories.\n\
289 "), stdout);
291 emit_mandatory_arg_note ();
293 fputs (_("\
294 -0, --null end each output line with NUL, not newline\n\
295 -a, --all write counts for all files, not just directories\n\
296 --apparent-size print apparent sizes rather than device usage; although\
298 the apparent size is usually smaller, it may be\n\
299 larger due to holes in ('sparse') files, internal\n\
300 fragmentation, indirect blocks, and the like\n\
301 "), stdout);
302 fputs (_("\
303 -B, --block-size=SIZE scale sizes by SIZE before printing them; e.g.,\n\
304 '-BM' prints sizes in units of 1,048,576 bytes;\n\
305 see SIZE format below\n\
306 -b, --bytes equivalent to '--apparent-size --block-size=1'\n\
307 -c, --total produce a grand total\n\
308 -D, --dereference-args dereference only symlinks that are listed on the\n\
309 command line\n\
310 -d, --max-depth=N print the total for a directory (or file, with --all)\n\
311 only if it is N or fewer levels below the command\n\
312 line argument; --max-depth=0 is the same as\n\
313 --summarize\n\
314 "), stdout);
315 fputs (_("\
316 --files0-from=F summarize device usage of the\n\
317 NUL-terminated file names specified in file F;\n\
318 if F is -, then read names from standard input\n\
319 -H equivalent to --dereference-args (-D)\n\
320 -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)\
322 --inodes list inode usage information instead of block usage\n\
323 "), stdout);
324 fputs (_("\
325 -k like --block-size=1K\n\
326 -L, --dereference dereference all symbolic links\n\
327 -l, --count-links count sizes many times if hard linked\n\
328 -m like --block-size=1M\n\
329 "), stdout);
330 fputs (_("\
331 -P, --no-dereference don't follow any symbolic links (this is the default)\n\
332 -S, --separate-dirs for directories do not include size of subdirectories\n\
333 --si like -h, but use powers of 1000 not 1024\n\
334 -s, --summarize display only a total for each argument\n\
335 "), stdout);
336 fputs (_("\
337 -t, --threshold=SIZE exclude entries smaller than SIZE if positive,\n\
338 or entries greater than SIZE if negative\n\
339 --time show time of the last modification of any file in the\n\
340 directory, or any of its subdirectories\n\
341 --time=WORD show time as WORD instead of modification time:\n\
342 atime, access, use, ctime or status\n\
343 --time-style=STYLE show times using STYLE, which can be:\n\
344 full-iso, long-iso, iso, or +FORMAT;\n\
345 FORMAT is interpreted like in 'date'\n\
346 "), stdout);
347 fputs (_("\
348 -X, --exclude-from=FILE exclude files that match any pattern in FILE\n\
349 --exclude=PATTERN exclude files that match PATTERN\n\
350 -x, --one-file-system skip directories on different file systems\n\
351 "), stdout);
352 fputs (HELP_OPTION_DESCRIPTION, stdout);
353 fputs (VERSION_OPTION_DESCRIPTION, stdout);
354 emit_blocksize_note ("DU");
355 emit_size_note ();
356 emit_ancillary_info (PROGRAM_NAME);
358 exit (status);
361 /* Try to insert the INO/DEV pair into DI_SET.
362 Return true if the pair is successfully inserted,
363 false if the pair was already there. */
364 static bool
365 hash_ins (struct di_set *di_set, ino_t ino, dev_t dev)
367 int inserted = di_set_insert (di_set, dev, ino);
368 if (inserted < 0)
369 xalloc_die ();
370 return inserted;
373 /* Print N_BYTES. Convert it to a readable value before printing. */
375 static void
376 print_only_size (uintmax_t n_bytes)
378 char buf[LONGEST_HUMAN_READABLE + 1];
379 fputs ((n_bytes == UINTMAX_MAX
380 ? _("Infinity")
381 : human_readable (n_bytes, buf, human_output_opts,
382 1, output_block_size)),
383 stdout);
386 /* Print size (and optionally time) indicated by *PDUI, followed by STRING. */
388 static void
389 print_size (const struct duinfo *pdui, char const *string)
391 print_only_size (opt_inodes
392 ? pdui->inodes
393 : pdui->size);
395 if (opt_time)
397 putchar ('\t');
398 bool ok = show_date (time_format, pdui->tmax, localtz);
399 if (!ok)
401 /* If failed to format date, print raw seconds instead. */
402 char buf[INT_BUFSIZE_BOUND (intmax_t)];
403 fputs (timetostr (pdui->tmax.tv_sec, buf), stdout);
406 printf ("\t%s%c", string, opt_nul_terminate_output ? '\0' : '\n');
407 fflush (stdout);
410 /* Fill the di_mnt set with local mount point dev/ino pairs. */
412 static void
413 fill_mount_table (void)
415 struct mount_entry *mnt_ent = read_file_system_list (false);
416 while (mnt_ent)
418 struct mount_entry *mnt_free;
419 if (!mnt_ent->me_remote && !mnt_ent->me_dummy)
421 struct stat buf;
422 if (!stat (mnt_ent->me_mountdir, &buf))
423 hash_ins (di_mnt, buf.st_ino, buf.st_dev);
424 else
426 /* Ignore stat failure. False positives are too common.
427 E.g., "Permission denied" on /run/user/<name>/gvfs. */
431 mnt_free = mnt_ent;
432 mnt_ent = mnt_ent->me_next;
433 free_mount_entry (mnt_free);
437 /* This function checks whether any of the directories in the cycle that
438 fts detected is a mount point. */
440 static bool
441 mount_point_in_fts_cycle (FTSENT const *ent)
443 FTSENT const *cycle_ent = ent->fts_cycle;
445 if (!di_mnt)
447 /* Initialize the set of dev,inode pairs. */
448 di_mnt = di_set_alloc ();
449 if (!di_mnt)
450 xalloc_die ();
452 fill_mount_table ();
455 while (ent && ent != cycle_ent)
457 if (di_set_lookup (di_mnt, ent->fts_statp->st_dev,
458 ent->fts_statp->st_ino) > 0)
460 return true;
462 ent = ent->fts_parent;
465 return false;
468 /* This function is called once for every file system object that fts
469 encounters. fts does a depth-first traversal. This function knows
470 that and accumulates per-directory totals based on changes in
471 the depth of the current entry. It returns true on success. */
473 static bool
474 process_file (FTS *fts, FTSENT *ent)
476 bool ok = true;
477 struct duinfo dui;
478 struct duinfo dui_to_print;
479 size_t level;
480 static size_t n_alloc;
481 /* First element of the structure contains:
482 The sum of the sizes of all entries in the single directory
483 at the corresponding level. Although this does include the sizes
484 corresponding to each subdirectory, it does not include the size of
485 any file in a subdirectory. Also corresponding last modified date.
486 Second element of the structure contains:
487 The sum of the sizes of all entries in the hierarchy at or below the
488 directory at the specified level. */
489 static struct dulevel *dulvl;
491 char const *file = ent->fts_path;
492 const struct stat *sb = ent->fts_statp;
493 int info = ent->fts_info;
495 if (info == FTS_DNR)
497 /* An error occurred, but the size is known, so count it. */
498 error (0, ent->fts_errno, _("cannot read directory %s"), quoteaf (file));
499 ok = false;
501 else if (info != FTS_DP)
503 bool excluded = excluded_file_name (exclude, file);
504 if (! excluded)
506 /* Make the stat buffer *SB valid, or fail noisily. */
508 if (info == FTS_NSOK)
510 fts_set (fts, ent, FTS_AGAIN);
511 MAYBE_UNUSED FTSENT const *e = fts_read (fts);
512 affirm (e == ent);
513 info = ent->fts_info;
516 if (info == FTS_NS || info == FTS_SLNONE)
518 error (0, ent->fts_errno, _("cannot access %s"), quoteaf (file));
519 return false;
522 /* The --one-file-system (-x) option cannot exclude anything
523 specified on the command-line. By definition, it can exclude
524 a file or directory only when its device number is different
525 from that of its just-processed parent directory, and du does
526 not process the parent of a command-line argument. */
527 if (fts->fts_options & FTS_XDEV
528 && FTS_ROOTLEVEL < ent->fts_level
529 && fts->fts_dev != sb->st_dev)
530 excluded = true;
533 if (excluded
534 || (! opt_count_all
535 && (hash_all || (! S_ISDIR (sb->st_mode) && 1 < sb->st_nlink))
536 && ! hash_ins (di_files, sb->st_ino, sb->st_dev)))
538 /* If ignoring a directory in preorder, skip its children.
539 Ignore the next fts_read output too, as it's a postorder
540 visit to the same directory. */
541 if (info == FTS_D)
543 fts_set (fts, ent, FTS_SKIP);
544 MAYBE_UNUSED FTSENT const *e = fts_read (fts);
545 affirm (e == ent);
548 return true;
551 switch (info)
553 case FTS_D:
554 return true;
556 case FTS_ERR:
557 /* An error occurred, but the size is known, so count it. */
558 error (0, ent->fts_errno, "%s", quotef (file));
559 ok = false;
560 break;
562 case FTS_DC:
563 /* If not following symlinks and not a (bind) mount point. */
564 if (cycle_warning_required (fts, ent)
565 && ! mount_point_in_fts_cycle (ent))
567 emit_cycle_warning (file);
568 return false;
570 return true;
574 duinfo_set (&dui,
575 (apparent_size
576 ? (usable_st_size (sb) ? MAX (0, sb->st_size) : 0)
577 : (uintmax_t) STP_NBLOCKS (sb) * ST_NBLOCKSIZE),
578 (time_type == time_mtime ? get_stat_mtime (sb)
579 : time_type == time_atime ? get_stat_atime (sb)
580 : get_stat_ctime (sb)));
582 level = ent->fts_level;
583 dui_to_print = dui;
585 if (n_alloc == 0)
587 n_alloc = level + 10;
588 dulvl = xcalloc (n_alloc, sizeof *dulvl);
590 else
592 if (level == prev_level)
594 /* This is usually the most common case. Do nothing. */
596 else if (level > prev_level)
598 /* Descending the hierarchy.
599 Clear the accumulators for *all* levels between prev_level
600 and the current one. The depth may change dramatically,
601 e.g., from 1 to 10. */
603 if (n_alloc <= level)
605 dulvl = xnrealloc (dulvl, level, 2 * sizeof *dulvl);
606 n_alloc = level * 2;
609 for (size_t i = prev_level + 1; i <= level; i++)
611 duinfo_init (&dulvl[i].ent);
612 duinfo_init (&dulvl[i].subdir);
615 else /* level < prev_level */
617 /* Ascending the hierarchy.
618 Process a directory only after all entries in that
619 directory have been processed. When the depth decreases,
620 propagate sums from the children (prev_level) to the parent.
621 Here, the current level is always one smaller than the
622 previous one. */
623 affirm (level == prev_level - 1);
624 duinfo_add (&dui_to_print, &dulvl[prev_level].ent);
625 if (!opt_separate_dirs)
626 duinfo_add (&dui_to_print, &dulvl[prev_level].subdir);
627 duinfo_add (&dulvl[level].subdir, &dulvl[prev_level].ent);
628 duinfo_add (&dulvl[level].subdir, &dulvl[prev_level].subdir);
632 prev_level = level;
634 /* Let the size of a directory entry contribute to the total for the
635 containing directory, unless --separate-dirs (-S) is specified. */
636 if (! (opt_separate_dirs && IS_DIR_TYPE (info)))
637 duinfo_add (&dulvl[level].ent, &dui);
639 /* Even if this directory is unreadable or we can't chdir into it,
640 do let its size contribute to the total. */
641 duinfo_add (&tot_dui, &dui);
643 if ((IS_DIR_TYPE (info) && level <= max_depth)
644 || (opt_all && level <= max_depth)
645 || level == 0)
647 /* Print or elide this entry according to the --threshold option. */
648 uintmax_t v = opt_inodes ? dui_to_print.inodes : dui_to_print.size;
649 if (opt_threshold < 0
650 ? v <= -opt_threshold
651 : v >= opt_threshold)
652 print_size (&dui_to_print, file);
655 return ok;
658 /* Recursively print the sizes of the directories (and, if selected, files)
659 named in FILES, the last entry of which is null.
660 BIT_FLAGS controls how fts works.
661 Return true if successful. */
663 static bool
664 du_files (char **files, int bit_flags)
666 bool ok = true;
668 if (*files)
670 FTS *fts = xfts_open (files, bit_flags, nullptr);
672 while (true)
674 FTSENT *ent;
676 ent = fts_read (fts);
677 if (ent == nullptr)
679 if (errno != 0)
681 error (0, errno, _("fts_read failed: %s"),
682 quotef (fts->fts_path));
683 ok = false;
686 /* When exiting this loop early, be careful to reset the
687 global, prev_level, used in process_file. Otherwise, its
688 (level == prev_level - 1) assertion could fail. */
689 prev_level = 0;
690 break;
692 FTS_CROSS_CHECK (fts);
694 ok &= process_file (fts, ent);
697 if (fts_close (fts) != 0)
699 error (0, errno, _("fts_close failed"));
700 ok = false;
704 return ok;
708 main (int argc, char **argv)
710 char *cwd_only[2];
711 bool max_depth_specified = false;
712 bool ok = true;
713 char *files_from = nullptr;
715 /* Bit flags that control how fts works. */
716 int bit_flags = FTS_NOSTAT;
718 /* Select one of the three FTS_ options that control if/when
719 to follow a symlink. */
720 int symlink_deref_bits = FTS_PHYSICAL;
722 /* If true, display only a total for each argument. */
723 bool opt_summarize_only = false;
725 cwd_only[0] = bad_cast (".");
726 cwd_only[1] = nullptr;
728 initialize_main (&argc, &argv);
729 set_program_name (argv[0]);
730 setlocale (LC_ALL, "");
731 bindtextdomain (PACKAGE, LOCALEDIR);
732 textdomain (PACKAGE);
734 atexit (close_stdout);
736 exclude = new_exclude ();
738 human_options (getenv ("DU_BLOCK_SIZE"),
739 &human_output_opts, &output_block_size);
741 while (true)
743 int oi = -1;
744 int c = getopt_long (argc, argv, "0abd:chHklmst:xB:DLPSX:",
745 long_options, &oi);
746 if (c == -1)
747 break;
749 switch (c)
751 #if DU_DEBUG
752 case FTS_DEBUG:
753 fts_debug = true;
754 break;
755 #endif
757 case '0':
758 opt_nul_terminate_output = true;
759 break;
761 case 'a':
762 opt_all = true;
763 break;
765 case APPARENT_SIZE_OPTION:
766 apparent_size = true;
767 break;
769 case 'b':
770 apparent_size = true;
771 human_output_opts = 0;
772 output_block_size = 1;
773 break;
775 case 'c':
776 print_grand_total = true;
777 break;
779 case 'h':
780 human_output_opts = human_autoscale | human_SI | human_base_1024;
781 output_block_size = 1;
782 break;
784 case HUMAN_SI_OPTION:
785 human_output_opts = human_autoscale | human_SI;
786 output_block_size = 1;
787 break;
789 case 'k':
790 human_output_opts = 0;
791 output_block_size = 1024;
792 break;
794 case 'd': /* --max-depth=N */
796 intmax_t tmp;
797 if (xstrtoimax (optarg, nullptr, 0, &tmp, "") == LONGINT_OK
798 && tmp <= IDX_MAX)
800 max_depth_specified = true;
801 max_depth = tmp;
803 else
805 error (0, 0, _("invalid maximum depth %s"),
806 quote (optarg));
807 ok = false;
810 break;
812 case 'm':
813 human_output_opts = 0;
814 output_block_size = 1024 * 1024;
815 break;
817 case 'l':
818 opt_count_all = true;
819 break;
821 case 's':
822 opt_summarize_only = true;
823 break;
825 case 't':
827 enum strtol_error e;
828 e = xstrtoimax (optarg, nullptr, 0, &opt_threshold,
829 "kKmMGTPEZYRQ0");
830 if (e != LONGINT_OK)
831 xstrtol_fatal (e, oi, c, long_options, optarg);
832 if (opt_threshold == 0 && *optarg == '-')
834 /* Do not allow -0, as this wouldn't make sense anyway. */
835 error (EXIT_FAILURE, 0, _("invalid --threshold argument '-0'"));
838 break;
840 case 'x':
841 bit_flags |= FTS_XDEV;
842 break;
844 case 'B':
846 enum strtol_error e = human_options (optarg, &human_output_opts,
847 &output_block_size);
848 if (e != LONGINT_OK)
849 xstrtol_fatal (e, oi, c, long_options, optarg);
851 break;
853 case 'H': /* NOTE: before 2008-12, -H was equivalent to --si. */
854 case 'D':
855 symlink_deref_bits = FTS_COMFOLLOW | FTS_PHYSICAL;
856 break;
858 case 'L': /* --dereference */
859 symlink_deref_bits = FTS_LOGICAL;
860 break;
862 case 'P': /* --no-dereference */
863 symlink_deref_bits = FTS_PHYSICAL;
864 break;
866 case 'S':
867 opt_separate_dirs = true;
868 break;
870 case 'X':
871 if (add_exclude_file (add_exclude, exclude, optarg,
872 EXCLUDE_WILDCARDS, '\n'))
874 error (0, errno, "%s", quotef (optarg));
875 ok = false;
877 break;
879 case FILES0_FROM_OPTION:
880 files_from = optarg;
881 break;
883 case EXCLUDE_OPTION:
884 add_exclude (exclude, optarg, EXCLUDE_WILDCARDS);
885 break;
887 case INODES_OPTION:
888 opt_inodes = true;
889 break;
891 case TIME_OPTION:
892 opt_time = true;
893 time_type =
894 (optarg
895 ? XARGMATCH ("--time", optarg, time_args, time_types)
896 : time_mtime);
897 localtz = tzalloc (getenv ("TZ"));
898 break;
900 case TIME_STYLE_OPTION:
901 time_style = optarg;
902 break;
904 case_GETOPT_HELP_CHAR;
906 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
908 default:
909 ok = false;
913 if (!ok)
914 usage (EXIT_FAILURE);
916 if (opt_all && opt_summarize_only)
918 error (0, 0, _("cannot both summarize and show all entries"));
919 usage (EXIT_FAILURE);
922 if (opt_summarize_only && max_depth_specified && max_depth == 0)
924 error (0, 0,
925 _("warning: summarizing is the same as using --max-depth=0"));
928 if (opt_summarize_only && max_depth_specified && max_depth != 0)
930 error (0, 0, _("warning: summarizing conflicts with --max-depth=%td"),
931 max_depth);
932 usage (EXIT_FAILURE);
935 if (opt_summarize_only)
936 max_depth = 0;
938 if (opt_inodes)
940 if (apparent_size)
942 error (0, 0, _("warning: options --apparent-size and -b are "
943 "ineffective with --inodes"));
945 output_block_size = 1;
948 /* Process time style if printing last times. */
949 if (opt_time)
951 if (! time_style)
953 time_style = getenv ("TIME_STYLE");
955 /* Ignore TIMESTYLE="locale", for compatibility with ls. */
956 if (! time_style || STREQ (time_style, "locale"))
957 time_style = "long-iso";
958 else if (*time_style == '+')
960 /* Ignore anything after a newline, for compatibility
961 with ls. */
962 char *p = strchr (time_style, '\n');
963 if (p)
964 *p = '\0';
966 else
968 /* Ignore "posix-" prefix, for compatibility with ls. */
969 static char const posix_prefix[] = "posix-";
970 static const size_t prefix_len = sizeof posix_prefix - 1;
971 while (STREQ_LEN (time_style, posix_prefix, prefix_len))
972 time_style += prefix_len;
976 if (*time_style == '+')
977 time_format = time_style + 1;
978 else
980 switch (XARGMATCH ("time style", time_style,
981 time_style_args, time_style_types))
983 case full_iso_time_style:
984 time_format = "%Y-%m-%d %H:%M:%S.%N %z";
985 break;
987 case long_iso_time_style:
988 time_format = "%Y-%m-%d %H:%M";
989 break;
991 case iso_time_style:
992 time_format = "%Y-%m-%d";
993 break;
998 struct argv_iterator *ai;
999 if (files_from)
1001 /* When using --files0-from=F, you may not specify any files
1002 on the command-line. */
1003 if (optind < argc)
1005 error (0, 0, _("extra operand %s"), quote (argv[optind]));
1006 fprintf (stderr, "%s\n",
1007 _("file operands cannot be combined with --files0-from"));
1008 usage (EXIT_FAILURE);
1011 if (! (STREQ (files_from, "-") || freopen (files_from, "r", stdin)))
1012 error (EXIT_FAILURE, errno, _("cannot open %s for reading"),
1013 quoteaf (files_from));
1015 ai = argv_iter_init_stream (stdin);
1017 /* It's not easy here to count the arguments, so assume the
1018 worst. */
1019 hash_all = true;
1021 else
1023 char **files = (optind < argc ? argv + optind : cwd_only);
1024 ai = argv_iter_init_argv (files);
1026 /* Hash all dev,ino pairs if there are multiple arguments, or if
1027 following non-command-line symlinks, because in either case a
1028 file with just one hard link might be seen more than once. */
1029 hash_all = (optind + 1 < argc || symlink_deref_bits == FTS_LOGICAL);
1032 if (!ai)
1033 xalloc_die ();
1035 /* Initialize the set of dev,inode pairs. */
1036 di_files = di_set_alloc ();
1037 if (!di_files)
1038 xalloc_die ();
1040 /* If not hashing everything, process_file won't find cycles on its
1041 own, so ask fts_read to check for them accurately. */
1042 if (opt_count_all || ! hash_all)
1043 bit_flags |= FTS_TIGHT_CYCLE_CHECK;
1045 bit_flags |= symlink_deref_bits;
1046 static char *temp_argv[] = { nullptr, nullptr };
1048 while (true)
1050 bool skip_file = false;
1051 enum argv_iter_err ai_err;
1052 char *file_name = argv_iter (ai, &ai_err);
1053 if (!file_name)
1055 switch (ai_err)
1057 case AI_ERR_EOF:
1058 goto argv_iter_done;
1059 case AI_ERR_READ:
1060 error (0, errno, _("%s: read error"),
1061 quotef (files_from));
1062 ok = false;
1063 goto argv_iter_done;
1064 case AI_ERR_MEM:
1065 xalloc_die ();
1066 default:
1067 affirm (!"unexpected error code from argv_iter");
1070 if (files_from && STREQ (files_from, "-") && STREQ (file_name, "-"))
1072 /* Give a better diagnostic in an unusual case:
1073 printf - | du --files0-from=- */
1074 error (0, 0, _("when reading file names from stdin, "
1075 "no file name of %s allowed"),
1076 quoteaf (file_name));
1077 skip_file = true;
1080 /* Report and skip any empty file names before invoking fts.
1081 This works around a glitch in fts, which fails immediately
1082 (without looking at the other file names) when given an empty
1083 file name. */
1084 if (!file_name[0])
1086 /* Diagnose a zero-length file name. When it's one
1087 among many, knowing the record number may help.
1088 FIXME: currently print the record number only with
1089 --files0-from=FILE. Maybe do it for argv, too? */
1090 if (files_from == nullptr)
1091 error (0, 0, "%s", _("invalid zero-length file name"));
1092 else
1094 /* Using the standard 'filename:line-number:' prefix here is
1095 not totally appropriate, since NUL is the separator, not NL,
1096 but it might be better than nothing. */
1097 idx_t file_number = argv_iter_n_args (ai);
1098 error (0, 0, "%s:%td: %s", quotef (files_from),
1099 file_number, _("invalid zero-length file name"));
1101 skip_file = true;
1104 if (skip_file)
1105 ok = false;
1106 else
1108 temp_argv[0] = file_name;
1109 ok &= du_files (temp_argv, bit_flags);
1112 argv_iter_done:
1114 argv_iter_free (ai);
1115 di_set_free (di_files);
1116 if (di_mnt)
1117 di_set_free (di_mnt);
1119 if (files_from && (ferror (stdin) || fclose (stdin) != 0) && ok)
1120 error (EXIT_FAILURE, 0, _("error reading %s"), quoteaf (files_from));
1122 if (print_grand_total)
1123 print_size (&tot_dui, _("total"));
1125 return ok ? EXIT_SUCCESS : EXIT_FAILURE;