#!/usr/bin/perl

#Copyright (C) 1999-2001 by  Sbastien Chaumat <schaumat@ens-lyon.fr>
#                        and Loc Prylli <lprylli@lhpca.univ-lyon1.fr>

#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.

#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.

#    A copy of the GNU General Public License is available as
#    `/usr/share/common-licences/GPL' in the Debian GNU/Linux distribution
#    or on the World Wide Web at http://www.gnu.org/copyleft/gpl.html.  You
#    can also obtain it by writing to the Free Software Foundation, Inc.,
#    59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

use FileHandle;

#
#set default values
#
$scriptsdir="/usr/sbin/replicator";
$rules_file="/root/update_rules";
$dryrun="--dry-run";
$rsh = 'rsh';
$rcp = 'rcp';

@initscripts=qw(
bind
apache
netsolve
squid
gpm
dhcpc
dhcp
dhcp-beta
postgresql
);

require("/usr/share/replicator/repli-common");
sub dosystem;
sub error;
sub check_list_var;

sub usage {
  error "@_\nusage: repli-update --real --master <masterdir> --destdir <destdir>";
}

sub canon_arch {
 my $a = $_[0];
 chomp ($a);
 $a =~ s/i\d86/i386/;
 return $a;
}

#
#include/exclude rules are loaded in repli-common
#
#
#Sanity checks
#
#I need to change this to a "clever" default :)
sub warn_if_empty_update_rule {
  foreach $vv  ( @_ ) {
    if (@$vv) {
      if ($verbose){
	print "\@$vv=@$vv\n"
      }
    } 
    else {error("\@$vv is not defined. 
    To define an empty update rule use the syntax : 
    \@$vv=qw(\"\");
    in $user_rules_file .\n")}
  } 
}

@update_rules=qw(slash_exclude usr_exclude var_include);
warn_if_empty_update_rule @update_rules;

#
#proceed command line arguments
#
while (@ARGV) {
  $_ = shift @ARGV;
  if ($_ eq '--master') {
    @ARGV > 0 || usage "incomplete arg\n";
    $master = shift @ARGV;
  } elsif ($_ eq '--destdir') {
    @ARGV > 0 || usage "incomplete arg\n";
    $destdir = shift @ARGV;
  } elsif ($_ eq '--real') {
    $dryrun = '';
  } elsif ($_ eq '--dev') {
    $withdev = 1;
  } elsif ($_ eq '--nousr') {
    $nousr = 1;
  } elsif ($_ eq '--cfengine') {
    $cfengine = 1;
  } elsif ($_ eq '--noboot') {
    $noboot = 1;
  } elsif ($_ eq '-e') {
    @ARGV > 0 || usage "incomplete arg\n";
    $rsh = shift @ARGV;
    if ($rsh =~ m/ssh/) {
      $rcp = $rsh;
      $rcp =~ s/ssh/scp/;
    }
  } elsif ($_ eq '--exclude') {
    push @slash_exclude, shift @ARGV;
  } elsif ($_ eq '--usr_exclude') {
    push @usr_exclude, shift @ARGV;
  } elsif ($_ eq '--config') {
    @ARGV > 0 || usage;
    my $f = shift @ARGV;
    eval (scalar(`cat $f`)) or error "reading conffile $f";
  } else {
    die "usage: repli-update [--noboot ] [ --nousr ] --master <masterdir> --destdir <destdir> [-real] \n";
  }
}

check_var(qw(master destdir));

#$master =~ m,/$, or die "srcdir ($master) should end with a slash";

if ($cfengine) {
  dosystem("$rcp $master/etc/cfengine/cfengine.conf /tmp/cfengine.conf.$$");
}

#if ($master =~ /^(?:(.+)@)?(.+):/) {
#  my ($login,$target);
#  if ($1) {
#    ($login,$target) = ($1,$2);
#  } else {
#    ($login,$target) = ("root",$2);
#  }
#  print STDERR "exec:$rsh -l $login -n $target uname -m\n";
#  my $rarch = canon_arch(`$rsh -l $login -n $target uname -m`);
#  chomp $rarch;
#  my $larch = canon_arch(`uname -m`);
#  chomp $larch;
#  if (!($rarch  eq $larch)) {
#    error "remote architecture ($rarch) does not match local architecture ($larch) !!!!\n";
#  }
#}

#
#rsync configuration
#
$rsyncopt = "$dryrun --archive  --hard-links --sparse -v ";
#if ($verbose){$rsyncopt .=" -v "};

#
# "/" replication
#
#we copy everything EXCEPT @slash_exclude, files already handled by cfengine,

#ignore files already handled by cfengine
if ($cfengine) {
  $fh = new FileHandle "cfengine --dry-run -c -v --file /tmp/cfengine.conf.$$ 2>&1 |" or die "parsing cfengine.conf\n";
  @lines=();
  while ($_ = $fh->getline) { push @lines,$_; }
  $fh->close or die "terminating cfengine";
  #print "lines from cfengine @lines\n";
  #unlink("/tmp/cfengine.conf.$$");
  foreach (@lines) { 
    if (/: Checking\s+fs-object ([^\s]+)$/ ||
	/Checking copy from .*:.* to ([^\s]+)$/ || 
	/: Link \(([^\s]+)\s+->.*\) exists.$/ ||
	/: Error while trying to link ([^\s]+) ->/) {
      #    print STDERR "excluding $1\n";
      push @slash_exclude,$1;
  } else {
    #    print "line ingnored: $_";
  }
  }
}

#handle /etc/rc.d
if ($norcd) {
  push @slash_exclude,"/etc/rc?.d";
}

foreach (@initscripts) { push @slash_exclude,"/etc/rc?.d/*$_",
                           "/etc/cron.daily/$_","/etc/cron.d/$_";}

#handle /dev
push @slash_exclude,"/dev" unless $withdev;

#handle /boot
if (!$noboot) {
  $dryrun or -d "$destdir/boot/." or mkdir "$destdir/boot",0755 or die "cannot make $destdir/boot";
  dosystem("rsync $rsyncopt ${master}::replicator/boot/ $destdir/boot/. 2>&1");
}

#handle @slash_exclude
$excludefile = "/tmp/excl.rsync.$$";
$fh = new FileHandle $excludefile,"w" or die "opening exclude file:$!\n";;
foreach (@slash_exclude) { print $fh "- $_\n"; }
$fh->close;
#$includefile = "/tmp/incl.rsync.$$";
#$fh = new FileHandle $includefile,"w" or die "opening include file:$!\n";;
#foreach (@slash_include) { print $fh "+ $_\n"; }
#$fh->close;

chdir('/') or die "Unable to cd /";

dosystem("rsync $rsyncopt --one-file-system --exclude-from=$excludefile ${master}::replicator/ $destdir 2>&1");

#
#replication of /usr
#
#we copy everything EXCEPT @usr_exclude
-d "$destdir/usr" or mkdir "$destdir/usr",0755;

if (!$nousr) {
  my $usr_exclude = '';
  foreach (@usr_exclude) { $usr_exclude .= " --exclude '- $_' "; }
  dosystem("rsync $rsyncopt $usr_exclude  ${master}::replicator/usr/. $destdir/usr/. 2>&1");
}


#
#replication of /var
#
$dryrun or -d "$destdir/var/." or mkdir "$destdir/var",0755 or die "cannot make $destdir/var";;

#excludes possibles: /spool/squid/*    includes possibles: yp/nicknames  

#recreate var directory structure
dosystem("rsync $rsyncopt  --include '+ */' --exclude '- *' ${master}::replicator/var/. $destdir/var/.");

#copie only content of directories in @var_include
foreach (@var_include) {
  dosystem("rsync $rsyncopt ${master}::replicator/var/$_/ $destdir/var/$_ 2>&1");
}


#$fh = new FileHandle "/etc/var.dirs","r" or die "opening /etc/var.dirs";
#@lines = $fh->getlines;
#$fh->close;
#
#foreach (@lines) {
#  die "var.dirs format:$_\n" unless m,\./([^\s]+) ([^\s]+) ([^\s]+) (\d+),;
#  my ($file,$owner,$group,$mode)=("/var/$1",$2,$3,$4);
#  push @dirs, [ $file,"owner=$owner","group=$group","mode=$mode" ];
#}
#
#foreach $tab (@dirs) {
#  my @tab = @$tab;
#  $name = shift @tab;
#  my (@s) = stat($name);
#  if (!@s) {
#    print "mkdir $name\n";
#    mkdir($name,0755) unless $dryrun;
#    
#    $o="root";
#    $g="root";
#    $m=0755;
#  } else {
#    $o = getpwuid($s[4]);    
#    $g = getgrgid($s[5]);
#    $m = $s[2];
#  }
#  my $dochown=0;
#  foreach (@tab) {
#    if (/^o(wner)?=(.*)$/) {
#      $o = $2, $dochown=1 if ($2 ne $o);
#    } elsif (/^g(roup)?=(.*)$/) {
#      $g = $2, $dochown=1 if ($2 ne $g);
#    } elsif (/^m(ode)?=(.*)$/) {
#      $newm = oct($2);
#      if ($newm != ($m & 07777)) {
#        printf "chmod (%s,0%o)\n",$name,$newm;
#        chmod($newm,$name) or die "chmod $name" unless $dryrun;
#      }
#    } else {
#      die "unknown option:$_\n";
#    }
#  }
#  if ($dochown) {
#    printf "chown %s.%s %s\n",$o,$g,$name;
#    chown(scalar(getpwnam($o)),scalar(getgrnam($g)),$name) or die "chown $name" unless $dryrun;
#  }
#}
#         



