1 /* du -- summarize disk usage
2 Copyright (C) 1988-2018 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17 /* Differences from the Unix du:
18 * Doesn't simply ignore the names of regular files given as arguments
19 when -a is given.
20
21 By tege@sics.se, Torbjorn 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. */
25
26 #include <config.h>
27 #include <getopt.h>
28 #include <sys/types.h>
29 #include <assert.h>
30 #include "system.h"
31 #include "argmatch.h"
32 #include "argv-iter.h"
33 #include "di-set.h"
34 #include "die.h"
35 #include "error.h"
36 #include "exclude.h"
37 #include "fprintftime.h"
38 #include "human.h"
39 #include "mountlist.h"
40 #include "quote.h"
41 #include "stat-size.h"
42 #include "stat-time.h"
43 #include "stdio--.h"
44 #include "xfts.h"
45 #include "xstrtol.h"
46
47 extern bool fts_debug;
48
49 /* The official name of this program (e.g., no 'g' prefix). */
50 #define PROGRAM_NAME "du"
51
52 #define AUTHORS \
53 proper_name ("Torbjorn Granlund"), \
54 proper_name ("David MacKenzie"), \
55 proper_name ("Paul Eggert"), \
56 proper_name ("Jim Meyering")
57
58 #if DU_DEBUG
59 # define FTS_CROSS_CHECK(Fts) fts_cross_check (Fts)
60 #else
61 # define FTS_CROSS_CHECK(Fts)
62 #endif
63
64 /* A set of dev/ino pairs to help identify files and directories
65 whose sizes have already been counted. */
66 static struct di_set *di_files;
67
68 /* A set containing a dev/ino pair for each local mount point directory. */
69 static struct di_set *di_mnt;
70
71 /* Keep track of the preceding "level" (depth in hierarchy)
72 from one call of process_file to the next. */
73 static size_t prev_level;
74
75 /* Define a class for collecting directory information. */
76 struct duinfo
77 {
78 /* Size of files in directory. */
79 uintmax_t size;
80
81 /* Number of inodes in directory. */
82 uintmax_t inodes;
83
84 /* Latest timestamp found. If tmax.tv_sec == TYPE_MINIMUM (time_t)
85 && tmax.tv_nsec < 0, no timestamp has been found. */
86 struct timespec tmax;
87 };
88
89 /* Initialize directory data. */
90 static inline void
duinfo_init(struct duinfo * a)91 duinfo_init (struct duinfo *a)
92 {
93 a->size = 0;
94 a->inodes = 0;
95 a->tmax.tv_sec = TYPE_MINIMUM (time_t);
96 a->tmax.tv_nsec = -1;
97 }
98
99 /* Set directory data. */
100 static inline void
duinfo_set(struct duinfo * a,uintmax_t size,struct timespec tmax)101 duinfo_set (struct duinfo *a, uintmax_t size, struct timespec tmax)
102 {
103 a->size = size;
104 a->inodes = 1;
105 a->tmax = tmax;
106 }
107
108 /* Accumulate directory data. */
109 static inline void
duinfo_add(struct duinfo * a,struct duinfo const * b)110 duinfo_add (struct duinfo *a, struct duinfo const *b)
111 {
112 uintmax_t sum = a->size + b->size;
113 a->size = a->size <= sum ? sum : UINTMAX_MAX;
114 a->inodes = a->inodes + b->inodes;
115 if (timespec_cmp (a->tmax, b->tmax) < 0)
116 a->tmax = b->tmax;
117 }
118
119 /* A structure for per-directory level information. */
120 struct dulevel
121 {
122 /* Entries in this directory. */
123 struct duinfo ent;
124
125 /* Total for subdirectories. */
126 struct duinfo subdir;
127 };
128
129 /* If true, display counts for all files, not just directories. */
130 static bool opt_all = false;
131
132 /* If true, rather than using the disk usage of each file,
133 use the apparent size (a la stat.st_size). */
134 static bool apparent_size = false;
135
136 /* If true, count each hard link of files with multiple links. */
137 static bool opt_count_all = false;
138
139 /* If true, hash all files to look for hard links. */
140 static bool hash_all;
141
142 /* If true, output the NUL byte instead of a newline at the end of each line. */
143 static bool opt_nul_terminate_output = false;
144
145 /* If true, print a grand total at the end. */
146 static bool print_grand_total = false;
147
148 /* If nonzero, do not add sizes of subdirectories. */
149 static bool opt_separate_dirs = false;
150
151 /* Show the total for each directory (and file if --all) that is at
152 most MAX_DEPTH levels down from the root of the hierarchy. The root
153 is at level 0, so 'du --max-depth=0' is equivalent to 'du -s'. */
154 static size_t max_depth = SIZE_MAX;
155
156 /* Only output entries with at least this SIZE if positive,
157 or at most if negative. See --threshold option. */
158 static intmax_t opt_threshold = 0;
159
160 /* Human-readable options for output. */
161 static int human_output_opts;
162
163 /* Output inodes count instead of blocks used. */
164 static bool opt_inodes = false;
165
166 /* If true, print most recently modified date, using the specified format. */
167 static bool opt_time = false;
168
169 /* Type of time to display. controlled by --time. */
170
171 enum time_type
172 {
173 time_mtime, /* default */
174 time_ctime,
175 time_atime
176 };
177
178 static enum time_type time_type = time_mtime;
179
180 /* User specified date / time style */
181 static char const *time_style = NULL;
182
183 /* Format used to display date / time. Controlled by --time-style */
184 static char const *time_format = NULL;
185
186 /* The local time zone rules, as per the TZ environment variable. */
187 static timezone_t localtz;
188
189 /* The units to use when printing sizes. */
190 static uintmax_t output_block_size;
191
192 /* File name patterns to exclude. */
193 static struct exclude *exclude;
194
195 /* Grand total size of all args, in bytes. Also latest modified date. */
196 static struct duinfo tot_dui;
197
198 #define IS_DIR_TYPE(Type) \
199 ((Type) == FTS_DP \
200 || (Type) == FTS_DNR)
201
202 /* For long options that have no equivalent short option, use a
203 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
204 enum
205 {
206 APPARENT_SIZE_OPTION = CHAR_MAX + 1,
207 EXCLUDE_OPTION,
208 FILES0_FROM_OPTION,
209 HUMAN_SI_OPTION,
210 FTS_DEBUG,
211 TIME_OPTION,
212 TIME_STYLE_OPTION,
213 INODES_OPTION
214 };
215
216 static struct option const long_options[] =
217 {
218 {"all", no_argument, NULL, 'a'},
219 {"apparent-size", no_argument, NULL, APPARENT_SIZE_OPTION},
220 {"block-size", required_argument, NULL, 'B'},
221 {"bytes", no_argument, NULL, 'b'},
222 {"count-links", no_argument, NULL, 'l'},
223 /* {"-debug", no_argument, NULL, FTS_DEBUG}, */
224 {"dereference", no_argument, NULL, 'L'},
225 {"dereference-args", no_argument, NULL, 'D'},
226 {"exclude", required_argument, NULL, EXCLUDE_OPTION},
227 {"exclude-from", required_argument, NULL, 'X'},
228 {"files0-from", required_argument, NULL, FILES0_FROM_OPTION},
229 {"human-readable", no_argument, NULL, 'h'},
230 {"inodes", no_argument, NULL, INODES_OPTION},
231 {"si", no_argument, NULL, HUMAN_SI_OPTION},
232 {"max-depth", required_argument, NULL, 'd'},
233 {"null", no_argument, NULL, '0'},
234 {"no-dereference", no_argument, NULL, 'P'},
235 {"one-file-system", no_argument, NULL, 'x'},
236 {"separate-dirs", no_argument, NULL, 'S'},
237 {"summarize", no_argument, NULL, 's'},
238 {"total", no_argument, NULL, 'c'},
239 {"threshold", required_argument, NULL, 't'},
240 {"time", optional_argument, NULL, TIME_OPTION},
241 {"time-style", required_argument, NULL, TIME_STYLE_OPTION},
242 {GETOPT_HELP_OPTION_DECL},
243 {GETOPT_VERSION_OPTION_DECL},
244 {NULL, 0, NULL, 0}
245 };
246
247 static char const *const time_args[] =
248 {
249 "atime", "access", "use", "ctime", "status", NULL
250 };
251 static enum time_type const time_types[] =
252 {
253 time_atime, time_atime, time_atime, time_ctime, time_ctime
254 };
255 ARGMATCH_VERIFY (time_args, time_types);
256
257 /* 'full-iso' uses full ISO-style dates and times. 'long-iso' uses longer
258 ISO-style timestamps, though shorter than 'full-iso'. 'iso' uses shorter
259 ISO-style timestamps. */
260 enum time_style
261 {
262 full_iso_time_style, /* --time-style=full-iso */
263 long_iso_time_style, /* --time-style=long-iso */
264 iso_time_style /* --time-style=iso */
265 };
266
267 static char const *const time_style_args[] =
268 {
269 "full-iso", "long-iso", "iso", NULL
270 };
271 static enum time_style const time_style_types[] =
272 {
273 full_iso_time_style, long_iso_time_style, iso_time_style
274 };
275 ARGMATCH_VERIFY (time_style_args, time_style_types);
276
277 void
usage(int status)278 usage (int status)
279 {
280 if (status != EXIT_SUCCESS)
281 emit_try_help ();
282 else
283 {
284 printf (_("\
285 Usage: %s [OPTION]... [FILE]...\n\
286 or: %s [OPTION]... --files0-from=F\n\
287 "), program_name, program_name);
288 fputs (_("\
289 Summarize disk usage of the set of FILEs, recursively for directories.\n\
290 "), stdout);
291
292 emit_mandatory_arg_note ();
293
294 fputs (_("\
295 -0, --null end each output line with NUL, not newline\n\
296 -a, --all write counts for all files, not just directories\n\
297 --apparent-size print apparent sizes, rather than disk usage; although\
298 \n\
299 the apparent size is usually smaller, it may be\n\
300 larger due to holes in ('sparse') files, internal\n\
301 fragmentation, indirect blocks, and the like\n\
302 "), stdout);
303 fputs (_("\
304 -B, --block-size=SIZE scale sizes by SIZE before printing them; e.g.,\n\
305 '-BM' prints sizes in units of 1,048,576 bytes;\n\
306 see SIZE format below\n\
307 -b, --bytes equivalent to '--apparent-size --block-size=1'\n\
308 -c, --total produce a grand total\n\
309 -D, --dereference-args dereference only symlinks that are listed on the\n\
310 command line\n\
311 -d, --max-depth=N print the total for a directory (or file, with --all)\n\
312 only if it is N or fewer levels below the command\n\
313 line argument; --max-depth=0 is the same as\n\
314 --summarize\n\
315 "), stdout);
316 fputs (_("\
317 --files0-from=F summarize disk usage of the\n\
318 NUL-terminated file names specified in file F;\n\
319 if F is -, then read names from standard input\n\
320 -H equivalent to --dereference-args (-D)\n\
321 -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)\
322 \n\
323 --inodes list inode usage information instead of block usage\n\
324 "), stdout);
325 fputs (_("\
326 -k like --block-size=1K\n\
327 -L, --dereference dereference all symbolic links\n\
328 -l, --count-links count sizes many times if hard linked\n\
329 -m like --block-size=1M\n\
330 "), stdout);
331 fputs (_("\
332 -P, --no-dereference don't follow any symbolic links (this is the default)\n\
333 -S, --separate-dirs for directories do not include size of subdirectories\n\
334 --si like -h, but use powers of 1000 not 1024\n\
335 -s, --summarize display only a total for each argument\n\
336 "), stdout);
337 fputs (_("\
338 -t, --threshold=SIZE exclude entries smaller than SIZE if positive,\n\
339 or entries greater than SIZE if negative\n\
340 --time show time of the last modification of any file in the\n\
341 directory, or any of its subdirectories\n\
342 --time=WORD show time as WORD instead of modification time:\n\
343 atime, access, use, ctime or status\n\
344 --time-style=STYLE show times using STYLE, which can be:\n\
345 full-iso, long-iso, iso, or +FORMAT;\n\
346 FORMAT is interpreted like in 'date'\n\
347 "), stdout);
348 fputs (_("\
349 -X, --exclude-from=FILE exclude files that match any pattern in FILE\n\
350 --exclude=PATTERN exclude files that match PATTERN\n\
351 -x, --one-file-system skip directories on different file systems\n\
352 "), stdout);
353 fputs (HELP_OPTION_DESCRIPTION, stdout);
354 fputs (VERSION_OPTION_DESCRIPTION, stdout);
355 emit_blocksize_note ("DU");
356 emit_size_note ();
357 emit_ancillary_info (PROGRAM_NAME);
358 }
359 exit (status);
360 }
361
362 /* Try to insert the INO/DEV pair into DI_SET.
363 Return true if the pair is successfully inserted,
364 false if the pair was already there. */
365 static bool
hash_ins(struct di_set * di_set,ino_t ino,dev_t dev)366 hash_ins (struct di_set *di_set, ino_t ino, dev_t dev)
367 {
368 int inserted = di_set_insert (di_set, dev, ino);
369 if (inserted < 0)
370 xalloc_die ();
371 return inserted;
372 }
373
374 /* FIXME: this code is nearly identical to code in date.c */
375 /* Display the date and time in WHEN according to the format specified
376 in FORMAT. */
377
378 static void
show_date(const char * format,struct timespec when,timezone_t tz)379 show_date (const char *format, struct timespec when, timezone_t tz)
380 {
381 struct tm tm;
382 if (localtime_rz (tz, &when.tv_sec, &tm))
383 fprintftime (stdout, format, &tm, tz, when.tv_nsec);
384 else
385 {
386 char buf[INT_BUFSIZE_BOUND (intmax_t)];
387 char *when_str = timetostr (when.tv_sec, buf);
388 error (0, 0, _("time %s is out of range"), quote (when_str));
389 fputs (when_str, stdout);
390 }
391 }
392
393 /* Print N_BYTES. Convert it to a readable value before printing. */
394
395 static void
print_only_size(uintmax_t n_bytes)396 print_only_size (uintmax_t n_bytes)
397 {
398 char buf[LONGEST_HUMAN_READABLE + 1];
399 fputs ((n_bytes == UINTMAX_MAX
400 ? _("Infinity")
401 : human_readable (n_bytes, buf, human_output_opts,
402 1, output_block_size)),
403 stdout);
404 }
405
406 /* Print size (and optionally time) indicated by *PDUI, followed by STRING. */
407
408 static void
print_size(const struct duinfo * pdui,const char * string)409 print_size (const struct duinfo *pdui, const char *string)
410 {
411 print_only_size (opt_inodes
412 ? pdui->inodes
413 : pdui->size);
414
415 if (opt_time)
416 {
417 putchar ('\t');
418 show_date (time_format, pdui->tmax, localtz);
419 }
420 printf ("\t%s%c", string, opt_nul_terminate_output ? '\0' : '\n');
421 fflush (stdout);
422 }
423
424 /* Fill the di_mnt set with local mount point dev/ino pairs. */
425
426 static void
fill_mount_table(void)427 fill_mount_table (void)
428 {
429 struct mount_entry *mnt_ent = read_file_system_list (false);
430 while (mnt_ent)
431 {
432 struct mount_entry *mnt_free;
433 if (!mnt_ent->me_remote && !mnt_ent->me_dummy)
434 {
435 struct stat buf;
436 if (!stat (mnt_ent->me_mountdir, &buf))
437 hash_ins (di_mnt, buf.st_ino, buf.st_dev);
438 else
439 {
440 /* Ignore stat failure. False positives are too common.
441 E.g., "Permission denied" on /run/user/<name>/gvfs. */
442 }
443 }
444
445 mnt_free = mnt_ent;
446 mnt_ent = mnt_ent->me_next;
447 free_mount_entry (mnt_free);
448 }
449 }
450
451 /* This function checks whether any of the directories in the cycle that
452 fts detected is a mount point. */
453
454 static bool
mount_point_in_fts_cycle(FTSENT const * ent)455 mount_point_in_fts_cycle (FTSENT const *ent)
456 {
457 FTSENT const *cycle_ent = ent->fts_cycle;
458
459 if (!di_mnt)
460 {
461 /* Initialize the set of dev,inode pairs. */
462 di_mnt = di_set_alloc ();
463 if (!di_mnt)
464 xalloc_die ();
465
466 fill_mount_table ();
467 }
468
469 while (ent && ent != cycle_ent)
470 {
471 if (di_set_lookup (di_mnt, ent->fts_statp->st_dev,
472 ent->fts_statp->st_ino) > 0)
473 {
474 return true;
475 }
476 ent = ent->fts_parent;
477 }
478
479 return false;
480 }
481
482 /* This function is called once for every file system object that fts
483 encounters. fts does a depth-first traversal. This function knows
484 that and accumulates per-directory totals based on changes in
485 the depth of the current entry. It returns true on success. */
486
487 static bool
process_file(FTS * fts,FTSENT * ent)488 process_file (FTS *fts, FTSENT *ent)
489 {
490 bool ok = true;
491 struct duinfo dui;
492 struct duinfo dui_to_print;
493 size_t level;
494 static size_t n_alloc;
495 /* First element of the structure contains:
496 The sum of the st_size values of all entries in the single directory
497 at the corresponding level. Although this does include the st_size
498 corresponding to each subdirectory, it does not include the size of
499 any file in a subdirectory. Also corresponding last modified date.
500 Second element of the structure contains:
501 The sum of the sizes of all entries in the hierarchy at or below the
502 directory at the specified level. */
503 static struct dulevel *dulvl;
504
505 const char *file = ent->fts_path;
506 const struct stat *sb = ent->fts_statp;
507 int info = ent->fts_info;
508
509 if (info == FTS_DNR)
510 {
511 /* An error occurred, but the size is known, so count it. */
512 error (0, ent->fts_errno, _("cannot read directory %s"), quoteaf (file));
513 ok = false;
514 }
515 else if (info != FTS_DP)
516 {
517 bool excluded = excluded_file_name (exclude, file);
518 if (! excluded)
519 {
520 /* Make the stat buffer *SB valid, or fail noisily. */
521
522 if (info == FTS_NSOK)
523 {
524 fts_set (fts, ent, FTS_AGAIN);
525 FTSENT const *e = fts_read (fts);
526 assert (e == ent);
527 info = ent->fts_info;
528 }
529
530 if (info == FTS_NS || info == FTS_SLNONE)
531 {
532 error (0, ent->fts_errno, _("cannot access %s"), quoteaf (file));
533 return false;
534 }
535
536 /* The --one-file-system (-x) option cannot exclude anything
537 specified on the command-line. By definition, it can exclude
538 a file or directory only when its device number is different
539 from that of its just-processed parent directory, and du does
540 not process the parent of a command-line argument. */
541 if (fts->fts_options & FTS_XDEV
542 && FTS_ROOTLEVEL < ent->fts_level
543 && fts->fts_dev != sb->st_dev)
544 excluded = true;
545 }
546
547 if (excluded
548 || (! opt_count_all
549 && (hash_all || (! S_ISDIR (sb->st_mode) && 1 < sb->st_nlink))
550 && ! hash_ins (di_files, sb->st_ino, sb->st_dev)))
551 {
552 /* If ignoring a directory in preorder, skip its children.
553 Ignore the next fts_read output too, as it's a postorder
554 visit to the same directory. */
555 if (info == FTS_D)
556 {
557 fts_set (fts, ent, FTS_SKIP);
558 FTSENT const *e = fts_read (fts);
559 assert (e == ent);
560 }
561
562 return true;
563 }
564
565 switch (info)
566 {
567 case FTS_D:
568 return true;
569
570 case FTS_ERR:
571 /* An error occurred, but the size is known, so count it. */
572 error (0, ent->fts_errno, "%s", quotef (file));
573 ok = false;
574 break;
575
576 case FTS_DC:
577 /* If not following symlinks and not a (bind) mount point. */
578 if (cycle_warning_required (fts, ent)
579 && ! mount_point_in_fts_cycle (ent))
580 {
581 emit_cycle_warning (file);
582 return false;
583 }
584 return true;
585 }
586 }
587
588 duinfo_set (&dui,
589 (apparent_size
590 ? MAX (0, sb->st_size)
591 : (uintmax_t) ST_NBLOCKS (*sb) * ST_NBLOCKSIZE),
592 (time_type == time_mtime ? get_stat_mtime (sb)
593 : time_type == time_atime ? get_stat_atime (sb)
594 : get_stat_ctime (sb)));
595
596 level = ent->fts_level;
597 dui_to_print = dui;
598
599 if (n_alloc == 0)
600 {
601 n_alloc = level + 10;
602 dulvl = xcalloc (n_alloc, sizeof *dulvl);
603 }
604 else
605 {
606 if (level == prev_level)
607 {
608 /* This is usually the most common case. Do nothing. */
609 }
610 else if (level > prev_level)
611 {
612 /* Descending the hierarchy.
613 Clear the accumulators for *all* levels between prev_level
614 and the current one. The depth may change dramatically,
615 e.g., from 1 to 10. */
616
617 if (n_alloc <= level)
618 {
619 dulvl = xnrealloc (dulvl, level, 2 * sizeof *dulvl);
620 n_alloc = level * 2;
621 }
622
623 for (size_t i = prev_level + 1; i <= level; i++)
624 {
625 duinfo_init (&dulvl[i].ent);
626 duinfo_init (&dulvl[i].subdir);
627 }
628 }
629 else /* level < prev_level */
630 {
631 /* Ascending the hierarchy.
632 Process a directory only after all entries in that
633 directory have been processed. When the depth decreases,
634 propagate sums from the children (prev_level) to the parent.
635 Here, the current level is always one smaller than the
636 previous one. */
637 assert (level == prev_level - 1);
638 duinfo_add (&dui_to_print, &dulvl[prev_level].ent);
639 if (!opt_separate_dirs)
640 duinfo_add (&dui_to_print, &dulvl[prev_level].subdir);
641 duinfo_add (&dulvl[level].subdir, &dulvl[prev_level].ent);
642 duinfo_add (&dulvl[level].subdir, &dulvl[prev_level].subdir);
643 }
644 }
645
646 prev_level = level;
647
648 /* Let the size of a directory entry contribute to the total for the
649 containing directory, unless --separate-dirs (-S) is specified. */
650 if (! (opt_separate_dirs && IS_DIR_TYPE (info)))
651 duinfo_add (&dulvl[level].ent, &dui);
652
653 /* Even if this directory is unreadable or we can't chdir into it,
654 do let its size contribute to the total. */
655 duinfo_add (&tot_dui, &dui);
656
657 if ((IS_DIR_TYPE (info) && level <= max_depth)
658 || (opt_all && level <= max_depth)
659 || level == 0)
660 {
661 /* Print or elide this entry according to the --threshold option. */
662 uintmax_t v = opt_inodes ? dui_to_print.inodes : dui_to_print.size;
663 if (opt_threshold < 0
664 ? v <= -opt_threshold
665 : v >= opt_threshold)
666 print_size (&dui_to_print, file);
667 }
668
669 return ok;
670 }
671
672 /* Recursively print the sizes of the directories (and, if selected, files)
673 named in FILES, the last entry of which is NULL.
674 BIT_FLAGS controls how fts works.
675 Return true if successful. */
676
677 static bool
du_files(char ** files,int bit_flags)678 du_files (char **files, int bit_flags)
679 {
680 bool ok = true;
681
682 if (*files)
683 {
684 FTS *fts = xfts_open (files, bit_flags, NULL);
685
686 while (1)
687 {
688 FTSENT *ent;
689
690 ent = fts_read (fts);
691 if (ent == NULL)
692 {
693 if (errno != 0)
694 {
695 error (0, errno, _("fts_read failed: %s"),
696 quotef (fts->fts_path));
697 ok = false;
698 }
699
700 /* When exiting this loop early, be careful to reset the
701 global, prev_level, used in process_file. Otherwise, its
702 (level == prev_level - 1) assertion could fail. */
703 prev_level = 0;
704 break;
705 }
706 FTS_CROSS_CHECK (fts);
707
708 ok &= process_file (fts, ent);
709 }
710
711 if (fts_close (fts) != 0)
712 {
713 error (0, errno, _("fts_close failed"));
714 ok = false;
715 }
716 }
717
718 return ok;
719 }
720
721 int
main(int argc,char ** argv)722 main (int argc, char **argv)
723 {
724 char *cwd_only[2];
725 bool max_depth_specified = false;
726 bool ok = true;
727 char *files_from = NULL;
728
729 /* Bit flags that control how fts works. */
730 int bit_flags = FTS_NOSTAT;
731
732 /* Select one of the three FTS_ options that control if/when
733 to follow a symlink. */
734 int symlink_deref_bits = FTS_PHYSICAL;
735
736 /* If true, display only a total for each argument. */
737 bool opt_summarize_only = false;
738
739 cwd_only[0] = bad_cast (".");
740 cwd_only[1] = NULL;
741
742 initialize_main (&argc, &argv);
743 set_program_name (argv[0]);
744 setlocale (LC_ALL, "");
745 bindtextdomain (PACKAGE, LOCALEDIR);
746 textdomain (PACKAGE);
747
748 atexit (close_stdout);
749
750 exclude = new_exclude ();
751
752 human_options (getenv ("DU_BLOCK_SIZE"),
753 &human_output_opts, &output_block_size);
754
755 while (true)
756 {
757 int oi = -1;
758 int c = getopt_long (argc, argv, "0abd:chHklmst:xB:DLPSX:",
759 long_options, &oi);
760 if (c == -1)
761 break;
762
763 switch (c)
764 {
765 #if DU_DEBUG
766 case FTS_DEBUG:
767 fts_debug = true;
768 break;
769 #endif
770
771 case '0':
772 opt_nul_terminate_output = true;
773 break;
774
775 case 'a':
776 opt_all = true;
777 break;
778
779 case APPARENT_SIZE_OPTION:
780 apparent_size = true;
781 break;
782
783 case 'b':
784 apparent_size = true;
785 human_output_opts = 0;
786 output_block_size = 1;
787 break;
788
789 case 'c':
790 print_grand_total = true;
791 break;
792
793 case 'h':
794 human_output_opts = human_autoscale | human_SI | human_base_1024;
795 output_block_size = 1;
796 break;
797
798 case HUMAN_SI_OPTION:
799 human_output_opts = human_autoscale | human_SI;
800 output_block_size = 1;
801 break;
802
803 case 'k':
804 human_output_opts = 0;
805 output_block_size = 1024;
806 break;
807
808 case 'd': /* --max-depth=N */
809 {
810 unsigned long int tmp_ulong;
811 if (xstrtoul (optarg, NULL, 0, &tmp_ulong, NULL) == LONGINT_OK
812 && tmp_ulong <= SIZE_MAX)
813 {
814 max_depth_specified = true;
815 max_depth = tmp_ulong;
816 }
817 else
818 {
819 error (0, 0, _("invalid maximum depth %s"),
820 quote (optarg));
821 ok = false;
822 }
823 }
824 break;
825
826 case 'm':
827 human_output_opts = 0;
828 output_block_size = 1024 * 1024;
829 break;
830
831 case 'l':
832 opt_count_all = true;
833 break;
834
835 case 's':
836 opt_summarize_only = true;
837 break;
838
839 case 't':
840 {
841 enum strtol_error e;
842 e = xstrtoimax (optarg, NULL, 0, &opt_threshold, "kKmMGTPEZY0");
843 if (e != LONGINT_OK)
844 xstrtol_fatal (e, oi, c, long_options, optarg);
845 if (opt_threshold == 0 && *optarg == '-')
846 {
847 /* Do not allow -0, as this wouldn't make sense anyway. */
848 die (EXIT_FAILURE, 0, _("invalid --threshold argument '-0'"));
849 }
850 }
851 break;
852
853 case 'x':
854 bit_flags |= FTS_XDEV;
855 break;
856
857 case 'B':
858 {
859 enum strtol_error e = human_options (optarg, &human_output_opts,
860 &output_block_size);
861 if (e != LONGINT_OK)
862 xstrtol_fatal (e, oi, c, long_options, optarg);
863 }
864 break;
865
866 case 'H': /* NOTE: before 2008-12, -H was equivalent to --si. */
867 case 'D':
868 symlink_deref_bits = FTS_COMFOLLOW | FTS_PHYSICAL;
869 break;
870
871 case 'L': /* --dereference */
872 symlink_deref_bits = FTS_LOGICAL;
873 break;
874
875 case 'P': /* --no-dereference */
876 symlink_deref_bits = FTS_PHYSICAL;
877 break;
878
879 case 'S':
880 opt_separate_dirs = true;
881 break;
882
883 case 'X':
884 if (add_exclude_file (add_exclude, exclude, optarg,
885 EXCLUDE_WILDCARDS, '\n'))
886 {
887 error (0, errno, "%s", quotef (optarg));
888 ok = false;
889 }
890 break;
891
892 case FILES0_FROM_OPTION:
893 files_from = optarg;
894 break;
895
896 case EXCLUDE_OPTION:
897 add_exclude (exclude, optarg, EXCLUDE_WILDCARDS);
898 break;
899
900 case INODES_OPTION:
901 opt_inodes = true;
902 break;
903
904 case TIME_OPTION:
905 opt_time = true;
906 time_type =
907 (optarg
908 ? XARGMATCH ("--time", optarg, time_args, time_types)
909 : time_mtime);
910 localtz = tzalloc (getenv ("TZ"));
911 break;
912
913 case TIME_STYLE_OPTION:
914 time_style = optarg;
915 break;
916
917 case_GETOPT_HELP_CHAR;
918
919 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
920
921 default:
922 ok = false;
923 }
924 }
925
926 if (!ok)
927 usage (EXIT_FAILURE);
928
929 if (opt_all && opt_summarize_only)
930 {
931 error (0, 0, _("cannot both summarize and show all entries"));
932 usage (EXIT_FAILURE);
933 }
934
935 if (opt_summarize_only && max_depth_specified && max_depth == 0)
936 {
937 error (0, 0,
938 _("warning: summarizing is the same as using --max-depth=0"));
939 }
940
941 if (opt_summarize_only && max_depth_specified && max_depth != 0)
942 {
943 unsigned long int d = max_depth;
944 error (0, 0, _("warning: summarizing conflicts with --max-depth=%lu"), d);
945 usage (EXIT_FAILURE);
946 }
947
948 if (opt_summarize_only)
949 max_depth = 0;
950
951 if (opt_inodes)
952 {
953 if (apparent_size)
954 {
955 error (0, 0, _("warning: options --apparent-size and -b are "
956 "ineffective with --inodes"));
957 }
958 output_block_size = 1;
959 }
960
961 /* Process time style if printing last times. */
962 if (opt_time)
963 {
964 if (! time_style)
965 {
966 time_style = getenv ("TIME_STYLE");
967
968 /* Ignore TIMESTYLE="locale", for compatibility with ls. */
969 if (! time_style || STREQ (time_style, "locale"))
970 time_style = "long-iso";
971 else if (*time_style == '+')
972 {
973 /* Ignore anything after a newline, for compatibility
974 with ls. */
975 char *p = strchr (time_style, '\n');
976 if (p)
977 *p = '\0';
978 }
979 else
980 {
981 /* Ignore "posix-" prefix, for compatibility with ls. */
982 static char const posix_prefix[] = "posix-";
983 static const size_t prefix_len = sizeof posix_prefix - 1;
984 while (STREQ_LEN (time_style, posix_prefix, prefix_len))
985 time_style += prefix_len;
986 }
987 }
988
989 if (*time_style == '+')
990 time_format = time_style + 1;
991 else
992 {
993 switch (XARGMATCH ("time style", time_style,
994 time_style_args, time_style_types))
995 {
996 case full_iso_time_style:
997 time_format = "%Y-%m-%d %H:%M:%S.%N %z";
998 break;
999
1000 case long_iso_time_style:
1001 time_format = "%Y-%m-%d %H:%M";
1002 break;
1003
1004 case iso_time_style:
1005 time_format = "%Y-%m-%d";
1006 break;
1007 }
1008 }
1009 }
1010
1011 struct argv_iterator *ai;
1012 if (files_from)
1013 {
1014 /* When using --files0-from=F, you may not specify any files
1015 on the command-line. */
1016 if (optind < argc)
1017 {
1018 error (0, 0, _("extra operand %s"), quote (argv[optind]));
1019 fprintf (stderr, "%s\n",
1020 _("file operands cannot be combined with --files0-from"));
1021 usage (EXIT_FAILURE);
1022 }
1023
1024 if (! (STREQ (files_from, "-") || freopen (files_from, "r", stdin)))
1025 die (EXIT_FAILURE, errno, _("cannot open %s for reading"),
1026 quoteaf (files_from));
1027
1028 ai = argv_iter_init_stream (stdin);
1029
1030 /* It's not easy here to count the arguments, so assume the
1031 worst. */
1032 hash_all = true;
1033 }
1034 else
1035 {
1036 char **files = (optind < argc ? argv + optind : cwd_only);
1037 ai = argv_iter_init_argv (files);
1038
1039 /* Hash all dev,ino pairs if there are multiple arguments, or if
1040 following non-command-line symlinks, because in either case a
1041 file with just one hard link might be seen more than once. */
1042 hash_all = (optind + 1 < argc || symlink_deref_bits == FTS_LOGICAL);
1043 }
1044
1045 if (!ai)
1046 xalloc_die ();
1047
1048 /* Initialize the set of dev,inode pairs. */
1049 di_files = di_set_alloc ();
1050 if (!di_files)
1051 xalloc_die ();
1052
1053 /* If not hashing everything, process_file won't find cycles on its
1054 own, so ask fts_read to check for them accurately. */
1055 if (opt_count_all || ! hash_all)
1056 bit_flags |= FTS_TIGHT_CYCLE_CHECK;
1057
1058 bit_flags |= symlink_deref_bits;
1059 static char *temp_argv[] = { NULL, NULL };
1060
1061 while (true)
1062 {
1063 bool skip_file = false;
1064 enum argv_iter_err ai_err;
1065 char *file_name = argv_iter (ai, &ai_err);
1066 if (!file_name)
1067 {
1068 switch (ai_err)
1069 {
1070 case AI_ERR_EOF:
1071 goto argv_iter_done;
1072 case AI_ERR_READ:
1073 error (0, errno, _("%s: read error"),
1074 quotef (files_from));
1075 ok = false;
1076 goto argv_iter_done;
1077 case AI_ERR_MEM:
1078 xalloc_die ();
1079 default:
1080 assert (!"unexpected error code from argv_iter");
1081 }
1082 }
1083 if (files_from && STREQ (files_from, "-") && STREQ (file_name, "-"))
1084 {
1085 /* Give a better diagnostic in an unusual case:
1086 printf - | du --files0-from=- */
1087 error (0, 0, _("when reading file names from stdin, "
1088 "no file name of %s allowed"),
1089 quoteaf (file_name));
1090 skip_file = true;
1091 }
1092
1093 /* Report and skip any empty file names before invoking fts.
1094 This works around a glitch in fts, which fails immediately
1095 (without looking at the other file names) when given an empty
1096 file name. */
1097 if (!file_name[0])
1098 {
1099 /* Diagnose a zero-length file name. When it's one
1100 among many, knowing the record number may help.
1101 FIXME: currently print the record number only with
1102 --files0-from=FILE. Maybe do it for argv, too? */
1103 if (files_from == NULL)
1104 error (0, 0, "%s", _("invalid zero-length file name"));
1105 else
1106 {
1107 /* Using the standard 'filename:line-number:' prefix here is
1108 not totally appropriate, since NUL is the separator, not NL,
1109 but it might be better than nothing. */
1110 unsigned long int file_number = argv_iter_n_args (ai);
1111 error (0, 0, "%s:%lu: %s", quotef (files_from),
1112 file_number, _("invalid zero-length file name"));
1113 }
1114 skip_file = true;
1115 }
1116
1117 if (skip_file)
1118 ok = false;
1119 else
1120 {
1121 temp_argv[0] = file_name;
1122 ok &= du_files (temp_argv, bit_flags);
1123 }
1124 }
1125 argv_iter_done:
1126
1127 argv_iter_free (ai);
1128 di_set_free (di_files);
1129 if (di_mnt)
1130 di_set_free (di_mnt);
1131
1132 if (files_from && (ferror (stdin) || fclose (stdin) != 0) && ok)
1133 die (EXIT_FAILURE, 0, _("error reading %s"), quoteaf (files_from));
1134
1135 if (print_grand_total)
1136 print_size (&tot_dui, _("total"));
1137
1138 return ok ? EXIT_SUCCESS : EXIT_FAILURE;
1139 }
1140