#!/usr/bin/perl -w

use strict;

use backup;

my $DEDIKIT_HOME = "/opt/dedikit";
my $JOBFILE = "$DEDIKIT_HOME/upload/jobfile";
my $DEBUG_FILE = "$DEDIKIT_HOME/do_job.log";

my $POSSIBLE_START_MARKS = "start_autogen|# START_AUTOGEN_SECTION|# START_DEDIKIT_AUTOGEN_SECTION.*";

my $DEBUG = 0;

my $MERGE_ERROR;

my %RET;
$RET{ERROR} = '';
$RET{SOURCE} = 'do_job';
$RET{OUTPUT} = '';

# parsing command line
while(my $i = shift) {
	if($i eq '-d') {
		$DEBUG = 1;
	}
}
my $job_crypted_content = '';

if(!(open JOBFD, "<$JOBFILE")) {
	ERR("Failed to open jobfile ($!)");
}

map { $job_crypted_content .= $_ } <JOBFD>;
close JOBFD;

my $job_content = $job_crypted_content;

no strict;
my $data_href = eval $job_content;
use strict;

my %DATA = %{$data_href};

# processing filelist

my %AUTOGEN = %{$DATA{autogen}};
my $TRANS_ID = $DATA{transaction_id};

for my $idx (sort { $a <=> $b } keys %AUTOGEN) {
	DEBUG("Processing filegen \#$idx");

	my $source = $AUTOGEN{$idx}{name};
	my $fname = $AUTOGEN{$idx}{fname};
	my $type = $AUTOGEN{$idx}{type};
	my $mode = $AUTOGEN{$idx}{mode};
	my $perm = $AUTOGEN{$idx}{perm};
	my $content = $AUTOGEN{$idx}{content};
	my $start_mark = $AUTOGEN{$idx}{start_mark} || '';
	my $end_mark = $AUTOGEN{$idx}{end_mark} || '';

	$RET{SOURCE} = $source;

	my $script_is_temp = 0;
	if($type eq 'SCRIPT') {
		if(!$fname) {
			$script_is_temp = 1;
			$mode = 'REPLACE';
			$fname = "$DEDIKIT_HOME/autogen/tmp/TEMPSCRIPT.$source";
			chomp $fname;
		}
	} else {
		if(!$fname) { # not SCRIPT and no fname, error!
			ERR("Not SCRIPT and fname is empty");
		}
	}
	if(!$perm) {
		if($type eq 'SCRIPT') {
			if($script_is_temp) {
				$perm = '0700';
			} else {
				$perm = '0755';
			}
		} else { # PLAIN
			$perm = '0644';
		}
	}

	if($fname !~ /^\//) { # relative path, adding autogen
		DEBUG("Relative path in '$fname', adding '$DEDIKIT_HOME/autogen/'");
		$fname = "$DEDIKIT_HOME/autogen/$fname";
	}

	DEBUG("source='$source', fname='$fname', type='$type', mode='$mode', perm='$perm', start_mark='$start_mark', end_mark='$end_mark', temporary='$script_is_temp'");

	$RET{ERROR} = '';
	$RET{SOURCE} = $source;
	$RET{OUTPUT} = '';

	# creating dirs if not exists
	if($mode eq 'REPLACE') {
		my ($dir_to_create) = DIRNAME($fname);
		my $tmpdir = '';
		for my $dir_part (split /\//, $dir_to_create) {
			$tmpdir .= "$dir_part/";
			if(! -d $tmpdir) {
				DEBUG("Creating dir $tmpdir");
				if(!mkdir $tmpdir, 0711) {
					ERR("Failed to create dir $tmpdir ($!)");
				}
			}
		}
	}

	if($type eq 'SCRIPT') {

		my $TMP = `mktemp $fname.XXXXXX`;
		if(($? >> 8) != 0) {
			ERR("Error creating SCRIPT temp file ($!)");
		}

		chomp $TMP;
		open SCRIPT, ">$TMP";
		print SCRIPT $content;
		close SCRIPT;

		chmod oct($perm), $TMP;

		if(!rename $TMP, $fname) {
			ERR("Failed to rename $TMP -> $fname");
		}

		$RET{OUTPUT} = `$fname 2>&1`;
		my $retcode = $? >> 8;
		if($retcode != 0) {
			$RET{ERROR} = "Script $fname (source '$source') returned error, code $retcode";
			FINISH();
		} else {
			# no error and script is temporary
			if($script_is_temp) {
				unlink $fname;
			}
		}
	} else {
		# plain file
		if($TRANS_ID) { # making backup
			DEBUG("Backing up $fname");
			backup::do(fname => $fname, id => $TRANS_ID);
		}

		if($mode eq 'MERGE') {
			if(! -e $fname) {
#				ERR("MERGE mode used and target file '$fname' not exist");
				open TOUCH, ">>$fname"; chmod oct($perm), $fname; close TOUCH;
			}

			if(!merge(target => $fname, content => $content,
			  start_mark => $start_mark, end_mark => $end_mark)) {
		
				ERR("ERROR while merge PLAIN file (source '$source' ($MERGE_ERROR)");
			}
		} else {
			# REPLACE mode

			my $TMP = `mktemp $fname.XXXXXX`;
			if(($? >> 8) != 0) {
				ERR("Error creating PLAIN temp file (source '$source') ($!)");
			}

			chomp $TMP;

			chmod oct($perm), $TMP;
			open DESTFILE, ">$TMP";
			print DESTFILE $content;
			if(!close DESTFILE) {
				ERR("Error writing PLAIN temp file (source '$source') ($!)");
			}

			rename $TMP, $fname;
		}
	}

}

FINISH();

sub merge
{
	my (%arg) = @_;

	my $ERROR = '';

	my $content = $arg{content};
	my $target = $arg{target};
	my $start_mark = $arg{start_mark} || "# START_DEDIKIT_AUTOGEN_SECTION";
	my $end_mark = $arg{end_mark} || "# END_DEDIKIT_AUTOGEN_SECTION";
	my $should_be_marks = $arg{strict} || 0;

	if(!$target) {
		$ERROR = "INTERNAL: no target file";
		return undef;
	}

	my $RETCODE = 0;
	
	
	my $data_before = '';
	my $data_after = '';
	my $state = "BEGIN";
	
	if(!open(FD, "<$target") ) {
		$state = "END_PASSED";
		goto SKIP_DEST_READ;
	}
	
	my (undef, undef, $perm, undef, $uid, $gid) = stat FD;
	
	my $ORIG_CONTENT = '';
	while(<FD>) {
		my $orig_line = $_;
		$ORIG_CONTENT .= $orig_line;
	
		my $line = $orig_line;
		chomp $line;
	
		# checking if file already have old start/end marks
		if($state =~ /^(BEGIN|END_PASSED)$/ && 
		  $line =~ /^($POSSIBLE_START_MARKS)$/) {

			my $mark = $1;
			if($mark ne $start_mark) {
				$ERROR = "OLD start mark '$mark' found, remove old section first";
				goto FINISH;
			}
		}

		if($line eq $start_mark) {
			if($state ne "BEGIN") {
				$ERROR = "START mark while $state, file broken!";
				goto FINISH;
			}
			$state = "START_PASSED";
		}
		if($line eq $end_mark) {
			if($state ne "START_PASSED") {
				$ERROR = "END mark while $state, file broken!";
				goto FINISH;
			}
			$state = "END_PASSED"; 
			next; 
		}

		if($state eq "BEGIN") {
			$data_before = "${data_before}$orig_line";
		}
		if($state eq "END_PASSED") {
			$data_after = "${data_after}$orig_line";
		}
	
	}
	close(FD);
	
SKIP_DEST_READ:
	
	if($state eq "START_PASSED") {
		$ERROR = "No END mark, file broken!";
		goto FINISH;
	}
	if($state eq "BEGIN" && $should_be_marks) {
		$ERROR = "Strict mode used and no marks found!";
		goto FINISH;
	}
	
	my $data = $data_before;
	$data = "$data_before\n" if($data_before!~/^.*\n$/s);
	$data = "$data$start_mark\n$content";
	$data = "$data\n" if($data!~/^.*\n$/s);
	$data = "$data$end_mark\n";
	$data = "$data$data_after";
	
	chomp(my $OLD_WORKDIR = `pwd`);
	
	my $tmp_target = $target;
	for my $iter (1 .. 10) {
		if(-l $tmp_target) {
			$tmp_target = readlink $tmp_target;
			(my $new_dir, $tmp_target) = DIRNAME($tmp_target);
			chdir $new_dir;
		}
	}
	
	if(-l $tmp_target) {
		$ERROR = "Too many symlink levels at $target";
		goto FINISH;
	}
	
	# changing target to full path
	my ($__dirname, $target_fname) = DIRNAME($tmp_target);
	if($__dirname) {
		chdir $__dirname;
	}
	
	chomp(my $target_dir = `pwd`);
	$tmp_target = "$target_dir/$target_fname";
	
	$target = $tmp_target;
	
	chdir $OLD_WORKDIR;
	
	
	my $TMP = `mktemp $target.XXXXXX`;
	chomp($TMP);
	
	
	if(!open(FD, ">$TMP") ) {
		$ERROR = "Cant open $TMP for writing ($!)";
		goto FINISH;
	}
	
	if(!(print FD $data) ) {
		$ERROR = "Error writing to $TMP ($!)";
		goto FINISH;
	}
	
	close(FD);
	
	chown $uid, $gid, $TMP;
	chmod $perm, $TMP;
	
	# be safe, do not touch any if new content is equal to old
	if($ORIG_CONTENT eq $data) {
		unlink $TMP;
		goto FINISH_OK;
	}
	
	if(!rename($TMP, $target)) {
		$ERROR = "Error renaming $TMP -> $target ($!)";
		goto FINISH;
	}

FINISH_OK:
	# all ok
	$RETCODE = 1;

FINISH:
	
	if($ERROR) {
		$MERGE_ERROR = $ERROR;
	}

	return $RETCODE;
}

sub DIRNAME
{
	my ($fname) = @_;

	if($fname =~ /(^.*\/)(.*$)/) {
		return ($1, $2);
	}

	return ('', $fname);
}

sub DEBUG
{
	if(!$DEBUG) {
		return;
	}

	my ($msg) = @_;

	open(DO_JOB_DEBUG, ">>$DEBUG_FILE");
	print DO_JOB_DEBUG "$msg\n";
	close DO_JOB_DEBUG;

	print STDERR "DBG: $msg\n";
}

sub FINISH
{
	print "ERROR=$RET{ERROR}\nSOURCE=$RET{SOURCE}\n\n$RET{OUTPUT}";
	exit;
}

sub ERR
{
	my ($error) = @_;

	if($error) {
		$RET{ERROR} = $error;
	}

	FINISH();
}

sub DO_BACKUP
{
	my (%arg) = @_;

	my $fname = $arg{fname};
	my $trans_id = $arg{trans_id};

	my $TRANS_DIR = "$DEDIKIT_HOME/fs_transactions";
	mkdir $TRANS_DIR, 0700;
	mkdir "$TRANS_DIR/$trans_id", 0700;
	my ($dirname) = DIRNAME($fname);

	my $tmpdir = "$TRANS_DIR/$trans_id";
	for my $dir_part (split /\//, $dirname) {
		$tmpdir .= "$dir_part/";
		mkdir $tmpdir, 0700;
	}
	open(IN_BACKUP, "<$fname");
	open(OUT_BACKUP, ">$TRANS_DIR/$trans_id/$fname");
	while(<IN_BACKUP>) {
		print OUT_BACKUP $_;
	}
	close IN_BACKUP;
	close OUT_BACKUP;
}
