# Copyright (c) 2014-2016 Carbonite, Inc.  All Rights Reserved.
#
# 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.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# Contact information: Carbonite Inc., 756 N Pastoria Ave
# Sunnyvale, CA 94085, or: http://www.zmanda.com


package Amanda::Cleanup::Message;
use strict;
use warnings;

use Amanda::Message;
use vars qw( @ISA );
@ISA = qw( Amanda::Message );

sub local_message {
    my $self = shift;

    if ($self->{'code'} == 3400000) {
        return "$self->{'process_name'} Process is running at PID $self->{'pid'} for $self->{'config_name'} configuration";
    } elsif ($self->{'code'} == 3400001) {
	return "$self->{'nb_amanda_process'} Amanda processes were found running";
    } elsif ($self->{'code'} == 3400002) {
        return "$self->{'nb_processes'} processes failed to terminate";
    } elsif ($self->{'code'} == 3400003) {
        return "no unprocessed logfile to clean up";
    } elsif ($self->{'code'} == 3400004) {
        return "$self->{'errfile'} exists, renaming it";
    } elsif ($self->{'code'} == 3400005) {
        return "failed to execute $self->{'program'}: $self->{'errstr'}";
    } elsif ($self->{'code'} == 3400006) {
        return "$self->{'program'} died with signal $self->{'signal'}";
    } elsif ($self->{'code'} == 3400007) {
        return "$self->{'program'} exited with value $self->{'exit_status'}";
    } elsif ($self->{'code'} == 3400008) {
        return "$self->{'program'} stdout: $self->{'line'}";
    } elsif ($self->{'code'} == 3400009) {
        return "$self->{'program'} stderr: $self->{'line'}";
    } elsif ($self->{'code'} == 3400010) {
        return "no trace_log";
    } else {
	return "no message for code $self->{'code'}";
    }
}

package Amanda::Cleanup;
use strict;
use warnings;

use Amanda::Config qw( :init :getconf config_dir_relative );;
use Amanda::Util qw( :constants );
use Amanda::Paths;
use Amanda::Constants;
use Amanda::Process;
use Amanda::Logfile;
use Amanda::Holding;
use Amanda::Debug qw( debug );
use Amanda::Logfile qw( :logtype_t log_add $amanda_log_trace_log );
use IPC::Open3;
use File::Basename;

sub new {
    my $class = shift;
    my %params = @_;

    my $self = \%params;
    bless $self, $class;

    return $self;
}

sub user_message {
    my $self = shift;
    my $message = shift;

    if (defined $self->{'user_message'}) {
	$self->{'user_message'}->($message);
    } else {
	push @{$self->{'result_messages'}}, $message;
    }
}
sub cleanup {
    my $self = shift;
    my %params = @_;

    $self->{'kill'}          = $params{'kill'}          if defined $params{'kill'};
    $self->{'process_alive'} = $params{'process_alive'} if defined $params{'process_alive'};
    $self->{'verbose'}       = $params{'verbose'}       if defined $params{'verbose'};
    $self->{'clean_holding'} = $params{'clean_holding'} if defined $params{'clean_holding'};
    $self->{'trace_log'}     = $params{'trace_log'}     if defined $params{'trace_log'};
    $self->{'user_message'}  = $params{'user_message'}  if defined $params{'user_message'};

    $self->{'logdir'} = config_dir_relative(getconf($CNF_LOGDIR));
    $self->{'logfile'} = "$self->{'logdir'}/log";
    $self->{'logfile'} = $self->{'trace_log'} if defined $self->{'trace_log'};
    if (-l $self->{'logfile'}) {
	my $dirname = dirname $self->{'logfile'};
	my $target = readlink $self->{'logfile'};
	$self->{'logfile'} = "$dirname/$target";
    }
    $self->{'amreport'} = "$sbindir/amreport";
    $self->{'amtrmidx'} = "$amlibexecdir/amtrmidx";
    $self->{'amcleanupdisk'} = "$sbindir/amcleanupdisk";

    my $config_name = Amanda::Config::get_config_name();

    my $Amanda_process = Amanda::Process->new($self->{'verbose'}, sub {my $message=shift; $self->user_message($message); });
    $Amanda_process->load_ps_table();

    $Amanda_process->scan_log($self->{'logfile'});
    return if (!-e $self->{'logfile'});
    $Amanda_process->add_child();

    my $nb_amanda_process = $Amanda_process->count_process();
    if ($nb_amanda_process > 0) {
	if ($self->{'process_alive'}) {
	    return;
	} elsif (!$self->{'kill'}) {
	    $self->user_message(Amanda::Cleanup::Message->new(
			source_filename	=> __FILE__,
			source_line	=> __LINE__,
			code		=> 3400000,
			severity	=> $Amanda::Message::INFO,
			process_name	=> $Amanda_process->{master_pname},
			pid		=> $Amanda_process->{master_pid},
			config_name	=> $config_name));
	    return $self->{'result_messages'};
	} else { # kill the processes
	    # Add the notes to the log file
	    Amanda::Logfile::set_logname($self->{'logfile'});
	    foreach my $note (@{$self->{'notes'}}) {
		log_add($L_INFO, $note);
	    }

	    # Add a line the the amdump file
	    my $amdump_log = $self->{'logfile'};
	    $amdump_log =~ s/log\.(\d\d\d\d\d\d\d\d\d\d\d\d\d\d)\.0/amdump.$1/;
	    if (-e $amdump_log) {
		open(my $amdump_log_file, ">>", $amdump_log)
		    or die("could not open amdump log file '$amdump_log'}': $!");
		print {$amdump_log_file} "amcleanup: aborted by amcleanup\n";
		close($amdump_log_file);
	    } else {
		debug("amdump log '$amdump_log' does not exists");
	    }

	    $self->user_message(Amanda::Cleanup::Message->new(
			source_filename	=> __FILE__,
			source_line	=> __LINE__,
			code		=> 3400001,
			severity	=> $Amanda::Message::INFO,
			nb_amanda_process => $nb_amanda_process));

	    Amanda::Debug::debug("Killing amanda process");
	    $Amanda_process->kill_process("SIGTERM");
	    my $count = 5;
	    my $pp;
	    while ($count > 0) {
		$pp = $Amanda_process->process_running();
		if ($pp > 0) {
		    $count--;
		    sleep 1;
		} else {
		    $count = 0;
		}
	    }
	    if ($pp > 0) {
		$count = 5;
		$Amanda_process->kill_process("SIGKILL");
		while ($count > 0 and $pp > 0) {
		    $count--;
		    sleep 1;
		    $pp = $Amanda_process->process_running();
		}
	    }
	    ($pp, my $pids) = $Amanda_process->which_process_running();
	    $self->user_message(Amanda::Cleanup::Message->new(
			source_filename	=> __FILE__,
			source_line	=> __LINE__,
			code		=> 3400002,
			severity	=> $pp == 0 ? $Amanda::Message::INFO : $Amanda::Message::ERROR,
			pids		=> $pids,
			nb_processes	=> $pp));

	    # rotate log
	    Amanda::Debug::debug("Processing log file");
	    $self->run_system(0, $self->{'amreport'}, $config_name, "--from-amdump");

	    $self->run_system(1, $self->{'amtrmidx'}, $config_name);
	}
    } else {
	$self->user_message(Amanda::Cleanup::Message->new(
			source_filename	=> __FILE__,
			source_line	=> __LINE__,
			code		=> 3400003,
			severity	=> $Amanda::Message::INFO));
    }

    my $tapecycle = getconf($CNF_TAPECYCLE);

    # cleanup logfiles
    chdir "$CONFIG_DIR/$config_name";
    foreach my $pname ("amdump", "amflush") {
	my $errfile = "$self->{'logdir'}/$pname";
	if (-f $errfile) {
	    $self->user_message(Amanda::Cleanup::Message->new(
			source_filename	=> __FILE__,
			source_line	=> __LINE__,
			code		=> 3400004,
			severity	=> $Amanda::Message::INFO,
			errfile		=> $errfile));

	    # Keep debug log through the tapecycle plus a couple days
	    my $maxdays=$tapecycle + 2;

	    my $days=1;
	    # First, find out the last existing errfile,
	    # to avoid ``infinite'' loops if tapecycle is infinite
	    while ($days < $maxdays  && -f "$errfile.$days") {
		$days++;
	    }

	    # Now, renumber the existing log files
	    while ($days >= 2) {
		my $ndays = $days - 1;
		rename("$errfile.$ndays", "$errfile.$days");
		$days=$ndays;
	    }
	    rename($errfile, "$errfile.1");
	}
    }

    my @amcleanupdisk;
    push @amcleanupdisk, $self->{'amcleanupdisk'};
    push @amcleanupdisk, "-v" if $self->{'verbose'};
    push @amcleanupdisk, "-r" if $self->{'clean_holding'};
    push @amcleanupdisk, $config_name;
    $self->run_system(0, @amcleanupdisk);

    if (defined $self->{'result_messages'}) {
	return \@{$self->{'result_messages'}};
    } else {
	return;
    }
}

sub run_system {
    my $self = shift;
    my $check_code = shift;
    my @cmd = @_;
    my $program = $cmd[0];
    my @result_messages;

    debug("Running: " . join(' ', @cmd));
    my ($pid, $wtr, $rdr, $err);
    $err = Symbol::gensym;
    $pid = open3($wtr, $rdr, $err, @cmd);
    close $wtr;
    while (my $line = <$rdr>) {
	chomp;
	$self->user_message(Amanda::Cleanup::Message->new(
			source_filename	=> __FILE__,
			source_line	=> __LINE__,
			code		=> 3400008,
			severity	=> $Amanda::Message::INFO,
			program		=> $program,
			line		=> $line));
    }
    close $rdr;
    while (my $line = <$err>) {
	chomp;
	$self->user_message(Amanda::Cleanup::Message->new(
			source_filename	=> __FILE__,
			source_line	=> __LINE__,
			code		=> 3400009,
			severity	=> $Amanda::Message::ERROR,
			program		=> $program,
			line		=> $line));
    }
    close $err;
    waitpid $pid, 0;
    my $child_error = $?;
    my $errno = $!;

    if ($child_error == -1) {
	$self->user_message(Amanda::Cleanup::Message->new(
			source_filename	=> __FILE__,
			source_line	=> __LINE__,
			code		=> 3400005,
			severity	=> $Amanda::Message::INFO,
			program		=> $program,
			errstr		=> $errno));
    } elsif ($child_error & 127) {
	$self->user_message(Amanda::Cleanup::Message->new(
			source_filename	=> __FILE__,
			source_line	=> __LINE__,
			code		=> 3400006,
			severity	=> $Amanda::Message::INFO,
			program		=> $program,
			signal		=> ($child_error & 127)));
    } elsif ($check_code && $child_error > 0) {
	$self->user_message(Amanda::Cleanup::Message->new(
			source_filename	=> __FILE__,
			source_line	=> __LINE__,
			code		=> 3400007,
			severity	=> $Amanda::Message::INFO,
			program		=> $program,
			exit_status	=> ($child_error >> 8)));
    }
}

1;
