#!/usr/bin/perl
use warnings FATAL => qw/all/;
use strict;
use Carp;
use Fcntl;
use Sys::Hostname ();

# Generate sorted usage reports for /home type filesystems

my $du_path	= '/usr/bin/du';
my $df_path	= '/bin/df';
my $hostname	= Sys::Hostname::hostname();
my $def_outdir	= '/usr/local/share/';

sub stats_for	($$%); # directory-tree, usagefile
sub read_cmd	($$@);

my $dir = '/home';
my $out = 'USAGE-home';
my %extras;
$extras{symlinks} = 1 unless scalar @ARGV;

$dir = shift @ARGV if scalar @ARGV > 0;
$out = shift @ARGV if scalar @ARGV > 0;
foreach (@ARGV) {
	if (m/^([^:]+):(.*)$/) {
		$extras{$1} = $2;
	} else {
		$extras{$_} = 1;
	}
}

stats_for($dir, $out, %extras);

exit 0;


sub stats_for ($$%)
{
	my ($dir, $usagefile, %opts) = @_;

	my $outdir = $def_outdir;
	if ($usagefile =~ m:(.+)/([^/]+):) {
		$outdir = $1;
		$usagefile = $2;
	}
	die "Not a directory: $outdir\n" unless -d $outdir;


	if (not chdir $dir) {
		carp "Can't change to dir '${dir}' - skipping\n";
		return;
	}

	my @disk_stats = read_cmd 'df', $df_path, qw(-k .);
# Example output:
#Filesystem   1K-blocks     Used    Avail Capacity  Mounted on
#/dev/wd0s1h    7145014  2392261  4181152    36%    /home
# Want:
# Partition Size: 7145014k Used: 2392261k (36%) Avail: 4181152k Name: /home
# Well, actually we want "df -h" but nevermind ...
	chomp $disk_stats[1];
	if (not $disk_stats[1] =~
			/^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+%)\s+(.+)$/) {
		croak "Bad df output: ${disk_stats[1]} malformed\n";
	}
	my $disk_line = "# Partition Size: $1k Used: $2k ($4) Avail: $3k" .
		" Name: $5\n";

	opendir(DIR, '.')
		or croak "Can't open current directory: $!\n";
	my @list = grep { -d $_ and $_ !~ /^\.{1,2}/ } readdir(DIR);
	closedir(DIR);

	my $du_opts = '-xsk';
	$du_opts .= 'H' if exists $opts{symlinks} and $opts{symlinks};
	my @lines = read_cmd 'du', $du_path, $du_opts, @list;


	if (not chdir $outdir) {
		croak "Can't change to dir '${outdir}'\n";
		return;
	}

	my $ofd;
	if ($usagefile ne '-') {
		sysopen(OUT, "${usagefile}.new", O_WRONLY|O_TRUNC|O_CREAT)
			or croak "Can't write to '${usagefile}.new': $!\n";
		$ofd = select OUT;
	}

	print "# Autogenerated disk usage (in kB) for ${hostname}:${dir}\n";
	print "# @{[scalar gmtime()]} UTC\n";
	print $disk_line;
	print sort {($b =~ /^(\d+)/)[0] <=> ($a =~ /^(\d+)/)[0]} @lines;

	if ($usagefile ne '-') {
		select $ofd;
		close(OUT);

		chmod 0644, "${usagefile}.new";
		rename $usagefile, ".${usagefile}.old" if -f $usagefile;
		rename "${usagefile}.new", $usagefile;
		chmod 0640, ".${usagefile}.old" if -f ".${usagefile}.old";
	}

	return;
}

sub read_cmd ($$@)
{
	my ($cmdname, $path) = ($_[0], $_[1]);
	shift; shift;

	my $pid = open(CMD, "-|");
	croak "Can't fork: $!\n" unless defined $pid;

	if (not $pid) { # child
		exec {$path} ($cmdname, @_)
			or croak "Exec of '${path}' failed: $!\n";
	}
	my @lines = <CMD>;
	close(CMD);

	if ($? != 0) {
		my ($ex, $sig, $core) = ($? >> 8, $? & 127, $? & 128);
		my $msg = "$cmdname died";
		$msg .= ", exitting $ex"	if $ex;
		$msg .= ", signal $sig"		if $sig;
		$msg .= ' (core dumped)'	if $core;
		croak $msg;
	}

	return @lines;
}
