#!/usr/bin/perl -w

# $Id$
# $URL$

#
# Copyright (c) 2003 Ward Wouts <ward@wouts.nl>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#


use strict;
use Sys::Hostname;
use Getopt::Std;
use File::Basename;
use File::Copy;
use Cwd;
use Cwd 'abs_path';
use vars qw(
	$hostname
	$cvspath
	$editor
	$catcmd
	$chowncmd
	$cpcmd
	$cvscmd
	$mkdircmd
	$sudocmd
	$permsfile
);

$hostname = hostname();
#my $cvspath = "$ENV{HOME}/worktrees/systems/$hostname";
$editor = "vi";
$catcmd = "/bin/cat";
$chowncmd = "/sbin/chown";
$cpcmd = "/bin/cp";
$cvscmd = "/usr/bin/cvs";
$mkdircmd = "/bin/mkdir";
$sudocmd = "/usr/bin/sudo";

&readconfig;

unless ( $permsfile ) {
	$permsfile = $cvspath ? "$cvspath/PERMS" : "";
}

my %option = ();
&cmdline;

if ( $cvspath ) {
	&checkcvspath;

	foreach ( @ARGV ) {
		if ( -f $_ ) {
			&incvs($_);				# check if file exists in cvs else make it so
			next unless &inperms($_);		# check if file perms are registered or make it so
			unless (&cmptocvs($_)) {		# compare file to cvs
				print "$_ not in sync with cvs version\n";
				if ( $option{f} ) {
					print "cvs update forced\n";
					&cvsupdate($_);
					if ( $option{e} ) {
						&edit($_);
						unless (&cmptocvs($_)) {
							&cvsupdate($_);
						}
					}
				} else {
					print "please fix this\n";
					print "press enter to continue\n";
					my $line = <>;
					next;
				}
			}
			if ( $option{e} ) {
				&edit($_);			# edit file
				unless (&cmptocvs($_)) {	# if changed... then:
					&cvsupdate($_);		# copy file to cvs & check in into cvs
								# copy file back; don't change permissions
				}
			}
		} else {
			if ( ! -e $_ && $option{e} ) {
				&edit($_);			# edit file
				if ( -f $_ ) {
					&cvsupdate($_);		# copy file to cvs & check in into cvs
								# copy file back; don't change permissions
				}
			} else {
				print "$_ is not a file, skipping. Press enter to continue.\n";
				my $enter = <>;
			}
		}
	}
} else {
	foreach ( @ARGV ) {
		&backup($_);
		if ( $option{e} ) {
			&edit;
		}
	}
}

sub cmdline {
	getopts("efhp", \%option);

	if ( $option{h} ) { &help; }
	if ( scalar(@ARGV) == 0 ) { &help; }
}

sub readconfig {
	if ( -f "$ENV{HOME}/.mycprc" && -r "$ENV{HOME}/.mycprc" ) {
		do "$ENV{HOME}/.mycprc" or die "Couldn't parse $ENV{HOME}/.mycprc";
	} elsif ( -f "/etc/mycp" && -r "/etc/mycp" ) {
		do "/etc/mycp" or die "Couldn't parse /etc/mycp";
	}
}

sub backup($) {
	my $file = shift;
	my ( $base, $dir, $backup, @stat, @timestamp, $sfx, $n );
	$base = basename($file);
	$dir = dirname($file);
	my @months = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
	if ( -f $file ) {
		@stat = stat($file);
		@timestamp = localtime($stat[9]);
		$timestamp[5] += 1900;
		if ( length($timestamp[3]) < 2 ) { $timestamp[3] = "0$timestamp[3]"; }

#
# ouderwetse moron style
# sane style introduceren
#

		$sfx = "$timestamp[3]$months[$timestamp[4]]$timestamp[5]";

		print "Creating \"$dir/OLD\" directory\n";
		unless ( -d "$dir/OLD" ) {
			if ( -w $dir ) {
				mkdir "$dir/OLD", 0777 or die "Couldn't create $dir/OLD: $!";
			} else {
				system("$sudocmd mkdir " . &escape("$dir/OLD"));
			}
		}
		$backup = "$dir/OLD/$base.$sfx";

		if ( -e $backup && ! &cmp($file, $backup) ) {
			$n = 1;
			while ( -e "$backup-$n" && ! &cmp($file, "$backup-$n") ) {
				$n++;
			}
			$backup = "$backup-$n";
		}

		if ( -w $file || -w &fullpath($file) ) {
			copy("$file", "$backup") or die "Couldn't copy $file to $backup: $!";
		} else {
			system("$sudocmd $cpcmd " . &escape($file) . " " . &escape($backup));
		}

	} else {
		die "No such file: $file";
	}
}

sub incvs($) {
	my $file = shift;
	my $path = &fullpath($file);
	if ( -f "$cvspath/$path/" . basename($file) ) {
		return 1;
	} else {
		&cvsadd($file);
		&checkin($file);
	}
}

sub inperms($) {
	my $file = shift;
	my $path = &fullpath($file);
	my $fullname = "$path/" . basename($file);
	my @stats = stat $fullname;
	my $user = (getpwuid($stats[4]))[0];
	my $group = (getgrgid($stats[5]))[0];
	my $mode = sprintf "%lo", $stats[2];
	$mode =~ s/.*(\d{4})$/$1/;

	unless ( -f $permsfile ) {
		open FH, ">$permsfile";
		print FH <<EOT;
# \$Id\$
# \$Source\$

EOT
		close FH;
		my $cwd = cwd;
		chdir dirname($permsfile);
		system("$cvscmd add " . basename($permsfile));
		system("$cvscmd ci -m initial " . basename($permsfile));
		chdir $cwd;
	}

	my $perms = &slurp($permsfile);

	if ( $perms =~ /^(\d{4}):([^-:][^:]*):([^:]+?):($fullname)$/m ) {
		if ( $1 eq $mode && $2 eq $user && $3 eq $group ) {
			return 1;
		} elsif ( $option{p} ){
			$perms =~ s/^(\d{4}):([^-:][^:]*):([^:]+?):$fullname$/$mode:$user:$group:$fullname/m;
			open(FH, ">>$permsfile");
			print FH $perms;
			close FH;
			return 1;
		} else {
			print "$_ perms not in sync with registered version\n";
			print "please fix this\n";
			print "press enter to continue\n";
			my $line = <>;
			return 0;
		}
	} else {
		
		open(FH, ">>$permsfile");
		print FH "$mode:$user:$group:$fullname\n";
		close FH;
		my $cwd = cwd;
		chdir dirname($permsfile);
		system("$cvscmd ci -m \"adding $fullname\" " . basename($permsfile));
		chdir $cwd;
		return 1;
	}
}

sub edit($) {
	if ( exists $ENV{'EDITOR'} ) {
		$editor = $ENV{'EDITOR'};
	}
	if ( -w $_ ) {
		system("$editor " . &escape($_));
	} else {
		system("$sudocmd $editor ". &escape($_));
	}
}

sub checkcvspath($) {
	unless ( -d $cvspath ) {
		print "$cvspath is not a directory. Set up cvs environment first.\n";
		exit;
	}
}

sub cvsadd($) {
	my $file = shift;
	my $cwd = cwd;
	my $path = &fullpath($file);
	$path =~ s|^\/||;
	my $checkpath = $cvspath;
	my $parent = $cvspath;
	print "PATH: $path\n";
	foreach my $i (split '/', $path) {
		$checkpath .= "/$i";
		if ( -d $checkpath ) {
			print "$i issa dir\n";
		} elsif ( -e $checkpath ) {
			print "$i is not a dir, PROBLEM. Fix this by hand.\n";
			exit;
		} else {
			print "making $checkpath\n";
			mkdir $checkpath;
			print "cd $parent\n";
			chdir $parent;
			system("$cvscmd add " . &escape($i));
		}
		$parent = $checkpath;
	}
	print "FILE: $file\n";
	print "PATH: $checkpath\n";
	chdir $cwd;
	copy($file, $checkpath) || die "error copying file to cvstree: $!";
	chdir $checkpath;
	system("$cvscmd add " . &escape(basename($file)));
	chdir $cwd;
}

sub cvsupdate($) {
	my $file = shift;
	&copytocvs($file);
	&checkin($file);
	&copyfromcvs($file);
}

sub copytocvs($) {
	my $file = shift;
	my $cwd = cwd;
	my $path = "$cvspath/" . &fullpath($file);
	if ( -r $file ) {
		copy($file, $path) or die "Couldn't copy file: $!";
	} else {
		system("$sudocmd $cpcmd $file $path");
		system("$sudocmd $chowncmd $<:" . (split(' ', $())[0] . " $path/" . basename($file));
	}
	chdir($path);
	system("$cvscmd ci " . &escape(basename($file)));
	chdir $cwd;
}

sub checkin($) {
	my $file = shift;
	my $cwd = cwd;
	my $path = &fullpath($file);
	chdir "$cvspath/$path";
	system("$cvscmd ci " . &escape(basename($file)));
	chdir $cwd;
}

sub copyfromcvs($) {
	my $file = shift;
	my $path = &fullpath($file);
	my $fullname = "$path/" . basename($file);
	if ( -w $fullname) {
		copy("$cvspath/$fullname", $fullname);
	} else {
		system("$sudocmd $cpcmd " . &escape("$cvspath/$fullname") . " " . &escape($fullname));
	}
	# also check file permissions
}

sub cmptocvs($) {
	my $file = shift;
	my $path = &fullpath($file);
	my $fullname = "$path/" . basename($file);
	return &cmp("$cvspath/$fullname", $fullname);
}

sub cmp($$) {
	my $source = shift;
	my $target = shift;
	my $sf = &slurp($source);
	my $tf = &slurp($target);
	$tf eq $sf;
}

sub askyn($) {
	my $question = shift;
	while (1) {
		print "$question [y/n]? ";
		my $line = <>;
		if ( $line =~ /^y$/ ) {
			return 1;
		} elsif ( $line =~ /^n$/ ) {
			return 0;
		}
	}
}

sub escape($) {
	my $string = shift;
	$string =~ s/([ \&\;\`\'\\\"\|\*\?\~\<\>\^\(\)\[\]\{\}\$\010\013\020\011])/\\${1}/g;
	return $string;
}

sub fullpath($) {
	my $file = shift;
	my $path = dirname($file);
	if ( $path =~ /^\// ) {
		return $path;
	} elsif ( $path =~ /^\.$/ ) {
		return cwd;
	} else {
		$path = cwd . "/" . dirname($file);
		$path = abs_path($path);
		return $path;
	}
}

sub slurp($) {
	my $file = shift;
	if ( -r $file) {
		open(FH, "<$file") or die "Couldn't open $file: $!";
	} else {
		open(FH, "$sudocmd $catcmd $file|") or die "Couldn't exec $sudocmd $catcmd $file: $!";
	}
	my $result = do { local $/; <FH>; };
	close FH;
	return $result;
}

sub help {
	print <<EOT;

Usage: mycp [-e] [-f] [-h] <file1> [file2] ...";

	-e		edit file
	-f		force current version into cvs
	-h		show this help message

EOT
	exit;
}
