#
# Copyright (c) 1990,1991,1992 The Ohio State University.
# All rights reserved.
#
# Redistribution and use in source and binary forms are permitted
# provided that: (1) source distributions retain this entire copyright
# notice and comment, and (2) distributions including binaries display
# the following acknowledgement:  ``This product includes software
# developed by The Ohio State University and its contributors''
# in the documentation or other materials provided with the distribution
# and in all advertising materials mentioning features or use of this
# software. Neither the name of the University nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#

#
# getback.perl - handle restores in a friendly fashion
#
# given host, directory and date - find the "best" sequence of tapes
# to restore from and ask that the user mount them in turn.  For each
# tape, check to be certain that its the right tape, skip to the right
# file, (check to be certain that its the right file) and run
# restore-type to extract the files. 
#
# Info we need from the user: 
#    -d level 			debug.
#    -T timestamp		time (default now, will ask).
#    -m interactive|full|pick
#				select mode of restore
#    -f host:tape		where's the tapes (will-ask).
#    -x				expert mode - lets us put things in the wrong 
#				places without asking permission.
#

$options = "hd:D:T:m:f:xoe";
$usage = 
"usage: getback [-d debuglevel] [-D host:directory] [-T time] [-m mode] 
    [-f host:tape] [-x] [-h] [-l] [-e] [file...]
    -d debuglevel	print debugging messages
    -D host:directory	what host, directory to restore FROM
    -T time		time to restore as of
    -m mode		type of restore - interactive, full or pick
    -f host:tape	which tape host, device to use
    -x			expert mode - don't ask questions
    -o                  include offsite tapes in the list
    -h			display this message
    -e			Don't eject tape when done.
    file...		optional list of files to extract\n";

require 'global.defs';
require 'local.defs';
require $yagrip_perl; 
require $backuplib_perl;
require $unctime_perl;
require $ctime_perl;

    #
    # Make sure that we clean up after ourselves if someone wants to kill us.
    #
$SIG{'INT'} = 'handle_interrupts';
$SIG{'HUP'} = 'handle_interrupts';
$SIG{'TERM'} = 'handle_interrupts';
$SIG{'QUIT'} = 'handle_interrupts';

&read_config();

#
# Deal with args and switches
#

die $usage if ! &getopt($options);
die $usage if defined($opt_h);

$ignore_offsite_tapes = ! defined($opt_o);

if (defined($opt_D)) {
    ($user, $host, $directory) = &splittapedev($opt_D);
    if ($host eq "") {
	$host = $hostname;
    }
} else {
    $host = &askdef("Host to restore from", $hostname, 
"Enter the name of the host from whose backups you want to restore the
files from.\n" );
    $directory = &askdef("File system to restore from", "/export/0",
"Enter the name of the file system or directory that you want to restore
the files from.  NOTE that this is the name of the file system or directory
that the backup system knows - if it was backed up as '/export/exec', then
that's the name to use.  The backup system DOES NOT know what subdirectories
are located within each backup, so you don't want to give the name of the
specific directory that you want to restore from.\n");
}

$file_list = join(' ', @ARGV);	# make a list of the file on the command line
				# for later use

if (defined($opt_T)) {
    $date_string = $opt_T;
} else {
    $date_string = &askdef("Time to restore as of", "now", 
"Enter the time that you want to restore the files as of.  The system will 
accept just about any kind of date and time formats.  Try something like 
'mar 23 1991 11:23'.  Giving a time causes the backup system to ignore any 
backups made after that time, which is useful if you are trying to retrieve
a previous version of a file, rather than the most recent.\n");
}

$tstamp = &parse_date_into_timestamp($date_string);
if ($tstamp == -1) {
    exit(1);
}

#
# Get restore mode if it wasn't given on the command line.  Be sure to check it 
#
while (1) {
    if (defined($opt_m)) {
	if ($opt_m eq "interactive" || $opt_m eq "full" || $opt_m eq "pick") {
	    last;
	} else {
	    warn "warning: restore mode must be one of interactive, full or pick.\n";
	}
    }

    $opt_m = &askdef("How do you want to restore (pick, interactive, full)", "interactive",
"pick mode gives you a menu of backups, sorted in reverse chronological order, 
from which you can repeatedly choose and examine tapes.  interactive mode selects
the 'best' tapes to restore from and walks you through them one by one, allowing 
you to select and extract files from each.  full mode picks the best tapes but 
extracts EVERYTHING from the backups onto the file system.  If you can guess what
backup a file is on, you probably want to use pick mode. If you don't know, use
interactive mode and browse through the tapes one by one.  If you are restoring
an entire file system use full mode.\n");
}

if (! defined($opt_x) && $host ne $hostname) {
    $is_ok = &askdef("You are restoring tapes from host $host onto host $hostname.\nIs this ok", "yes",
"Just being cautious.  If you are sure that you want to do this, type 'yes', 
otherwise type 'no'.\n");
    if ($is_ok ne "yes") {
	die "Ok, I'll abort the restore.\n";
    }
}

if (! defined($opt_x)) {
    $is_ok = &askdef("Change to directory $directory", "yes",
"Just being cautious.  Since the backups store files by relative pathnames, 
you probably ought to restore them into the same directories that the backups
were done from.  If you don't want to risk overwriting existing files, you
probably want to 'cd' to a temporary directory somewhere to do the restore.
Type 'yes' to 'cd' to the directory that the backups were done from, and 'no'
to stay where you are.  If you want to 'cd' somewhere else, type in that 
pathname.\n");
    if ($is_ok =~ /^y$/i || $is_ok =~ /^yes$/i) {
	chdir($directory) || 
	  die "error (getback): chdir to %s failed, exiting.\n", $directory;
    } elsif ($is_ok !~ /^n$/ && $is_ok !~ /^no$/i) {
	$directory = $is_ok;
	chdir($directory) || 
	  die "error (getback): chdir to %s failed, exiting.\n", $directory;
    }
}

&make_log_entry("getback $opt_m $host $directory $tstamp");

if ($opt_m eq "pick") {
    &do_pick_restore();
} else {
    &do_normal_restore();
}


sub do_normal_restore {
    local(%backup_list, %backup_times, @list_of_keys, $key, $buid, $chain, $level);
    local(%result, $tape_id, $file_number, $command);
    local($mode, $archive_host, $archive_file, $status);

    if ($opt_m eq "interactive") {
	$mode = "-I";
    } else {
	$mode = "-F";
    }

    #
    # Now the fun begins.
    #
    &get_recent_backups($host, $directory, $tstamp, *backup_list, *backup_times);
    @list_of_keys = &get_best_sequence(*backup_list, *backup_times);

    if (@list_of_keys == 0) {
	printf 
"warning: I cannot find a backup sequence to fullfill your request.  Try using
pick mode, or use list-backups $host:$directory to see the list of known
backups for this directory.\n";
	return;
    }

    printf("Looks like the best backup sequence is \n");
    foreach $key (@list_of_keys) {
	($buid, $chain, $level) = split(/,/, $key);
	&parse_db_entry($backup_list{$key}, *result);

	printf("    chain %s, level %s backup from run %s, buid %s, tape %s\n",
	       $chain,
	       $level,
	       $result{"run"},
	       $buid,
	       $result{"tflist"});

	#
	# If the archive file is available and done, mention that also...
	#
	($archive_host, $archive_file) = 
	  &get_archive_host_and_file($result{"afile"});
	$status = $result{"d-flags"};

	if ($archive_file ne "" && $status eq "done" &&
	    -r $archive_file) {
	    print "        online: $archive_host:$archive_file\tstatus: $status\n";
        }
    }

    foreach $key (@list_of_keys) {
        &restore_from_backup($backup_list{$key}, $mode, $file_list);
    }
}


sub do_pick_restore {
    local(%backup_list, %backup_times, @list_of_keys, $key, $buid, $chain, $level);
    local(%result, $tape_id, $file_number, $command);
    local($mode, $archive_host, $archive_file, $status);
    
    $mode = "-I";

    #
    # Now the fun begins.
    #
    %list = ();

    &get_recent_backups($host, $directory, $tstamp, *backup_list, *backup_times);
    @list_of_keys = sort chrono keys %backup_times;

    for ($i = 0; $i <= $#list_of_keys; $i++) {
	&parse_db_entry($backup_list{$list_of_keys[$i]}, *budb_array);
	
	($buid, $chain, $level) = split(/,/, $list_of_keys[$i]);
	($tape_id, $file_number) = split(/\./, $budb_array{"tflist"});
	$date = &short_date($budb_array{"todate"} + 0);

	@archives = ();

	if ($tape_id ne "") {
	    # fixme: need to get tape location
	    $location = "";
	    push(@archives, sprintf("tape: %-7s %s.%d%s",
				    $budb_array{"flags"},
				    $tape_id,
				    $file_number,
				    $location));
	}

	if ($budb_array{"d-flags"} eq "done") {
	    push(@archives, sprintf("disk: %s", $budb_array{"afile"}));
	}
    
#               chain,
# #  run  buid  level type   date                tape or disk archive
#--- ---- ----  ----  ----   ----                --------------------\n");
#123 1234 12345 11,11 dumpx  3/28/92 17:52:28 tape: done test-exb-23.2
#                                             disk: done /n/misc/0/repository/shape.12345

	$entry = sprintf("%-3d %4d %5d %2d,%-2d %-5s %-18s %s\n",
			 $i,
			 $budb_array{"run"},
			 $buid,
			 $chain,
			 $level,
			 $budb_array{"type"},
			 $date,
			 $archives[0]);

	if ($#archives == 1) {
	    $entry .= sprintf("                                              %s\n",
			      $archives[1]);
	}

	push(@print_list, $entry);
    }
	
which_loop:
    while (1) {

	printf("
               chain,
 #  run  buid  level type  date               tape or disk archive
--- ---- ----  ----  ----  ----               --------------------\n");

	for ($i = 0; $i <= $#list_of_keys; $i++) {
	    print $print_list[$i];
	}

        $which = &askdef("Which backup do you want to restore from (q to quit)", 
			 "",
"Enter the line number of the backup that you want to restore from.  
Enter 'q' to quit.\n");

	next if $which eq "";

	last if $which =~ m/q|quit|exit|bye|adios/i;

	$which += 0;
	if ($which < 0 || $which > $#list_of_keys) {
	    next which_loop;
	}

        &restore_from_backup($backup_list{$list_of_keys[$which]}, $mode, $file_list);
    }
}


#
# restore_from_backup handles running the restore program to get stuff from 
# a single backup.  checks tape, skips to file, runs program, ejects tape
#
sub restore_from_backup {
    local($bu_data, $mode, $file_list) = @_;
    local(%bu_entry, $drive_name, $tape_host, $tape_dev, $rsh, $rest_cmd);
    local(%replace, %add, %result);
    local($using_tape, $tape_id, $file_number);
    local($archive_host, $archive_file);
    local($accuracy);

    &parse_db_entry($bu_data, *bu_entry);

    #
    # If the archive file is available and done, use that instead of the tape...
    #
    ($archive_host, $archive_file) =
       &get_archive_host_and_file($bu_entry{"afile"});
    
    if ($archive_file ne "" && $bu_entry{"d-flags"} eq "done" &&
	-r $archive_file) {
        print "Looks like this backup is online in '$archive_host:$archive_file'.\nLet's use that for the restore...\n";

	$rest_cmd = sprintf("$zcat_prog $archive_file | $restore_prog-%s $mode -f - $file_list",
			   $bu_entry{"type"});

	$using_tape = 0;
	if ($riscos) {
		$rest_cmd =~ s/rrest/rest/;
	}
    } elsif ($bu_entry{"tflist"} ne "") { 
	($tape_id, $file_number) = split(/\./, $bu_entry{"tflist"});
	if (defined($bu_entry{"accuracy"})) {
	    $accuracy = $bu_entry{"accuracy"};
	} else {
	    $accuracy = "high";
	}

	if (&update_db($tape_db_file, $tape_db_file, $tape_id, "tape_db",
		       $DB_READ, *replace, *add, *result)) {
	    die "error (getback): no entry for $tape_id, but there should be!\n";
	}

	$using_tape = 1;
	($drive_name, $tape_user, $tape_host, $tape_dev) = 
	   &get_tape_name($result{"type"}, $result{"format"}, "read");

	$opt_f = "$tape_host:$tape_dev";

	$rsh = &make_rsh_cmd($tape_user, $tape_host);

	while (! &verify_tape($tape_id, $tape_host, $tape_dev, 
			      $rsh, $tape_type{$drive_name})) {
#	    &tape_offload($rsh, $tape_dev);
	}

	#
	# Position the tape.
	#
	&tape_rewind($rsh, $tape_dev);
	print "Skipping to file $file_number on the tape (this may take a while)...\n";
	if (&tape_fsf($rsh, $tape_dev, $file_number)) {
	    warn "warning (getback): couldn't position the tape to the correct file number: results might be screwy!\n";
	}

	#
	# for debugging...(only works if mt status works on the tape host).
	#
	system("$rsh $mt_prog -f $tape_dev status") if $opt_d & $DEBUG_INFO;

	#
	# If the accuracy isn't "high", then warn the user that it might 
	# not be at the right place and give him a chance to reposition the tape.
	#
	if ($accuracy ne "high") {
	    print 
"\nThis backup was made on a tape host that doesn't support a good 'mt status' 
command, so I had to estimate the file number when the backup was made.\n";
	    if ($accuracy eq "so-so") {
		print
"No errors occurred earlier on this tape, so the tape is probably 
positioned correctly.\n\n";
	    } else {
		print
"Some backup errors occurred earlier on this tape, so the tape position might 
not be correct.\n\n";
	    }

	    &reposition_the_tape($rsh, $tape_dev);
	}

	#
	# Construct the command that is to be run...
	#
	$rest_cmd = sprintf("$restore_prog-%s $mode -f $tape_host:$tape_dev $file_list",
			   $bu_entry{"type"});
    } else {			# Don't know what the heck we're using!
        warn "warning (getback): Wah! can't find the archive file and there are no tape files listed!\n";
	return;
    }

    print "Starting the restore program...\n";

    #
    # Run restore-${type} to extract the stuff from the tape.
    #
    &my_system($rest_cmd);

    if ($using_tape) {
	#
	# Offload the tape.
	#
	print "Rewinding and unloading the tape...this may take awhile.\n";
	if (defined($opt_e)) {
	    &tape_rewind($rsh, $tape_dev);
	} else {
	    &tape_offload($rsh, $tape_dev);
	}
    }
}


#
# &get_best_sequence picks out the best sequence of backups to use in 
# restoring a file system.  %list is an assoc array as returned by
# &get_recent_backups.  &get_best_sequence winds through this list and
# makes a list of the keys in %list that should be used.  The array of
# keys is returned to the caller.
#
# To do this, we sort the keywords into chronological order (most to
# least recent).  We take whatevers on the top of the list, and work
# back through that chain until we hit a level 0 backup.  We only need
# the most recent highest level backups, so we carefully pick those
# out (%most_recent is used for that).
#
# Consider this example:
# keyword list (after sorting)				keyword list (result)
# ----------------------------				---------------------
#	57,0,9		--> most recent 0,9, keep	--> 57,0,9
#	52,0,9		--> superceded by 57,0,9
#	45,0,2		--> most recent 0,2, keep	--> 45,0,2
#	44,1,0		--> most recent 1,0, keep	--> 44,1,0
#	40,0,2		--> superceded by 45,0,2
#	20,0,0		--> most recent 0,0, keep	--> 20,0,0
#	18,1,2		--> superceded by 44,1,0
# 
# fixme:
#    should be more robust - if the chain we are following has gaps or
#    doesn't end in a level 0, need to retry on a different chain.
#
#    takes both args since we should eventually check the flags in the 
#    backup DB entry to ensure that we only use DONE backups.
#
sub get_best_sequence {
    local(*backup_list, *backup_times) = @_;
    local($key, @keywords);
    local($buid, $chain, $level);
    local(%most_recent);
    local($target_chain) = '';

key_loop:
    foreach $key (sort chrono keys %backup_times) {
	($buid, $chain, $level) = split(/,/, $key);
        $level += 0;

	if ($target_chain eq "") {
	    $target_chain = $chain;
	} elsif ($chain ne $target_chain) {
	    next key_loop;
	}

        if (! defined($most_recent{$chain})) {
	    $most_recent{$chain} = $level;
	    push(@keywords, $key);
	} else {
	    if ($level < $most_recent{$chain}) {
		$most_recent{$chain} = $level;
		push(@keywords, $key);
	    }
	}
    }

    return(reverse(@keywords));
}

sub reposition_the_tape {
    local($rsh, $tape_dev) = @_;

    $| = 1;
    print "Go reposition the tape if you want, and press <return> to continue.";
    $| = 0;
    <stdin>;
}

    
