#!/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 %files = {};		# make sure we do each file name only once
my %inodes = {};	# make sure we do each inode only onc3
my $VERSION = "0.4";

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;

	$tmp = mktemp("$cache.XXXXX");
	link($cache, "$tmp") || 
	    die "link from $cache to $tmp failed";
	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 ($files{$f})) {
		print STDERR "Skipping duplicate $f\n";
		next;
	}
	$files{$f} = 1;
	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";
		link_copy($f, $orig_link);
		utime $st->atime, $st->mtime, $f;
		next;
	}
	$inodes{"$dev:$ino"} = $f;
	while( -e "$cachedir/$index") {
		$index++;
	}
	print STDERR "Processing $f to $cachedir/$index\n";
	copy("$f", "$cachedir/$index") || die "copy failed for $f";
	link_copy($f, "$cachedir/$index");
	utime $st->atime, $st->mtime, $f;

	# 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++;
}

