#!/usr/bin/perl
#
# copy files using dd		--symlynX 2011
#
# I couldn't find anything like this on the Internet, either because
# it is very hard to find suitable search words to find it, or maybe
# because it is a really stupid idea to do this. Yet, my hard disks
# seem to love it even after I upgraded kernels and OS versions. Now
# I even use RAM proxy by default since truecrypt likes to freeze when
# working straight from one container into another.
#
# use also like this:
#   alias ddmv	ddcp -m
#   alias ddcat ddcp -a

# one meg seems to perform well, but YMMV
my $bs = "bs=1M";
my $tmpfx = "/dev/shm/.ddcp-";
my $t = $tmpfx . $$;

sub debug() { 1 }

use File::Find;
use Getopt::Std;
getopt('o');

# allow for both cp (<dest> comes last) and -o <dest> syntax
my $dest = $opt_o || pop;

die <<X unless $dest;
Usage: $0 [<flags>] [<source files/dirs>] [<destination>]

Recursively copy directories containing large files using /bin/dd
with a large block size. Sometimes dd will outperform regular file
transfer tools like cp, cpio, rsync, tar. Sometimes even local loop
HTTP achieves better performance than those. I have no idea why this
happens to me. Maybe it doesn't to you, but if it does, you may find
this useful. Also I found it useful if the tool by default sends
things into the RAM disk first, then moves it to the final destination.
Weird. It shouldn't matter. Usage just like "/bin/cp -rv". Options:

    -m	move files (erase original after successful copy)
    -a	append to end of existing file
    -d	try dd's dsync flag
    -s	try dd's sync flag
    -f  copy directly to destination without passing via RAM (force)
    -o	output directory, so you don't have to specify it at the end
    -n	don't call /bin/sync after each dd
    -c	continue even in the face of errors

When no source files or directories are given, the current directory
is used. Using none of the flags above has provided me with the best
dd performance, even when copying between file systems on the same
external USB drive. So the world isn't completely rotten after all.
X

# system '/bin/ls', "$tmpfx*";
system '/bin/mkdir', $dest unless $opt_a or -d $dest;
# print "Dest: $dest\n";

# bs will be passed anyhow, but i can't pass a null string there
my $f = $opt_s ? "oflag=sync" : $opt_d ? "oflag=dsync" : $bs;

File::Find::find({ bydepth => 1, wanted => \&wanted,
		   postprocess => \&postprocess }, @ARGV ? @ARGV : '.' );
exit;

sub postprocess {
#	print "Unlink $_ ?\n";
#	unlink($_) if $opt_m;
}

sub wanted {
	my $dir = $File::Find::dir;
	if (-d $_) {
		next;
	}
#	my $name = $File::Find::name;
	$dir =~ s:^./::;
	my $target = $dir? ($dir =~ m:^/: ? "$dest$dir" : "$dest/$dir"): $dest;
 	print "Mkdir: $target\n" unless $target;
	system '/bin/mkdir', '-p', $target unless $opt_a or -d $target;
	# ($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_);
	my $out = $target;
	$out = m:^/: ? "$target$_" : "$target/$_" unless $opt_a;
	print "\t\t", -s $out, " vs ", -s $_, "\n";
	if (-r $out and -s $out == -s $_) {
		# same size doesnt always mean it's done.. but
		print "<skip>\t$out\n";
	} else {
		print "\t$out\n";
		if ($opt_a) {
			&system('/bin/dd', $bs, 'oflag=append', 'conv=notrunc', $f, "if=$_", "of=$out");
		} elsif (not $opt_f) {
			&system('/bin/dd', $bs, $f, "if=$_", "of=$t");
			&system('/bin/dd', $bs, $f, "if=$t", "of=$out");
			unlink($t);
		} else {
			&system('/bin/dd', $bs, $f, "if=$_", "of=$out");
		}
		# make sure it arrived safely before removing anything
		&system('/bin/sync') unless $opt_n;
	}
	if ($opt_m and -s $_ eq -s "$out") {
		unlink($_);
	}
}

sub system {
	if (system(@_)) {
		if ($? == -1) {
			print "failed to execute: $!\n";
		} elsif ($? & 127) {
			printf "child died with signal %d, %s coredump\n",
			 ($? & 127),  ($? & 128) ? 'with' : 'without';
		} else {
			printf "child exited with value %d\n", $? >> 8;
		}
		exit $? unless $opt_c;
	}
}

