#!/usr/bin/perl -w
#
# Copyright (c) 2005, 2006 Miek Gieben; Mark J Hewitt
# See LICENSE for the license
#
# This script implement a mirroring backup scheme
# -c is used for remote mirroring

use strict;

use Getopt::Long qw(:config no_ignore_case bundling);
use Sys::Syslog;
use File::Basename;
use File::Path;
use File::Copy;
use File::Copy::Recursive qw(dircopy rcopy fcopy);
use Fcntl qw(:mode);

# common functions
my $prefix="/usr";
require "/usr/share/rdup/shared.pl" or die "** Require \`shared.pl' failed";

my $ts = time;
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($ts);
my $progName = basename $0;
my ($help, $version, $verbose, $attr, $remote, $backupDir);

GetOptions("h" => \$help,
        "V" => \$version,
        "a" => \$attr, # extended attr support
        "c" => \$remote,
        "v" => \$verbose,
        "b=s" => \$backupDir);

usage() if $help;
version($progName) if $version;
die "** Need a -b option" if !$backupDir;
my $hostname = `hostname`; chomp $hostname;
my $attr_there = check_attr() if $attr;

# Statistics
my $ftsize = 0;        # Total file size
my $ireg = 0;        # Number of regular files
my $idir = 0;        # Number of directories
my $ilnk = 0;        # Number of symbolic links
my $irm  = 0;        # Number of files removed
my ($t, $bits, $uid, $gid, $psize, $fsize, $path);

syslog_open($progName);
if (!exist($backupDir)) {
        mkpath($backupDir) or die "** Cannot create `$backupDir': $!";
}


if ($remote) {
        syslog_log("%s %s", "Commencing remote backup to:" ,$backupDir);
        while (($_ = <STDIN>)) {
                chomp;
                ($t, $bits, $uid, $gid, $psize, $fsize) = split(" ", $_, 6);
                _mirror($backupDir);
        }
} else {
        syslog_log("%s %s", "Commencing local backup to:" ,$backupDir);
        while (($_ = <STDIN>)) {
                chomp;
                ($t, $bits, $uid, $gid, $psize, $fsize, $path) = split(" ", $_, 7);
                _mirror($backupDir);
        }
}
my $te = time;
syslog_log("Successfully finished backup in %.2f m, %.2f MB",
        (($te - $ts) / 60), ($ftsize/1024/1024));
closelog;
printf STDERR "** #REG FILES  : %d\n", $ireg;
printf STDERR "** #DIRECTORIES: %d\n", $idir;
printf STDERR "** #LINKS      : %d\n", $ilnk;
printf STDERR "** #(RE)MOVED  : %d\n", $irm;
printf STDERR "** FILE SIZE   : %.1f MB\n", ($ftsize/1024/1024 * 1.0);
printf STDERR "** STORED IN   : %s\n",  $backupDir;
printf STDERR "** ELAPSED     : %.2f m\n", (($te - $ts) / 60 * 1.00);
rchmod(755, $backupDir);
exit 0;

sub _mirror {
        my $dir = $_[0];
        my $dump = substr($t, 0, 1);
        my $type = substr($t, 1, 1);

        sanity_check($dump, $bits, $psize, $fsize, $uid, $gid);
        if ($remote) {
                $path = "";
                read STDIN, $path, $psize;
        }
        die "** Empty path"  if ($path eq "");
        print STDERR "$path\n" if $verbose;

#       NOTE - mirror_suffix() calls stat() on the target.
#       We rely on that below to avoid multiple expensive calls to stat.
#       If mirror_suffix is changed, or is not called, it will be necassary to revise this assumtion.
#
        if ($dump eq '+') {        # add
                my $target = "$dir/$path";
                if ($type eq '-') {        # REG
                        my $suffix = mirror_suffix($target);
                        if ($suffix) {      # We only have a suffix if the file exists
                                rename $target, ($target . $suffix) or warn "** Cannot rename: $target: $!";
                        }
                        if ($remote) {
                                open FILE, ">$target" or warn "** Cannot create $target: $!";
                                if ($fsize != 0) {
                                        copyout($fsize, *FILE);
                                }
                                close FILE or warn "** Failed to close: $!";
                                rchown($uid, $gid, $target);
                                chown_attr($attr_there, $uid, $gid, $target);
                                rchmod($bits, $target);
                        } else {
                                copy($path, $target) or warn "** Copy failed: $! $path $target";
                                rchown($uid, $gid, $target);
                                chown_attr($attr_there, $uid, $gid, $target);
                                rchmod($bits, $target);
                        }
                        $ftsize += $fsize;
                        $ireg++;
                        if ($ireg % 1000 eq 0) {
                                syslog_log("progress: %d files", $ireg);
                        }
                } elsif ($type eq 'd') {        # DIR
                        my $suffix = mirror_suffix($target);
                        if (-f _ || -l _) {      # Type of entiry has changed
                                rename $target, ($target . $suffix) or warn "** Cannot rename: $target: $!";
                        }
                        mkpath($target) unless -d _;
                        rchown($uid, $gid, $target);
                        chown_attr($attr_there, $uid, $gid, $target);
                        if ($> != 0) {
                                if (length($bits) == 3 and substr($bits, 0, 1) ne "7") {
                                        print STDERR "** Chmod $target u+rxw\n";
                                        $bits = "7" . substr($bits, 1, 2);
                                }
                                if (length($bits) == 4 and substr($bits, 1, 1) ne "7") {
                                        print STDERR "** Chmod $target u+rxw\n";
                                        $bits = substr($bits, 0, 1) . "7" . substr($bits, 2, 2);
                                }
                        }
                        rchmod($bits, $target);
                        $idir++;
                } elsif ($type eq 'l') {        # LNK; target id the content
                        my $suffix = mirror_suffix($target);
                        if ($suffix) {      # We only have a suffix if the file exists
                                rename $target, ($target . $suffix) or warn "** Cannot rename: $target: $!";
                        }
                        if ($remote) {
                                my $linkTarget = "";
                                read STDIN, $linkTarget, $fsize;
                                symlink $linkTarget, $target or warn "** Cannot create link: $target -> $linkTarget: $!";
                        } else {
                                symlink(readlink($path), $target) or warn "** Cannot create link: $target -> $path: $!";
                        }
                        rchown($uid, $gid, $target);
                        chown_attr($attr_there, $uid, $gid, $target);
                        $ftsize += $fsize;
                        $ilnk++;
                }
        } else {        # Remove
                my $target = "$dir/$path";
                my $suffix = mirror_suffix($target);
                if ($suffix) {      # We only have a suffix if the file exists
                        rename $target, ($target . $suffix) or warn "** Cannot rename: $target: $!";
                }
                $irm++;
        }
}

# Eg:
# 2006-05-05 01:50:18.000000000 +0100
#         ^^ ^^^^^
# => "+05.01:50
#
sub mirror_suffix {
        my $filename = $_[0];

        lstat($filename);
        return "" unless -e _;
        my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime((lstat(_))[9]);
        return sprintf "+%02d.%02d:%02d", $mday, $hour, $min;
}

sub usage {
        print "$progName -b DIR [OPTIONS]\n\n";
        print "Mirror the files from the filelist of rdup\n\n";
        print "OPTIONS\n";
        print " -b DIR  use DIR as the backup directory\n";
        print " -a      write extended attributes r_uid/r_gid with uid/gid\n";
        print " -c      process the file content also (rdup -c), for remote backups\n";
        print " -v      print the files processed to standard error\n";
        print " -h      this help\n";
        print " -V      print version\n";
        exit 0;
}
