#!/usr/bin/perl -w

use strict;
use FileHandle;

my $cmdline = join(' ',$0,@ARGV);
my $out = shift(@ARGV) or die "need output file";
my $in  = shift(@ARGV) or die "need input file";
my $inh = new FileHandle $in, "r" or die "could not open file $in";
my $newh="$out.h.perlnew";
open(HEADER,">$newh");
open(SOURCE,">$out.c");

print SOURCE "/* this file was automatically generated by $cmdline */

#include \"base.h\"
\n";
my @funcnames=();
my @lines=<$inh>;
my $funcre=qr/(\w+\s*\*?)\s+(\w+)\s*\((.*)\)\s*;/;
foreach(@lines) {
	s"#.*"";
	s"//.*"";
	if(m"^\s*$") {next}
	unless(/$funcre/) {next}
	my $rettype=$1;
	my $funcname=$2;
	my $params=$3;
	print HEADER "#if defined(__NR_$funcname)\n";
	print HEADER "extern $rettype redirecting_sys_$funcname($params);\n";
	print HEADER "extern $rettype (*orig_sys_$funcname)($params);\n";
	print HEADER "#endif\n";
	print SOURCE "#if defined(__NR_$funcname)\n";
	print SOURCE "$rettype (*orig_sys_$funcname)($params);\n";
	print SOURCE "#endif\n";
	push(@funcnames,$funcname);
}
print SOURCE "
#define redir(a,b,c) b=sys_call_table[a];sys_call_table[a]=c
#define unredir(a,b,c) sys_call_table[a]=b

/* redirect syscalls (save old, install new) */
void init_redir_calltable(void)
{
";
foreach(@funcnames) {
	print SOURCE "#if defined( __NR_$_)\n";
	print SOURCE "\tredir(__NR_$_, orig_sys_$_, redirecting_sys_$_);\n";
	print SOURCE "#endif\n";
}
print SOURCE "}

/* unredirect syscalls
 this is problematic when several syscall-redirecting modules are loaded
 and not unloaded in reverse order!!!
*/
void restore_redir_calltable(void)
{
";
foreach(@funcnames) {
	print SOURCE "#if defined(__NR_$_)\n";
	print SOURCE "\tunredir(__NR_$_, orig_sys_$_, redirecting_sys_$_);\n";
	print SOURCE "#endif\n";
}
print SOURCE "}\n\n";

foreach(@lines) {
	if(m"^\s*$") {next}
        my $symfollow=0; while(s/^\^//g) {$symfollow++;}
	my $creation=0; while(s/^://g) {$creation++;}
	my $headonly=0; while(s/^-//g) {$headonly++;}
	my $redirtype=0; while(s/^\+//g) {$redirtype++;}
	my $redirflags=("0","LOOKUP_MKDIR","LOOKUP_MKDIR|LOOKUP_CREATE")[$redirtype];
	unless(/$funcre/) {next}
	if($headonly>1) {next}
	my $rettype=$1;
	my $funcname=$2;
	my $params=$3;
	my @params=split(/\s*,\s*/,$params);
	my @paramnames=();
	my @localparams=();
	my @localdefs=();
	my @inputcopy=();
	my @outputcopy=();
        my @redir=();
	my $varcount=0;
	my $xn;
        my $lastresult="rresult";
	my $laststr;
	my $firststr;
        if($creation&1) { $redirflags.="|LOOKUP_CREATES" }
        if($symfollow&1) { $redirflags.="|LOOKUP_NOFOLLOW" }
	foreach(@params) {
	  /(\w+)$/;
	  my $name=$1;push(@paramnames,$name);
	  if(/(.*)\*\s*\w+$/){
		my $n="local$varcount";
		local $_=$1;
		my $input=s/const//g;s/^\s*//;s/\s*$//;
		my $def="\t$_ $n";
		my $addr="";
		my $pn=$paramnames[$varcount];
		my $nn=$params[$varcount+1];
		$nn=~s/.*\W// if $nn; # name of following var
		if(/char/) {
			$def.="[REDIR_BUFSIZE]";
			$firststr=$n unless defined $firststr;
			if($input) {
                                my $ispath=($pn=~/path|file/);
                                push(@inputcopy,"if(strncpy_from_user($n, $pn, REDIR_BUFSIZE)<0) return -EFAULT;");
                                if($ispath){
                                        $laststr=$n;
                                        if($varcount>=1){push(@localdefs,"\tint rresult2;");$lastresult="rresult2";push(@inputcopy,"if((rresult2=redirect2($n, $redirflags))<0) return rresult2;");}}
                                        push(@redir,[$n,$pn,$lastresult,$redirflags]);
                        } else {
				$xn=$n."size";
				splice(@inputcopy,1,0,"size_t $xn=($nn<REDIR_BUFSIZE?$nn:REDIR_BUFSIZE);");
				push(@outputcopy,"if(result>=0){if(copy_to_user($pn, $n, result)) return -EFAULT;}")
			}
		} elsif(/void/) {
			$addr="p";
			$def=~s/$n/*p$n=NULL/;
			push(@inputcopy,"if($nn>0 && $pn) { p$n=kmalloc($nn,GFP_KERNEL); }");
			if($input) {
				$inputcopy[-1]=~s/}/if(copy_from_user(p$n, $pn, $nn)) {kfree(p$n);return -EFAULT;} }/;
			}
			else {
				push(@outputcopy,"if(p$n && result>=0 && $nn>0 && copy_to_user($paramnames[$varcount], p$n, result)) {kfree(p$n);return -EFAULT;}");
			}
			push(@outputcopy,"if(p$n) kfree(p$n);");
		} else {
			if($input) {$addr="p"; $def.=", *p$n=NULL";push(@inputcopy,"if($pn && copy_from_user((p$n=&$n), $pn, sizeof($n))) return -EFAULT;")}
			else {$addr="&"; push(@outputcopy,"if(copy_to_user($pn, &$n, sizeof($n))) return -EFAULT;")}
		}
		push(@localdefs,$def.";");
		push(@localparams,"$addr$n");
	  } else {
	  	if($xn) { $name=$xn; $xn=undef; }
	  	push(@localparams,$name);
	  }
	  $varcount++;
	}
	my $manyargs=$laststr ne $firststr;
	if($funcname eq "symlink") {my $h=$inputcopy[1];$inputcopy[1]=$inputcopy[0];$inputcopy[0]=$h;$firststr=$laststr;$inputcopy[2]="";$localdefs[1]="";$lastresult="rresult";}
	if($creation&1) {
		push(@inputcopy, "if($lastresult&4) BEGIN_KMEM orig_sys_unlink($laststr); END_KMEM");
		push(@outputcopy, "if(result<0 && $lastresult&4) translucent_create_whiteout($laststr);"); 
	}
	if($creation&2) { push(@outputcopy, "if(result==0 && (rresult&2) && !(translucent_flags&no_whiteout)) translucent_create_whiteout(local0);"); }

# individual patches
	if($funcname eq "access") {$redirflags.="|(mode==2/*W_OK*/?LOOKUP_MKDIR|LOOKUP_CREATE:0)"}
	if($funcname eq "mkdir") {$inputcopy[0].=" rresult=strlen(local0)-1;if(local0[rresult]=='/'){local0[rresult]=0;}";}
        if($funcname eq "link") {$redirflags="0"}
        if($funcname eq "rename") {$redirflags=~s/\|LOOKUP_CREATES//}
        if($funcname eq "unlink") {$redirflags.="|LOOKUP_TRUNCATE";}

	my $paramnames=join(", ", @paramnames);
	my $localdefs=join("\n",@localdefs); #"char local0[REDIR_BUFSIZE+1];";
	my $localparams=join(", ", @localparams);
	my $inputcopy=""; foreach(@inputcopy[1..$#inputcopy]) {$inputcopy.="\n\t\t".$_};
	my $outputcopy=""; foreach(@outputcopy) { $outputcopy.="\n\t\t".$_}
	print SOURCE "#if defined(__NR_$funcname)
$rettype redirecting_sys_$funcname($params)\n{\n\tint rresult;\n";
	unless($headonly) {
		print SOURCE "$localdefs
	$inputcopy[0]
	if((rresult=redirect2($firststr, $redirflags))>0) {
		$rettype result;$inputcopy
		BEGIN_KMEM
			result = orig_sys_$funcname($localparams);
		END_KMEM$outputcopy
		if(no_fallback(result)) return result;
	}
	if(rresult<0) return rresult;
	return orig_sys_$funcname($paramnames);
";}
	print SOURCE "}\n#endif\n\n";
}

print SOURCE "\n/* EOF */\n";
close(SOURCE);
close(HEADER);


# prevent recompiling all files on running makeext.pl with no header changes
system("diff", "--brief", "-N", "$out.h", $newh);
my $diff=$? >> 8;
if($diff) { rename($newh, "$out.h") }
else { unlink($newh); }

