package VSD::Change;
require VSD;

# FIX VSD and VSD::* orders etc...

# Perl module for logging and backing up config changes
#  made by VSD.
# Copyright (c) 2000 Idaya Ltd.
# Written by Sol Burt <sol@idaya.co.uk>

# This file is part of the Virtual Server Administrator (FreeVSD)

# FreeVSD 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, or (at your option)
# any later version.

# FreeVSD 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.

# You should have received a copy of the GNU General Public License
# along with FreeVSD; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

use strict;
use File::Copy;
use File::Path;
use File::Basename;
use Exporter;

use vars qw( @ISA @EXPORT @EXPORT_OK ); 

@ISA = ('Exporter', 'VSD'); #  FIX hmmmmmm...
#@EXPORT = qw( );
#@EXPORT_OK qw( );


# FIX Can't mix command changes into file changes for the same file...yet.

# FIX make all log tags lower case...

# FIX change dies to warns...

# FIX temporarily hardcoded....to be put in VSD::Path
#
my $vsdconfdir = $VSD::path{vsdconfdir};

my $undo_log = "$vsdconfdir/undo/vsd.undo";

# my %callers = ();



sub new {
    my $self = {};
    bless ( $self, shift );
    $self->id('new');
    return $self->_init( @_ );
}

sub _init {
    my $self = shift;
    if (@_) {
        my %init = @_;
        @$self{keys %init} = values %init;
    }
    defined $self->{file} && $self->_init_file();
    # If changes already made and not undone, don't return undef...
#    return undef unless defined $self->{check} && $self->_check();
    $self->log('start');
    return $self;
}

sub _init_file {
    my $self = shift;
    (-f $self->{file}) || 
	die "File to change, $self->{file}, doesn't exist.";
#    $self->log('file');  # FIX move this stuff to _init
    $self->backup;
}

#sub _check {
#    my $self = shift;
#    my ($p, $script, $l, $s) = caller(2);
##    my $script = (caller(3))[1];  # FIX make into mamber var
#print "\nscript: $script\n";
#    $script =~ s/^.*?([^\/]+?)$/$1/ || return undef;
#    require VSD::Undo;
#    defined VSD::Undo->new(script=>$script, check=>1) || return undef;
#    return 1;
#}

sub id {
    my $self = shift;

    @_ && ($self->{id} = $self->log( "$_[0]_id" ));
    return $self->{id};
}


sub log {
    my $self = shift;
    my $action = shift;
    my %handlers = (
		    start => sub {
			my ($p, $f, $l, $s) = caller(3);
			defined $self->{log} && die "Log is already started";
			$_[0]->{log} = "<Change date=\"\" time=\"\" id=\"$self->{id}\" script=\"$f\">\n";
		    },
		    entry => sub {
		        my $self = shift;
			my ($p, $f, $l, $s) = caller(2);
                        if ( $s =~ /command/ ) {
                           $self->{log} .= "\t<Command>$_[0]".
			       (defined $_[1] ? "<reverse>$_[1]</reverse>" : '').
			       (defined $_[2] ? "<comment>$_[2]</comment>" : '' )."</Command>\n";
                        } else {
			   $self->{log} .= "\t<Edit file=\"$self->{file}\" line=\"$l\" action=\"$s\">".
                                                             (defined @_ ? "@_" : '')."</Edit>\n";
                        }
		    },
		    end => sub {
			$_[0]->{log} .= "</Change>\n";
		    },
                    write => sub {
			$_[0]->{log} !~ /<Change .+<\/Change>/s && $_[0]->log('end');
			open LOG, ">>$undo_log" or 
			    die "Can't open $undo_log :$!";
			print LOG $_[0]->{log};
		        close LOG;
			undef $self->{log};
		    },
		    code => sub { # FIX write anon subs to call for special undos...
		        my $self = shift;
			my ($p, $f, $l, $s) = caller(2);
                        if ( $s =~ /command/ ) {
                           $self->{log} .= "\t<Command>$_[0]<reverse>$_[1]</reverse></Command>\n";
                        } else {
			   $self->{log} .= "\t<Edit file=\"$self->{file}\" line=\"$l\" action=\"$s\">".
                                                             (defined @_ ? "@_" : '')."</Edit>\n";
                        }
		    },
		    new_id => sub {
			my $self = shift;
			(-f $undo_log) && do {
			    open LOG, "$undo_log" or 
				die "Can't open $undo_log :$!";
			    my $id = 0;
			    while (<LOG>){ /<Change.+?id="(\d+?)"/ && ($id = $1) }
			    close LOG;
			    return ($id + 1);
			};
			return 1;
		    }
		    );
    return $handlers{ $action }->($self, @_);
}


sub backup_old {
    my $self = shift;
    (-e "$vsdconfdir/undo/$self->{id}") or   # FIX not necessary
	mkpath ( "$vsdconfdir/undo/$self->{id}", 0755 ) or die "Cannot make $vsdconfdir/undo";
    # If file is already backed up, don't do it again. OK??
    (-f "$vsdconfdir/undo/$self->{id}$self->{file}") || do { 
	$self->{file_bak} = "$vsdconfdir/undo/$self->{id}$self->{file}";
	$self->{file_bak_path} = dirname $self->{file_bak};
	#  print "\nbak_path: ".$self->{file_bak_path}."\n";
	(-e $self->{file_bak_path}) or 
	    mkpath $self->{file_bak_path}, 0, 0755 or die "Cannot make path to backup file.";
	copy $self->{file}, $self->{file_bak} or die "$!:";
    }
}


sub backup {
    my $self = shift;
    $self->{file_bak} = "$vsdconfdir/undo/$self->{id}".($self->{file} =~ /^\// ? '' : '/')."$self->{file}";
    $self->{file_bak_path} = dirname $self->{file_bak};
    (-d $self->{file_bak_path}) or   # FIX not necessary
	mkpath ( $self->{file_bak_path}, 0, 0755 ) or 
	    die "Cannot make path to backup file $self->{file_bak_path}: $!";
        # If file is already backed up with same id, don't do it again. OK??
    (-f $self->{file_bak}) or 
	copy $self->{file}, $self->{file_bak} or 
	    die "Can't copy $self->{file} to $self->{file_bak}: $!";
}


sub file {
    my $self = shift;
    @_ && do {
	$self->{file} = shift;
	$self->_init_file();
    };
    return (defined $self->{file} ? $self->{file} : undef);
}

sub file_bak {
    my $self = shift;
    return (defined $self->{file_bak} ? $self->{file_bak} : undef);
}

sub get_log {
    my $self = shift;
    return (defined $self->{log} ? $self->{log} : undef);
}



sub substitute { # FIX escaping.....
    my $self = shift;

    !defined $self->{file_tmp} && do {
	$self->{file_tmp} = '/tmp/vsd'.$self->{file};
	mkpath (dirname ($self->{file_tmp}), 0, 0755) 
	    or die "Cannot make path to $self->{file_tmp} file: $!";
    };
    copy $self->{file}, $self->{file_tmp} or die "$!";
    open IN, $self->{file_tmp} or die "Can't open $self->{file_tmp}:$!";
    open OUT, ">$self->{file}" or die "Can't open $self->{file}:$!";
# Elements of @_ must be string literals, ie. ''

  EEK:  while (<IN>) {
      foreach my $regexp (@_){
	#  print "\n\n$regexp";
	#  $regexp =~ s/^s\/(.+)\/$/$1/;
	#  my @bits = split /[^\\]\//, $regexp;
	#  $bits[0] =~ s/\//\\\//g;
	#  $bits[1] =~ s/\//\\\//g;
	#  $regexp = 's/'.(join '/', @bits).'/';
	#  print "\n$regexp\n\n";
	  eval $regexp && $self->log( 'entry', $regexp );
      }
      print OUT;
  }
    close OUT;
    close IN;
}

sub append {
    my $self = shift;

    my $tmp = $";
    $" = "\n";
    open OUT, ">>$self->{file}" or die "Can't open $self->{file}:$!";
	print OUT "@_\n";
    close OUT;
    $self->log( 'entry', @_ );
    $" = $tmp;
}

sub comment {
    my $self = shift;

    !defined $self->{file_tmp} && do {
	$self->{file_tmp} = '/tmp/vsd'.$self->{file};
	mkpath dirname $self->{file_tmp}, 0, 0755 
	    or die "Cannot make path to $self->{file_tmp} file: $!";
    };
    copy $self->{file}, $self->{file_tmp} or die "$!";
    open IN, $self->{file_tmp} or die "Can't open $self->{file_tmp}:$!";
    open OUT, ">$self->{file}" or die "Can't open $self->{file}:$!";
# Elements of @_ must be string literals, ie. ''

  EEK:  while (<IN>) {
      foreach my $regexp (@_){
	  # defined $self->{verbose} && (print $_);
	  s/^($regexp)/#$1/ && $self->log( 'entry', $regexp );
      }
      print OUT;
  }
    close OUT;
    close IN;
}

sub command {
    my $self = shift;

    my $command = shift;
    my $reverse = shift;
    my $comment = ( @_ ? shift : undef );

    # FIX Add error checking
    `$command`;
    $self->log( 'entry', $command, $reverse, $comment ); # FIX
}

# FIX not being used yet and needs more. Find perl module that does it already...
#    For commands.
sub escape {
    $_ = shift;
    s/</&lt;/g;
    s/>/&gt;/g;
    return $_;
}

sub xinetd_virtualise {
    my $self = shift;

  # FIX so not to break other scripts...
    copy ( $self->{file}, $self->{file}.'pre-freevsd') or die "$!";

    !defined $self->{file_tmp} && do {
	$self->{file_tmp} = '/tmp/vsd'.$self->{file};
	mkpath dirname $self->{file_tmp}, 0, 0755 
	    or die "Cannot make path to $self->{file_tmp} file: $!";
    };
    copy $self->{file}, $self->{file_tmp} or die "$!";
    open IN, $self->{file_tmp} or die "Can't open $self->{file_tmp}:$!";
    open OUT, ">$self->{file}" or die "Can't open $self->{file}:$!";

    my $line;
    while ($line = <IN>) {
	
	# Make sure entires that are NOT to be virtualised are copied to new config file
#	if ((($line =~ /^service vsd/) or ($line =~ /^service vsmon/) or ($line =~ /^defaults/)) and ($line !~ /^\s*\#/)){
	if ( (($line =~ /^service vsmon/) or ($line =~ /^defaults/)) and ($line !~ /^\s*\#/) ){
	    print OUT $line or die("error writing to $self->{file}: $!");
	    while ($line = <IN>){
		print OUT $line or die("error writing to $self->{file}: $!");
		last if $line =~ /}/    # {
	    }
        }
        # Virtualise all other services
        elsif (($line =~ /^service \S/) and ($line !~ /^\s*\#/)) {
	    my ($old_server, $server_args);
	    # Set $old_server and $server_args to null string for each new service found
	    $old_server = "";
	    $server_args = "";
	    print OUT $line;
	    # Run through service and pick out the server and its agruments
	    while ($line = <IN>){
		if ($line =~ /^\s*server\s*= (\S+)/){
		    $line =~ /virtuald/ && do {
			print OUT $line  or die("error writing to $self->{file}: $!");
			last
		    };
		    $old_server = $1;
		    print "   $old_server\n";
		}
		elsif ($line =~ /^\s*server_args\s*= (.*)/){
		    $server_args = $1;
		}
		# last line in service found so print virtuald as server, and the old server and its arguments as server_args
		elsif ($line =~ /}/){
		    print OUT "	server		= $VSD::path{vsdsbindir}/virtuald\n" or die("error writing to $self->{file}: $!");
       		    print OUT "	server_args	= $old_server $server_args\n" or die("error writing to $self->{file}: $!");
		    print OUT $line or die("error writing to $self->{file}: $!");
		    last;
	        }
	        else {
		    print OUT $line or die("error writing to $self->{file}: $!");
		}
	    }
       }
       elsif ($line =~ /^includedir .*/){
           # Do nothing, we don't want this line in the new xinetd.conf file
       }
       else {
           print OUT $line  or die("error writing to $self->{file}: $!");
       }
   }

  close IN or die "Error closing $self->{file_tmp}: $!";
  close OUT or die "Error closing $self->{file}: $!";

  $self->log( 'entry' );

}

sub xinetd_append {
    my $self = shift;

    my %services = %{$_[0]};
    my $services = '';

    open IN, $self->{file} or die "Can't open $self->{file}:$!";
    while (<IN>){
	(/service\s+(\S+)[\s\{]/ || /(defaults)/) && ($services .= ":$1");
    }
    close IN;

    foreach ( keys %services ) {
	$self->append( "", "service $_", '{', @{$services{$_}}, '}' ) if $services !~ /:$_/;
    }
}


# FIX 2B continued...

#sub virtualise_xinetd {
#    my $self = shift;

#    my @exceptions = @_ ? shift : ();

#    my ( $xinetd_conf, %services );

#    !defined $self->{file_tmp} && do {
#	$self->{file_tmp} = '/tmp/vsd'.$self->{file};
#	mkpath dirname $self->{file_tmp}, 0, 0755 
#	    or die "Cannot make path to $self->{file_tmp} file: $!";
#    };
#    copy $self->{file}, $self->{file_tmp} or die "$!";

#    my $xinetd_conf;

#    open IN, $self->{file_tmp} or die "Can't open $self->{file_tmp}:$!";
#    while (<IN>) { $xinetd_conf .= $_ }
#    close IN;


#    my %services = get_service() ; 


#   open OUT, ">$self->{file}" or die "Can't open $self->{file}:$!";  

#    close OUT;
#}



sub DESTROY {
    my $self = shift;
    # write changes here if the Change object disappears B4 being written...
    $self->{log} !~ /<Change .+<\/Change>/s && $self->log('write');
    rmtree '/tmp/vsd', 0, 0;
}

1;




