File Coverage

script/dusage
Criterion Covered Total %
statement 138 196 70.4
branch 71 178 39.8
condition 13 38 34.2
subroutine 7 9 77.7
pod n/a
total 229 421 54.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             # dusage.pl -- gather disk usage statistics
4             # Author : Johan Vromans
5             # Created On : Sun Jul 1 21:49:37 1990
6             # Last Modified By: Johan Vromans
7             # Last Modified On: Tue Jun 15 08:32:04 2021
8             # Update Count : 212
9             # Status : OK
10             #
11             # This program requires Perl version 5.10.1, or higher.
12              
13             ################ Common stuff ################
14              
15 3     3   2281 use strict;
  3         5  
  3         132  
16              
17             my $my_name = qw( dusage );
18             our $VERSION = "2.02";
19              
20             ################ Command line parameters ################
21              
22 3     3   1256 use Getopt::Long 2.13;
  3         19201  
  3         64  
23              
24             my $verbose = 0; # verbose processing
25             my $noupdate = 1; # do not update the control file
26             my $retain = 0; # retain emtpy entries
27             my $gather = 0; # gather new data
28             my $follow = 0; # follow symlinks
29             my $allfiles = 0; # also report file stats
30             my $allstats = 0; # provide all stats
31              
32             my $root; # root of all eveil
33             my $prefix; # root prefix for reporting
34             my $data; # the data, or how to get it
35             my $table;
36              
37             our $runtype; # file or directory
38              
39             # Development options (not shown with -help).
40             my $debug = 0; # debugging
41             my $trace = 0; # trace (show process)
42             my $test = 0; # test (no actual processing)
43              
44             unless ( caller ) {
45             app_options();
46              
47             # Options post-processing.
48             $trace |= ($debug || $test);
49             }
50              
51             ################ Presets ################
52              
53             my $TMPDIR = $ENV{TMPDIR} || '/usr/tmp';
54              
55             ################ The Process ################
56              
57             my @targets = (); # directories to process, and more
58             my %newblocks = (); # du values
59             my %oldblocks = (); # previous values
60             my @excludes = (); # excluded entries
61             my %testglob;
62              
63             unless ( caller ) {
64             parse_ctl(); # read the control file
65             gather(); # gather new info
66             report_and_update(); # write report and update control file
67             }
68              
69             ################ Subroutines ################
70              
71             sub parse_ctl {
72              
73             # Parsing the control file.
74             #
75             # This file contains the names of the (sub)directories to tally,
76             # and the values dereived from previous runs.
77             # The names of the directories are relative to the $root.
78             # The name may contain '*' or '?' characters, and will be globbed if so.
79             # An entry starting with ! is excluded.
80             #
81             # To add a new dir, just add the name. The special name '.' may
82             # be used to denote the $root directory. If used, '-p' must be
83             # specified.
84             #
85             # Upon completion:
86             # - %oldblocks is filled with the previous values,
87             # colon separated, for each directory.
88             # - @targets contains a list of names to be looked for. These include
89             # break indications and globs info, which will be stripped from
90             # the actual search list.
91              
92 1     1   3 my $tb; # ctl file entry
93              
94 1 50       32 open( my $ctl, "<", $table )
95             or die ("Cannot open control file $table: $!\n");
96              
97 1         15 while ( $tb = <$ctl> ) {
98              
99             # For testing. Please ignore.
100 9 100       16 if ( $tb =~ /^# glob\s+(.*?)\s+->\s+(.+)/ ) {
101 2         7 $testglob{$1} = $2;
102             }
103              
104 9 100       14 next if $tb =~ /^#/;
105 7 50       16 next unless $tb =~ /\S/;
106              
107             # syntax: ::....
108             # possible
109              
110 7 50       9 if ( $tb =~ /^-(?!\t)(.*)/ ) { # break
111 0         0 push (@targets, "-$1");
112 0 0       0 print STDERR ("tb: *break* $1\n") if $debug;
113 0         0 next;
114             }
115              
116 7 50       8 if ( $tb =~ /^!(.*)/ ) { # exclude
117 0         0 push (@excludes, $1);
118 0         0 push (@targets, "!".$1);
119 0 0       0 print STDERR ("tb: *excl* $1\n") if $debug;
120 0         0 next;
121             }
122              
123 7         5 my @blocks;
124             my $name;
125 7 100       13 if ( $tb =~ /^(.+)\t([\d:]+)/ ) {
126 5         7 $name = $1;
127 5         16 @blocks = split (/:/, $2 . "::::::::", -1);
128 5         9 $#blocks = 7;
129             }
130             else {
131 2         3 chomp ($name = $tb);
132 2         3 @blocks = ("") x 8;
133             }
134              
135 7 50       10 if ( $name eq "." ) {
136 0 0       0 if ( $root eq "" ) {
137 0         0 warn ("Warning: \".\" in control file w/o \"-p path\" - ignored\n");
138 0         0 next;
139             }
140 0         0 $name = $root;
141             }
142             else {
143 7 50       12 $name = $prefix . $name unless ord($name) == ord ("/");
144             }
145              
146             # Check for globs ...
147             # if ( ($gather|$debug|%testglob) && $name =~ /\*|\?/ ) {
148 7 100       14 if ( $name =~ /\*|\?/ ) {
149 2 50       4 print STDERR ("glob: $name\n") if $debug;
150             my @glob = $testglob{$name}
151 2 50       7 ? split( ' ', $testglob{$name} )
152             : glob($name);
153 2         2 foreach my $n ( @glob ) {
154 5 50 33     52 next unless $allfiles || -d $n;
155             # Globs never overwrite existing entries
156 5 50       12 unless ( defined $oldblocks{$n} ) {
157 0         0 $oldblocks{$n} = ":::::::";
158 0         0 push (@targets, " $n");
159             }
160 5 50       7 print STDERR ("glob: -> $n\n") if $debug;
161             }
162             # Put on the globs list, and terminate this entry
163 2         4 push (@targets, "*$name");
164 2         13 next;
165             }
166              
167 5         8 push (@targets, " $name");
168              
169             # Entry may be rewritten (in case of globs)
170 5         12 $oldblocks{$name} = join (":", @blocks[0..7]);
171              
172 5 50       13 print STDERR ("tb: $name\t$oldblocks{$name}\n") if $debug;
173             }
174              
175 1 50       4 if ( @excludes ) {
176 0         0 foreach my $excl ( @excludes ) {
177 0 0       0 my $try = ord($excl) == ord("/") ? " $excl" : " $prefix$excl";
178 0         0 @targets = grep ($_ ne $try, @targets);
179             }
180 0 0       0 print STDERR ("targets after exclusion: @targets\n") if $debug;
181             }
182              
183 1         10 close ($ctl);
184             }
185              
186             sub gather {
187              
188             # Build a targets match string, and an optimized list of
189             # directories to search. For example, if /foo and /foo/bar are
190             # both in the list, only /foo is used since du will produce the
191             # statistics for /foo/bar as well.
192              
193 1     1   5 my %targets = ();
194 1         1 my @list = ();
195             # Get all entries, and change the / to nul chars.
196 5 100       12 my @a = map { s;/;\0;g ? ($_) : ($_) }
197             # Only dirs unless $allfiles
198 5 50       52 grep { $allfiles || -d }
199             # And only the file/dir info entries
200 1 100       1 map { /^ (.*)/ ? $1 : () } @targets;
  7         18  
201              
202 1         2 my $prev = "\0\0\0";
203 1         3 foreach my $name ( sort (@a) ) {
204             # If $prev is a complete prefix of $name, we've already got a
205             # better one in the tables.
206 5 100       7 unless ( index ($name, $prev) == 0 ) {
207             # New test arg -- including the trailing nul.
208 4         4 $prev = $name . "\0";
209             # Back to normal.
210 4         3 $name =~ s;\0;/;g;
211             # Register.
212 4         5 push (@list, $name);
213 4         5 $targets{$name}++;
214             }
215              
216             }
217              
218 1 50       3 if ( $debug ) {
219 0         0 print STDERR ("dirs: ", join(" ",sort(keys(%targets))),"\n",
220             "list: @list\n");
221             }
222              
223 1         1 my $fh = do { local(*FH); *FH };
  1         2  
  1         2  
224 1         1 my $out = do { local(*FH); *FH };
  1         1  
  1         2  
225 1 50 33     4 if ( !$gather && defined $data ) { # we have a data file
226 1 50       3 print STDERR ("Using data from $data\n" ) if $debug;
227 1 50       26 open( $fh, "<", $data )
228             or die ("Cannot get data from $data: $!\n");
229 1         3 undef $data;
230 1         2 $gather++;
231             }
232             else {
233 0         0 my @du = ("du");
234 0 0       0 push (@du, "-a") if $allfiles;
235 0 0       0 push( @du, "-L" ) if $follow;
236 0         0 push (@du, "--", @list);
237 0 0       0 print STDERR ("Gather data from @du\n" ) if $debug;
238 0   0     0 my $ret = open( $fh, "-|" ) || exec @du;
239 0 0       0 die ("Cannot get input from -| @du\n") unless $ret;
240 0 0       0 if ( defined $data ) {
241 0 0       0 open($out, ">", $data) or die ("Cannot create $data: $!\n");
242             }
243             }
244              
245             # Process the data. If a name is found in the target list,
246             # %newblocks will be set to the new blocks value.
247 1         1 %targets = map { $_ => 1 } @targets;
  7         10  
248 1         3 my %excludes = map { $prefix.$_ => 1 } @excludes;
  0         0  
249 1         1 my $du;
250 1         11 while ( defined ($du = <$fh>) ) {
251 18 50       20 print $out $du if defined $data;
252 18         15 chomp ($du);
253 18         24 my ($blocks, $name) = split (/\t/, $du);
254 18 100 66     40 if ( exists ($targets{" ".$name}) && !exists ($excludes{$name}) ) {
255             # Tally and remove entry from search list.
256 5         6 $newblocks{$name} = $blocks;
257 5 50       6 print STDERR ("du: $name $blocks\n") if $debug;
258 5         15 delete ($targets{" ".$name});
259             }
260             }
261 1         8 close ($fh);
262 1 50       8 close ($out) if defined $data;
263             }
264              
265             # Variables used in the formats.
266             our $date; # date
267             our $name; # name
268             our $subtitle; # subtitle
269             our @a;
270             our $d_day; # day delta
271             our $d_week; # week delta
272             our $blocks;
273              
274             sub report_and_update {
275 1   50 1   13 my $rep = shift || \*STDOUT;
276 1         2 select($rep);
277              
278 1         2 my $ctl;
279              
280             # Prepare update of the control file
281 1 50       2 unless ( $noupdate ) {
282 1 50       81 unless ( open( $ctl, ">", $table ) ) {
283 0         0 warn ("Warning: cannot update control file $table [$!] - continuing\n");
284 0         0 $noupdate = 1;
285             }
286             }
287              
288             # For testing. Please ignore.
289 1 50 33     6 if ( !$noupdate && %testglob ) {
290 1         4 foreach my $k ( sort keys %testglob ) {
291 2         7 print $ctl "# glob $k -> $testglob{$k}\n";
292             }
293             }
294              
295 1 50       3 if ( $allstats ) {
296 0         0 $^ = "all_hdr";
297 0         0 $~ = "all_out";
298             }
299             else {
300 1         3 $^ = "std_hdr";
301 1         2 $~ = "std_out";
302             }
303              
304 1         21 $date = localtime;
305 1         2 $subtitle = "";
306              
307             # In one pass the report is generated, and the control file rewritten.
308              
309 1         1 foreach my $nam ( @targets ) {
310              
311 7 50       12 if ( $nam =~ /^-(.*)/ ) {
312 0         0 $subtitle = $1;
313 0 0       0 print $ctl ($nam, "\n") unless $noupdate;
314 0 0       0 print STDERR ("tb: $nam\n") if $debug;
315 0         0 $- = 0; # force page feed
316 0         0 next;
317             }
318              
319 7 100       20 if ($nam =~ /^\*\Q$prefix\E(.*)/o ) {
320 2 50       5 print $ctl ("$1\n") unless $noupdate;
321 2 50       4 print STDERR ("tb: $1\n") if $debug;
322 2         3 next;
323             }
324              
325 5 50       14 if ( $nam =~ /^ (.*)/ ) {
326 5         7 $nam = $1
327             }
328             else {
329 0 0       0 print $ctl $nam, "\n" unless $noupdate;
330 0 0       0 print STDERR ("tb: $nam\n") if $debug;
331 0         0 next;
332             }
333              
334             warn("Oops1 $nam\n"), next
335 5 50 33     15 unless $nam =~ /\*/ || defined $oldblocks{$nam};
336             warn("Oops2 $nam\n"), next
337 5 50 33     12 unless $nam =~ /\*/ || defined $newblocks{$nam};
338              
339 5         18 @a = split (/:/, $oldblocks{$nam} . ":::::::", -1);
340 5         8 $#a = 7;
341 5 50       60 unshift (@a, $newblocks{$nam}) if $gather;
342 5 50       7 $nam = "." if $nam eq $root;
343 5 50       22 $nam = $1 if $nam =~ /^\Q$prefix\E(.*)/o;
344 5 50 33     19 warn ("Warning: ", scalar(@a), " entries for $nam\n")
345             if $debug && @a != 9;
346              
347             # check for valid data
348 5         11 my $try = join (":", @a[0..7]);
349 5 50       7 if ( $try eq ":::::::" ) {
350 0 0       0 if ($retain) {
351 0         0 @a = ("") x 8;
352             }
353             else {
354             # Discard.
355 0 0       0 print STDERR ("--: $nam\n") if $debug;
356 0         0 next;
357             }
358             }
359              
360 5         5 my $line = "$nam\t$try\n";
361 5 50       8 print $ctl ($line) unless $noupdate;
362 5 50       6 print STDERR ("tb: $line") if $debug;
363              
364 5         5 $blocks = $a[0];
365 5 50       6 unless ( $allstats ) {
366 5         6 $d_day = $d_week = "";
367 5 50       5 if ( $blocks ne "" ) {
368 5 50       5 if ( $a[1] ne "" ) { # daily delta
369 5         7 $d_day = $blocks - $a[1];
370 5 100       6 $d_day = "+" . $d_day if $d_day > 0;
371             }
372 5 50       7 if ( $a[7] ne "" ) { # weekly delta
373 0         0 $d_week = $blocks - $a[7];
374 0 0       0 $d_week = "+" . $d_week if $d_week > 0;
375             }
376             }
377             }
378              
379             # Using a outer my variable that is aliased in a loop within a
380             # subroutine still doesn't work...
381 5         6 $name = $nam;
382 5         5 write($rep);
383             }
384              
385             # Close control file, if opened
386 1 50       56 close ($ctl) unless $noupdate;
387             }
388              
389             ################ Option Processing ################
390              
391             sub app_options {
392 1     1   7 my $help = 0; # handled locally
393 1         1 my $ident = 0; # handled locally
394 1         1 my $man = 0; # handled locally
395              
396             my $pod2usage = sub {
397             # Load Pod::Usage only if needed.
398 0     0   0 require Pod::Usage;
399 0         0 Pod::Usage->import;
400 0         0 &pod2usage;
401 1         3 };
402              
403 1         3 Getopt::Long::Configure qw(bundling);
404             GetOptions(
405             'allstats|a' => \$allstats,
406             'allfiles|f' => \$allfiles,
407             'gather|g' => \$gather,
408             'follow|L' => \$follow,
409             'retain|r' => \$retain,
410 0     0   0 'update!' => sub { $noupdate = !$_[1] },
411 1     1   755 'u' => sub { $noupdate = !$_[1] },
412 1 50       25 'data|i=s' => \$data,
413             'dir|p=s' => \$root,
414             'verbose|v' => \$verbose,
415             'trace' => \$trace,
416             'help|h|?' => \$help,
417             'man' => \$man,
418             'debug' => \$debug,
419             ) or $pod2usage->(2);
420              
421 1 50 33     49 if ( $ident or $help or $man ) {
      33        
422 0         0 print STDERR ("This is $my_name $VERSION\n");
423             }
424 1 50 33     4 if ( $man or $help ) {
425 0 0       0 $pod2usage->(1) if $help;
426 0 0       0 $pod2usage->(VERBOSE => 2) if $man;
427             }
428 1 50       2 if ( @ARGV > 1 ) {
429 0         0 $pod2usage->(2);
430             }
431              
432 1 50       2 if ( defined $root ) {
433 0         0 $root =~ s;/+$;;;
434 0         0 $prefix = $root . "/";
435 0 0       0 $root = "/" if $root eq "";
436             }
437             else {
438 1         2 $prefix = $root = "";
439             }
440              
441 1 50       2 $table = @ARGV ? shift(@ARGV) : $prefix . ".du.ctl";
442 1 50       2 $runtype = $allfiles ? "file" : "directory";
443 1   33     3 $noupdate |= !$gather && ! $data && ! -s $data;
444              
445 1 50       4 if ( $debug ) {
446 0 0         print STDERR
    0          
    0          
    0          
    0          
    0          
    0          
447             ("$my_name $VERSION\n",
448             "Options:",
449             $debug ? " debug" : "" , # silly, isn't it...
450             $noupdate ? " no" : " " , "update",
451             $retain ? " " : " no", "retain",
452             $gather ? " " : " no", "gather",
453             $allstats ? " " : " no", "allstats",
454             "\n",
455             "Root = \"$root\", prefix = \"$prefix\"\n",
456             "Control file = \"$table\"\n",
457             $data ? (($gather ? "Output" : "Input") ." data = \"$data\"\n") : "",
458             "Run type = \"$runtype\"\n",
459             "\n");
460             }
461             }
462              
463             # Formats.
464              
465             format std_hdr =
466             Disk usage statistics@<<<<<<<<<<<<<<<<<<<<<@<<<<<<<<<<<<<<<
467             $subtitle, $date
468              
469             blocks +day +week @<<<<<<<<<<<<<<<
470             $runtype
471             -------- ------- ------- --------------------------------
472             .
473              
474             format std_out =
475             @>>>>>>> @>>>>>>> @>>>>>>> ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
476             $blocks, $d_day, $d_week, $name
477             .
478              
479             format all_hdr =
480             Disk usage statistics@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<
481             $subtitle, $date
482              
483             --0-- --1-- --2-- --3-- --4-- --5-- --6-- --7-- @<<<<<<<<<<<<<<<
484             $runtype
485             ------- ------- ------- ------- ------- ------- ------- ------- --------------------------------
486             .
487             format all_out =
488             @>>>>>> @>>>>>>> @>>>>>>> @>>>>>>> @>>>>>>> @>>>>>>> @>>>>>>> @>>>>>>> ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<..
489             @a, $name
490             .
491              
492             1;
493              
494             # Documentation appended from App::Dusage.
495              
496             =head1 NAME
497              
498             dusage - provide disk usage statistics
499              
500             =head1 SYNOPSIS
501              
502             dusage [options] ctlfile
503              
504             -a --allstats provide all statis
505             -f --allfiles also report file statistics
506             -g --gather gather new data
507             -i input --data=input input data as obtained by 'du dir'
508             or output with -g
509             -p dir --dir=dir path to which files in the ctlfile are relative
510             -r --retain do not discard entries which do not have data
511             -u --update update the control file with new values
512             -L resolve symlinks
513             -h --help this help message
514             --man show complete documentation
515             --debug provide debugging info
516              
517             ctlfile file which controls which dirs to report
518             default is dir/.du.ctl
519              
520             =head1 DESCRIPTION
521              
522             Ever wondered why your free disk space gradually decreases? This
523             program may provide you with some useful clues.
524              
525             B is a Perl program which produces disk usage statistics.
526             These statistics include the number of blocks that files or
527             directories occupy, the increment since the previous run (which is
528             assumed to be the day before if run daily), and the increment since 7
529             runs ago (which could be interpreted as a week, if run daily).
530              
531             B is driven by a control file that describes the names of the
532             files (directories) to be reported. It also contains the results of
533             previous runs.
534              
535             When B is run, it reads the control file, optionally gathers
536             new disk usage values by calling the B program, prints the report,
537             and optionally updates the control file with the new information.
538              
539             Filenames in the control file may have wildcards. In this case, the
540             wildcards are expanded, and all entries reported. Both the expanded
541             names as the wildcard info are maintained in the control file. New
542             files in these directories will automatically show up, deleted files
543             will disappear when they have run out of data in the control file (but
544             see the B<-r> option).
545              
546             Wildcard expansion only adds filenames that are not already on the list.
547              
548             The control file may also contain filenames preceded with an
549             exclamation mark C; these entries are skipped. This is meaningful
550             in conjunction with wildcards, to exclude entries which result from a
551             wildcard expansion.
552              
553             The control file may have lines starting with a dash C<-> that is
554             I followed by a C, which will cause the report to start a
555             new page here. Any text following the dash is placed in the page
556             header, immediately following the text ``Disk usage statistics''.
557              
558             The available command line options are:
559              
560             =over 4
561              
562             =item B<-a> B<--allstats>
563              
564             Reports the statistics for this and all previous runs, as opposed to
565             the normal case, which is to generate the statistics for this run, and
566             the differences between the previous and 7th previous run.
567              
568             =item B<-f> B<--allfiles>
569              
570             Reports file statistics also. Default is to only report directories.
571              
572             =item B<-g> B<--gather>
573              
574             Gathers new data by calling the B program. See also the C<-i>
575             (B<--data>) option below.
576              
577             =item B<-i> I or <--data> I
578              
579             With B<-g> (B<--gather>), write the obtained raw info (the output of the B program) to this file for subsequent use.
580              
581             Without B<-g> (B<--gather>), a data file written in a previous run is reused.
582              
583             =item B<-p> I or B<--dir> I
584              
585             All filenames in the control file are interpreted relative to this
586             directory.
587              
588             =item B<-L> B<--follow>
589              
590             Follow symbolic links.
591              
592             =item B<-r> B<--retain>
593              
594             Normally, entries that do not have any data anymore are discarded.
595             If this option is used, these entries will be retained in the control file.
596              
597             =item B<-u> B<--update>
598              
599             Update the control file with new values. Only effective if B<-g>
600             (B<--gather>) is also supplied.
601              
602             =item B<-h> B<--help> B<-?>
603              
604             Provides a help message. No work is done.
605              
606             =item B<--man>
607              
608             Provides the complete documentation. No work is done.
609              
610             =item B<--debug>
611              
612             Turns on debugging, which yields lots of trace information.
613              
614             =back
615              
616             The default name for the control file is
617             I<.du.ctl>, optionally preceded by the name supplied with the
618             B<-p> (B<--dir>) option.
619              
620             =head1 EXAMPLES
621              
622             Given the following control file:
623              
624             - for manual pages
625             maildir
626             maildir/*
627             !maildir/unimportant
628             src
629              
630             This will generate the following (example) report when running the
631             command ``dusage -gu controlfile'':
632              
633             Disk usage statistics for manual pages Wed Nov 23 22:15:14 2000
634              
635             blocks +day +week directory
636             ------- ------- ------- --------------------------------
637             6518 maildir
638             2 maildir/dirent
639             498 src
640              
641             After updating the control file, it will contain:
642              
643             - for manual pages
644             maildir 6518::::::
645             maildir/dirent 2::::::
646             maildir/*
647             !maildir/unimportant
648             src 498::::::
649              
650             The names in the control file are separated by the values with a C;
651             the values are separated by colons. Also, the entries found by
652             expanding the wildcard are added. If the wildcard expansion had
653             generated a name ``maildir/unimportant'' it would have been skipped.
654              
655             When the program is rerun after one day, it could print the following
656             report:
657              
658             Disk usage statistics for manual pages Thu Nov 23 17:25:44 2000
659              
660             blocks +day +week directory
661             ------- ------- ------- --------------------------------
662             6524 +6 maildir
663             2 0 maildir/dirent
664             486 -12 src
665              
666             The control file will contain:
667              
668             - for manual pages
669             maildir 6524:6518:::::
670             maildir/dirent 2:2:::::
671             maildir/*
672             !maildir/unimportant
673             src 486:498:::::
674              
675             It takes very little fantasy to imagine what will happen on subsequent
676             runs...
677              
678             When the contents of the control file are to be changed, e.g. to add
679             new filenames, a normal text editor can be used. Just add or remove
680             lines, and they will be taken into account automatically.
681              
682             When run without B<-g> (B<--gather>) option, it reproduces the report
683             from the previous run.
684              
685             When multiple runs are required, save the output of the B program
686             in a file, and pass this file to B using the B<-i> (B<--data>)
687             option.
688              
689             Running the same control file with differing values of the B<-f>
690             (B<--allfiles>) or B<-r> (B<--retain>) options may cause strange
691             results.
692              
693             =head1 CAVEATS
694              
695             The program will screw up when you have filenames with newlines in
696             them. This must be considered an early warning that there may be
697             some serious troubles ahead.
698              
699             =head1 COMPATIBILITY NOTICE
700              
701             This program is rewritten for Perl 5.005 and later. However, it is
702             still fully backward compatible with its 1990 predecessor.
703              
704             =head1 AUTHOR
705              
706             Johan Vromans, C<< >>
707              
708             =head1 SUPPORT AND DOCUMENTATION
709              
710             Development of this module takes place on GitHub:
711             https://github.com/sciurius/perl-App-Dusage.
712              
713             You can find documentation for this module with the perldoc command.
714              
715             perldoc App::Dusage
716              
717             Please report any bugs or feature requests using the issue tracker on
718             GitHub.
719              
720             =head1 LICENSE
721              
722             Copyright (C) 1990, 2000, 2021, Johan Vromans
723              
724             This module is free software. You can redistribute it and/or
725             modify it under the terms of the Artistic License 2.0.
726              
727             This program is distributed in the hope that it will be useful,
728             but without any warranty; without even the implied warranty of
729             merchantability or fitness for a particular purpose.
730              
731             =cut
732              
733             1;