Line data Source code
1 : /* du -- summarize disk usage
2 : Copyright (C) 1988-1991, 1995-2007 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 <http://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 <stdio.h>
28 : #include <getopt.h>
29 : #include <sys/types.h>
30 : #include <assert.h>
31 : #include "system.h"
32 : #include "argmatch.h"
33 : #include "error.h"
34 : #include "exclude.h"
35 : #include "fprintftime.h"
36 : #include "hash.h"
37 : #include "human.h"
38 : #include "inttostr.h"
39 : #include "quote.h"
40 : #include "quotearg.h"
41 : #include "readtokens0.h"
42 : #include "same.h"
43 : #include "stat-time.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 : "Torbjorn Granlund", "David MacKenzie, Paul Eggert", "Jim Meyering"
54 :
55 : #if DU_DEBUG
56 : # define FTS_CROSS_CHECK(Fts) fts_cross_check (Fts)
57 : # define DEBUG_OPT "d"
58 : #else
59 : # define FTS_CROSS_CHECK(Fts)
60 : # define DEBUG_OPT
61 : #endif
62 :
63 : /* Initial size of the hash table. */
64 : #define INITIAL_TABLE_SIZE 103
65 :
66 : /* Hash structure for inode and device numbers. The separate entry
67 : structure makes it easier to rehash "in place". */
68 :
69 : struct entry
70 : {
71 : ino_t st_ino;
72 : dev_t st_dev;
73 : };
74 :
75 : /* A set of dev/ino pairs. */
76 : static Hash_table *htab;
77 :
78 : /* Define a class for collecting directory information. */
79 :
80 : struct duinfo
81 : {
82 : /* Size of files in directory. */
83 : uintmax_t size;
84 :
85 : /* Latest time stamp found. If tmax.tv_sec == TYPE_MINIMUM (time_t)
86 : && tmax.tv_nsec < 0, no time stamp has been found. */
87 : struct timespec tmax;
88 : };
89 :
90 : /* Initialize directory data. */
91 : static inline void
92 75107 : duinfo_init (struct duinfo *a)
93 : {
94 75107 : a->size = 0;
95 75107 : a->tmax.tv_sec = TYPE_MINIMUM (time_t);
96 75107 : a->tmax.tv_nsec = -1;
97 75107 : }
98 :
99 : /* Set directory data. */
100 : static inline void
101 409656 : duinfo_set (struct duinfo *a, uintmax_t size, struct timespec tmax)
102 : {
103 409656 : a->size = size;
104 409656 : a->tmax = tmax;
105 409656 : }
106 :
107 : /* Accumulate directory data. */
108 : static inline void
109 969638 : duinfo_add (struct duinfo *a, struct duinfo const *b)
110 : {
111 969638 : a->size += b->size;
112 969638 : if (timespec_cmp (a->tmax, b->tmax) < 0)
113 106042 : a->tmax = b->tmax;
114 969638 : }
115 :
116 : /* A structure for per-directory level information. */
117 : struct dulevel
118 : {
119 : /* Entries in this directory. */
120 : struct duinfo ent;
121 :
122 : /* Total for subdirectories. */
123 : struct duinfo subdir;
124 : };
125 :
126 : /* Name under which this program was invoked. */
127 : char *program_name;
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, output the NUL byte instead of a newline at the end of each line. */
140 : static bool opt_nul_terminate_output = false;
141 :
142 : /* If true, print a grand total at the end. */
143 : static bool print_grand_total = false;
144 :
145 : /* If nonzero, do not add sizes of subdirectories. */
146 : static bool opt_separate_dirs = false;
147 :
148 : /* Show the total for each directory (and file if --all) that is at
149 : most MAX_DEPTH levels down from the root of the hierarchy. The root
150 : is at level 0, so `du --max-depth=0' is equivalent to `du -s'. */
151 : static size_t max_depth = SIZE_MAX;
152 :
153 : /* Human-readable options for output. */
154 : static int human_output_opts;
155 :
156 : /* If true, print most recently modified date, using the specified format. */
157 : static bool opt_time = false;
158 :
159 : /* Type of time to display. controlled by --time. */
160 :
161 : enum time_type
162 : {
163 : time_mtime, /* default */
164 : time_ctime,
165 : time_atime
166 : };
167 :
168 : static enum time_type time_type = time_mtime;
169 :
170 : /* User specified date / time style */
171 : static char const *time_style = NULL;
172 :
173 : /* Format used to display date / time. Controlled by --time-style */
174 : static char const *time_format = NULL;
175 :
176 : /* The units to use when printing sizes. */
177 : static uintmax_t output_block_size;
178 :
179 : /* File name patterns to exclude. */
180 : static struct exclude *exclude;
181 :
182 : /* Grand total size of all args, in bytes. Also latest modified date. */
183 : static struct duinfo tot_dui;
184 :
185 : #define IS_DIR_TYPE(Type) \
186 : ((Type) == FTS_DP \
187 : || (Type) == FTS_DNR)
188 :
189 : /* For long options that have no equivalent short option, use a
190 : non-character as a pseudo short option, starting with CHAR_MAX + 1. */
191 : enum
192 : {
193 : APPARENT_SIZE_OPTION = CHAR_MAX + 1,
194 : EXCLUDE_OPTION,
195 : FILES0_FROM_OPTION,
196 : HUMAN_SI_OPTION,
197 : MAX_DEPTH_OPTION,
198 : MEGABYTES_LONG_OPTION,
199 : TIME_OPTION,
200 : TIME_STYLE_OPTION
201 : };
202 :
203 : static struct option const long_options[] =
204 : {
205 : {"all", no_argument, NULL, 'a'},
206 : {"apparent-size", no_argument, NULL, APPARENT_SIZE_OPTION},
207 : {"block-size", required_argument, NULL, 'B'},
208 : {"bytes", no_argument, NULL, 'b'},
209 : {"count-links", no_argument, NULL, 'l'},
210 : {"dereference", no_argument, NULL, 'L'},
211 : {"dereference-args", no_argument, NULL, 'D'},
212 : {"exclude", required_argument, NULL, EXCLUDE_OPTION},
213 : {"exclude-from", required_argument, NULL, 'X'},
214 : {"files0-from", required_argument, NULL, FILES0_FROM_OPTION},
215 : {"human-readable", no_argument, NULL, 'h'},
216 : {"si", no_argument, NULL, HUMAN_SI_OPTION},
217 : {"max-depth", required_argument, NULL, MAX_DEPTH_OPTION},
218 : {"null", no_argument, NULL, '0'},
219 : {"no-dereference", no_argument, NULL, 'P'},
220 : {"one-file-system", no_argument, NULL, 'x'},
221 : {"separate-dirs", no_argument, NULL, 'S'},
222 : {"summarize", no_argument, NULL, 's'},
223 : {"total", no_argument, NULL, 'c'},
224 : {"time", optional_argument, NULL, TIME_OPTION},
225 : {"time-style", required_argument, NULL, TIME_STYLE_OPTION},
226 : {GETOPT_HELP_OPTION_DECL},
227 : {GETOPT_VERSION_OPTION_DECL},
228 : {NULL, 0, NULL, 0}
229 : };
230 :
231 : static char const *const time_args[] =
232 : {
233 : "atime", "access", "use", "ctime", "status", NULL
234 : };
235 : static enum time_type const time_types[] =
236 : {
237 : time_atime, time_atime, time_atime, time_ctime, time_ctime
238 : };
239 : ARGMATCH_VERIFY (time_args, time_types);
240 :
241 : /* `full-iso' uses full ISO-style dates and times. `long-iso' uses longer
242 : ISO-style time stamps, though shorter than `full-iso'. `iso' uses shorter
243 : ISO-style time stamps. */
244 : enum time_style
245 : {
246 : full_iso_time_style, /* --time-style=full-iso */
247 : long_iso_time_style, /* --time-style=long-iso */
248 : iso_time_style /* --time-style=iso */
249 : };
250 :
251 : static char const *const time_style_args[] =
252 : {
253 : "full-iso", "long-iso", "iso", NULL
254 : };
255 : static enum time_style const time_style_types[] =
256 : {
257 : full_iso_time_style, long_iso_time_style, iso_time_style
258 : };
259 : ARGMATCH_VERIFY (time_style_args, time_style_types);
260 :
261 : void
262 12 : usage (int status)
263 : {
264 12 : if (status != EXIT_SUCCESS)
265 11 : fprintf (stderr, _("Try `%s --help' for more information.\n"),
266 : program_name);
267 : else
268 : {
269 1 : printf (_("\
270 : Usage: %s [OPTION]... [FILE]...\n\
271 : or: %s [OPTION]... --files0-from=F\n\
272 : "), program_name, program_name);
273 1 : fputs (_("\
274 : Summarize disk usage of each FILE, recursively for directories.\n\
275 : \n\
276 : "), stdout);
277 1 : fputs (_("\
278 : Mandatory arguments to long options are mandatory for short options too.\n\
279 : "), stdout);
280 1 : fputs (_("\
281 : -a, --all write counts for all files, not just directories\n\
282 : --apparent-size print apparent sizes, rather than disk usage; although\n\
283 : the apparent size is usually smaller, it may be\n\
284 : larger due to holes in (`sparse') files, internal\n\
285 : fragmentation, indirect blocks, and the like\n\
286 : "), stdout);
287 1 : fputs (_("\
288 : -B, --block-size=SIZE use SIZE-byte blocks\n\
289 : -b, --bytes equivalent to `--apparent-size --block-size=1'\n\
290 : -c, --total produce a grand total\n\
291 : -D, --dereference-args dereference only symlinks that are listed on the\n\
292 : command line\n\
293 : "), stdout);
294 1 : fputs (_("\
295 : --files0-from=F summarize disk usage of the NUL-terminated file\n\
296 : names specified in file F\n\
297 : -H like --si, but also evokes a warning; will soon\n\
298 : change to be equivalent to --dereference-args (-D)\n\
299 : -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)\n\
300 : --si like -h, but use powers of 1000 not 1024\n\
301 : "), stdout);
302 1 : fputs (_("\
303 : -k like --block-size=1K\n\
304 : -l, --count-links count sizes many times if hard linked\n\
305 : -m like --block-size=1M\n\
306 : "), stdout);
307 1 : fputs (_("\
308 : -L, --dereference dereference all symbolic links\n\
309 : -P, --no-dereference don't follow any symbolic links (this is the default)\n\
310 : -0, --null end each output line with 0 byte rather than newline\n\
311 : -S, --separate-dirs do not include size of subdirectories\n\
312 : -s, --summarize display only a total for each argument\n\
313 : "), stdout);
314 1 : fputs (_("\
315 : -x, --one-file-system skip directories on different file systems\n\
316 : -X FILE, --exclude-from=FILE Exclude files that match any pattern in FILE.\n\
317 : --exclude=PATTERN Exclude files that match PATTERN.\n\
318 : --max-depth=N print the total for a directory (or file, with --all)\n\
319 : only if it is N or fewer levels below the command\n\
320 : line argument; --max-depth=0 is the same as\n\
321 : --summarize\n\
322 : "), stdout);
323 1 : fputs (_("\
324 : --time show time of the last modification of any file in the\n\
325 : directory, or any of its subdirectories\n\
326 : --time=WORD show time as WORD instead of modification time:\n\
327 : atime, access, use, ctime or status\n\
328 : --time-style=STYLE show times using style STYLE:\n\
329 : full-iso, long-iso, iso, +FORMAT\n\
330 : FORMAT is interpreted like `date'\n\
331 : "), stdout);
332 1 : fputs (HELP_OPTION_DESCRIPTION, stdout);
333 1 : fputs (VERSION_OPTION_DESCRIPTION, stdout);
334 1 : fputs (_("\n\
335 : SIZE may be (or may be an integer optionally followed by) one of following:\n\
336 : kB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\
337 : "), stdout);
338 1 : emit_bug_reporting_address ();
339 : }
340 12 : exit (status);
341 : }
342 :
343 : static size_t
344 90 : entry_hash (void const *x, size_t table_size)
345 : {
346 90 : struct entry const *p = x;
347 :
348 : /* Ignoring the device number here should be fine. */
349 : /* The cast to uintmax_t prevents negative remainders
350 : if st_ino is negative. */
351 90 : return (uintmax_t) p->st_ino % table_size;
352 : }
353 :
354 : /* Compare two dev/ino pairs. Return true if they are the same. */
355 : static bool
356 49 : entry_compare (void const *x, void const *y)
357 : {
358 49 : struct entry const *a = x;
359 49 : struct entry const *b = y;
360 49 : return SAME_INODE (*a, *b) ? true : false;
361 : }
362 :
363 : /* Try to insert the INO/DEV pair into the global table, HTAB.
364 : Return true if the pair is successfully inserted,
365 : false if the pair is already in the table. */
366 : static bool
367 90 : hash_ins (ino_t ino, dev_t dev)
368 : {
369 : struct entry *ent;
370 : struct entry *ent_from_table;
371 :
372 90 : ent = xmalloc (sizeof *ent);
373 90 : ent->st_ino = ino;
374 90 : ent->st_dev = dev;
375 :
376 90 : ent_from_table = hash_insert (htab, ent);
377 90 : if (ent_from_table == NULL)
378 : {
379 : /* Insertion failed due to lack of memory. */
380 0 : xalloc_die ();
381 : }
382 :
383 90 : if (ent_from_table == ent)
384 : {
385 : /* Insertion succeeded. */
386 41 : return true;
387 : }
388 :
389 : /* That pair is already in the table, so ENT was not inserted. Free it. */
390 49 : free (ent);
391 :
392 49 : return false;
393 : }
394 :
395 : /* Initialize the hash table. */
396 : static void
397 52 : hash_init (void)
398 : {
399 52 : htab = hash_initialize (INITIAL_TABLE_SIZE, NULL,
400 : entry_hash, entry_compare, free);
401 52 : if (htab == NULL)
402 0 : xalloc_die ();
403 52 : }
404 :
405 : /* FIXME: this code is nearly identical to code in date.c */
406 : /* Display the date and time in WHEN according to the format specified
407 : in FORMAT. */
408 :
409 : static void
410 0 : show_date (const char *format, struct timespec when)
411 : {
412 0 : struct tm *tm = localtime (&when.tv_sec);
413 0 : if (! tm)
414 : {
415 : char buf[INT_BUFSIZE_BOUND (intmax_t)];
416 0 : error (0, 0, _("time %s is out of range"),
417 : (TYPE_SIGNED (time_t)
418 : ? imaxtostr (when.tv_sec, buf)
419 : : umaxtostr (when.tv_sec, buf)));
420 0 : fputs (buf, stdout);
421 0 : return;
422 : }
423 :
424 0 : fprintftime (stdout, format, tm, 0, when.tv_nsec);
425 : }
426 :
427 : /* Print N_BYTES. Convert it to a readable value before printing. */
428 :
429 : static void
430 41227 : print_only_size (uintmax_t n_bytes)
431 : {
432 : char buf[LONGEST_HUMAN_READABLE + 1];
433 41227 : fputs (human_readable (n_bytes, buf, human_output_opts,
434 : 1, output_block_size), stdout);
435 41227 : }
436 :
437 : /* Print size (and optionally time) indicated by *PDUI, followed by STRING. */
438 :
439 : static void
440 41227 : print_size (const struct duinfo *pdui, const char *string)
441 : {
442 41227 : print_only_size (pdui->size);
443 41227 : if (opt_time)
444 : {
445 0 : putchar ('\t');
446 0 : show_date (time_format, pdui->tmax);
447 : }
448 41227 : printf ("\t%s%c", string, opt_nul_terminate_output ? '\0' : '\n');
449 41227 : fflush (stdout);
450 41227 : }
451 :
452 : /* This function is called once for every file system object that fts
453 : encounters. fts does a depth-first traversal. This function knows
454 : that and accumulates per-directory totals based on changes in
455 : the depth of the current entry. It returns true on success. */
456 :
457 : static bool
458 449528 : process_file (FTS *fts, FTSENT *ent)
459 : {
460 : bool ok;
461 : struct duinfo dui;
462 : struct duinfo dui_to_print;
463 : size_t level;
464 : static size_t prev_level;
465 : static size_t n_alloc;
466 : /* First element of the structure contains:
467 : The sum of the st_size values of all entries in the single directory
468 : at the corresponding level. Although this does include the st_size
469 : corresponding to each subdirectory, it does not include the size of
470 : any file in a subdirectory. Also corresponding last modified date.
471 : Second element of the structure contains:
472 : The sum of the sizes of all entries in the hierarchy at or below the
473 : directory at the specified level. */
474 : static struct dulevel *dulvl;
475 449528 : bool print = true;
476 :
477 449528 : const char *file = ent->fts_path;
478 449528 : const struct stat *sb = ent->fts_statp;
479 : bool skip;
480 :
481 : /* If necessary, set FTS_SKIP before returning. */
482 449528 : skip = excluded_file_name (exclude, ent->fts_path);
483 449528 : if (skip)
484 0 : fts_set (fts, ent, FTS_SKIP);
485 :
486 449528 : switch (ent->fts_info)
487 : {
488 15 : case FTS_NS:
489 15 : error (0, ent->fts_errno, _("cannot access %s"), quote (file));
490 15 : return false;
491 :
492 0 : case FTS_ERR:
493 : /* if (S_ISDIR (ent->fts_statp->st_mode) && FIXME */
494 0 : error (0, ent->fts_errno, _("%s"), quote (file));
495 0 : return false;
496 :
497 21 : case FTS_DNR:
498 : /* Don't return just yet, since although the directory is not readable,
499 : we were able to stat it, so we do have a size. */
500 21 : error (0, ent->fts_errno, _("cannot read directory %s"), quote (file));
501 21 : ok = false;
502 21 : break;
503 :
504 449492 : default:
505 449492 : ok = true;
506 449492 : break;
507 : }
508 :
509 : /* If this is the first (pre-order) encounter with a directory,
510 : or if it's the second encounter for a skipped directory, then
511 : return right away. */
512 449513 : if (ent->fts_info == FTS_D || skip)
513 39808 : return ok;
514 :
515 : /* If the file is being excluded or if it has already been counted
516 : via a hard link, then don't let it contribute to the sums. */
517 409705 : if (skip
518 409705 : || (!opt_count_all
519 409241 : && ! S_ISDIR (sb->st_mode)
520 369435 : && 1 < sb->st_nlink
521 90 : && ! hash_ins (sb->st_ino, sb->st_dev)))
522 : {
523 : /* Note that we must not simply return here.
524 : We still have to update prev_level and maybe propagate
525 : some sums up the hierarchy. */
526 49 : duinfo_init (&dui);
527 49 : print = false;
528 : }
529 : else
530 : {
531 1228968 : duinfo_set (&dui,
532 : (apparent_size
533 410584 : ? sb->st_size
534 408728 : : (uintmax_t) ST_NBLOCKS (*sb) * ST_NBLOCKSIZE),
535 409656 : (time_type == time_mtime ? get_stat_mtime (sb)
536 0 : : time_type == time_atime ? get_stat_atime (sb)
537 : : get_stat_ctime (sb)));
538 : }
539 :
540 409705 : level = ent->fts_level;
541 409705 : dui_to_print = dui;
542 :
543 409705 : if (n_alloc == 0)
544 : {
545 47 : n_alloc = level + 10;
546 47 : dulvl = xcalloc (n_alloc, sizeof *dulvl);
547 : }
548 : else
549 : {
550 409658 : if (level == prev_level)
551 : {
552 : /* This is usually the most common case. Do nothing. */
553 : }
554 63822 : else if (level > prev_level)
555 : {
556 : /* Descending the hierarchy.
557 : Clear the accumulators for *all* levels between prev_level
558 : and the current one. The depth may change dramatically,
559 : e.g., from 1 to 10. */
560 : size_t i;
561 :
562 26264 : if (n_alloc <= level)
563 : {
564 3 : dulvl = xnrealloc (dulvl, level, 2 * sizeof *dulvl);
565 3 : n_alloc = level * 2;
566 : }
567 :
568 63793 : for (i = prev_level + 1; i <= level; i++)
569 : {
570 37529 : duinfo_init (&dulvl[i].ent);
571 37529 : duinfo_init (&dulvl[i].subdir);
572 : }
573 : }
574 : else /* level < prev_level */
575 : {
576 : /* Ascending the hierarchy.
577 : Process a directory only after all entries in that
578 : directory have been processed. When the depth decreases,
579 : propagate sums from the children (prev_level) to the parent.
580 : Here, the current level is always one smaller than the
581 : previous one. */
582 37558 : assert (level == prev_level - 1);
583 37558 : duinfo_add (&dui_to_print, &dulvl[prev_level].ent);
584 37558 : if (!opt_separate_dirs)
585 37556 : duinfo_add (&dui_to_print, &dulvl[prev_level].subdir);
586 37558 : duinfo_add (&dulvl[level].subdir, &dulvl[prev_level].ent);
587 37558 : duinfo_add (&dulvl[level].subdir, &dulvl[prev_level].subdir);
588 : }
589 : }
590 :
591 409705 : prev_level = level;
592 :
593 : /* Let the size of a directory entry contribute to the total for the
594 : containing directory, unless --separate-dirs (-S) is specified. */
595 409705 : if ( ! (opt_separate_dirs && IS_DIR_TYPE (ent->fts_info)))
596 409703 : duinfo_add (&dulvl[level].ent, &dui);
597 :
598 : /* Even if this directory is unreadable or we can't chdir into it,
599 : do let its size contribute to the total. */
600 409705 : duinfo_add (&tot_dui, &dui);
601 :
602 : /* If we're not counting an entry, e.g., because it's a hard link
603 : to a file we've already counted (and --count-links), then don't
604 : print a line for it. */
605 409705 : if (!print)
606 49 : return ok;
607 :
608 409656 : if ((IS_DIR_TYPE (ent->fts_info) && level <= max_depth)
609 369849 : || ((opt_all && level <= max_depth) || level == 0))
610 41226 : print_size (&dui_to_print, file);
611 :
612 409656 : return ok;
613 : }
614 :
615 : /* Recursively print the sizes of the directories (and, if selected, files)
616 : named in FILES, the last entry of which is NULL.
617 : BIT_FLAGS controls how fts works.
618 : Return true if successful. */
619 :
620 : static bool
621 52 : du_files (char **files, int bit_flags)
622 : {
623 52 : bool ok = true;
624 :
625 52 : if (*files)
626 : {
627 49 : FTS *fts = xfts_open (files, bit_flags, NULL);
628 :
629 : while (1)
630 449528 : {
631 : FTSENT *ent;
632 :
633 449577 : ent = fts_read (fts);
634 449577 : if (ent == NULL)
635 : {
636 49 : if (errno != 0)
637 : {
638 : /* FIXME: try to give a better message */
639 0 : error (0, errno, _("fts_read failed"));
640 0 : ok = false;
641 : }
642 49 : break;
643 : }
644 : FTS_CROSS_CHECK (fts);
645 :
646 449528 : ok &= process_file (fts, ent);
647 : }
648 :
649 : /* Ignore failure, since the only way it can do so is in failing to
650 : return to the original directory, and since we're about to exit,
651 : that doesn't matter. */
652 49 : fts_close (fts);
653 : }
654 :
655 52 : if (print_grand_total)
656 1 : print_size (&tot_dui, _("total"));
657 :
658 52 : return ok;
659 : }
660 :
661 : int
662 68 : main (int argc, char **argv)
663 : {
664 : char *cwd_only[2];
665 68 : bool max_depth_specified = false;
666 : char **files;
667 68 : bool ok = true;
668 68 : char *files_from = NULL;
669 : struct Tokens tok;
670 :
671 : /* Bit flags that control how fts works. */
672 68 : int bit_flags = FTS_TIGHT_CYCLE_CHECK;
673 :
674 : /* Select one of the three FTS_ options that control if/when
675 : to follow a symlink. */
676 68 : int symlink_deref_bits = FTS_PHYSICAL;
677 :
678 : /* If true, display only a total for each argument. */
679 68 : bool opt_summarize_only = false;
680 :
681 68 : cwd_only[0] = ".";
682 68 : cwd_only[1] = NULL;
683 :
684 : initialize_main (&argc, &argv);
685 68 : program_name = argv[0];
686 68 : setlocale (LC_ALL, "");
687 : bindtextdomain (PACKAGE, LOCALEDIR);
688 : textdomain (PACKAGE);
689 :
690 68 : atexit (close_stdout);
691 :
692 68 : exclude = new_exclude ();
693 :
694 68 : human_options (getenv ("DU_BLOCK_SIZE"),
695 : &human_output_opts, &output_block_size);
696 :
697 : for (;;)
698 39 : {
699 107 : int oi = -1;
700 107 : int c = getopt_long (argc, argv, DEBUG_OPT "0abchHklmsxB:DLPSX:",
701 : long_options, &oi);
702 107 : if (c == -1)
703 64 : break;
704 :
705 43 : switch (c)
706 : {
707 : #if DU_DEBUG
708 : case 'd':
709 : fts_debug = true;
710 : break;
711 : #endif
712 :
713 4 : case '0':
714 4 : opt_nul_terminate_output = true;
715 4 : break;
716 :
717 5 : case 'a':
718 5 : opt_all = true;
719 5 : break;
720 :
721 1 : case APPARENT_SIZE_OPTION:
722 1 : apparent_size = true;
723 1 : break;
724 :
725 1 : case 'b':
726 1 : apparent_size = true;
727 1 : human_output_opts = 0;
728 1 : output_block_size = 1;
729 1 : break;
730 :
731 1 : case 'c':
732 1 : print_grand_total = true;
733 1 : break;
734 :
735 2 : case 'h':
736 2 : human_output_opts = human_autoscale | human_SI | human_base_1024;
737 2 : output_block_size = 1;
738 2 : break;
739 :
740 1 : case 'H': /* FIXME: remove warning and move this "case 'H'" to
741 : precede --dereference-args in late 2006. */
742 1 : error (0, 0, _("WARNING: use --si, not -H; the meaning of the -H\
743 : option will soon\nchange to be the same as that of --dereference-args (-D)"));
744 : /* fall through */
745 1 : case HUMAN_SI_OPTION:
746 1 : human_output_opts = human_autoscale | human_SI;
747 1 : output_block_size = 1;
748 1 : break;
749 :
750 1 : case 'k':
751 1 : human_output_opts = 0;
752 1 : output_block_size = 1024;
753 1 : break;
754 :
755 0 : case MAX_DEPTH_OPTION: /* --max-depth=N */
756 : {
757 : unsigned long int tmp_ulong;
758 0 : if (xstrtoul (optarg, NULL, 0, &tmp_ulong, NULL) == LONGINT_OK
759 : && tmp_ulong <= SIZE_MAX)
760 : {
761 0 : max_depth_specified = true;
762 0 : max_depth = tmp_ulong;
763 : }
764 : else
765 : {
766 0 : error (0, 0, _("invalid maximum depth %s"),
767 : quote (optarg));
768 0 : ok = false;
769 : }
770 : }
771 0 : break;
772 :
773 0 : case MEGABYTES_LONG_OPTION:
774 0 : error (0, 0,
775 : _("the --megabytes option is deprecated; use -m instead"));
776 : /* fall through */
777 1 : case 'm':
778 1 : human_output_opts = 0;
779 1 : output_block_size = 1024 * 1024;
780 1 : break;
781 :
782 1 : case 'l':
783 1 : opt_count_all = true;
784 1 : break;
785 :
786 1 : case 's':
787 1 : opt_summarize_only = true;
788 1 : break;
789 :
790 1 : case 'x':
791 1 : bit_flags |= FTS_XDEV;
792 1 : break;
793 :
794 3 : case 'B':
795 : {
796 3 : enum strtol_error e = human_options (optarg, &human_output_opts,
797 : &output_block_size);
798 3 : if (e != LONGINT_OK)
799 2 : xstrtol_fatal (e, oi, c, long_options, optarg);
800 : }
801 1 : break;
802 :
803 1 : case 'D': /* This will eventually be 'H' (-H), too. */
804 1 : symlink_deref_bits = FTS_COMFOLLOW | FTS_PHYSICAL;
805 1 : break;
806 :
807 2 : case 'L': /* --dereference */
808 2 : symlink_deref_bits = FTS_LOGICAL;
809 2 : break;
810 :
811 1 : case 'P': /* --no-dereference */
812 1 : symlink_deref_bits = FTS_PHYSICAL;
813 1 : break;
814 :
815 2 : case 'S':
816 2 : opt_separate_dirs = true;
817 2 : break;
818 :
819 0 : case 'X':
820 0 : if (add_exclude_file (add_exclude, exclude, optarg,
821 : EXCLUDE_WILDCARDS, '\n'))
822 : {
823 0 : error (0, errno, "%s", quotearg_colon (optarg));
824 0 : ok = false;
825 : }
826 0 : break;
827 :
828 1 : case FILES0_FROM_OPTION:
829 1 : files_from = optarg;
830 1 : break;
831 :
832 0 : case EXCLUDE_OPTION:
833 0 : add_exclude (exclude, optarg, EXCLUDE_WILDCARDS);
834 0 : break;
835 :
836 0 : case TIME_OPTION:
837 0 : opt_time = true;
838 0 : time_type =
839 : (optarg
840 0 : ? XARGMATCH ("--time", optarg, time_args, time_types)
841 0 : : time_mtime);
842 0 : break;
843 :
844 0 : case TIME_STYLE_OPTION:
845 0 : time_style = optarg;
846 0 : break;
847 :
848 1 : case_GETOPT_HELP_CHAR;
849 :
850 1 : case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
851 :
852 11 : default:
853 11 : ok = false;
854 : }
855 : }
856 :
857 64 : if (!ok)
858 11 : usage (EXIT_FAILURE);
859 :
860 53 : if (opt_all & opt_summarize_only)
861 : {
862 0 : error (0, 0, _("cannot both summarize and show all entries"));
863 0 : usage (EXIT_FAILURE);
864 : }
865 :
866 53 : if (opt_summarize_only && max_depth_specified && max_depth == 0)
867 : {
868 0 : error (0, 0,
869 : _("warning: summarizing is the same as using --max-depth=0"));
870 : }
871 :
872 53 : if (opt_summarize_only && max_depth_specified && max_depth != 0)
873 : {
874 0 : unsigned long int d = max_depth;
875 0 : error (0, 0, _("warning: summarizing conflicts with --max-depth=%lu"), d);
876 0 : usage (EXIT_FAILURE);
877 : }
878 :
879 53 : if (opt_summarize_only)
880 1 : max_depth = 0;
881 :
882 : /* Process time style if printing last times. */
883 53 : if (opt_time)
884 : {
885 0 : if (! time_style)
886 : {
887 0 : time_style = getenv ("TIME_STYLE");
888 :
889 : /* Ignore TIMESTYLE="locale", for compatibility with ls. */
890 0 : if (! time_style || STREQ (time_style, "locale"))
891 0 : time_style = "long-iso";
892 0 : else if (*time_style == '+')
893 : {
894 : /* Ignore anything after a newline, for compatibility
895 : with ls. */
896 0 : char *p = strchr (time_style, '\n');
897 0 : if (p)
898 0 : *p = '\0';
899 : }
900 : else
901 : {
902 : /* Ignore "posix-" prefix, for compatibility with ls. */
903 : static char const posix_prefix[] = "posix-";
904 0 : while (strncmp (time_style, posix_prefix, sizeof posix_prefix - 1)
905 : == 0)
906 0 : time_style += sizeof posix_prefix - 1;
907 : }
908 : }
909 :
910 0 : if (*time_style == '+')
911 0 : time_format = time_style + 1;
912 : else
913 : {
914 0 : switch (XARGMATCH ("time style", time_style,
915 : time_style_args, time_style_types))
916 : {
917 0 : case full_iso_time_style:
918 0 : time_format = "%Y-%m-%d %H:%M:%S.%N %z";
919 0 : break;
920 :
921 0 : case long_iso_time_style:
922 0 : time_format = "%Y-%m-%d %H:%M";
923 0 : break;
924 :
925 0 : case iso_time_style:
926 0 : time_format = "%Y-%m-%d";
927 0 : break;
928 : }
929 : }
930 : }
931 :
932 53 : if (files_from)
933 : {
934 : /* When using --files0-from=F, you may not specify any files
935 : on the command-line. */
936 1 : if (optind < argc)
937 : {
938 0 : error (0, 0, _("extra operand %s"), quote (argv[optind]));
939 0 : fprintf (stderr, "%s\n",
940 : _("File operands cannot be combined with --files0-from."));
941 0 : usage (EXIT_FAILURE);
942 : }
943 :
944 1 : if (! (STREQ (files_from, "-") || freopen (files_from, "r", stdin)))
945 1 : error (EXIT_FAILURE, errno, _("cannot open %s for reading"),
946 : quote (files_from));
947 :
948 0 : readtokens0_init (&tok);
949 :
950 0 : if (! readtokens0 (stdin, &tok) || fclose (stdin) != 0)
951 0 : error (EXIT_FAILURE, 0, _("cannot read file names from %s"),
952 : quote (files_from));
953 :
954 0 : files = tok.tok;
955 : }
956 : else
957 : {
958 52 : files = (optind < argc ? argv + optind : cwd_only);
959 : }
960 :
961 : /* Initialize the hash structure for inode numbers. */
962 52 : hash_init ();
963 :
964 : /* Report and filter out any empty file names before invoking fts.
965 : This works around a glitch in fts, which fails immediately
966 : (without looking at the other file names) when given an empty
967 : file name. */
968 : {
969 52 : size_t i = 0;
970 : size_t j;
971 :
972 128 : for (j = 0; ; j++)
973 : {
974 204 : if (i != j)
975 21 : files[i] = files[j];
976 :
977 128 : if ( ! files[i])
978 52 : break;
979 :
980 76 : if (files[i][0])
981 63 : i++;
982 : else
983 : {
984 13 : if (files_from)
985 : {
986 : /* Using the standard `filename:line-number:' prefix here is
987 : not totally appropriate, since NUL is the separator, not NL,
988 : but it might be better than nothing. */
989 0 : unsigned long int file_number = j + 1;
990 0 : error (0, 0, "%s:%lu: %s", quotearg_colon (files_from),
991 : file_number, _("invalid zero-length file name"));
992 : }
993 : else
994 13 : error (0, 0, "%s", _("invalid zero-length file name"));
995 : }
996 : }
997 :
998 52 : ok = (i == j);
999 : }
1000 :
1001 52 : bit_flags |= symlink_deref_bits;
1002 52 : ok &= du_files (files, bit_flags);
1003 :
1004 : /* This isn't really necessary, but it does ensure we
1005 : exercise this function. */
1006 52 : if (files_from)
1007 0 : readtokens0_free (&tok);
1008 :
1009 52 : hash_free (htab);
1010 :
1011 52 : exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
1012 : }
|