#!/usr/bin/perl

#Copyright (C) 1999-2005 by  Sbastien Chaumat <schaumat@debian.org>
#                        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-licenses/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.

# changelog
#
# 08-29-2004
#   *change : &loadconf called only if no -config argument was given on the command line
#   *change : use File::Temp to secure temporary files creation
#
# 09-20-2003
#
#   *change : switch between module and local source for rsync according with $locallink and $module (Harri Jarvi)

use FileHandle;
use Getopt::Long;
use File::Temp qw/ tempfile tempdir /;

print STDERR "repli-update should only be called trough repli-install
or repli-sync\n";

#
#set default values
#
$dryrun="--dry-run";
$rsh = 'rsh';
$rcp = 'rcp';

$locallink='';
$module="replicator";



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

use lib (".","/usr/share/replicator");
require("repli-common");
sub dosystem;
sub error;
sub check_list_var;
sub loadconf;

sub usage {
  #should be handled with Pod::Usage ?
  print STDERR "@_\nusage: repli-update [--noboot ] [ --nousr ] [--real] --model <masterdir> --destdir <destdir>\n";
  die;
}

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

#I need to change this to a "clever" default :)
sub warn_if_empty_update_rules {
  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")}
  }
}

################################
#proceed command line arguments#
################################

GetOptions ('model=s' => \$model,
	    'destdir=s' => \$destdir,
            'real'   => sub { $dryrun = ''; },
	    'dev'   => \$withdev,
	    'nousr'   => \$nousr,
	    'cfengine' => \$cfengine,
	    'noboot' => \$noboot,
	    'e=s' => sub { $rsh = $_[1];
			   if ($rsh =~ m/ssh/) {
			     $rcp = $rsh;
			     $rcp =~ s/ssh/scp/;
			   }
			 },
	    'exclude=s' => sub { push(@new_slash_exclude,$_[1]);},
	    'usr_exclude=s' => sub { push(@new_usr_exclude,$_[1]);},
	    'config=s' => sub { my $conffile=$_[1];
				if(-r $conffile) {
				  &verbose("checking syntax of $conffile");
				  &dosystem("perl -c $conffile");
				  &verbose('ok');
				  require($conffile);
				  $conf_loaded=1;
				}
				else {
				  &error("$conffile not readable");
				}
			      }
	   ) or &usage;

###########################
#loading the configuration#
###########################

#this trick allow to avoid needing  replicator.conf when calling repli-update from repli-sync
unless ($conf_loaded) {
  &verbose("No configuration file forced, using default");
  &loadconf;
}

if(-r "$sharedir_in_miniroot/$default_rules_file") {
  require("$sharedir_in_miniroot/$default_rules_file");
} else {
  &error("no file $sharedir/$default_rules_file available\n");
}

if($modify_rules_file) {
  require($user_rules_file);
}

if (@new_slash_exclude) {
@slash_exclude=(@slash_exclude,@new_slash_exclude);
}

if (@new_usr_exclude) {
@usr_exclude=(@usr_exclude,@new_usr_exclude);
}

@update_rules=qw(slash_exclude usr_exclude var_include);
&warn_if_empty_update_rules(@update_rules);

&check_var(qw(model destdir));

$tmp="$destdir/tmp";

if ($cfengine) {
  ($fh_cfengine_conf, $cfengine_conf) = tempfile("cfengine.conf_XXXXX", DIR => $tmp);
  &dosystem("$rcp $model/etc/cfengine/cfengine.conf $tmp/$cfengine_conf");
}

#
#rsync configuration
#
$rsyncopt = "$dryrun --archive  --hard-links --sparse --whole-file --delete";
if ($verbose && !$serial) { $rsyncopt .=" -v "};
#are we using a module on the model or a local rsync source (e.g. a cdrom)
if ($locallink) {
  $rsync_src = $locallink;
} else {
  $rsync_src = "${model}::${module}";
}

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

if ($cfengine) {
  my $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";
  $fh_cfengine_conf->close;
  foreach (@lines) {
    if (/: Checking\s+fs-object ([^\s]+)$/ ||
	/Checking copy from .*:.* to ([^\s]+)$/ ||
	/: Link \(([^\s]+)\s+->.*\) exists.$/ ||
	/: Error while trying to link ([^\s]+) ->/) {
     &verbose("cfengine  : excluding $1\n");
      push @slash_exclude,$1;
    } else {
     &verbose("cfengine : line ignored: $_");
    }
  }
}

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

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

#handle /dev
#PB : if model use devfs then the --one-filesystem option from rsync will prevent /dev from being copied : it will be copied from the miniroot later
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 ${rsync_src}/boot/ $destdir/boot/. 2>&1");
}

#handle @slash_exclude
unless (-d $tmp) {
  &dosystem("mkdir -m 1777 $tmp");
}
#&dosystem("mkdir -p $tmp");
#$excludefile = "$tmp/excl.rsync.$$";
#$fh = new FileHandle $excludefile,"w" or die "opening exclude file:$!\n";;
($fh_excludefile, $excludefile) = tempfile("excl.rsync_XXXXX", DIR => $tmp, UNLINK => 1);
foreach (@slash_exclude) { print $fh_excludefile "- $_\n"; }
$fh_excludefile->close;

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

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

#if devfs is running on the model then rsync /dev from the miniroot :
if ($withdev and not (-r "/target/dev/MAKEDEV") ){ &dosystem("rsync -avx /dev /target"); };

#
#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  ${rsync_src}/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 '- *' ${rsync_src}/var/. $destdir/var/.");

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

if ($dryrun) {
  print STDERR "
###################################################

repli-update was call without the --real argument.

Nothing changes on disk.

###################################################
"
}
