#!/usr/bin/perl

use strict;
use File::Copy;
use File::stat;
use Getopt::Long;
use Fcntl ':mode';
use File::Temp ':mktemp';

my $cachedir = "/appcache/boot";
my $index = 0;
my $ret;
my %inodes = {};	# make sure we do each inode only onc3
my %hardlinks = {};
my %stats = {};
my @order = ();
my $VERSION = "0.8";

sub print_usage {
	print STDERR "bootcache $VERSION\n";
	print STDERR "usage: bootcache [ -c cache dir ]\n";
	print STDERR "\tfeed files to cache over stdin\n";
	exit 1;
}

sub link_copy($$) {
	my ($f, $cache) = @_;
	my $tmp;
	my $sum;
	my $tmpsum;

	$tmp = mktemp("$cache.XXXXX");
	link($cache, "$tmp") || 
	    die "link from $cache to $tmp failed";

	if (0) {
		$sum = `md5sum $f`;
		chomp $sum;
		$sum =~ s/ .*$//;
		$tmpsum = `md5sum $tmp`;
		chomp $tmpsum;
		$tmpsum =~ s/ .*$//;
		if ($sum ne $tmpsum) {
		    print STDERR "md5sums of $f ($sum) and $tmp ($tmpsum) don't match, stopping\n";
		    exit(1);
		}
	}
	rename($tmp, $f) || die "rename $tmp $f failed";
}

$ret = GetOptions("cachedir=s" => \$cachedir) || print_usage();
if (scalar(@ARGV)) {
	print_usage();
}

if (! -d $cachedir) {
	print STDERR "creating $cachedir\n";
	system("mkdir -p $cachedir");
	if ($?) {
	    my $ret = $? >> 8;
	    die "Unable to create $cachedir error $ret";
	}
} 
printf "Using $cachedir as the cache directory\n";

my $cachestat = stat($cachedir) || die "Stat failed on $cachedir";
while(<>) {
	my $ret;
	my $dev;
	my $ino;

	chomp;
	my $f = $_;
	if (! -r $f) {
		print STDERR "Skipping $f not readable\n";
		next;
	}
	if (defined ($hardlinks{$f})) {
		print STDERR "Skipping duplicate $f\n";
		next;
	}
	my $st = lstat($f) || die "Stat failed on $f";

	$dev = $st->dev;
	$ino = $st->ino;
	if (!S_ISREG($st->mode)) {
		print STDERR "Skipping $f (not regular)\n";
		next;
	}
	if ($dev != $cachestat->dev) {
		print STDERR "$f is on a different device then $cachedir, skipping\n";
		next;
	}

	if (defined($inodes{"$dev:$ino"})) {
		my $orig_link = $inodes{"$dev:$ino"};
		print STDERR "linking $f back to $dev:$ino $orig_link\n";
		utime $st->atime, $st->mtime, $f;
		my $ref = $hardlinks{$orig_link};
		push @$ref, $f;
	} else {
		$inodes{"$dev:$ino"} = $f;
		$hardlinks{$f} = [$f];
		$stats{$f} = $st;
		push @order, $f;
	}
}

foreach my $o (@order) {
	my $ref = $hardlinks{$o};
	foreach my $ol (@$ref) {
		print "$o: $ol\n";
	}
}

foreach my $f (@order) {
	my $st = $stats{$f};
	while( -e "$cachedir/$index") {
		$index++;
	}
	print STDERR "Processing $f to $cachedir/$index\n";
	copy("$f", "$cachedir/$index") || die "copy failed for $f";
	my $ref = $hardlinks{$f};
	foreach my $ol (@$ref) {
		link_copy($ol, "$cachedir/$index");
		utime $st->atime, $st->mtime, $ol;
	}

	# TODO copy acls/xattrs
	$ret = chown $st->uid, $st->gid, "$cachedir/$index";
	if ($ret != 1) {
		die "chown $st->uid, $st->gid, failed for $cachedir/$index ($f)";
	}
	$ret = chmod $st->mode, "$cachedir/$index";
	if ($ret != 1) {
		die "chmod $st->mode failed for $cachedir/$index ($f)";
	}
	$index++;
}

