#!/usr/bin/perl -w # NetCP (C) Erkki Seppälä 1999-2007 use File::stat; use IO::Handle; use Time::HiRes; STDERR->autoflush(1); local $version = "0.2"; local $recursive = 0; local $move = 0; local $prevEstimate = 0; while (@ARGV > 0 && $ARGV[0] =~ /^-/) { if ($ARGV[0] eq "-r") { $recursive = 1; shift @ARGV; } if ($ARGV[0] eq "-m") { $move = 1; shift @ARGV; } if ($ARGV[0] eq "-h") { shift @ARGV; print "netcp $version (c) Erkki Seppälä 1999-2007\n"; print "cp with time estimation and continuing an interrupted copy (purely based on file sizes)"; print "usage: netcp [-r] [-m] file1 [file2 [file3..]] target_dir\n"; print "-r recursive\n"; print "-m delete after copy\n"; print "Note: cannot handle symbolic links.\n"; exit; } } my @files = @ARGV; my $target = pop @files; $| = 1; if (! scalar(@files)) { print STDERR "No files to copy.\n"; exit 1; } my $targetIsDir = 0; if ( -d $target ) { $targetIsDir = 1; } if ( !defined $target || ( ! $targetIsDir && @files > 1) ) { print STDERR "Target must be directory.\n"; exit 1; } local @estimations = (); print STDERR "Reading size..\r"; my $totalSize = 0; for (my $c = 0; $c < @files; $c++) { $totalSize += du($files[$c]); } $totalSize = 1 if $totalSize == 0; my $offset = 0; for (my $c = 0; $c < @files; $c++) { if ( ! -d $files[$c]) { # print "Would copy $files[$c] to $target/$baseFiles[$c]\n"; if ($targetIsDir) { copyFile($files[$c], "$target/" . baseName($files[$c]), \$offset, $totalSize); } else { copyFile($files[$c], $target, \$offset, $totalSize); } } elsif ($recursive) { copyDirectory($files[$c], "$target/" . baseName($files[$c]), \$offset, $totalSize); } else { print STDERR "Ignoring directory ", baseName($files[$c]), "\n"; } } sub feedEstimation { my ($pos) = @_; my $t = time; if (@estimations == 0 || $t - $estimations[@estimations - 1]->{'t'} >= 5) { push @estimations, {t=>$t, b=>$pos}; if (@estimations > 60) { shift @estimations; } } } sub getRate { if (@estimations >= 2) { my $samples = 0; my $value = 0; my $balance = 1; for (my $i = 0; $i < 60 && $i < @estimations - 1; $i++) { my $diffTime = $estimations[@estimations - 1 - $i]->{'t'} - $estimations[@estimations - 2 - $i]->{'t'}; my $diffBytes = $estimations[@estimations - 1 - $i]->{'b'} - $estimations[@estimations - 2 - $i]->{'b'}; if ($diffTime) { $balance = 1/($i+1); $value += scalar($diffBytes / $diffTime) * $balance; $samples += $balance; } } return $samples ? ($value / $samples) : undef; } else { return undef; } } sub getEstimation { my ($size) = @_; my $rate = getRate(); if ($rate) { my $time = ($size - $offset) / $rate; return $time; } else { return undef; } } sub dirName { my $f = $_[0]; my $i = rindex($f, "/"); if ($i != -1) { $f = substr($f, 0, $i); } return $f; } print " \r"; sub baseName { my $f = $_[0]; my $i = rindex($f, "/"); if ($i != -1) { $f = substr($f, $i + 1, length($f) - 1); } return $f; } sub copyDirectory { my ($dirIn, $dirOut, $offsetRef, $totalSize) = @_; eval { my $dirInfo = stat($dirIn); # permissions will be set later, so we can be sure we can write there now mkdir("$dirOut", 0700); local *DIR; opendir(DIR, $dirIn); my @files = grep(!/^\.|\.\.$/, readdir(DIR)); closedir(DIR); for (my $c = 0; $c < @files; $c++) { if (-d "$dirIn/$files[$c]") { if (copyDirectory("$dirIn/$files[$c]", "$dirOut/$files[$c]", $offsetRef, $totalSize)) { } } else { if (copyFile("$dirIn/$files[$c]", "$dirOut/$files[$c]", $offsetRef, $totalSize)) { } } } chmod $dirInfo->mode, $dirIn; }; if ($@) { print "Problem copying directory $dirIn: $@\n"; return 0; } else { if ($move) { rmdir "$dirIn"; } return 1; } } sub copyFile { my ($fileIn, $fileOut, $offsetRef, $totalSize) = @_; # permissions will be set at the end, so continuing will work eval { local *IN; local *OUT; my $fileInfo = lstat $fileIn; #x if ($EUID == 0 && $fileInfo-> open(IN, "<$fileIn"); if ( ! -e $fileOut ) { open(OUT, ">$fileOut") or die "Cannot open $fileOut"; } else { open(OUT, ">>$fileOut") or die "Cannot open $fileOut"; my $st = stat($fileOut); seek(IN, $st->size, 0); $$offsetRef += $st->size; } my $written = 0; my $n = 0; print STDERR "copying ", baseName($fileIn), "\e[K\n"; my $prevTime = Time::HiRes::time(); my $waitRead = 0; my $waitWrite = 0; while ($bytes = read(IN, $buffer, int(1024 * 1024 / 10))) { feedEstimation($$offsetRef); my $now = Time::HiRes::time(); if ($now > $prevEstimate + 1) { $prevEstimate = $now; my $rate = getRate(); my $est = getEstimation($totalSize); my $bound = $waitRead > $waitWrite ? "read-bound" : "write-bound"; print(int(100 * $$offsetRef / $totalSize), "%=", int($$offsetRef/1024), "kb/", int($totalSize / 1024), "kb", (defined $rate?(", ", int(getRate() / 1000), " kb/sec"):""), (defined $est?(", ", int($est / 60 / 60), " h ", int($est / 60 % 60), " m ", int($est % 60), " s" ):""), " $bound\e[K\r"); } print OUT $buffer or die "Failed to write"; my $now2 = Time::HiRes::time(); $$offsetRef += $bytes; $waitRead += $now - $prevTime; $waitWrite += $now2 - $now; $prevTime = $now2; } print "\e[1Acopied $fileIn to $fileOut\e[K\n\e[K"; close(OUT); close(IN); chmod $fileInfo->mode, $fileOut; utime $fileInfo->atime, $fileInfo->mtime, $fileOut; }; if ($@) { print "Problem copying file $fileIn: $@\n"; return 0; } else { if ($move) { unlink "$fileIn"; } return 1; } } sub du { my $sum = 0; for (my $argc = 0; $argc < @_; $argc++) { if (-f $_[$argc]) { my $st = lstat("$_[$argc]"); $sum += $st->size; } else { local *DIR; opendir(DIR, $_[0]); my @files = readdir(DIR); closedir(DIR); foreach my $k (@files) { if (my $st = lstat("$_[$argc]/$k")) { if ($k ne "." and $k ne "..") { if ($st->mode & 040000) { if ($recursive) { $sum += du("$_[$argc]/$k") + $st->size; } } else { $sum += $st->size; } } } } } } return $sum; }