Subject: bug#10281: change in behavior of du with multiple arguments (commit

From: "Alan Curry" <[email-masked]>
Date: Sat, 17 Dec 2011 03:39:55 -0500 (GMT+5)

Paul Eggert writes:
> 
> On 12/16/11 18:36, Alan Curry wrote:
> > The straightforward method would be to simply the directory you intend to
> > remove and keep track of the discrepancy between st_nlink and how many links
> > you've seen.
> 
> Sorry, I can't parse that.  But whatever it is, it sounds like you're
> talking about what one could do with a program written in C, not with
> either GNU or Solaris du.

Yes, I'm saying that du is just not the tool for this job, although you've
managed to twist it to fit.

The "predict free space after rm -rf foo" operation can be done without
searching other directories and without requiring the user to specify a list
of other directories that might contain links. What you do with du is kludgy
by comparison.

[...]
> Of course I'd never want to do that in an actual link farm: it's tricky
> and brittle and could mess up currently-running builds.  But the point is that
> GNU du is not being inefficient here, any more than Solaris du is.
> 

By comparison to a proper tool which doesn't do any unnecessary traversals of
extra directories, your use of du is slow and brittle (if the user forgets
an alternate directory containing a link, the result is wrong) and has only
the slight advantage of already being implemented.

Here's a working outline of the single-traversal method. I wouldn't suggest
that du should contain equivalent code. A single-purpose perl script, even
without pretty output formatting, feels clean enough to me. Since I've gone
to the trouble (not much) of writing it, I'll keep it as ~/bin/predict_rm_rf
for future use.

#!/usr/bin/perl -W
use strict;
use File::Find;

@ARGV or die "Usage: $0 directory [directory ...]\n";

my $total = 0;
my %pending = ();

File::Find::find({wanted => sub {
  my ($dev,$ino,$nlink,$blocks) = (lstat($_))[0,1,3,12];
  if(-d _ || $nlink==1) {
    $total += $blocks;
    return;
  }
  if($nlink == ++$pending{"$dev.$ino"}) {
    delete $pending{"$dev.$ino"};
    $total += $blocks;
  }
}}, @ARGV);

print "$total blocks would be freed by rm -rf @ARGV\n";
__END__

-- 
Alan Curry