=pod
perl -w xor-bigint.pl --debug=1 --dir='/home/guest/xor-test' --re='test/5\.txt$' --key='xyz0 % ^ &88**' --rotate_key='<<' --area_size='10%' --area_offset='30%'
perl -w xor-bigint.pl --debug=1 --dir='/home/guest/xor-test' --re='test/5\.txt$' --key='xyz0 % ^ &88**' --rotate_key='<<' --area_size=1000 --area_offset=4000

=cut

use strict;
use Getopt::Long;
use File::Find;
use Math::BigInt try => 'GMP';
#~ use POSIX ":sys_wait_h"; # perldoc -f waitpid
#~ use Fcntl;                          # for SEEK_SET and SEEK_CUR
# 0 to set the new position to POSITION; SEEK_SET
# 1 to set the it to the current position plus POSITION; SEEK_CUR


my %arg=(forks=>1, dir=>'.', area_offset=>0, count=>1, test=>0, rotate_start_bit=>1,); #
GetOptions(
	'forks=s'=>\$arg{forks}, #
	'dir=s'=>\$arg{dir}, # default='.' full path!!
	're=s'=>\$arg{re}, # �������������������� ������������������ ������ �������������� ���������� ���������� (���������� - ������ ����������) default=undef
	'key=s'=>\$arg{key}, #mandatory
	'area_offset=s'=>\$arg{area_offset}, # �� ������������ �������������������� ���� ������������ ���������� default=0 (���������� �� %)
	'area_size=s'=>\$arg{area_size}, # �� ������������ undef -> ���� ���������� (���������� �� %)
	# 4 ������������������ ������������������ ������ ���������������� ���������� ����������
	'rotate_key=s'=>\$arg{rotate_key}, # �������� �������������� ��������  << ���������� >> ������������ �� ���������� count (���� ���������� rot()) �������� ���� �������������� - ���� ���������� ���������������� ���������� default=undef
	'rotate_count=i'=>\$arg{rotate_count}, # �������������� ������ �������������� default=count bits of key
	'rotate_start_bit=i'=>\$arg{rotate_start_bit},#  (���� ���������� rotate()) default=1
	'rotate_mask_bits=i'=>\$arg{rotate_mask_bits},# �������������������� ���������� �� ���������� (���� ���������� rotate()) default=count bits of key (���� ������������!!!)
	
	'debug=i'=>\$arg{debug},# default=undef
	'test=i'=>\$arg{test},# default=0
);

my $key_len = length($arg{key})
	or die "Empty --key= option";

my $key_bits = $key_len*8;
my $key_bigint = str2b($arg{key});
$arg{rotate_count} //= $key_bits;
my $mask = Math::BigInt->new('0b'.(1 x ($arg{rotate_mask_bits} || $key_bits)));# ����-����, ���������� ��������������
#~ print "$key_len\tlen\n$key_bits\tkey_bits\n", unpack('B*', 'xz'), "\txz\n", $key_bigint->badd(1)->as_bin, "\tkey+1\n", $mask->as_bin, "\tmask\n";
# ������������������ ���������� ���� ���������� �� ������ ��������������������������
my $cypher;
map {
	my $place = $arg{rotate_start_bit}+$_-1;
	my $rot = rotate($key_bigint, $arg{rotate_key}, $place, $mask);
	#~ print $rot->as_bin, "\trot[$arg{rotate_key} $place]\t", ($rot->is_nan && ""), "\tnan\n";
	$cypher .= b2str($rot) unless $rot->is_nan;
	
} (1..($arg{rotate_count})) if $arg{rotate_key};

my $cypher_len = length($cypher);
$arg{debug} && print #unpack('b*', $cypher), "\tcypher\n",
	"$cypher_len\t bit cypher len\n";
#~ exit;

my $offset_coeff = ($arg{area_offset} =~ /(\d+)/)[0]/100 if $arg{area_offset} =~ /%/;
print "Offset k=[$offset_coeff][$arg{area_offset}]\n" if $offset_coeff;
my $size_coeff = ($arg{area_size} =~ /(\d+)/)[0]/100 if $arg{area_size} && $arg{area_size} =~ /%/;
print "Size k=[$size_coeff][$arg{area_size}]\n" if $size_coeff;

do {
	print "WARN!!! IT IS STRONGLY RECOMEND TO COPY/SAVE THIS COMMAND LINE FOR NEXT TIME (ok?)";
	my $ans = <>;
	chomp($ans);
	exit
		unless lc($ans) eq 'ok';
	print "WARN!!! STRONGLY BE SURE THAT YOU DID [\$ unset HISTFILE]. (y/n):";
	$ans = <>;
	chomp($ans);
	print "PLEASE DO [\$ unset HISTFILE]!\n"
		and exit
		unless lc($ans) eq 'y';
	#~ exec('bash -c "unset HISTFILE"');
};
#~ exit;

my %pids;
find({ wanted => sub {
=pod
$File::Find::dir is the current directory name,
$_ is the current filename within that directory
$File::Find::name is the complete pathname to the file.
for each directory found, it will "chdir()" into that directory and continue the search
=cut
	return if -d; # �������������� ��������������������
	($arg{debug} && print "Skip: [$File::Find::name]\n") || 1
		and return
		unless $File::Find::name =~ /$arg{re}/;
	
	if( scalar keys %pids == $arg{forks}) { 
		my $pid = wait();
		delete $pids{$pid};
	}
	make_child($File::Find::name);
	
} , follow => 1, # symlinks
}, $arg{dir});

waitpid $_, 0 for keys %pids;


sub make_child {
	my $fn = shift;# @find or return undef;
	my $pid = fork();
	if ($pid) {# parent
		$pids{$pid}++;
		#~ print "�������������� �������������� [$pid] ������ ���������� [$fn]\n";
		return 1;
	} elsif ($pid == 0) {# child
		print "Process[$$] [$File::Find::name]...";# if $arg{debug};
		process($fn);
		print " done\n";# if $arg{debug};
		exit;
	} else {
		die "���� ���������� fork: $!\n";
	}
}

sub process {
	#~ return;
	my $fn = shift;
	open(FH, "+<", $fn)
		or warn "[$fn] opening: $!\n"
		and return;
	binmode(FH);
	my $fsize = (stat($fn))[7];
	print "\t skip size == 0"
		and (close FH
			or warn "[$fn] closing: $!")
		and return
		if $fsize == 0;

	my $foffset = defined $offset_coeff ?
		int($fsize*$offset_coeff)
		:	$arg{area_offset};
	# ���������������� ������������
	sysseek(FH, $foffset, 0)
		or warn "[$fn] forward seeking: $!" # SEEK_SET
		and (close FH
			or warn "[$fn] closing: $!")
		and return;
	
	my $count = defined $size_coeff ?# ������������������ �������������� ��������������������
		int(($fsize-$foffset)*$size_coeff)# int((size*%/100)/len)
		:	$arg{area_size} // ($fsize-$foffset);
	my $len = $cypher_len > $count ? $count : $cypher_len;
	while ($count && (my $read = sysread(FH, my $buffer, $len))) {
		$count -= $read;
		$len = $cypher_len > $count ? $count : $cypher_len;
		#~ push my @zero, @- while $buffer =~ /(\0)/g;
		#~ print "Found zero @zero" if @zero;
		#~ my $read = sysread(FH, my $buffer, $len);
		#~ warn "[$fn] reading whole chunk[$_] [$read] != [$len]"
			#~ and (close FH
				#~ or warn "[$fn] closing: $!")
			#~ and return
			#~ unless $read == $len;
		#~ print " after read POS: ", sysseek(FH, 0, 1) if $arg{debug};
		# ���������������� ��������������
		sysseek(FH, -$read, 1) #SEEK_CUR
			or warn "[$fn] back seeking: $!"
			and (close FH
				or warn "[$fn] closing: $!")
			and return; 
		#~ print FH $BUFFER ^ $cypher ;
		print "write pos: ", sysseek(FH, 0, 1), "; " if $arg{debug};
		my $write = $buffer ^ substr($cypher, 0, $read);
		#~ substr($write, $_, 1, chr(0x00)) for @zero;
		syswrite FH, $write
			or warn "[$fn] writing: $!"
			and (close FH
				or warn "[$fn] closing: $!")
			and return;
		
	}
	close FH
		or warn "[$fn] closing: $!";
}

sub str2b {# convert string to Math::BigInt
	Math::BigInt->new("0b".unpack('B*', $_[0]));
}

sub b2str {# pack Math::BigInt to string, (is not ->bstr)
	pack('B*', substr($_[0]->as_bin , 2));
}

sub rotate {# rotate($bigint, $direction, 1, $mask) - $direction(<<|>>); ���� 1 ������, $mask - 
	my $rev = $_[1] eq '<<' ? '>>' : '<<';
	my $mask_len = length(substr($_[3]->as_bin, 2));
	my $places = ($_[2] % $mask_len) || $_[2];
	my $shift1 = eval "\$_[0] $_[1] \$places";
	my $shift2 = eval "\$_[0] $rev (\$mask_len - \$places)";
	#~ my $mask = Math::BigInt->new(2) **$_[2] - 1;
	#~ my $mask = Math::BigInt->new('0b'.(1 x $_[3]));# ����-����, ���������� ��������������
	return ($shift1 | $shift2) & $_[3];
}
#~ sub rol { return ($_[0]<<$_[1] | $_[0]>>($_[2]-$_[1])) & (2**$_[2]-1); }
#~ sub ror { return ($_[0]>>$_[1] | $_[0]<<($_[2]-$_[1])) & (2**$_[2]-1); }

#~ substr($BUFFER ^ $cypher, 0, $read);
__END__

#~ use Math::BigInt;
use Math::BigInt try => 'GMP';
my $key = "r\0 4r";
my $up = unpack('b*', $key);
my $bits = length($up);
my $x =Math::BigInt->new("0b$up");
my $y;
print "$key key\t", length($key)," len\n",
	$x->as_bin(), " as is\t", length(bpack($x)), " len\t", "$bits bits\n",
	map {$y = rot($x, '>>', $_+0, $bits+0); $y->as_bin." rot[$_]\t".length(substr($y->as_bin , 2))." bits\t".length(bpack($y))." bytes\t".($x cmp $y)." cmp\t".(bpack($x) eq bpack($y))." eq\n";} (1..100);