#!/usr/bin/perl # # Copyright (C) 2001-2004 MandrakeSoft by Sebastien DUPONT # Updated 2002-2004 by Stew Benedict # Redistribution of this file is permitted under the terms of the GNU # Public License (GPL) # # 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, 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. use lib qw(/usr/lib/libDrakX); use standalone; #- warning, standalone must be loaded very first, for 'explanations' use strict; use interactive; use common; use detect_devices; # Backend Options. # make this global for status screen my ($window1, $my_win); my $central_widget; my $previous_widget; my $current_widget; my $interactive; my $up_box; my $advanced_box; my $box2; my $cfg_file_exist = 0; my @user_list_all; my $DEBUG = 0; my $restore_sys = 1; my $restore_user = 1; my $restore_other = 1; my $restore_step_sys_date = ""; my $restore_step_other_date = ""; my @user_backuped; my @sys_backuped; my @other_backuped; my @user_list_to_restore; my @sys_list_to_restore; my @other_list_to_restore; my $button_box; my $button_box_tmp; my $next_widget; my $system_state; my $restore_state; my $save_path_entry; my $restore_find_path_entry; my $new_path_entry; my $pbar; my $pbar1; my $pbar2; my $pbar3; my $plabel; my $plabel1; my $plabel2; my $plabel3; my $stext; my $list_model; my $the_time; my @user_list_to_restore2; my $restore_path = "/"; my $restore_other_path = 0; my $restore_other_src; my $path_to_find_restore; my $other_media_hd; my $backup_bef_restore = 0; my $table; my @user_list_backuped; my @files_corrupted; my %check_user_to_restore; my $remove_user_before_restore = 0; my @file_list_to_send_by_ftp; my $results; my @net_methods = ("ftp", "rsync", "ssh", "webdav"); my @media_types = (translate(N_("hd")), "cd", translate(N_("tape"))); my %cd_devices; my $std_device; my @tape_devices; my $tar_ext = "tar.gz"; my $in; # config. FILES -> Default PATH & Global variables. my @user_list; my $cfg_dir = "/etc/drakxtools/drakbackup/"; my $cfg_file = $cfg_dir . "drakbackup.conf"; my $log_file = "/var/log/drakbackup"; my $log_buff; my $manual_user = 0; my $backup_daemon = 0; my $daemon = 0; my $use_hd = 1; my $custom_cron = 0; my $session_offset = ''; my $scp_port = 22; my $user_home = $ENV{HOME}; my $nonroot_user = 0; my $media_problem = 0; my $vol_name = 'Drakbackup'; my $good_restore_path = 1; my @no_devices = translate(N_("No devices found")); my %help; my %conf; my $time_string = "* * * * *"; my $exec_string = "export USER=$ENV{USER}; /usr/sbin/drakbackup --daemon > /dev/null 2>&1"; my $ignore_files_list; my @list_of_rpm_to_install; my @other_files; my @sys_files = "/etc"; # allow not-root user with own config if ($ENV{USER} ne 'root' && $ENV{HOME} ne '/root') { standalone::explanations("Running as $ENV{USER}..."); #- doesn't get defined when run from cron $user_home = "/home/$ENV{USER}" if $user_home eq ''; $cfg_dir = "$user_home/.drakbackup/"; $conf{PATH_TO_SAVE} = $cfg_dir . "backups"; $log_file = $cfg_dir . "drakbackup.log"; $nonroot_user = 1; $conf{NO_SYS_FILES} = 1; @user_list = $ENV{USER}; } else { $user_home = "/root"; $conf{PATH_TO_SAVE} = "/var/lib/drakbackup"; } $cfg_file = $cfg_dir . "drakbackup.conf"; my $backup_key = $user_home . "/.ssh/identity-drakbackup"; foreach (@ARGV) { /--default/ and backend_mode(); /--daemon/ and daemon_mode(); /--show-conf/ and show_conf(); /--config-info/ and explain_conf(); /--cd-info/ and get_cd_info(), exit(0); /--debug/ and $DEBUG = 1, next; } sub setup_tooltips() { %help = ( 'use_expect' => N("Expect is an extension to the Tcl scripting language that allows interactive sessions without user intervention."), 'remember_pass' => N("Store the password for this system in drakbackup configuration."), 'erase_cdrw' => N("For a multisession CD, only the first session will erase the cdrw. Otherwise the cdrw is erased before each backup."), 'use_incr_decr' => N("This option will save files that have changed. Exact behavior depends on whether incremental or differential mode is used."), 'use_incremental' => N("Incremental backups only save files that have changed or are new since the last backup."), 'use_differential' => N("Differential backups only save files that have changed or are new since the original 'base' backup."), 'send_mail_to' => N("This should be a local user or email addresse that you want the backup results sent to. You will need to define a functioning mail server."), 'backupignore' => N("Files or wildcards listed in a .backupignore file at the top of a directory tree will not be backed up."), 'delete_files' => N("For backups to other media, files are still created on the hard drive, then moved to the other media. Enabling this option will remove the hard drive tar files after the backup."), 'dir_or_module' => N("Some protocols, like rsync, may be configured at the server end. Rather than using a directory path, you would use the 'module' name for the service path."), 'when_space' => N("Custom allows you to specify your own day and time. The other options use run-parts in /etc/crontab."), ); } sub show_conf() { print "DrakBackup configuration:\n\n"; read_conf_file(); system_state(); print "$system_state\n"; exit(0); } sub explain_conf() { print "\nConfiguration File Options: \n\n"; print "Configuration file is located in:\n"; print " Root Mode: /etc/drakxtools/drakbackup/drakbackup.conf.\n"; print " User Mode: ~/.drakbackup/drakbackup.conf.\n\n"; print "SYS_FILES Comma separated list of system directories to backup.\n"; print "HOME_FILES Comma separated list of user home directories to backup.\n"; print "OTHER_FILES Comma separated list of other files to backup.\n"; print "PATH_TO_SAVE Default Hard Drive path to create backup files in.\n"; print " Root Mode: default is /var/lib/drakbackup.\n"; print " User Mode: default is ~/.drakbackup/backups.\n"; print "NO_SYS_FILES Don't backup system files.\n"; print "NO_USER_FILES Don't backup user files.\n"; print "BACKUPIGNORE Honor .backupignore files in backup directories\n"; print "OPTION_COMP Compression option (tar.gz is default, 1 = tar.bz2).\n"; print "NO_BROWSER_CACHE Skip web browser cache.\n"; print "CDRW Backup media is re-writable CD.\n"; print "DVDR Backup media is recordable DVD (not fully supported yet).\n"; print "DVDRW Backup media is recordable DVD+RW.\n"; print "DVDRAM Backup media is DVDRAM (not fully supported yet).\n"; print "NET_PROTO Network protocol to use for remote backups: \n"; print " ftp, rsync, ssh, or webdav.\n"; print "HOST_NAME Remote backup host.\n"; print "HOST_PATH Backup storage path or module on remote host.\n"; print "REMEMBER_PASS Remember password on remote host in config file.\n"; print "USER_KEYS Ssh keys are already setup for communicating with remote host.\n"; print "DRAK_KEYS Use special drakbackup generated host keys.\n"; print " (requires perl-Expect).\n"; print "USE_EXPECT Use expect to do the whole scp transfer, without keys.\n"; print " (requires perl-Expect).\n"; print "LOGIN Remote host login name.\n"; print "PASSWD Password on remote host (if REMEMBER_PASS is enabled).\n"; print "DAEMON_MEDIA Daemon mode backup via given media.\n"; print " (hd, cd, tape, ftp, rsync, ssh, or webdav).\n"; print "HD_QUOTA Use quota to limit hard drive space used for backups.\n"; print " (not supported yet).\n"; print "USE_HD Use Hard Drive for backups (currently all modes use HD also).\n"; print "MAX_SPACE Maximum Hard Drive Space(MB) to consume for backups. \n"; print "USE_CD Use CD for backups.\n"; print "USE_NET Use network for backups (driven by NET_PROTO).\n"; print "USE_TAPE Use tape for backup.\n"; print "DEL_HD_FILES Delete local hard drive tar files after backup to other media.\n"; print "TAPE_NOREWIND Use non-rewinding tape device.\n"; print "CD_TIME Length of CD media (not currently utilized).\n"; print "DAEMON_TIME_SPACE Interval between daemon backup runs (hourly, daily, weekly)..\n"; print "CD_WITH_INSTALL_BOOT Build a bootable restore CD (currently not utilized).\n"; print "CD_DEVICE Cdrecord style CD device name (ie: 1,3,0).\n"; print "USER_MAIL User to send backup results to via email.\n"; print "SEND_MAIL Do send backup results via email.\n"; print "TAPE_DEVICE Device to use for tape backup (ie: /dev/st0).\n"; print "MEDIA_ERASE Erase media before new backup (applies to tape, CD).\n"; print "MEDIA_EJECT Eject media after backup completes.\n"; print "MULTI_SESSION Allow muliple sessions to be written to CD media.\n"; print "SYS_INCREMENTAL_BACKUPS Do incremental or differential backups of system files.\n"; print "USER_INCREMENTAL_BACKUPS Do incremental or differential backups of user files.\n"; print "OTHER_INCREMENTAL_BACKUPS Do incremental or differential backups if other files.\n"; print "SYS_DIFFERENTIAL_BACKUPS Do differential backups of system files.\n"; print "USER_DIFFERENTIAL_BACKUPS Do differential backups of user files.\n"; print "OTHER_DIFFERENTIAL_BACKUPS Do differential backups if other files.\n"; print "NO_CRITICAL_SYS Do not backup critical system files:\n"; print " passwd, fstab, group, mtab\n"; print "CRITICAL_SYS Do backup above system files.\n"; exit(0); } sub backend_mode() { build_backup_files(); exit(0); } sub daemon_mode() { $daemon = 1; build_backup_files(); exit(0); } if (check_for_xserver()) { eval { require ugtk2 }; die "Can't load ugtk2...\n" if $@; ugtk2->import(qw(:create :dialogs :helpers :wrappers)); interactive_mode(); } else { die "Can't run in console mode..."; } sub set_help_tip { my ($entry, $key) = @_; gtkset_tip(new Gtk2::Tooltips, $entry, formatAlaTeX($help{$key})); } sub all_user_list() { if ($nonroot_user) { @user_list_all = $ENV{USER}; return; } my $user; my $uid; @user_list_all = (); my @passwd = cat_("/etc/passwd"); foreach (@passwd) { ($user, $uid) = (split(/:/, $_))[0, 2]; if ($uid >= 500 && $uid < 65000 || $uid == 0) { push @user_list_all, $user; } } } sub the_time() { $the_time = "_"; my ($sec, $min, $hour, $day, $month, $year) = localtime(time()); $the_time .= $year + 1900; if ($month < 9) { $the_time .= "0" } $the_time .= $month + 1; if ($day < 10) { $the_time .= "0" } $the_time .= $day; $the_time .= "_"; if ($hour < 10) { $the_time .= "0" } $the_time .= $hour; if ($min < 10) { $the_time .= "0" } $the_time .= $min; if ($sec < 10) { $the_time .= "0" } $the_time .= $sec; } sub get_tape_info() { my @line_data; my $info = "$ENV{HOME}/tmp/dmesg"; @tape_devices = (); system("dmesg | grep 'st[0-9] at' > $info"); local *INFO; open INFO, $info or warn("Can't open $info\n"); local $_; while () { @line_data = split(/[ \t,]+/, $_); push @tape_devices, "/dev/" . $line_data[3]; } close INFO; unlink($info); } sub get_free_space { my ($dir) = @_; my $free = `df -P $dir | tail -1`; my @line_data = split(/[ \t,]+/, $free); my $free_space = int($line_data[3] / 1024); return $free_space; } sub check_storage_quota { my ($dir) = @_; my $used = `du $dir`; my $used_space = $used / 1024; if ($used_space > $conf{MAX_SPACE}) { return $used_space; } else { return 0; } } sub get_cd_info() { my @line_data; my @drive_names; my $i; require_rpm("cdrecord") if $nonroot_user; # just trying load ide-cd, since it doesn't seem to be loaded by default $nonroot_user ? `cdrecord -scanbus -dev=ATA > /dev/null 2>&1` : `modprobe ide-cd` unless -f "/proc/sys/dev/cdrom"; my @cd_info = cat_("/proc/sys/dev/cdrom/info"); my %data = ( "drive speed" => 'speed', "Can change speed" => 'chg_speed', "Can read multisession" => 'multisession', "Can write CD-R" => 'cdr', "Can write CD-RW" => 'cdrw', "Can write DVD-R" => 'dvdr', "Can write DVD-RAM" => 'dvdram' ); my $cd_drives; foreach (@cd_info) { @line_data = split(/[:\t]+/, $_); if ($line_data[0] =~ /drive name/) { $cd_drives = @line_data-1; chop($line_data[$cd_drives]); @drive_names = @line_data; print "drives: $cd_drives\n" unless $interactive; } chop($line_data[$cd_drives]) if $cd_drives; foreach my $key (keys %data) { if ($line_data[0] eq $key) { for ($i = 1; $i <= $cd_drives; $i++) { $cd_devices{$drive_names[$i]}{$data{$key}} = $line_data[$i]; } } } } #- now just report the data if we called --cd-info from the command line foreach my $key (keys %cd_devices) { my $rec_dev = $key; my $prefix; $rec_dev =~ s/sr/sg/; $prefix = "ATAPI:" if $rec_dev =~ /hd/; my $can_record = $cd_devices{$key}{cdr} || $cd_devices{$key}{cdrw} || $cd_devices{$key}{dvdr}; $cd_devices{$key}{rec_dev} = $prefix . "/dev/" . $rec_dev if $can_record; if (!$interactive) { print "\n{$key}->{rec_dev} = $cd_devices{$key}{rec_dev}\n"; print "{$key}->{speed} = $cd_devices{$key}{speed}\n"; print "{$key}->{chg_speed} = $cd_devices{$key}{chg_speed}\n"; print "{$key}->{multisession} = $cd_devices{$key}{multisession}\n"; print "{$key}->{cdr} = $cd_devices{$key}{cdr}\n"; print "{$key}->{cdrw} = $cd_devices{$key}{cdrw}\n"; print "{$key}->{dvdr} = $cd_devices{$key}{dvdr}\n"; print "{$key}->{dvdram} = $cd_devices{$key}{dvdram}\n"; } else { delete $cd_devices{$key} if $cd_devices{$key}{rec_dev} eq '' } } } sub save_conf_file() { write_sitecopyrc() if $conf{NET_PROTO} eq 'webdav'; write_password_file() if $conf{NET_PROTO} eq 'rsync' && $conf{PASSWD}; return 1 if $conf{SEND_MAIL} && verify_mail_setup(); if ($backup_daemon && $conf{DAEMON_MEDIA} eq '') { show_warning("f", N("No media selected for cron operation.")); return 1; } if ($backup_daemon && $conf{DAEMON_TIME_SPACE} eq '') { show_warning("f", N("No interval selected for cron operation.")); return 1; } if (!$backup_daemon) { $conf{DAEMON_TIME_SPACE} = ""; $conf{DAEMON_MEDIA} = ""; } $conf{NO_USER_FILES} = '' if @user_list == (); $conf{OTHER_FILES} = list_to_conf(@other_files); $conf{HOME_FILES} = list_to_conf(@user_list); $conf{SYS_FILES} = list_to_conf(@sys_files); setVarsInSh($cfg_file, \%conf); chmod(0600, $cfg_file); save_cron_files(); 0; } sub read_cron_files() { my $daemon_found = 0; foreach (qw(hourly daily weekly monthly)) { if (-f "/etc/cron.$_/drakbackup" && !$nonroot_user) { $conf{DAEMON_TIME_SPACE} = $_; $daemon_found = 1; last; } } if ($conf{DAEMON_TIME_SPACE} ne "custom") { !$daemon_found and $backup_daemon = 0; } else { $custom_cron = 1; my $tmpcron = "$ENV{HOME}/tmp/crontab.tmp"; $tmpcron = `crontab -l | tail +4`; my @cronline = grep { /drakbackup/ } $tmpcron; if (@cronline) { @cronline = split(" ", $cronline[0]); my @crondetail = splice(@cronline, 0, 5); $time_string = join(" ", @crondetail); } } } sub save_cron_files() { my $tmpcron = "$ENV{HOME}/tmp/crontab.tmp"; if ($nonroot_user && $conf{DAEMON_TIME_SPACE} ne "custom" && $conf{DAEMON_TIME_SPACE} ne '' && $backup_daemon) { show_warning("w", N("Interval cron not available as non-root")); $conf{DAEMON_TIME_SPACE} = 'custom'; return 1; } else { foreach (qw(hourly daily weekly monthly)) { -f "/etc/cron.$_/drakbackup" and rm_rf("/etc/cron.$_/drakbackup") if !$nonroot_user; } } my @cron_file = ("#!/bin/sh\n", "export USER=root\n", "/usr/sbin/drakbackup --daemon > /dev/null 2>&1\n"); if ($conf{DAEMON_TIME_SPACE} ne "custom" && $conf{DAEMON_TIME_SPACE} ne '' && $backup_daemon) { output_p("/etc/cron.$conf{DAEMON_TIME_SPACE}/drakbackup", @cron_file); system("chmod +x /etc/cron.$conf{DAEMON_TIME_SPACE}/drakbackup"); } if ($conf{DAEMON_TIME_SPACE} eq "custom" || !$backup_daemon) { my $newdetail = join(" ", $time_string, $exec_string, "\n") if $backup_daemon; system("crontab -l | tail +4 > $tmpcron"); my @cronlines = cat_($tmpcron); my $index = 0; foreach (@cronlines) { if (/$exec_string/) { splice(@cronlines, $index, 1); } $index++ } push(@cronlines, $newdetail) if $backup_daemon; output($tmpcron, @cronlines); system("crontab $tmpcron"); unlink($tmpcron); } } sub upgrade_conf_file() { my @new_conf; $DEBUG and print "Old syntax...upgrading...\n"; my @conf_data = cat_($cfg_file); chop @conf_data; foreach (@conf_data) { push @new_conf, $_ . "=1\n" if !/=/; if (/^OTHER_FILES/) { my (@new_data) = split /=/; my @new_args = split(" ", $new_data[1]); push @new_conf, $new_data[0] . "=" . join(",", @new_args) . "\n"; } elsif (/=/ && !/TAR.GZ/) { my $has_arg = split /=/; push @new_conf, "$_\n" if $has_arg > 1; } } output_p($cfg_file, @new_conf); } sub read_conf_file() { if (-e $cfg_file) { my $conf_version = `grep USE_HD $cfg_file`; upgrade_conf_file() if $conf_version !~ /^USE_HD=1/; %conf = getVarsFromSh($cfg_file); @other_files = conf_to_list($conf{OTHER_FILES}); @user_list = conf_to_list($conf{HOME_FILES}); @sys_files = conf_to_list($conf{SYS_FILES}) if exists($conf{SYS_FILES}); $backup_daemon = 1 if exists($conf{DAEMON_TIME_SPACE}); read_cron_files(); $cfg_file_exist = 1; } else { $cfg_file_exist = 0; #- these were 1 by default, but that made it so the user could never save the #- inverse behavior. this allows incremental as the default if not configured $conf{SYS_INCREMENTAL_BACKUPS} = 1; $conf{USER_INCREMENTAL_BACKUPS} = 1; } # some basic defaults $conf{SMTP_SERVER} = "localhost" if !exists($conf{SMTP_SERVER}); $conf{MAX_SPACE} = 1000.0 if !exists($conf{MAX_SPACE}); $conf{USE_HD} = 1 if !exists($conf{USE_HD}); # deal with users that may have been deleted from the system check_valid_users() if $cfg_file_exist; $use_hd = !($conf{USE_CD} || $conf{USE_TAPE} || $conf{USE_NET}); } sub verify_mail_setup() { all_user_list() if @user_list_all == (); if ($conf{USER_MAIL} ne "root" && $conf{USER_MAIL} !~ /[\w.-]*\@[\w.-]/ && !member($conf{USER_MAIL}, @user_list_all)) { show_warning("f", N("\"%s\" neither is a valid email nor is an existing local user!", $conf{USER_MAIL})); return 1; } if (member($conf{USER_MAIL}, @user_list_all) && $conf{SMTP_SERVER} ne "localhost") { show_warning("f", N("\"%s\" is a local user, but you did not select a local smtp, so you must use a complete email address!", $conf{USER_MAIL})); return 1; } } sub check_valid_users() { all_user_list(); my @new_user_list = intersection(\@user_list, \@user_list_all); if (@user_list != @new_user_list) { log::l(N("Valid user list changed, rewriting config file.")); if ($DEBUG) { print N("Old user list:\n"); print "@user_list\n"; print N("New user list:\n"); print "@new_user_list\n"; } @user_list = @new_user_list; save_conf_file(); } } sub write_sitecopyrc() { #- FIXME - how to deal with existing sitecopyrc my @cfg_list = ("site drakbackup\n", "\tserver $conf{HOST_NAME}\n", "\tremote /$conf{HOST_PATH}\n", "\tlocal $conf{PATH_TO_SAVE}\n", "\tusername $conf{LOGIN}\n", "\tpassword $conf{PASSWD}\n", "\tprotocol webdav\n" ); output_p("$user_home/.sitecopyrc", @cfg_list); chmod(0600, "$user_home/.sitecopyrc"); -d "$user_home/.sitecopy" or mkdir_p("$user_home/.sitecopy"); chmod(0700, "$user_home/.sitecopy"); } sub write_password_file() { output_p("$cfg_dir/rsync.user", "$conf{PASSWD}\n"); chmod(0600, "$cfg_dir/rsync.user"); } sub show_warning { my ($mode, $warning) = @_; $mode = N("Warning") if $mode eq "w"; $mode = N("Error") if $mode eq "f"; $mode = N("Information") if $mode eq "i"; if ($interactive) { $in->ask_warn($mode, translate($warning)); } else { warn "$mode: $warning\n"; } $log_buff .= "\n$mode: $warning\n"; } sub complete_results() { system_state(); $results .= "***********************************************************************\n\n"; $daemon or $results .= N("\n DrakBackup Report \n"); $daemon and $results .= N("\n DrakBackup Daemon Report\n"); my $datem = `date`; $results .= " $datem\n\n"; $results .= "***********************************************************************\n\n"; $results .= $system_state; $results .= "\n\n***********************************************************************\n\n"; $results .= N("\n DrakBackup Report Details\n\n\n"); $results .= "***********************************************************************\n\n"; } sub ftp_client() { use Net::FTP; my $ftp; foreach (1..5) { $ftp = Net::FTP->new($conf{HOST_NAME}, Debug => 0) or return 1; $ftp && $ftp->login($conf{LOGIN}, $conf{PASSWD}) and last; log::l("ftp login failed, sleeping before trying again"); sleep 5 * $_; $ftp = 0; } return 1 if !$ftp; $ftp->cwd($conf{HOST_PATH}); foreach (@file_list_to_send_by_ftp) { $interactive and $pbar->set_fraction(0); $interactive and progress($pbar, $plabel, 0.5, $_); $interactive and $pbar->set_text($_); $ftp->put($_, undef, undef); $interactive and progress($pbar, $plabel, 0.5, $_); $interactive and $pbar->set_text($_); $interactive and progress($pbar3, $plabel3, 1/@file_list_to_send_by_ftp, N("Total progress")); } $ftp->quit; return 0; } sub do_expect { #- Sort of a general purpose expect routine, we use it to backup files to #- a remote server, as well as transfer a key and restore. #- Using the key after it is setup is preferred. my ($mode) = @_; eval { require Expect }; if ($@) { if ($mode eq 'sendkey') { destroy_widget(); check_pkg_needs(); } else { $log_buff .= "perl-Expect not installed!", } return 1; } #- for debugging set to 1 $Expect::Exp_Internal = 0; #- for debugging set to 1 $Expect::Debug = 0; $Expect::Log_Stdout = 0; my $spawn_ok; my $no_perm; my $bad_passwd; my $bad_dir; my $timeout = 20; my $exp_command; my @send_files = "$backup_key.pub"; #- just bypass progress for sendkey for now $interactive = 0 if $mode eq "sendkey"; @send_files = @file_list_to_send_by_ftp if $mode eq "backup"; $interactive and $pbar->set_fraction(0); $interactive and $pbar3->set_fraction(0); $interactive and progress($pbar, $plabel, 0.5, "File Transfer..."); foreach (@send_files) { $exp_command = "scp -P $scp_port $_ $conf{LOGIN}\@$conf{HOST_NAME}:$conf{HOST_PATH}" if $mode eq "backup"; $exp_command = "ssh-copy-id -i $_ $conf{LOGIN}\@$conf{HOST_NAME}" if $mode eq "sendkey"; if (-e $backup_key && $mode eq "sendkey") { if ($in->ask_yesorno(N("Warning"), N("%s exists, delete?\n\nIf you've already done this process you'll probably\n need to purge the entry from authorized_keys on the server.", $backup_key))) { unlink($backup_key); unlink($backup_key . '.pub'); } else { return 0; } } if (!(-e $backup_key) && $mode eq "sendkey") { $in->ask_warn(N("Information"), N("This may take a moment to generate the keys.")); cursor_wait(); #- not using a passphrase for the moment system("ssh-keygen -P '' -t dsa -f $backup_key"); cursor_norm(); } my $exp = Expect->spawn($exp_command) or $in->ask_warn(N("Error"), N("Cannot spawn %s.", $exp_command)); $interactive and progress($pbar3, $plabel3, 1/@send_files, N("Total progress")); $interactive and $stext->set_text($_); #- run scp, look for some common errors and try to track successful progress for GUI $exp->expect($timeout, [ qr 'password: $', sub { $spawn_ok = 1; my $fh = shift; $fh->send("$conf{PASSWD}\n"); Expect::exp_continue() } ], [ '-re', 'please try again', sub { $bad_passwd = 1; Expect::exp_continue() } ], [ '-re', 'Permission denied', sub { $no_perm = 1; Expect::exp_continue() } ], [ '-re', 'No such file or directory', sub { $bad_dir = 1; Expect::exp_continue() } ], # [ '-re', '%', sub { update_scp_progress(); Expect::exp_continue(); } ], [ eof => sub { if (!$spawn_ok) { show_warning("f", N("No password prompt on %s at port %s", $conf{HOST_NAME}, $scp_port)) } if ($bad_passwd) { show_warning("f", N("Bad password on %s", $conf{HOST_NAME})) } if ($no_perm) { show_warning("f", N("Permission denied transferring %s to %s", $_, $conf{HOST_NAME})) } if ($bad_dir) { show_warning("f", N("Can't find %s on %s", $conf{HOST_PATH}, $conf{HOST_NAME})) } } ], [ timeout => sub { show_warning("f", N("%s not responding", $conf{HOST_NAME})) } ], ); my $exit_stat = $exp->exitstatus; $in->ask_warn(N("Information"), N("Transfer successful\nYou may want to verify you can login to the server with:\n\nssh -i %s %s\@%s\n\nwithout being prompted for a password.", $backup_key, $conf{LOGIN}, $conf{HOST_NAME})) if $exit_stat == 0 && $mode eq "sendkey"; $log_buff .= "$_\n" if $exit_stat == 0 && $mode eq "backup"; $exp->hard_close; } $interactive and progress($pbar, $plabel, 0.5, "Done..."); $interactive = 1 if $mode eq "sendkey"; } sub ssh_client() { $DEBUG and print "file list to send: $_\n " foreach @file_list_to_send_by_ftp; my $command; my $value; foreach (@file_list_to_send_by_ftp) { if ($conf{USER_KEYS}) { $command = "scp -P $scp_port $_ $conf{LOGIN}\@$conf{HOST_NAME}:$conf{HOST_PATH}"; } else { $command = "scp -P $scp_port -i $backup_key $_ $conf{LOGIN}\@$conf{HOST_NAME}:$conf{HOST_PATH}"; } $interactive and $pbar->set_fraction(0); $interactive and progress($pbar, $plabel, 0.5, "File Transfer..."); $interactive and $stext->set_text($_); $log_buff .= $command . "\n\n"; local *TMP; open TMP, "$command 2>&1 |"; while ($value = ) { $log_buff .= $value; } close TMP; $log_buff .= "\n"; $interactive and progress($pbar, $plabel, 0.5, "Done..."); $interactive and progress($pbar3, $plabel3, 1/@file_list_to_send_by_ftp, N("Total progress")); } return 0; } sub webdav_client() { $DEBUG and print "file list to send: $_\n " foreach @file_list_to_send_by_ftp; if (!(-e "$user_home/.sitecopy/drakbackup")) { my $command = "sitecopy -f $conf{HOST_PATH}"; spawn_progress($command, "Initializing sitecopy"); } my $command = "sitecopy -u drakbackup"; spawn_progress($command, "Running sitecopy..."); if ($log_buff =~ /Nothing to do - no changes found/) { show_warning("w", N("WebDAV remote site already in sync!")); return 1; } if ($log_buff !~ /Update completed successfully/) { show_warning("f", N("WebDAV transfer failed!")); return 1; } return 0; } sub rsync_client() { $DEBUG and print "file list to send: $_\n " foreach @file_list_to_send_by_ftp; my $rsync_cmd = "rsync -tv $conf{PATH_TO_SAVE}/* "; $rsync_cmd = $rsync_cmd . "--password-file=$cfg_dir/rsync.user " if $conf{PASSWD}; $rsync_cmd = $rsync_cmd . "$conf{LOGIN}\@" if $conf{LOGIN}; $rsync_cmd = $rsync_cmd . $conf{HOST_NAME} . "::" . $conf{HOST_PATH}; spawn_progress($rsync_cmd, "Running rsync"); return 0; } sub check_for_cd() { #- check for a cd my $command = "cdrecord dev=$conf{CD_DEVICE} -atip"; spawn_progress($command, "Check for media in drive"); if ($log_buff =~ /No disk/) { show_warning("f", N("No CD-R/DVD-R in drive!")); return 1; } if ($log_buff !~ /ATIP info from disk|Found DVD media/) { show_warning("f", N("Does not appear to be recordable media!")); return 1; } if ($log_buff =~ /Is not erasable/ && $conf{MEDIA_ERASE}) { show_warning("f", N("Not erasable media!")); return 1; } if ($conf{MULTI_SESSION}) { $command = "cdrecord -s dev=$conf{CD_DEVICE} -msinfo"; spawn_progress($command, "Check for previous session status"); #- if we don't find a previous session, start fresh if ($log_buff =~ /Cannot read session offset/) { $conf{MEDIA_ERASE} = 1; return 0; } else { #- extract the session info from $log_buff my $code_loc = rindex($log_buff, "msinfo") + 8; if ($code_loc != -1) { my $bufflen = length($log_buff); $session_offset = substr($log_buff, $code_loc, $bufflen-$code_loc-1); return 0; } return 1; } } } sub write_on_cd() { my $command = "cdrecord -v dev=$conf{CD_DEVICE} -data "; # DVD+RW use -dao $command .= "-dao " if $conf{DVDRW}; #- only blank if it's the first session $command .= "blank=fast " if $conf{MEDIA_ERASE} && $session_offset eq ''; #- multi-session mode $command .= "-multi -pad " if $conf{MULTI_SESSION}; $command .= "$conf{PATH_TO_SAVE}/drakbackup.iso"; spawn_progress($command, "Running cdrecord"); unlink("$conf{PATH_TO_SAVE}/drakbackup.iso"); } sub erase_cdrw() { #- we can only hit this via interactive $interactive = 0; $in->ask_warn(N("Information"), N("This may take a moment to erase the media.")); cursor_wait(); my $command = "cdrecord dev=$conf{CD_DEVICE} -blank=fast"; spawn_progress($command, "Erasing CDRW..."); cursor_norm(); $interactive = 1; } sub spawn_progress { my ($command, $descr) = @_; my $value; my $timer; $interactive and progress($pbar3, $plabel3, 0, translate($descr)); $interactive and $pbar3->set_pulse_step(0.1); $interactive and $timer = Glib::Timeout->add(20, sub { $pbar3->pulse }); $log_buff .= "\n" . $descr . ":\n"; $log_buff .= $command . "\n\n"; standalone::explanations("Running $command"); local *TMP; open TMP, "$command 2>&1 |"; while ($value = ) { $log_buff .= $value; if ($interactive) { $stext->set_text($value); gtkflush(); } } close TMP; $interactive and Glib::Source->remove($timer); } sub get_cd_volname() { my $vol_device = $conf{CD_DEVICE}; $vol_device =~ s/sg/scd/; $vol_name = `volname $vol_device` if $conf{CD_DEVICE}; $vol_name =~ s/[ \t]+\n$//; $vol_name; } sub build_iso() { if ($conf{MULTI_SESSION} && $session_offset) { $vol_name = get_cd_volname(); } else { $vol_name = "Drakbackup" . $the_time; } #this is safe to change the volname on rewrites, as is seems to get ignored anyway my $command = "mkisofs -r -J -T -v -V '$vol_name' "; $command .= "-C $session_offset -M $conf{CD_DEVICE} " if $conf{MULTI_SESSION} && $session_offset; $command .= "-o $conf{PATH_TO_SAVE}/drakbackup.iso @file_list_to_send_by_ftp"; spawn_progress($command, "Running mkisofs..."); } sub build_cd() { if (!check_for_cd()) { build_iso(); if ($log_buff =~ /Permission denied/) { show_warning("f", N("Permission problem accessing CD.")); $media_problem = 1; return 1; } else { write_on_cd(); } } } sub get_tape_label { my ($device) = @_; cursor_wait(); system("mt -f $device rewind"); system("tar -C $cfg_dir -xf $device"); my @volname = cat_("$cfg_dir/drakbackup.label"); unlink("$cfg_dir/drakbackup.label"); $vol_name = $volname[0]; cursor_norm(); $vol_name; } sub build_tape() { my $command; #- do we have a tape? $command = "mt -f $conf{TAPE_DEVICE} status"; spawn_progress($command, "Checking for tape"); if ($log_buff =~ /DR_OPEN/) { show_warning("f", N("No tape in %s!", $conf{TAPE_DEVICE})); return 1; } #- try to roll to the end of the data if we're not erasing if (!$conf{MEDIA_ERASE}) { $command = "mt -f $conf{TAPE_DEVICE} rewind"; spawn_progress($command, "Rewind to find tape label"); $command = "tar -tf $conf{TAPE_DEVICE}"; spawn_progress($command, "Check for label"); if ($log_buff =~ /drakbackup.label/) { if ($conf{TAPE_NOREWIND}) { $command = "mt -f $conf{TAPE_DEVICE} rewind"; spawn_progress($command, "Rewind to get tape label"); } $command = "tar -C $cfg_dir -xf $conf{TAPE_DEVICE}"; spawn_progress($command, "Reading tape label"); my @volname = cat_("$cfg_dir/drakbackup.label"); unlink("$cfg_dir/drakbackup.label"); $vol_name = $volname[0]; } $command = "mt -f $conf{TAPE_DEVICE} eod"; spawn_progress($command, "Running mt to find eod"); } else { $command = "mt -f $conf{TAPE_DEVICE} rewind"; spawn_progress($command, "Running mt to rewind"); # make a tape label for the catalog # if we're using the rewinding device, change modes briefly if (!$conf{TAPE_NOREWIND}) { $conf{TAPE_DEVICE} =~ s|/st|/nst|; } $vol_name = "Drakbackup" . $the_time; my $f = "$cfg_dir/drakbackup.label"; output($f, $vol_name); $command = "tar -C $cfg_dir -cf $conf{TAPE_DEVICE} drakbackup.label;"; spawn_progress($command, "Creating tape label"); unlink $f; if (!$conf{TAPE_NOREWIND}) { $conf{TAPE_DEVICE} =~ s|/nst|/st|; } } #- do the backup $command = "tar -cvf $conf{TAPE_DEVICE} @file_list_to_send_by_ftp"; spawn_progress($command, "Running tar to tape"); #- eject the tape? if ($conf{MEDIA_EJECT}) { $command = "mt -f $conf{TAPE_DEVICE} rewoff"; spawn_progress($command, "Running mt to eject tape"); } } sub send_mail { my ($result) = @_; my $datem = `date`; use Mail::Mailer; my $mailer = Mail::Mailer->new('smtp', Server => $conf{SMTP_SERVER}); $mailer->open({ From => 'drakbackup', To => $conf{USER_MAIL}, Subject => "DrakBackup report on $datem", }) or return 1; print $mailer $result; $mailer->close; 0; } sub build_backup_files() { my $path_name; my $tar_cmd; my $more_recent; my $tar_cmd_sys; my $tar_cmd_user; my $tar_cmd_other; my @dir_content; my $incr; my $base; my $find_args = "! -type d -print"; local $_; $results = ""; $log_buff = ""; #- flush this so if the user does 2 runs in a row we don't try to send the same files @file_list_to_send_by_ftp = (); $interactive and cursor_wait(); read_conf_file(); the_time(); $conf{SEND_MAIL} and complete_results(); -d $conf{PATH_TO_SAVE} or mkdir_p($conf{PATH_TO_SAVE}); if ($conf{OPTION_COMP}) { $DEBUG and $tar_cmd = "tar cvpj "; $DEBUG or $tar_cmd = "tar cpj "; $tar_ext = "tar.bz2"; } else { $DEBUG and $tar_cmd = "tar cvpz "; $DEBUG or $tar_cmd = "tar cpz "; $tar_ext = "tar.gz" } my $used_space = check_storage_quota($conf{PATH_TO_SAVE}); if ($used_space) { my $msg = N("Backup quota exceeded!\n%d MB used vs %d MB allocated.", $used_space, $conf{MAX_SPACE}); show_warning("f", $msg); $interactive and cursor_norm(); $results .= $msg; $interactive and show_status(); results_to_logfile(); return 1; } $tar_cmd_sys = $tar_cmd; $tar_cmd_user = $tar_cmd; $tar_cmd_other = $tar_cmd; $conf{NO_CRITICAL_SYS} and $tar_cmd_sys .= "--exclude passwd --exclude fstab --exclude group --exclude mtab"; $conf{NO_BROWSER_CACHE} and $tar_cmd_user .= "--exclude NewCache --exclude Cache --exclude cache"; $nonroot_user and $tar_cmd_user .= " --exclude .drakbackup"; $conf{BACKUPIGNORE} && -f "/etc/.backupignore" and $tar_cmd_sys .= " -X /etc/.backupignore"; -d $conf{PATH_TO_SAVE} and @dir_content = all($conf{PATH_TO_SAVE}); if ($conf{USE_HD} && !$daemon || $daemon) { $interactive and progress($pbar, $plabel, 0.5, N("Backup system files...")); unless ($conf{NO_SYS_FILES}) { my $find_args_sys = $find_args; my $first_done; $ignore_files_list = ''; $find_args_sys = handle_ignores2("/etc", $find_args_sys) if $conf{BACKUPIGNORE}; if ($conf{SYS_INCREMENTAL_BACKUPS}) { $base = $incr = "incr_sys"; ($base, $incr) = swap_prefix($base, $incr) if $conf{SYS_DIFFERENTIAL_BACKUPS}; if (any { /^list_base_sys/ } @dir_content) { $more_recent = get_more_recent($base, @dir_content); my $list_file = name_list_file($incr); do_find($more_recent, $find_args_sys, $list_file, @sys_files); if (check_rm_list($list_file)) { do_tar($tar_cmd_sys, "backup_$incr", $list_file, undef); } $first_done = 1; } else { $incr = "base_sys"; } } else { $incr = "sys"; clean_dest($incr); } if (!$first_done) { my $list_file = name_list_file($incr); do_find(undef, $find_args_sys, $list_file, @sys_files); do_tar($tar_cmd_sys, "backup_$incr", undef, @sys_files); } push_list("list_$incr") if $incr =~ /_sys/; files_to_results($incr); } $interactive and progress($pbar, $plabel, 0.5, N("Backup system files...")); $interactive and progress($pbar3, $plabel3, 0.3, N("Hard Disk Backup files...")); unless ($conf{NO_USER_FILES}) { foreach (@user_list) { my $user = $_; my $tar_cmd_cuser = $tar_cmd_user; $path_name = return_path($user); $conf{BACKUPIGNORE} && -f "$path_name/.backupignore" and $tar_cmd_cuser .= " -X $path_name/.backupignore"; my $find_args_user = $find_args; my $first_done; $ignore_files_list = ''; $find_args_user = handle_ignores2($path_name, $find_args_user) if $conf{BACKUPIGNORE}; if ($conf{USER_INCREMENTAL_BACKUPS}) { $base = $incr = "incr_user_"; ($base, $incr) = swap_prefix($base, $incr) if $conf{USER_DIFFERENTIAL_BACKUPS}; if (any { /^list_base_user_$user/ } @dir_content) { $more_recent = get_more_recent("$base$user", @dir_content); my $list_file = name_list_file($incr . $user); do_find($more_recent, $find_args_user, $list_file, $path_name); if (check_rm_list($list_file)) { do_tar($tar_cmd_user, "backup_$incr$user", $list_file, undef); } $first_done = 1; } else { $incr = "base_user_"; } } else { $incr = "user_"; clean_dest("$incr$user"); } if (!$first_done) { my $list_file = name_list_file($incr . $user); do_find(undef, $find_args_user, $list_file, $path_name); do_tar($tar_cmd_user, "backup_$incr$user", undef, $path_name); } push_list("$incr$user") if $incr =~ /_user/; files_to_results("$incr$user"); } } $interactive and progress($pbar2, $plabel1, 1, N("Backup User files...")); $interactive and progress($pbar3, $plabel3, 0.4, N("Hard Disk Backup files...")); if ($conf{OTHER_FILES}) { my $find_args_other = $find_args; my $first_done; $ignore_files_list = ''; ($tar_cmd_other, $find_args_other) = handle_ignores($tar_cmd_other, $find_args_other, @other_files) if $conf{BACKUPIGNORE}; if ($conf{OTHER_INCREMENTAL_BACKUPS}) { $base = $incr = "incr_other"; ($base, $incr) = swap_prefix($base, $incr) if $conf{OTHER_DIFFERENTIAL_BACKUPS}; if (any { /^list_base_other/ } @dir_content) { $more_recent = get_more_recent($base, @dir_content); my $list_file = name_list_file($incr); do_find($more_recent, $find_args_other, $list_file, @other_files); if (check_rm_list($list_file)) { do_tar($tar_cmd_other, "backup_$incr", $list_file, undef); } $first_done = 1; } else { $incr = "base_other"; } } else { $incr = "other"; clean_dest($incr); } if (!$first_done) { my $list_file = name_list_file($incr); do_find(undef, $find_args_other, $list_file, @other_files); do_tar($tar_cmd_user, "backup_$incr", undef, @other_files); } push_list("list_$incr") if $incr =~ /_other/; files_to_results($incr); } $interactive and progress($pbar1, $plabel2, 1, N("Backup Other files...")); $interactive and progress($pbar3, $plabel3, 0.3, N("Hard Disk Backup Progress...")); } my $filecount = @file_list_to_send_by_ftp; if (!$filecount) { my $msg = N("No changes to backup!"); show_warning("w", $msg); $interactive and cursor_norm(); $interactive and interactive_mode(); results_to_logfile(); return 1; } #- should hit this block if running daemon mode only if ($daemon && $conf{DAEMON_MEDIA}) { # ftp_client() if $ftp_daemon; rsync_client() if $conf{DAEMON_MEDIA} eq 'rsync'; ssh_client() if $conf{DAEMON_MEDIA} eq 'ssh' && !$conf{USE_EXPECT}; do_expect("backup") if $conf{DAEMON_MEDIA} eq 'ssh' && $conf{USE_EXPECT}; webdav_client() if $conf{DAEMON_MEDIA} eq 'webdav'; build_cd() if $conf{DAEMON_MEDIA} eq 'cd'; build_tape() if $conf{DAEMON_MEDIA} eq 'tape'; $results .= N("\nDrakbackup activities via %s:\n\n", $conf{DAEMON_MEDIA}) if $conf{DAEMON_MEDIA} ne 'hd'; $results .= $log_buff; } #- leave this one alone for now - works well #- integrate with other methods later if (($conf{USE_NET} && !$daemon && $conf{NET_PROTO} eq 'ftp') || $daemon && $conf{DAEMON_MEDIA} eq 'ftp') { $interactive and build_backup_ftp_status(); if (ftp_client()) { $results .= N("\n FTP connection problem: It was not possible to send your backup files by FTP.\n"); $interactive and $in->ask_warn(N("Error"), N("Error during sending file via FTP. Please correct your FTP configuration.")); } else { $results .= N("file list sent by FTP: %s\n", $_) foreach @file_list_to_send_by_ftp; } } #- consolidate all the other methods under here - interactive and --default should land here if (!$daemon) { if ($conf{USE_NET} && $conf{NET_PROTO} && $conf{NET_PROTO} ne 'ftp') { rsync_client() if $conf{NET_PROTO} eq 'rsync'; ssh_client() if $conf{NET_PROTO} eq 'ssh' && !$conf{USE_EXPECT}; do_expect("backup") if $conf{NET_PROTO} eq 'ssh' && $conf{USE_EXPECT}; webdav_client() if $conf{NET_PROTO} eq 'webdav'; $results .= N("\nDrakbackup activities via %s:\n\n", $conf{NET_PROTO}); } if ($conf{USE_CD}) { build_cd(); $results .= N("\nDrakbackup activities via CD:\n\n"); } if ($conf{USE_TAPE}) { build_tape(); $results .= N("\nDrakbackup activities via tape:\n\n"); } $results .= $log_buff; } results_to_logfile(); if ($conf{SEND_MAIL}) { if (send_mail($results)) { $interactive and $in->ask_warn(N("Error"), N("Error sending mail. Your report mail was not sent.")); $interactive or print N(" Error while sending mail. \n"); } } #- write our catalog file if (!$media_problem) { my $catalog = substr($the_time, 1); if (!$conf{USE_NET} && !$conf{USE_TAPE} && !$conf{USE_CD}) { $catalog .= ":HD:localhost:$conf{PATH_TO_SAVE}"; $conf{NET_PROTO} = ''; } $catalog .= ":$conf{NET_PROTO}:$conf{LOGIN}\@$conf{HOST_NAME}:$conf{HOST_PATH}" if $conf{NET_PROTO}; $catalog .= ":CD:$vol_name:$conf{CD_DEVICE}" if $conf{USE_CD}; $catalog .= ":Tape:$vol_name:$conf{TAPE_DEVICE}" if $conf{USE_TAPE}; $catalog .= ":System" unless $conf{NO_SYS_FILES}; $catalog .= ":I" if $conf{SYS_INCREMENTAL_BACKUPS} && !$conf{NO_SYS_FILES} && !$conf{SYS_DIFFERENTIAL_BACKUPS}; $catalog .= ":D" if $conf{SYS_INCREMENTAL_BACKUPS} && !$conf{NO_SYS_FILES} && $conf{SYS_DIFFERENTIAL_BACKUPS}; $catalog .= ":F" if !$conf{SYS_INCREMENTAL_BACKUPS} && !$conf{NO_SYS_FILES}; $catalog .= ":Users=(@user_list)" unless $conf{NO_USER_FILES}; $catalog .= ":I" if $conf{USER_INCREMENTAL_BACKUPS} && !$conf{NO_USER_FILES} && !$conf{USER_DIFFERENTIAL_BACKUPS}; $catalog .= ":D" if $conf{USER_INCREMENTAL_BACKUPS} && !$conf{NO_USER_FILES} && $conf{USER_DIFFERENTIAL_BACKUPS}; $catalog .= ":F" if !$conf{USER_INCREMENTAL_BACKUPS} && !$conf{NO_USER_FILES};; $catalog .= ":Other=(@other_files)" if $conf{OTHER_FILES}; $catalog .= ":I" if $conf{OTHER_INCREMENTAL_BACKUPS} && $conf{OTHER_FILES} && !$conf{OTHER_DIFFERENTIAL_BACKUPS}; $catalog .= ":D" if $conf{OTHER_INCREMENTAL_BACKUPS} && $conf{OTHER_FILES} && $conf{OTHER_DIFFERENTIAL_BACKUPS}; $catalog .= ":F" if !$conf{OTHER_INCREMENTAL_BACKUPS} && $conf{OTHER_FILES}; $catalog .= "\n"; append_to_file("$cfg_dir/drakbackup_catalog", $catalog) or show_warning("w", N("Can't create catalog!")); } #- clean up HD files if del_hd_files and media isn't hd if ($conf{DEL_HD_FILES} && ($conf{USE_CD} || $conf{USE_TAPE} || $conf{USE_NET}) && $conf{DAEMON_MEDIA} ne 'hd') { foreach (@file_list_to_send_by_ftp) { unlink($_) if /$tar_ext$/; } } #- if we had a media problem then get rid of the text log of the backed up files too if ($media_problem) { system("rm $conf{PATH_TO_SAVE}/list*$the_time.txt"); } $interactive and cursor_norm(); $interactive and show_status(); } sub swap_prefix { my ($base, $incr) = @_; $incr =~ s/incr/diff/; $base =~ s/incr/base/; return $base, $incr; } sub name_list_file { my ($suffix) = @_; return $conf{PATH_TO_SAVE} . "/list_" . $suffix . $the_time . ".txt"; } sub check_rm_list { my ($list_file) = @_; if (!cat_($list_file)) { unlink($list_file); return 0; } else { return 1; } } sub get_more_recent { my ($match, @directory) = @_; $match = "list_" . $match; my @more_recent = grep { /^$match/ } sort @directory; my $more_recent = pop @more_recent; $DEBUG and print "more recent file: $more_recent\n"; return $more_recent; } sub clean_dest { my ($wildcard) = @_; system("cd $conf{PATH_TO_SAVE} && rm -f backup*$wildcard*"); } sub do_find { my ($newer, $more_args, $into, @where) = @_; #- $newer may be undef - if it's defined then "-cnewer $newer" $newer = $conf{PATH_TO_SAVE} . "/" . $newer if defined($newer); defined($newer) ? system("find @where -cnewer $newer $more_args > $into") : system("find @where $more_args > $into"); } sub do_tar { my ($tar_cmd, $dest_file, $list_file, @files) = @_; my $full_dest_file = $conf{PATH_TO_SAVE} . "/" . $dest_file . $the_time . "." . $tar_ext; #- if $list_file is undefined, then use the @files list defined($list_file) ? system("$tar_cmd -f $full_dest_file -T $list_file") : system("$tar_cmd -f $full_dest_file @files"); push_list($dest_file); } sub push_list { my ($prefix) = @_; my $filename = $conf{PATH_TO_SAVE} . "/" . $prefix . $the_time . "."; $filename .= $tar_ext if $prefix =~ /^backup/; $filename .= "txt" if $prefix =~ /^list/; push @file_list_to_send_by_ftp, $filename; } sub files_to_results { my ($basename) = @_; $results .= "\nfile: " . $conf{PATH_TO_SAVE} . "/backup_" . $basename . $the_time . "." . $tar_ext . "\n\n"; $results .= cat_("$conf{PATH_TO_SAVE}/list_" . $basename . $the_time . ".txt"); $results .= "\nignored:\n" . $ignore_files_list . "\n" if $ignore_files_list; } sub handle_ignores { my ($tar_cmd, $find_args, @list) = @_; foreach my $dir (@list) { if (-d $dir) { -f "$dir/.backupignore" and $tar_cmd .= " -X $dir/.backupignore"; $find_args = handle_ignores2($dir, $find_args); } } return $tar_cmd, $find_args; } sub handle_ignores2 { my ($dir, $find_args) = @_; my @ignore_files = cat_("$dir/.backupignore"); foreach (@ignore_files) { $ignore_files_list .= $_; } $find_args .= " | grep -v -f $dir/.backupignore" if -f "$dir/.backupignore"; return $find_args; } sub require_rpm { my $all_rpms_found = 1; my $res; @list_of_rpm_to_install = (); foreach my $pkg (@_) { $res = system("rpm -q $pkg > /dev/null"); if ($res == 256) { $all_rpms_found = 0; push @list_of_rpm_to_install, $pkg; } } return $all_rpms_found; } sub check_pkg_needs() { my $extra_pkg = ''; if ($conf{USE_NET}) { $extra_pkg = 'rsync' if $conf{NET_PROTO} eq 'rsync'; $extra_pkg = 'sitecopy wget' if $conf{NET_PROTO} eq 'webdav'; $extra_pkg = 'perl-Expect' if $conf{NET_PROTO} eq 'ssh' && ($conf{USE_EXPECT} || $conf{DRAK_KEYS}); } $extra_pkg = 'mt-st' if $conf{USE_TAPE}; if ($extra_pkg) { if (require_rpm($extra_pkg)) { return 0; } else { #- this isn't entirely good, but it's the only way we get here currently #- was getting strange return behavior before #- still a problem, we can also get here from the cron screen install_rpm(\&advanced_where); return 1; } } } sub cursor_wait() { # turn the cursor to a watch $window1->window->set_cursor(new Gtk2::Gdk::Cursor("GDK_WATCH")); gtkflush(); } sub cursor_norm() { # restore normal cursor $window1->window->set_cursor(new Gtk2::Gdk::Cursor("GDK_LEFT_PTR")); gtkflush(); } sub show_status() { my $text = new Gtk2::TextView; destroy_widget(); my $scrolled_window = Gtk2::ScrolledWindow->new; $scrolled_window->set_border_width(10); $scrolled_window->add_with_viewport($text); gtktext_insert(gtkset_editable($text, 0), [ [ $results ] ]); gtkpack($advanced_box, $table = gtkpack_(new Gtk2::VBox(0,10), 1, $scrolled_window) ); $central_widget = \$table; $table->show_all; } sub results_to_logfile() { output_p($log_file, $results); } sub conf_to_list { my ($config) = @_; return split(",", $config); } sub list_to_conf { my (@list) = @_; return join(",", @list); } sub file_ok_sel { my ($file_selection) = @_; my $file_name = $file_selection->get_filename; if (!member($file_name, @other_files)) { push(@other_files, $file_name); $list_model->append_set(undef, $file_name); } } sub filedialog_where_hd() { my $file_dialog; $file_dialog = gtksignal_connect(new Gtk2::FileSelection(N("File Selection")), destroy => sub { $file_dialog->destroy }); $file_dialog->ok_button->signal_connect(clicked => sub { $save_path_entry->set_text($file_dialog->get_filename); $file_dialog->destroy }); $file_dialog->cancel_button->signal_connect(clicked => sub { $file_dialog->destroy }); $file_dialog->show; } sub filedialog_restore_find_path() { my $file_dialog; $file_dialog = gtksignal_connect(new Gtk2::FileSelection(N("File Selection")), destroy => sub { $file_dialog->destroy }); $file_dialog->ok_button->signal_connect(clicked => sub { $restore_find_path_entry->set_text($file_dialog->get_filename); $file_dialog->destroy; }); $file_dialog->cancel_button->signal_connect(clicked => sub { $file_dialog->destroy }); $file_dialog->show; } sub filedialog_generic { #- a more generic file dialog #- a title prompt, the widget to get updated and the variable to update my ($prompt, $widget, $set_var) = @_; my $file_dialog; $file_dialog = gtksignal_connect(new Gtk2::FileSelection(translate($prompt)), destroy => sub { $file_dialog->destroy }); $file_dialog->ok_button->signal_connect(clicked => sub { $$set_var = $file_dialog->get_filename; $$widget->set_text($$set_var); $file_dialog->destroy; }); $file_dialog->cancel_button->signal_connect(clicked => sub { $file_dialog->destroy }); $file_dialog->show; } sub filedialog() { my $file_dialog; $file_dialog = gtksignal_connect(new Gtk2::FileSelection(N("Select the files or directories and click on 'OK'")), destroy => sub { $file_dialog->destroy }); $file_dialog->ok_button->signal_connect(clicked => sub { file_ok_sel($file_dialog) }); $file_dialog->cancel_button->signal_connect(clicked => sub { $file_dialog->destroy }); $file_dialog->show; } ################################################ ADVANCED ################################################ sub check_list { foreach (@_) { my $ref = $_->[1]; $_->[2] ? gtkset_active($_->[0], !$$ref) : gtkset_active($_->[0], $$ref); gtksignal_connect($_->[0], toggled => sub { invbool $ref; destroy_widget(); $current_widget->(); }); } } sub fonction_env { ($central_widget, $current_widget, $previous_widget, $next_widget) = @_; } sub advanced_what_sys() { my $box_what_sys; gtkpack($advanced_box, $box_what_sys = gtkpack_(new Gtk2::VBox(0, 15), 1, N("\nPlease check all options that you need.\n"), 1, N("These options can backup and restore all files in your /etc directory.\n"), 0, my $check_what_sys = new Gtk2::CheckButton(N("Backup your System files. (/etc directory)")), 0, my $check_what_versions = new Gtk2::CheckButton(N("Use Incremental/Differential Backups (do not replace old backups)")), 0, gtkpack__(new Gtk2::HBox(0,0), my @mode_buttons = gtkradio((N("Use Incremental Backups")) x 2, N("Use Differential Backups")), ), 0, my $check_what_critical = new Gtk2::CheckButton(N("Do not include critical files (passwd, group, fstab)")), 0, N("With this option you will be able to restore any version\n of your /etc directory."), 1, new Gtk2::VBox(0, 15), ), ); check_list([$check_what_sys, \$conf{NO_SYS_FILES}, 1], [$check_what_critical, \$conf{NO_CRITICAL_SYS}]); $check_what_versions->set_active($conf{SYS_INCREMENTAL_BACKUPS}); $check_what_versions->signal_connect('toggled' => sub { invbool \$conf{SYS_INCREMENTAL_BACKUPS}; $mode_buttons[0]->set_sensitive($conf{SYS_INCREMENTAL_BACKUPS}); $mode_buttons[1]->set_sensitive($conf{SYS_INCREMENTAL_BACKUPS}); }); $mode_buttons[1]->set_active($conf{SYS_DIFFERENTIAL_BACKUPS}); $mode_buttons[0]->signal_connect('toggled' => sub { $conf{SYS_DIFFERENTIAL_BACKUPS} = $mode_buttons[1]->get_active }); $mode_buttons[0]->set_sensitive($conf{SYS_INCREMENTAL_BACKUPS}); $mode_buttons[1]->set_sensitive($conf{SYS_INCREMENTAL_BACKUPS}); set_help_tip($check_what_versions, 'use_incr_decr'); set_help_tip($mode_buttons[0], 'use_incremental'); set_help_tip($mode_buttons[1], 'use_differential'); fonction_env(\$box_what_sys, \&advanced_what_sys, \&advanced_what); $up_box->show_all; } sub advanced_what_user { my ($previous_function) = @_; my $box_what_user; my %check_what_user; all_user_list(); gtkpack($advanced_box, $box_what_user = gtkpack_(new Gtk2::VBox(0, 15), 0, N("Please check all users that you want to include in your backup."), 0, new Gtk2::HSeparator, 1, create_scrolled_window( gtkpack__(new Gtk2::VBox(0,0), map { my $name = $_; my @user_list_tmp; my $b = new Gtk2::CheckButton($name); if (any { /^$name$/ } @user_list) { $check_what_user{$_}[1] = 1; gtkset_active($b, 1); } else { $check_what_user{$_}[1] = 0; gtkset_active($b, 0); } $b->signal_connect(toggled => sub { if ($check_what_user{$name}[1]) { $check_what_user{$name}[1] = 0; @user_list_tmp = grep { !/^$name$/ } @user_list; @user_list = @user_list_tmp; } else { $check_what_user{$name}[1] = 1; if (!member($name, @user_list)) { push @user_list, $name } } }); $b } (@user_list_all) ), ), 0, my $check_what_browser = new Gtk2::CheckButton(N("Do not include the browser cache")), 0, my $check_what_user_versions = new Gtk2::CheckButton(N("Use Incremental/Differential Backups (do not replace old backups)")), 0, gtkpack__(new Gtk2::HBox(0,0), my @mode_buttons = gtkradio((N("Use Incremental Backups")) x 2, N("Use Differential Backups")), ), ), ); check_list([$check_what_browser, \$conf{NO_BROWSER_CACHE}]); $check_what_user_versions->set_active($conf{USER_INCREMENTAL_BACKUPS}); $check_what_user_versions->signal_connect('toggled' => sub { invbool \$conf{USER_INCREMENTAL_BACKUPS}; $mode_buttons[0]->set_sensitive($conf{USER_INCREMENTAL_BACKUPS}); $mode_buttons[1]->set_sensitive($conf{USER_INCREMENTAL_BACKUPS}); }); $mode_buttons[1]->set_active($conf{USER_DIFFERENTIAL_BACKUPS}); $mode_buttons[0]->signal_connect('toggled' => sub { $conf{USER_DIFFERENTIAL_BACKUPS} = $mode_buttons[1]->get_active }); $mode_buttons[0]->set_sensitive($conf{USER_INCREMENTAL_BACKUPS}); $mode_buttons[1]->set_sensitive($conf{USER_INCREMENTAL_BACKUPS}); set_help_tip($check_what_user_versions, 'use_incr_decr'); set_help_tip($mode_buttons[0], 'use_incremental'); set_help_tip($mode_buttons[1], 'use_differential'); if ($previous_function) { fonction_env(\$box_what_user, \&advanced_what_user, \&$previous_function, \&$previous_function) } else { fonction_env(\$box_what_user, \&advanced_what_user, \&advanced_what) } $up_box->show_all; } sub advanced_what_other() { my $box_what_other; my $file_iter; my $other_file; $list_model = Gtk2::ListStore->new("Glib::String"); my $list_others = Gtk2::TreeView->new_with_model($list_model); $list_others->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 0)); $list_others->set_headers_visible(0); foreach (@other_files) { $list_model->append_set(undef, $_); } $list_others->get_selection->signal_connect(changed => sub { my ($model, $iter) = $_[0]->get_selected; $model && $iter or return; $other_file = $model->get($iter, 0); $file_iter = $iter; }); gtkpack($advanced_box, $box_what_other = gtkpack_(new Gtk2::VBox(0, 15), 1, gtkpack_(new Gtk2::HBox(0,4), 1, create_scrolled_window($list_others), ), 0, gtkadd(gtkset_layout(new Gtk2::HButtonBox, 'spread'), gtksignal_connect(Gtk2::Button->new(N("Add")), clicked => sub { filedialog() }), gtksignal_connect(Gtk2::Button->new(N("Remove Selected")), clicked => sub { $list_model->remove($file_iter) if $file_iter; my $iindex = 0; foreach (@other_files) { if ($other_files[$iindex] eq $other_file) { splice(@other_files, $iindex, 1); last; } $iindex++; } }), ), 0, my $check_what_other_versions = new Gtk2::CheckButton(N("Use Incremental/Differential Backups (do not replace old backups)")), 0, gtkpack__(new Gtk2::HBox(0,0), my @mode_buttons = gtkradio((N("Use Incremental Backups")) x 2, N("Use Differential Backups")), ), ), ); $check_what_other_versions->set_active($conf{OTHER_INCREMENTAL_BACKUPS}); $check_what_other_versions->signal_connect('toggled' => sub { invbool \$conf{OTHER_INCREMENTAL_BACKUPS}; $mode_buttons[0]->set_sensitive($conf{OTHER_INCREMENTAL_BACKUPS}); $mode_buttons[1]->set_sensitive($conf{OTHER_INCREMENTAL_BACKUPS}); }); $mode_buttons[1]->set_active($conf{OTHER_DIFFERENTIAL_BACKUPS}); $mode_buttons[0]->signal_connect('toggled' => sub { $conf{OTHER_DIFFERENTIAL_BACKUPS} = $mode_buttons[1]->get_active }); $mode_buttons[0]->set_sensitive($conf{OTHER_INCREMENTAL_BACKUPS}); $mode_buttons[1]->set_sensitive($conf{OTHER_INCREMENTAL_BACKUPS}); set_help_tip($check_what_other_versions, 'use_incr_decr'); set_help_tip($mode_buttons[0], 'use_incremental'); set_help_tip($mode_buttons[1], 'use_differential'); fonction_env(\$box_what_other, \&advanced_what_other, \&advanced_what); $up_box->show_all; } sub advanced_what() { my $box_what; gtkpack($advanced_box, $box_what = gtkpack_(new Gtk2::HBox(0, 15), 1, new Gtk2::VBox(0, 5), 1, gtkpack_(new Gtk2::VBox(0, 15), 1, new Gtk2::VBox(0, 5), 1, gtksignal_connect(my $button_what_sys = Gtk2::Button->new, clicked => sub { $box_what->destroy; advanced_what_sys() }), 1, gtksignal_connect(my $button_what_user = Gtk2::Button->new, clicked => sub { destroy_widget(); advanced_what_user() }), 1, gtksignal_connect(my $button_what_other = Gtk2::Button->new, clicked => sub { destroy_widget(); advanced_what_other() }), 1, new Gtk2::VBox(0, 5), ), 1, new Gtk2::VBox(0, 5), ), ); $button_what_sys->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-system-40"), new Gtk2::Label(N("System")), new Gtk2::HBox(0, 5) )); $button_what_user->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-users-40"), new Gtk2::Label(N("Users")), new Gtk2::HBox(0, 5) )); $button_what_other->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-others-40"), new Gtk2::Label(N("Other")), new Gtk2::HBox(0, 5) )); gtkset_sensitive($button_what_sys, !$conf{NO_SYS_FILES}); fonction_env(\$box_what, \&advanced_what, \&advanced_box); $up_box->show_all; } sub advanced_where_net_types { my ($previous_function) = @_; my $box_where_net; gtkpack($advanced_box, $box_where_net = gtkpack_(new Gtk2::VBox(0, 10), 0, gtkpack_(new Gtk2::HBox(0,10), 0, my $check_where_use_net = new Gtk2::CheckButton(N("Use network connection to backup")), 1, new Gtk2::HBox(0,10), 0, new Gtk2::Label(N("Net Method:")), 0, gtkset_sensitive(my $entry_net_type = new Gtk2::OptionMenu(), $conf{USE_NET}), ), 0, gtkpack_(new Gtk2::HBox(0,5), 0, gtkset_sensitive(my $check_use_expect = new Gtk2::CheckButton(N("Use Expect for SSH")), ($conf{USE_NET} && $conf{NET_PROTO} eq 'ssh')), 0, gtkset_sensitive(my $check_xfer_keys = new Gtk2::CheckButton(N("Create/Transfer backup keys for SSH")), ($conf{USE_NET} && $conf{NET_PROTO} eq 'ssh')), 1, new Gtk2::HBox(0,10), 0, gtkset_sensitive(my $button_xfer_keys = Gtk2::Button->new(N("Transfer Now")), $conf{DRAK_KEYS}), ), 0, gtkset_sensitive(my $check_user_keys = new Gtk2::CheckButton(N("Other (not drakbackup) keys in place already")), ($conf{USE_NET} && $conf{NET_PROTO} eq 'ssh')), 0, new Gtk2::HSeparator, 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Host name or IP.")), $conf{USE_NET}), 1, new Gtk2::HBox(0,10), 0, gtkset_sensitive(my $host_name_entry = new Gtk2::Entry(), $conf{USE_NET}), ), 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Directory (or module) to put the backup on this host.")), $conf{USE_NET}), 1, new Gtk2::HBox(0,10), 0, gtkset_sensitive(my $host_path_entry = new Gtk2::Entry(), $conf{USE_NET}), ), 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Login name")), $conf{USE_NET}), 1, new Gtk2::HBox(0,10), 0, gtkset_sensitive(my $login_user_entry = new Gtk2::Entry(), $conf{USE_NET}), ), 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Password")), $conf{USE_NET}), 1, new Gtk2::HBox(0,10), 0, gtkset_sensitive(my $check_remember_pass = new Gtk2::CheckButton(N("Remember this password")), $conf{USE_NET}), 1, new Gtk2::HBox(0,10), 0, gtkset_sensitive(my $passwd_user_entry = new Gtk2::Entry(), $conf{USE_NET}), ), ), ); $entry_net_type->set_popdown_strings('', @net_methods); $entry_net_type->entry->set_text($conf{NET_PROTO}); $button_xfer_keys->signal_connect('clicked', sub { if ($conf{PASSWD} && $conf{LOGIN} && $conf{HOST_NAME}) { do_expect("sendkey"); } else { $in->ask_warn(N("Error"), N("Need hostname, username and password!")); } }); $passwd_user_entry->set_visibility(0); $passwd_user_entry->set_text($conf{PASSWD}); $passwd_user_entry->signal_connect('changed', sub { $conf{PASSWD} = $passwd_user_entry->get_text }); $host_path_entry->set_text($conf{HOST_PATH}); $host_name_entry->set_text($conf{HOST_NAME}); $login_user_entry->set_text($conf{LOGIN}); $host_name_entry->signal_connect('changed', sub { $conf{HOST_NAME} = $host_name_entry->get_text }); $host_path_entry->signal_connect('changed', sub { $conf{HOST_PATH} = $host_path_entry->get_text }); $login_user_entry->signal_connect('changed', sub { $conf{LOGIN} = $login_user_entry->get_text }); $entry_net_type->entry->signal_connect('changed', sub { $conf{NET_PROTO} = $entry_net_type->entry->get_text; my $sensitive = 0; $sensitive = 1 if $conf{NET_PROTO} eq 'ssh'; $check_use_expect->set_sensitive($sensitive); $check_xfer_keys->set_sensitive($sensitive); $button_xfer_keys->set_sensitive($sensitive); $check_user_keys->set_sensitive($sensitive); }); check_list([$check_remember_pass, \$conf{REMEMBER_PASS}]); gtksignal_connect(gtkset_active($check_where_use_net, $conf{USE_NET}), toggled => sub { invbool \$conf{USE_NET}; #- assure other methods disabled if ($conf{USE_NET} == 1) { $conf{USE_CD} = 0; $conf{USE_TAPE} = 0; } $conf{NET_PROTO} = '' if $conf{USE_NET} == 0; destroy_widget(); $current_widget->(); }); gtksignal_connect(gtkset_active($check_use_expect, $conf{USE_EXPECT}), toggled => sub { invbool \$conf{USE_EXPECT}; #- assure other methods disabled if ($conf{USE_EXPECT} == 1) { $conf{DRAK_KEYS} = 0; $conf{USER_KEYS} = 0; } destroy_widget(); $current_widget->(); }); gtksignal_connect(gtkset_active($check_xfer_keys, $conf{DRAK_KEYS}), toggled => sub { invbool \$conf{DRAK_KEYS}; #- assure other methods disabled if ($conf{DRAK_KEYS} == 1) { $conf{USE_EXPECT} = 0; $conf{USER_KEYS} = 0; } destroy_widget(); $current_widget->(); }); gtksignal_connect(gtkset_active($check_user_keys, $conf{USER_KEYS}), toggled => sub { invbool \$conf{USER_KEYS}; #- assure other methods disabled if ($conf{USER_KEYS} == 1) { $conf{DRAK_KEYS} = 0; $conf{USE_EXPECT} = 0; } destroy_widget(); $current_widget->(); }); set_help_tip($check_use_expect, 'use_expect'); set_help_tip($check_remember_pass, 'remember_pass'); set_help_tip($host_path_entry, 'dir_or_module'); if ($previous_function) { fonction_env(\$box_where_net, \&advanced_where_net_types, \&$previous_function, \&wizard_step3); button_box_wizard(); } else { fonction_env(\$box_where_net, \&advanced_where_net_types, \&advanced_where); } $up_box->show_all; } sub advanced_where_cd { my ($previous_function) = @_; my $box_where_cd; my %dev_codes; get_cd_info(); foreach my $key (keys %cd_devices) { $dev_codes{$cd_devices{$key}{rec_dev}} = $key; } my $combo_where_cd_device = new Gtk2::OptionMenu(); if (keys %cd_devices) { $combo_where_cd_device->set_popdown_strings('', sort keys %dev_codes); } else { $combo_where_cd_device->set_popdown_strings(@no_devices); } my $combo_where_cd_time = new Gtk2::OptionMenu(); $combo_where_cd_time->set_popdown_strings("650 MB", "700 MB", "750 MB", "800 MB", "4.7 GB"); gtkpack($advanced_box, $box_where_cd = gtkpack_(new Gtk2::VBox(0, 6), 0, my $check_where_cd = new Gtk2::CheckButton(N("Use CD-R/DVD-R to backup")), 0, new Gtk2::HSeparator, 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Choose your CD/DVD device")), $conf{USE_CD}), 1, new Gtk2::VBox(0, 5), 0, gtkset_sensitive($combo_where_cd_device, $conf{USE_CD}), ), 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Choose your CD/DVD media size")), $conf{USE_CD}), 1, new Gtk2::VBox(0, 5), 0, gtkset_sensitive($combo_where_cd_time, $conf{USE_CD}), ), 0, new Gtk2::VBox(0, 5), 0, gtkpack_(new Gtk2::HBox(0,10), 1, new Gtk2::VBox(0, 5), 0, gtkset_sensitive(new Gtk2::Label(N("Multisession CD")), $conf{USE_CD}), 0, gtkset_sensitive(my $check_multisession = new Gtk2::CheckButton(), $conf{USE_CD}), 0, gtkset_sensitive(new Gtk2::Label(N("CDRW media")), $conf{USE_CD}), 0, gtkset_sensitive(my $check_cdrw = new Gtk2::CheckButton(), $conf{USE_CD}), ), 0, new Gtk2::VBox(0, 5), 0, gtkpack_(new Gtk2::HBox(0,10), 1, new Gtk2::VBox(0, 5), 0, gtkset_sensitive(new Gtk2::Label(N("Erase your RW media (1st Session)")), $conf{CDRW} && $conf{USE_CD}), 0, gtkset_sensitive(my $button_erase_now = Gtk2::Button->new(N(" Erase Now ")), $conf{CDRW}), 0, gtkset_sensitive(my $check_cdrw_erase = new Gtk2::CheckButton(), $conf{CDRW} && $conf{USE_CD}), ), 0, new Gtk2::VBox(0, 5), 0, gtkpack_(new Gtk2::HBox(0,10), 1, new Gtk2::VBox(0, 5), 0, gtkset_sensitive(new Gtk2::Label(N("DVD+RW media")), $conf{USE_CD}), 0, gtkset_sensitive(my $check_dvdrw = new Gtk2::CheckButton(), $conf{USE_CD}), 0, gtkset_sensitive(new Gtk2::Label(N("DVD-R media")), $conf{USE_CD}), 0, gtkset_sensitive(my $check_dvdr = new Gtk2::CheckButton(), $conf{USE_CD}), 0, gtkset_sensitive(new Gtk2::Label(N("DVDRAM device")), $conf{USE_CD}), 0, gtkset_sensitive(my $check_dvdram = new Gtk2::CheckButton(), $conf{USE_CD}), ), ), ); foreach ([$check_cdrw_erase, \$conf{MEDIA_ERASE}], [$check_dvdrw, \$conf{DVDRW}], [$check_dvdr, \$conf{DVDR}], [$check_dvdram, \$conf{DVDRAM}], [$check_multisession, \$conf{MULTI_SESSION}]) { my $ref = $_->[1]; gtksignal_connect(gtkset_active($_->[0], $$ref), toggled => sub { $$ref = $$ref ? 0 : 1 }) } gtksignal_connect(gtkset_active($check_where_cd, $conf{USE_CD}), toggled => sub { $conf{USE_CD} = $conf{USE_CD} ? 0 : 1; #- toggle where_net, where_tape off if ($conf{USE_CD} == 1) { $conf{USE_NET} = 0; $conf{USE_TAPE} = 0; } destroy_widget(); $current_widget->(); }); gtksignal_connect(gtkset_active($check_cdrw, $conf{CDRW}), toggled => sub { $conf{CDRW} = $conf{CDRW} ? 0 : 1; $conf{MEDIA_ERASE} = $conf{MEDIA_ERASE} ? 0 : 1; $check_cdrw_erase->set_sensitive($conf{CDRW}); destroy_widget(); $current_widget->(); }); $button_erase_now->signal_connect('clicked', sub { if ($conf{CD_DEVICE}) { erase_cdrw(); } else { $in->ask_warn(N("Error"), N("No CD device defined!")); } }); $combo_where_cd_time->entry->set_text($conf{CD_TIME}); $combo_where_cd_time->entry->signal_connect('changed', sub { $conf{CD_TIME} = $combo_where_cd_time->entry->get_text }); $combo_where_cd_device->entry->set_text($conf{CD_DEVICE}); $combo_where_cd_device->entry->signal_connect('changed', sub { $conf{CD_DEVICE} = $combo_where_cd_device->entry->get_text; $std_device = $dev_codes{$conf{CD_DEVICE}}; $check_dvdr->set_active($cd_devices{$std_device}{dvdr}); $check_dvdrw->set_active($cd_devices{$std_device}{dvdr}); $check_dvdram->set_active($cd_devices{$std_device}{dvdram}); $check_cdrw->set_active($cd_devices{$std_device}{cdrw}); }); set_help_tip($button_erase_now, 'erase_cdrw'); if ($previous_function) { fonction_env(\$box_where_cd, \&advanced_where_cd, \&$previous_function, \&wizard_step3); button_box_wizard(); } else { fonction_env(\$box_where_cd, \&advanced_where_cd, \&advanced_where); } $up_box->show_all; } sub advanced_where_tape { my ($previous_function) = @_; #- look for tape devices; get_tape_info(); my $combo_where_tape_device = new Gtk2::OptionMenu(); if (@tape_devices) { $combo_where_tape_device->set_popdown_strings('', @tape_devices) } else { $combo_where_tape_device->set_popdown_strings(@no_devices); } my $box_where_tape; local $_; gtkpack($advanced_box, $box_where_tape = gtkpack_(new Gtk2::VBox(0, 6), 0, new Gtk2::HSeparator, 0, my $check_where_tape = new Gtk2::CheckButton(N("Use tape to backup")), 0, new Gtk2::HSeparator, 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Device name to use for backup")), $conf{USE_TAPE}), 1, new Gtk2::VBox(0, 6), 0, gtkset_sensitive($combo_where_tape_device, $conf{USE_TAPE}), ), 0, new Gtk2::VBox(0, 5), 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Don't rewind tape after backup")), $conf{USE_TAPE}), 1, new Gtk2::VBox(0, 5), 0, gtkset_sensitive(my $check_tape_rewind = new Gtk2::CheckButton(), $conf{USE_TAPE}), ), 0, new Gtk2::VBox(0, 5), 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Erase tape before backup")), $conf{USE_TAPE}), 1, new Gtk2::VBox(0, 5), 0, gtkset_sensitive(my $check_tape_erase = new Gtk2::CheckButton(), $conf{USE_TAPE}), ), 0, new Gtk2::VBox(0, 5), 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Eject tape after the backup")), $conf{USE_TAPE}), 1, new Gtk2::VBox(0, 5), 0, gtkset_sensitive(my $check_tape_eject = new Gtk2::CheckButton(), $conf{USE_TAPE}), ), 0, new Gtk2::VBox(0, 6), 0, gtkpack_(new Gtk2::HBox(0,10),), ), ); gtksignal_connect(gtkset_active($check_where_tape, $conf{USE_TAPE}), toggled => sub { $conf{USE_TAPE} = $conf{USE_TAPE} ? 0 : 1; #- assure other methods are off if ($conf{USE_TAPE} == 1) { $conf{USE_NET} = 0; $conf{USE_CD} = 0; } destroy_widget(); $current_widget->(); }); gtksignal_connect(gtkset_active($check_tape_rewind, $conf{TAPE_NOREWIND}), toggled => sub { $conf{TAPE_NOREWIND} = $conf{TAPE_NOREWIND} ? 0 : 1; $_ = $conf{TAPE_DEVICE}; if ($conf{TAPE_NOREWIND}) { $conf{TAPE_DEVICE} =~ s|/st|/nst|; } else { $conf{TAPE_DEVICE} =~ s|/nst|/st|; } $combo_where_tape_device->entry->set_text($conf{TAPE_DEVICE}); destroy_widget(); $current_widget->(); }); gtksignal_connect(gtkset_active($check_tape_erase, $conf{MEDIA_ERASE}), toggled => sub { $conf{MEDIA_ERASE} = $conf{MEDIA_ERASE} ? 0 : 1; destroy_widget(); $current_widget->(); }); gtksignal_connect(gtkset_active($check_tape_eject, $conf{MEDIA_EJECT}), toggled => sub { $conf{MEDIA_EJECT} = $conf{MEDIA_EJECT} ? 0 : 1; destroy_widget(); $current_widget->(); }); $combo_where_tape_device->entry->set_text($conf{TAPE_DEVICE}); $combo_where_tape_device->entry->signal_connect('changed', sub { $conf{TAPE_DEVICE} = $combo_where_tape_device->entry->get_text; }); if ($previous_function) { fonction_env(\$box_where_tape, \&advanced_where_tape, \&$previous_function, \&wizard_step3); button_box_wizard(); } else { fonction_env(\$box_where_tape, \&advanced_where_tape, \&advanced_where); } $up_box->show_all; } sub advanced_where_hd { my ($previous_function) = @_; my $box_where_hd; my $button; if ($conf{MAX_SPACE} == 1000.0) { $conf{MAX_SPACE} = int(0.8 * get_free_space($conf{PATH_TO_SAVE})) if -d $conf{PATH_TO_SAVE}; } my $adj = new Gtk2::Adjustment($conf{MAX_SPACE}, 0.0, $conf{MAX_SPACE}, 10.0, 5.0, 0.0); my $spinner; gtkpack($advanced_box, $box_where_hd = gtkpack_(new Gtk2::VBox(0, 6), 0, new Gtk2::HSeparator, 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Enter the directory to save to:")), $conf{USE_HD}), 1, new Gtk2::VBox(0, 6), 0, gtkset_size_request(gtkset_sensitive($save_path_entry = new Gtk2::Entry(), $conf{USE_HD}), 152, 20), 0, gtkset_sensitive($button = gtksignal_connect(Gtk2::Button->new, clicked => sub { filedialog_where_hd() }), $conf{USE_HD}), ), 0, new Gtk2::VBox(0, 6), 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Maximum size\n allowed for Drakbackup (MB)")), $conf{USE_HD}), 1, new Gtk2::VBox(0, 6), 0, gtkset_size_request(gtkset_sensitive($spinner = new Gtk2::SpinButton($adj, 0, 0), $conf{USE_HD}), 200, 20), ), ), ); $button->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-dossier-32"))); $save_path_entry->set_text($conf{PATH_TO_SAVE}); $spinner->signal_connect('changed', sub { $conf{MAX_SPACE} = $spinner->get_text }); $save_path_entry->signal_connect('changed', sub { $conf{PATH_TO_SAVE} = $save_path_entry->get_text; if (-d $conf{PATH_TO_SAVE}) { $conf{MAX_SPACE} = int(0.8 * get_free_space($conf{PATH_TO_SAVE})); # seems to be the easiest way to avoid the widgets fighting over values # and getting garbage in $max_value destroy_widget(); if ($previous_function) { advanced_where_hd(\&$previous_function); } else { advanced_where_hd(); } } }); if ($previous_function) { fonction_env(\$box_where_hd, \&advanced_where_hd, \&$previous_function, \&wizard_step3); button_box_wizard(); } else { fonction_env(\$box_where_hd, \&advanced_where_hd, \&advanced_where); } $up_box->show_all; } sub advanced_where() { my $box_where; gtkpack($advanced_box, $box_where = gtkpack_(new Gtk2::HBox(0, 15), 1, new Gtk2::VBox(0, 5), 1, gtkpack_(new Gtk2::VBox(0, 15), 1, new Gtk2::VBox(0, 5), 1, gtksignal_connect(my $button_where_net = Gtk2::Button->new, clicked => sub { destroy_widget(); advanced_where_net_types(); }), 1, gtksignal_connect(my $button_where_cd = Gtk2::Button->new, clicked => sub { destroy_widget(); if (require_rpm("mkisofs", "cdrecord")) { advanced_where_cd(); } else { destroy_widget(); install_rpm(\&advanced_where); } }), 1, gtksignal_connect(my $button_where_hd = Gtk2::Button->new, clicked => sub { destroy_widget(); advanced_where_hd(); }), 1, gtksignal_connect(my $button_where_tape = Gtk2::Button->new, clicked => sub { destroy_widget(); advanced_where_tape() }), 1, new Gtk2::VBox(0, 5), ), 1, new Gtk2::VBox(0, 5), ), ); $button_where_net->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-network-40"), new Gtk2::Label(N("Network")), new Gtk2::HBox(0, 5) )); $button_where_cd->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-CD-40"), new Gtk2::Label(N("CD-R / DVD-R")), new Gtk2::HBox(0, 5) )); $button_where_hd->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-discdurwhat-40"), new Gtk2::Label(N("HardDrive / NFS")), new Gtk2::HBox(0, 5) )); $button_where_tape->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-tape-40"), new Gtk2::Label(N("Tape")), new Gtk2::HBox(0, 5) )); fonction_env(\$box_where, \&advanced_where, \&advanced_box); $up_box->show_all; } sub advanced_when() { my $box_when; my $allow_custom = $backup_daemon && $custom_cron; my $combo_when_space = new Gtk2::OptionMenu(); my %trans = (N("hourly") => 'hourly', N("daily") => 'daily', N("weekly") => 'weekly', N("monthly") => 'monthly', N("custom") => 'custom'); my %trans2 = ('hourly' => N("hourly"), 'daily' => N("daily"), 'weekly' => N("weekly"), 'monthly' => N("monthly"), 'custom' => N("custom")); $combo_when_space->set_popdown_strings("", N("hourly"), N("daily"), N("weekly"), N("monthly"), N("custom")); set_help_tip($combo_when_space, 'when_space'); #- custom setup - let user specify month, day of month, day of week, hour, minute my $combo_month_when = new Gtk2::OptionMenu(); my @months = ("*", N("January"), N("February"), N("March"), N("April"), N("May"), N("June"), N("July"), N("August"), N("September"), N("October"), N("November"), N("December")); $combo_month_when->set_popdown_strings(@months); my $combo_day_when = new Gtk2::OptionMenu(); $combo_day_when->set_popdown_strings("*", (1..31)); my $combo_weekday_when = new Gtk2::OptionMenu(); my @weekdays = ("*", N("Sunday"), N("Monday"), N("Tuesday"), N("Wednesday"), N("Thursday"), N("Friday"), N("Saturday")); $combo_weekday_when->set_popdown_strings(@weekdays); my $combo_hour_when = new Gtk2::OptionMenu(); $combo_hour_when->set_popdown_strings("*", (0..23)); my $combo_minute_when = new Gtk2::OptionMenu(); $combo_minute_when->set_popdown_strings("*", (0..59)); my $entry_crontab = new Gtk2::Entry(); gtkset_editable($entry_crontab, 0); my @time_list = split(" ", $time_string); $combo_minute_when->entry->set_text($time_list[0]); $combo_hour_when->entry->set_text($time_list[1]); $combo_day_when->entry->set_text($time_list[2]); if ($time_list[3] =~ /\*/) { $combo_month_when->entry->set_text($time_list[3]); } else { $combo_month_when->entry->set_text($months[$time_list[3]]); } if ($time_list[4] =~ /\*/) { $combo_weekday_when->entry->set_text($time_list[4]); } else { $combo_weekday_when->entry->set_text($weekdays[$time_list[4] + 1]); } #- drop down list of possible media - default to config value my $entry_media_type = new Gtk2::OptionMenu(); $entry_media_type->set_popdown_strings(sort(@net_methods, @media_types)); $entry_media_type->entry->set_text($conf{DAEMON_MEDIA}); gtkpack($advanced_box, $box_when = gtkpack_(new Gtk2::VBox(0, 10), 0, gtkpack_(new Gtk2::HBox(0,10), 1, new Gtk2::HBox(0,10), 1, gtkcreate_img("ic82-when-40"), 0, my $check_when_daemon = new Gtk2::CheckButton(N("Use daemon")), 1, new Gtk2::HBox(0,10), ), 0, new Gtk2::HSeparator, 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Please choose the time interval between each backup")), $backup_daemon), 1, new Gtk2::HBox(0,10), 0, gtkset_sensitive($combo_when_space, $backup_daemon), ), 0, new Gtk2::HSeparator, 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Custom setup/crontab entry:")), $allow_custom), 1, gtkset_sensitive($entry_crontab, $allow_custom), ), 0, gtkpack_(new Gtk2::HBox(0,10), 1, gtkpack_(new Gtk2::VBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Minute")), $allow_custom), 0, gtkset_sensitive($combo_minute_when, $allow_custom), ), 1, gtkpack_(new Gtk2::VBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Hour")), $allow_custom), 0, gtkset_sensitive($combo_hour_when, $allow_custom), ), 1, gtkpack_(new Gtk2::VBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Day")), $allow_custom), 0, gtkset_sensitive($combo_day_when, $allow_custom), ), 1, gtkpack_(new Gtk2::VBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Month")), $allow_custom), 0, gtkset_sensitive($combo_month_when, $allow_custom), ), 1, gtkpack_(new Gtk2::VBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Weekday")), $allow_custom), 0, gtkset_sensitive($combo_weekday_when, $allow_custom), ), ), 0, new Gtk2::HSeparator, 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Please choose the media for backup.")), $backup_daemon), 1, new Gtk2::HBox(0,10), 0, gtkpack_(new Gtk2::VBox(0,10), 0, gtkset_sensitive($entry_media_type, $backup_daemon), ), ), 0, new Gtk2::HSeparator, 0, gtkset_sensitive(new Gtk2::Label(N("Please be sure that the cron daemon is included in your services.")), $backup_daemon), 0, gtkset_sensitive(new Gtk2::Label(N("Note that currently all 'net' media also use the hard drive.")), $backup_daemon), ), ); gtksignal_connect(gtkset_active($check_when_daemon, $backup_daemon), toggled => sub { $backup_daemon = $backup_daemon ? 0 : 1; destroy_widget(); advanced_when(); }); $combo_when_space->entry->set_text($trans2{$conf{DAEMON_TIME_SPACE}}); $combo_when_space->entry->signal_connect('changed', sub { $conf{DAEMON_TIME_SPACE} = $trans{$combo_when_space->entry->get_text}; $custom_cron = $conf{DAEMON_TIME_SPACE} eq "custom" ? 1 : 0; destroy_widget(); advanced_when(); }); if ($custom_cron) { $entry_crontab->set_text("$time_string $exec_string") } $combo_minute_when->entry->signal_connect('changed', sub { combo_to_cron_string($combo_minute_when->get_history - 1, 0) }); $combo_hour_when->entry->signal_connect('changed', sub { combo_to_cron_string($combo_hour_when->get_history - 1, 1) }); $combo_day_when->entry->signal_connect('changed', sub { combo_to_cron_string($combo_day_when->get_history, 2) }); $combo_month_when->entry->signal_connect('changed', sub { combo_to_cron_string($combo_month_when->get_history, 3) }); $combo_weekday_when->entry->signal_connect('changed', sub { combo_to_cron_string($combo_weekday_when->get_history - 1, 4) }); $entry_media_type->entry->signal_connect('changed', sub { $conf{DAEMON_MEDIA} = $entry_media_type->entry->get_text }); fonction_env(\$box_when, \&advanced_when, \&advanced_box); $up_box->show_all; } sub combo_to_cron_string { my ($field, $location) = @_; $field = "*" if $field == 0 && $location > 1 && $location < 4; $field = "*" if $field == -1 && ($location < 2 || $location == 4); my @time_list = split(" ", $time_string); splice(@time_list, $location, 1, $field); $time_string = join(" ", @time_list); destroy_widget(); advanced_when(); } sub advanced_options() { my $box_options; gtkpack($advanced_box, $box_options = gtkpack_(new Gtk2::VBox(0, 15), 0, my $check_tar_bz2 = new Gtk2::CheckButton(N("Use tar and bzip2 (rather than tar and gzip)")), 0, my $check_backupignore = new Gtk2::CheckButton(N("Use .backupignore files")), 0, gtkpack_(new Gtk2::HBox(0,10), 0, my $check_mail = new Gtk2::CheckButton(N("Send mail report after each backup to:")), 1, new Gtk2::HBox(0,10), 0, gtkset_sensitive(my $mail_entry = new Gtk2::Entry(), $conf{SEND_MAIL}), ), 0, gtkpack_(new Gtk2::HBox(0,10), 1, new Gtk2::HBox(0,10), 0, N("SMTP server for mail:"), 1, new Gtk2::HBox(0,10), 0, gtkset_sensitive(my $smtp_entry = new Gtk2::Entry(), $conf{SEND_MAIL}), ), 0, gtkpack_(new Gtk2::HBox(0,10), 0, my $check_del_hd_files = new Gtk2::CheckButton(N("Delete Hard Drive tar files after backup to other media.")), ), ), ); check_list([$check_mail, \$conf{SEND_MAIL}], [$check_tar_bz2, \$conf{OPTION_COMP}], [$check_del_hd_files, \$conf{DEL_HD_FILES}], [$check_backupignore, \$conf{BACKUPIGNORE}]); $mail_entry->set_text($conf{USER_MAIL}); $mail_entry->signal_connect('changed', sub { $conf{USER_MAIL} = $mail_entry->get_text }); $smtp_entry->set_text($conf{SMTP_SERVER}); $smtp_entry->signal_connect('changed', sub { $conf{SMTP_SERVER} = $smtp_entry->get_text }); set_help_tip($check_backupignore, 'backupignore'); set_help_tip($check_mail, 'send_mail_to'); set_help_tip($check_del_hd_files, 'delete_files'); fonction_env(\$box_options, \&advanced_options, \&advanced_box); $up_box->show_all; } sub advanced_box() { my $box_adv; gtkpack($advanced_box, $box_adv = gtkpack_(new Gtk2::HBox(0, 15), 1, new Gtk2::VBox(0, 5), 1, gtkpack_(new Gtk2::VBox(0, 15), 1, new Gtk2::VBox(0, 5), 1, gtksignal_connect(my $button_what = Gtk2::Button->new, clicked => sub { destroy_widget(); advanced_what() }), 1, gtksignal_connect(my $button_where = Gtk2::Button->new, clicked => sub { destroy_widget(); advanced_where() }), 1, gtksignal_connect(my $button_when = Gtk2::Button->new, clicked => sub { destroy_widget(); advanced_when() }), 1, gtksignal_connect(my $button_options = Gtk2::Button->new, clicked => sub { destroy_widget(); advanced_options() }), 1, new Gtk2::VBox(0, 5), ), 1, new Gtk2::VBox(0, 5), ), ); $button_what->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-discdurwhat-40"), new Gtk2::Label(N("What")), new Gtk2::HBox(0, 5) )); $button_where->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-where-40"), new Gtk2::Label(N("Where")), new Gtk2::HBox(0, 5) )); $button_when->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-when-40"), new Gtk2::Label(N("When")), new Gtk2::HBox(0, 5) )); $button_options->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-moreoption-40"), new Gtk2::Label(N("More Options")), new Gtk2::HBox(0, 5) )); fonction_env(\$box_adv, \&advanced_box, \&interactive_mode_box); $up_box->show_all; } ################################################ WIZARD ################################################ sub wizard_step3() { my $no_device = 1 if $conf{USE_CD} && $conf{CD_DEVICE} eq '' || $conf{USE_TAPE} && $conf{TAPE_DEVICE} eq '' || $conf{USE_NET} && $conf{NET_PROTO} eq ''; if ($no_device) { show_warning("f", N("Backup destination not configured...")); destroy_widget(); wizard_step2(); return; } my $box2; my $text = new Gtk2::TextView; save_conf_file(); read_conf_file(); system_state(); gtktext_insert($text, [ [ $system_state ] ]); button_box_restore_main(); gtkpack($advanced_box, $box2 = gtkpack_(new Gtk2::HBox(0, 15), 1, gtkpack_(new Gtk2::VBox(0,10), 0, N("Drakbackup Configuration"), 1, create_scrolled_window($text), ), ), ); fonction_env(\$box2, \&wizard_step3, \&wizard_step2); button_box_wizard_end(); $up_box->show_all; } sub wizard_step2() { my $box2; gtkpack($advanced_box, $box2 = gtkpack_(new Gtk2::HBox(0, 15), 1, new Gtk2::VBox(0, 5), 1, gtkpack_(new Gtk2::VBox(0, 15), 1, new Gtk2::VBox(0, 5), 0, N("Please choose where you want to backup"), 0, gtkpack_(new Gtk2::HBox(0, 15), 0, N("Hard Drive used to prepare backups for all media"), 1, new Gtk2::VBox(0, 5), 0, gtkset_sensitive(gtksignal_connect(Gtk2::Button->new(N("Configure")), clicked => sub { destroy_widget(); advanced_where_hd(\&wizard_step2); }), $use_hd), ), 0, gtkpack_(new Gtk2::HBox(0, 15), 0, my $check_wizard_net = new Gtk2::CheckButton(N("Across Network")), 1, new Gtk2::VBox(0, 5), 0, gtkset_sensitive(gtksignal_connect(Gtk2::Button->new(N("Configure")), clicked => sub { destroy_widget(); advanced_where_net_types(\&wizard_step2); }), $conf{USE_NET}), ), 0, gtkpack_(new Gtk2::HBox(0, 15), 0, my $check_wizard_cd = new Gtk2::CheckButton(N("On CD-R")), 1, new Gtk2::VBox(0, 5), 0, gtkset_sensitive(gtksignal_connect(Gtk2::Button->new(N("Configure")), clicked => sub { destroy_widget(); advanced_where_cd(\&wizard_step2); }), $conf{USE_CD}), ), 0, gtkpack_(new Gtk2::HBox(0, 15), 0, my $check_wizard_tape = new Gtk2::CheckButton(N("On Tape Device")), 1, new Gtk2::VBox(0, 5), 0, gtkset_sensitive(gtksignal_connect(Gtk2::Button->new(N("Configure")), clicked => sub { destroy_widget(); advanced_where_tape(\&wizard_step2); }), $conf{USE_TAPE}), ), 1, new Gtk2::VBox(0, 5), ), 1, new Gtk2::VBox(0, 5), ), ); gtksignal_connect(gtkset_active($check_wizard_cd, $conf{USE_CD}), toggled => sub { invbool \$conf{USE_CD}; if ($conf{USE_CD}) { $conf{USE_TAPE} = 0; $conf{USE_NET} = 0 }; refresh_wizard_step2(); }); gtksignal_connect(gtkset_active($check_wizard_net, $conf{USE_NET}), toggled => sub { invbool \$conf{USE_NET}; if ($conf{USE_NET}) { $conf{USE_TAPE} = 0; $conf{USE_CD} = 0 }; refresh_wizard_step2(); }); gtksignal_connect(gtkset_active($check_wizard_tape, $conf{USE_TAPE}), toggled => sub { invbool \$conf{USE_TAPE}; if ($conf{USE_TAPE}) { $conf{USE_CD} = 0; $conf{USE_NET} = 0 }; refresh_wizard_step2(); }); fonction_env(\$box2, \&wizard_step2, \&wizard, undef); button_box_wizard(); $up_box->show_all; } sub refresh_wizard_step2() { $use_hd = !($conf{USE_TAPE} || $conf{USE_CD} || $conf{USE_NET}); destroy_widget(); wizard_step2(); } sub wizard() { my $box2; my $user_string = N("Backup Users"); $user_string .= N(" (Default is all users)") if !$nonroot_user; if (!$conf{NO_USER_FILES} && !$manual_user) { @user_list = @user_list_all; } elsif (!$manual_user) { @user_list = (); } gtkpack($advanced_box, $box2 = gtkpack_(new Gtk2::HBox(0, 15), 1, new Gtk2::VBox(0, 5), 1, gtkpack_(new Gtk2::VBox(0, 15), 1, new Gtk2::VBox(0, 5), 0, N("Please choose what you want to backup"), 0, my $check_wizard_sys = new Gtk2::CheckButton(N("Backup System")), 0, my $check_wizard_user = new Gtk2::CheckButton($user_string), 0, gtksignal_connect(Gtk2::Button->new(N("Select user manually")), clicked => sub { $manual_user = 1; destroy_widget(); advanced_what_user(\&wizard); }), 1, new Gtk2::VBox(0, 5), ), 1, new Gtk2::VBox(0, 5), ), ); foreach ([$check_wizard_sys, \$conf{NO_SYS_FILES}], [$check_wizard_user, \$conf{NO_USER_FILES}]) { my $ref = $_->[1]; gtksignal_connect(gtkset_active($_->[0], !$$ref), toggled => sub { $$ref = $$ref ? 0 : 1; if (!$conf{NO_SYS_FILES} || !$conf{NO_USER_FILES} && @user_list) { $next_widget = \&wizard_step2; } else { $next_widget = \&wizard; } if (!$conf{NO_USER_FILES}) { @user_list = @user_list_all; } else { @user_list = (); } }) } if (!$conf{NO_SYS_FILES} || !$conf{NO_USER_FILES} && @user_list) { fonction_env(\$box2, \&wizard, \&interactive_mode_box, \&wizard_step2); } else { $in->ask_warn(N("Error"), N("Please select data to backup...")); fonction_env(\$box2, \&wizard, \&interactive_mode_box, \&wizard); } button_box_wizard(); $up_box->show_all; } ################################################ RESTORE ################################################ sub find_backup_to_restore() { my @list_backup; my @list_backup_tmp2; my $to_put; my $nom; @sys_backuped = (); local $_; @user_backuped = (); -d $path_to_find_restore and @list_backup_tmp2 = all($path_to_find_restore); foreach (@list_backup_tmp2) { s/_base//gi; s/_incr//gi; push @list_backup , $_; } foreach (grep { /^backup_sys_/ } @list_backup) { ($to_put, undef) = file_to_put($_, "sys"); push @sys_backuped , $to_put; } $restore_step_sys_date = $to_put; foreach (grep { /^backup_other_/ } @list_backup) { ($to_put, undef) = file_to_put($_, "other"); push @other_backuped , $to_put; } $restore_step_other_date = $to_put; foreach (grep { /^backup_user_/ } @list_backup) { ($to_put, $nom) = file_to_put($_, "user"); push @user_backuped , $to_put; any { /^$nom$/ } @user_list_backuped or push @user_list_backuped, $nom; } } sub file_to_put { my ($name, $type) = @_; my $to_put; my ($nom, $date, $heure); local $_ = $name; chomp; $name = "backup_" . $type . "_"; s/^$name//gi; s/.tar.gz$//gi; s/.tar.bz2$//gi; if ($type eq "user") { ($nom, $date, $heure) = /^(.*)_([^_]*)_([^_]*)$/; } else { ($date, $heure) = /^(.*)_([^_]*)$/; } my $year = substr($date, 0, 4); my $month = substr($date, 4, 2); my $day = substr($date, 6, 2); my $hour = substr($heure, 0, 2); my $min = substr($heure, 2, 2); if ($type eq "user") { $to_put = "$_ user: $nom, date: $day/$month/$year, hour: $hour:$min"; return $to_put, $nom; } else { $to_put = "$day/$month/$year $hour:$min $_"; return $to_put, undef; } } sub system_state() { if ($cfg_file_exist) { $system_state = N("\nBackup Sources: \n"); $conf{NO_SYS_FILES} or $system_state .= N("\n- System Files:\n"); $conf{NO_SYS_FILES} or $system_state .= "\t\t$_\n" foreach @sys_files; $conf{NO_USER_FILES} or $system_state .= N("\n- User Files:\n"); $conf{NO_USER_FILES} or $system_state .= "\t\t$_\n" foreach @user_list; $conf{OTHER_FILES} and $system_state .= N("\n- Other Files:\n"); $conf{OTHER_FILES} and $system_state .= "\t\t$_\n" foreach @other_files; $conf{USE_HD} and $system_state .= N("\n- Save on Hard drive on path: %s\n", $conf{PATH_TO_SAVE}); $conf{USE_HD} and $system_state .= N("\tLimit disk usage to %s MB\n", $conf{MAX_SPACE}); if ($conf{DEL_HD_FILES} && ($conf{USE_CD} || $conf{USE_TAPE} || $conf{USE_NET}) && $conf{DAEMON_MEDIA} ne 'hd') { $system_state .= N("\n- Delete hard drive tar files after backup.\n"); } #- tape and CDRW share some features my $erase_media = N("NO"); $erase_media = N("YES") if $conf{MEDIA_ERASE} && ($conf{USE_CD} || $conf{USE_TAPE}); $conf{USE_CD} and $system_state .= N("\n- Burn to CD"); $conf{USE_CD} and $conf{CDRW} and $system_state .= N("RW"); $conf{USE_CD} and $system_state .= N(" on device: %s", $conf{CD_DEVICE}); $conf{USE_CD} and $conf{MULTI_SESSION} and $system_state .= N(" (multi-session)"); $conf{USE_TAPE} and $system_state .= N("\n- Save to Tape on device: %s", $conf{TAPE_DEVICE}); (($conf{USE_CD} || $conf{USE_TAPE}) && $conf{MEDIA_ERASE}) and $system_state .= N("\t\tErase=%s", $erase_media); $conf{USE_CD} || $conf{USE_TAPE} and $system_state .= "\n"; $conf{USE_NET} and $system_state .= N("\n- Save via %s on host: %s\n", $conf{NET_PROTO}, $conf{HOST_NAME}); $conf{USE_NET} and $system_state .= N("\t\t user name: %s\n\t\t on path: %s \n", $conf{LOGIN}, $conf{HOST_PATH}); $system_state .= N("\n- Options:\n"); $conf{NO_SYS_FILES} and $system_state .= N("\tDo not include System Files\n"); if ($conf{OPTION_COMP}) { $system_state .= N("\tBackups use tar and bzip2\n"); } else { $system_state .= N("\tBackups use tar and gzip\n"); } $system_state .= N("\tUse .backupignore files\n") if $conf{BACKUPIGNORE}; $system_state .= N("\tSend mail to %s\n", $conf{USER_MAIL}) if $conf{SEND_MAIL}; $system_state .= N("\tUsing SMTP server %s\n", $conf{SMTP_SERVER}) if $conf{SEND_MAIL}; $conf{DAEMON_MEDIA} and $system_state .= N("\n- Daemon, %s via:\n", $conf{DAEMON_TIME_SPACE}); $conf{DAEMON_MEDIA} eq 'hd' and $system_state .= N("\t-Hard drive.\n"); $conf{DAEMON_MEDIA} eq 'cd' and $system_state .= N("\t-CD-R.\n"); $conf{DAEMON_MEDIA} eq 'tape' and $system_state .= N("\t-Tape \n"); $conf{DAEMON_MEDIA} eq 'ftp' and $system_state .= N("\t-Network by FTP.\n"); $conf{DAEMON_MEDIA} eq 'ssh' and $system_state .= N("\t-Network by SSH.\n"); $conf{DAEMON_MEDIA} eq 'rsync' and $system_state .= N("\t-Network by rsync.\n"); $conf{DAEMON_MEDIA} eq 'webdav' and $system_state .= N("\t-Network by webdav.\n"); } else { $system_state = N("No configuration, please click Wizard or Advanced.\n"); } } sub restore_state() { $restore_state = N("List of data to restore:\n\n"); if ($restore_sys) { $restore_state .= N("- Restore System Files.\n"); my @tmp = split(' ', $restore_step_sys_date); $restore_state .= N(" - from date: %s %s\n", $tmp[0], $tmp[1]); } if ($restore_user) { $restore_state .= N("- Restore User Files: \n"); $restore_state .= "\t\t$_\n" foreach @user_list_to_restore2; push @user_list_to_restore, (split(',', $_))[0] foreach @user_list_to_restore2; } if ($restore_other) { $restore_state .= N("- Restore Other Files: \n"); my @tmp = split(' ', $restore_step_other_date); $restore_state .= N(" - from date: %s %s\n", $tmp[0], $tmp[1]); } if ($restore_other_path) { $restore_state .= "- Path to Restore: $restore_path \n"; } } sub select_most_recent_selected_of { my ($user_name) = @_; my @list_tmp2; local $_; my @tmp = sort @user_list_to_restore2; foreach (grep { /$user_name/ } sort @tmp) { push @list_tmp2 , $_ } return pop @list_tmp2; } sub select_user_data_to_restore() { my $var_eq = 1; my @list_backup; my @list_tmp; my @list_tmp2; @user_list_to_restore = (); local $_; -d $path_to_find_restore and my @list_backup_tmp2 = grep { /^backup/ } all($path_to_find_restore); @list_tmp2 = @list_backup_tmp2; foreach (@list_backup_tmp2) { s/_base//gi; s/_incr//gi; push @list_backup , $_; } foreach my $var_tmp (@user_list_backuped) { $var_eq = 1; my $more_recent = (split(' ', select_most_recent_selected_of($var_tmp)))[0]; foreach (grep { /^backup_user_$var_tmp/ } sort @list_backup) { s/.tar.gz//gi; s/.tar.bz2//gi; if ($more_recent) { if (/$more_recent/) { push @list_tmp , $_; $var_eq = 0; } else { #- only if user asked for it - previously this was restoring everything (SB) my $tmp_name = $_; s/backup_user_//gi; foreach my $buff (@user_list_to_restore2) { if (index($buff, $_) >= 0) { $var_eq and push @list_tmp , $tmp_name; } } } } } } foreach my $var_to_restore (@list_tmp) { $var_to_restore =~ s/backup_//gi; foreach my $var_exist (sort @list_tmp2) { if ($var_exist =~ /$var_to_restore/) { push @user_list_to_restore, $var_exist; } } } $DEBUG and print "real user list to restore: $_ \n" foreach @user_list_to_restore; } sub select_sys_data_to_restore() { my $var_eq = 1; my @list_tmp; local $_; -d $path_to_find_restore and @list_tmp = grep { /^backup/ } all($path_to_find_restore); my @more_recent = split(' ', $restore_step_sys_date); my $more_recent = pop @more_recent; foreach my $var_exist (grep { /_sys_/ } sort @list_tmp) { if ($var_exist =~ /$more_recent/) { push @sys_list_to_restore, $var_exist; $var_eq = 0; } else { $var_eq and push @sys_list_to_restore, $var_exist; } } $DEBUG and print "sys list to restore: $_\n " foreach @sys_list_to_restore; } sub select_other_data_to_restore() { my $var_eq = 1; my @list_tmp; local $_; @other_list_to_restore = (); -d $path_to_find_restore and @list_tmp = grep { /^backup/ } all($path_to_find_restore); my @more_recent = split(' ', $restore_step_other_date); my $more_recent = pop @more_recent; foreach my $var_exist (grep { /_other_/ } sort @list_tmp) { if ($var_exist =~ /$more_recent/) { push @other_list_to_restore, $var_exist; $var_eq = 0; } else { $var_eq and push @other_list_to_restore, $var_exist; } } $DEBUG and print "other list to restore: $_\n " foreach @other_list_to_restore; } sub show_backup_details { my ($function, $mode, $name) = @_; my $archive_file_detail; my $value; my $command2; my $tarfile; my $extension = ".tar.gz"; if ($mode eq "user") { #- we've only got a partial filename in this case $tarfile = "$path_to_find_restore/backup_*" . $name . $extension; } if ($mode eq "sys") { #- funky string here we need to use to reconstruct the filename my @flist = split(/[ \t,]+/, $name); $tarfile = "$path_to_find_restore/backup_*" . $flist[2] . $extension; } my $command1 = "stat " . $tarfile; $command2 = "tar -tzvf " . $tarfile; local *TMP; open TMP, "$command1 2>&1 |"; while ($value = ) { $archive_file_detail .= $value; } close TMP; $archive_file_detail .= "\n\n"; open TMP, "$command2 2>&1 |"; while ($value = ) { #- drop the permissions display for the sake of readability $archive_file_detail .= substr($value, 11); } close TMP; my $text = new Gtk2::TextView; my $advanced_box_archive; gtktext_insert(gtkset_editable($text, 0), $archive_file_detail); gtkpack($advanced_box, $advanced_box_archive = gtkpack_(new Gtk2::VBox(0,10), 1, gtkpack_(new Gtk2::HBox(0,0), 1, create_scrolled_window($text), ), 0, gtkadd(gtkset_layout(new Gtk2::HButtonBox, 'spread'), gtksignal_connect(Gtk2::Button->new(N("Done")), clicked => sub { destroy_widget(); $function->() }), ), ) ); $central_widget = \$advanced_box_archive; $up_box->show_all; } sub valid_backup_test { my (@files_list) = @_; @files_corrupted = (); my $is_corrupted = 0; foreach (@files_list) { #- let's quiet this down (SB) if (system("gzip -l $path_to_find_restore/$_ > /dev/null 2>&1") > 1) { push @files_corrupted, $_; $is_corrupted = -1; } } return $is_corrupted; } sub restore_aff_backup_problems() { my $do_restore; my $text = new Gtk2::TextView; my $restore_pbs_state = N("List of data corrupted:\n\n"); $restore_pbs_state .= "\t\t$_\n" foreach @files_corrupted; $restore_pbs_state .= N("Please uncheck or remove it on next time."); gtktext_insert($text, [ [ $restore_pbs_state ] ]); button_box_restore_main(); gtkpack($advanced_box, $do_restore = gtkpack_(new Gtk2::VBox(0,10), 0, new Gtk2::VBox(0,10), 1, gtkpack_(new Gtk2::HBox(0, 15), 1, new Gtk2::VBox(0, 5), 0, gtkcreate_img('warning'), 0, N("Backup files are corrupted"), 1, new Gtk2::VBox(0, 5), ), 0, new Gtk2::VBox(0,10), 1, create_scrolled_window($text), ), ); button_box_restore_pbs_end(); fonction_env(\$do_restore, \&restore_aff_backup_problems, "restore_pbs"); $up_box->show_all; } sub restore_aff_result() { my $do_restore; my $text = new Gtk2::TextView; gtktext_insert($text, [ [ $restore_state ] ]); button_box_restore_main(); gtkpack($advanced_box, $do_restore = gtkpack_(new Gtk2::VBox(0,10), 1, new Gtk2::VBox(0,10), 0, N(" All of your selected data have been "), 0, N(" Successfuly Restored on %s ", $restore_path), 1, new Gtk2::VBox(0,10), ), ); button_box_build_backup_end(); $central_widget = \$do_restore; $up_box->show_all; } sub return_path { my ($username) = @_; my $usr; my $home_dir; my $passwdfile = "/etc/passwd"; local *PASSWD; open(PASSWD, $passwdfile) or exit 1; while (defined(my $line = )) { chomp($line); ($usr, $home_dir) = (split(/:/, $line))[0,5]; last if $usr eq $username; } close(PASSWD); return $home_dir; } sub restore_backend() { my $untar_cmd; my $exist_problem = 0; my $user_dir; my $username; local $_; -d $restore_path or mkdir_p $restore_path; if (any { /tar.gz$/ } all($path_to_find_restore)) { $untar_cmd = "tar xzf"; } else { $untar_cmd = "tar xjf"; } if ($restore_user) { select_user_data_to_restore(); if (valid_backup_test(@user_list_to_restore) == -1) { $exist_problem = 1; restore_aff_backup_problems(); } else { foreach (@user_list_to_restore) { if ($conf{USER_INCREMENTAL_BACKUPS}) { (undef, $username, undef) = /^(\w+_\w+_user_)(.*)_(\d+_\d+.*)$/; } else { (undef, $username, undef) = /^(\w+_user_)(.*)_(\d+_\d+.*)$/; } $user_dir = return_path($username); -d $user_dir and rm_rf($user_dir) if $remove_user_before_restore; $DEBUG and print "user name to restore: $username, user directory: $user_dir\n"; system("$untar_cmd $path_to_find_restore/$_ -C $restore_path"); } #- flush this out for another cycle (SB) @user_list_to_restore2 = (); } } if ($restore_sys) { select_sys_data_to_restore(); if (valid_backup_test(@sys_list_to_restore) == -1) { $exist_problem = 1; restore_aff_backup_problems(); } else { system("$untar_cmd $path_to_find_restore/$_ -C $restore_path") foreach @sys_list_to_restore; } } if ($restore_other) { if (valid_backup_test(@other_list_to_restore) == -1) { $exist_problem = 1; restore_aff_backup_problems(); } else { system("$untar_cmd $path_to_find_restore/$_ -C $restore_path") foreach @other_list_to_restore; } } $exist_problem or restore_aff_result(); } sub restore_do() { if ($backup_bef_restore) { if ($restore_sys) { $conf{NO_SYS_FILES} = 0; } else { $conf{NO_SYS_FILES} = 1; } if ($restore_user) { $conf{NO_USER_FILES} = 0; @user_list = @user_list_to_restore; } else { $conf{NO_USER_FILES} = 1; } build_backup_status(); read_conf_file(); build_backup_files(); $table->destroy; } restore_do2(); } sub restore_do2() { my $do_restore; my $text = new Gtk2::TextView; restore_state(); gtktext_insert($text, [ [ $restore_state ] ]); button_box_restore_main(); gtkpack($advanced_box, $do_restore = gtkpack_(new Gtk2::VBox(0,10), 0, N(" Restore Configuration "), 1, create_scrolled_window($text), ), ); button_box_restore_end(); fonction_env(\$do_restore, \&restore_do2, \&restore_box); $up_box->show_all; } sub restore_step_other() { my $retore_step_other; my $text = new Gtk2::TextView; my $untar_cmd = "tar tzf"; my $other_rest = ""; select_other_data_to_restore(); if ($restore_other) { foreach (@other_list_to_restore) { if (/tar.bz2$/) { $untar_cmd = "tar tjf"; } $other_rest .= "/" . `$untar_cmd $path_to_find_restore/$_ -C $restore_path`; } } gtktext_insert($text, [ [ $other_rest ] ]); gtkpack($advanced_box, $retore_step_other = gtkpack_(new Gtk2::VBox(0,10), 1, new Gtk2::VBox(0,10), 1, create_scrolled_window($text), 0, my $check_restore_other_sure = new Gtk2::CheckButton(N("OK to restore the other files.")), 1, new Gtk2::VBox(0,10), ), ); check_list([$check_restore_other_sure, \$restore_other]); fonction_env(\$retore_step_other, \&restore_step_other, \&restore_step2, \&restore_do); $up_box->show_all; } sub restore_step_user() { my $retore_step_user; my @tmp_list = sort @user_backuped; @user_backuped = @tmp_list; gtkpack($advanced_box, $retore_step_user = gtkpack_(new Gtk2::VBox(0,10), 0, new Gtk2::VBox(0,10), 0, N("User list to restore (only the most recent date per user is important)"), 1, create_scrolled_window(gtkpack__(new Gtk2::VBox(0,0), map { my $name; my $var2; my $name_complet = $_; $name = (split(' ', $name_complet))[0]; my @user_list_tmp; my $restore_row = new Gtk2::HBox(0,5); my $b = new Gtk2::CheckButton($name_complet); my $details = Gtk2::Button->new(" Details "); $restore_row->pack_start($b, 1, 1, 0); $restore_row->pack_end(new Gtk2::VBox(1,5), 0, 0, 0); $restore_row->pack_end($details, 0, 0, 0); foreach (@user_list_to_restore2) { if ($name_complet eq $_) { gtkset_active($b, 1); $check_user_to_restore{$name_complet}[1] = 1; } else { gtkset_active($b, 0); $check_user_to_restore{$name_complet}[1] = 0; } } $b->signal_connect(toggled => sub { if (!$check_user_to_restore{$name_complet}[1]) { $check_user_to_restore{$name_complet}[1] = 1; if (!any { /$name/ } @user_list_to_restore2) { push @user_list_to_restore2, $name_complet } } else { $check_user_to_restore{$name_complet}[1] = 0; foreach (@user_list_to_restore2) { $var2 = (split(' ', $_))[0]; if ($name ne $var2) { push @user_list_tmp, $_; } } @user_list_to_restore2 = @user_list_tmp; } }); $details->signal_connect('clicked', sub { destroy_widget(); show_backup_details(\&restore_step_user, "user", $name); }); $restore_row } (@user_backuped) ), ), ), ); if ($restore_other) { fonction_env(\$retore_step_user, \&restore_step_user, "", \&restore_step_other); } elsif ($restore_sys) { fonction_env(\$retore_step_user, \&restore_step_user, \&restore_step_sys, \&restore_step_other); } else { fonction_env(\$retore_step_user, \&restore_step_user, \&restore_step2, \&restore_do); } $up_box->show_all; } sub restore_step_sys() { my $restore_step_sys; my $combo_restore_step_sys = new Gtk2::OptionMenu(); $combo_restore_step_sys->set_popdown_strings(@sys_backuped); gtkpack($advanced_box, $restore_step_sys = gtkpack_(new Gtk2::VBox(0,10), 0, N("Please choose the date to restore:"), 0, gtkpack_(new Gtk2::HBox(0,10), 1, new Gtk2::HBox(0,10), 0, $combo_restore_step_sys, 0, my $details = Gtk2::Button->new(" Details "), 1, new Gtk2::HBox(0,10), ), ), ); $combo_restore_step_sys->entry->signal_connect('changed', sub { $restore_step_sys_date = $combo_restore_step_sys->entry->get_text; }); $details->signal_connect('clicked', sub { #- we're only passing a portion of the filename to #- the subroutine so we need to let it know this my $backup_date = $combo_restore_step_sys->entry->get_text; destroy_widget(); show_backup_details(\&restore_step_sys, "sys", $backup_date); }); $combo_restore_step_sys->entry->set_text($restore_step_sys_date); fonction_env(\$restore_step_sys, \&restore_step_sys, \&restore_step2, "restore"); if ($restore_user) { fonction_env(\$restore_step_sys, \&restore_step_sys, \&restore_step2, \&restore_step_user); } elsif ($restore_other) { fonction_env(\$restore_step_sys, \&restore_step_sys, \&restore_step2, \&restore_step_other); } else { fonction_env(\$restore_step_sys, \&restore_step_sys, \&restore_step2, \&restore_do); } $up_box->show_all; } sub restore_other_media() { my $box_find_restore; my $button; gtkpack($advanced_box, $box_find_restore = gtkpack_(new Gtk2::VBox(0, 6), 0, new Gtk2::HSeparator, 0, my $check_other_media_hd = new Gtk2::CheckButton(N("Restore from Hard Disk.")), 0, gtkpack_(new Gtk2::HBox(0,10), 0, gtkset_sensitive(new Gtk2::Label(N("Enter the directory where backups are stored")), $other_media_hd), 1, new Gtk2::VBox(0, 6), 0, gtkset_size_request(gtkset_sensitive($restore_find_path_entry = new Gtk2::Entry(), $other_media_hd), 152, 20), 0, gtkset_sensitive($button = gtksignal_connect(Gtk2::Button->new, clicked => sub { filedialog_restore_find_path(); }), $other_media_hd), ), 1, new Gtk2::VBox(0, 6), 0, new Gtk2::VBox(0, 6), ), ); gtksignal_connect(gtkset_active($check_other_media_hd, $other_media_hd), toggled => sub { $other_media_hd = $other_media_hd ? 0 : 1; destroy_widget(); $current_widget->(); }); $button->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-dossier-32"))); $restore_find_path_entry->set_text($path_to_find_restore); $restore_find_path_entry->signal_connect('changed', sub { $path_to_find_restore = $restore_find_path_entry->get_text }); fonction_env(\$box_find_restore, \&restore_other_media, \&restore_step2, \&restore_do); $up_box->show_all; } sub restore_step2() { my $retore_step2; my $other_exist; my $sys_exist; my $user_exist; local $_; my $restore_info_path = $conf{PATH_TO_SAVE}; $restore_info_path = $path_to_find_restore if $conf{USE_HD} || $conf{USE_CD}; my $info_prefix = "backup"; $info_prefix = "list" if $conf{USE_NET} || $conf{USE_TAPE}; if (any { /_other_/ } grep { /^$info_prefix/ } all("$restore_info_path/")) { $other_exist = 1; } else { $other_exist = 0; $restore_other = 0; } if (any { /_sys_/ } grep { /^$info_prefix/ } all("$restore_info_path/")) { $sys_exist = 1; } else { $sys_exist = 0; $restore_sys = 0; } if (any { /_user_/ } grep { /^$info_prefix/ } all("$restore_info_path/")) { $user_exist = 1 } else { $user_exist = 0; $restore_user = 0; } gtkpack($advanced_box, $retore_step2 = gtkpack_(new Gtk2::VBox(0,10), 1, new Gtk2::VBox(0,10), 1, new Gtk2::VBox(0,10), 0, gtkpack_(new Gtk2::HBox(0,10), 0, my $check_restore_other_src = new Gtk2::CheckButton(N("Select another media to restore from")), 1, new Gtk2::HBox(0,10), 0, gtkset_sensitive(gtksignal_connect(Gtk2::Button->new(N("Other Media")), clicked => sub { destroy_widget(); restore_other_media(); }), $restore_other_src), ), 0, gtkset_sensitive(my $check_restore_sys = new Gtk2::CheckButton(N("Restore system")), $sys_exist), 0, gtkset_sensitive(my $check_restore_user = new Gtk2::CheckButton(N("Restore Users")), $user_exist), 0, gtkset_sensitive(my $check_restore_other = new Gtk2::CheckButton(N("Restore Other")), $other_exist), 0, gtkpack_(new Gtk2::HBox(0,10), 0, my $check_restore_other_path = new Gtk2::CheckButton(N("select path to restore (instead of /)")), 1, new Gtk2::HBox(0,10), 0, gtkset_sensitive(my $restore_path_entry = new Gtk2::Entry(), $restore_other_path), ), 0, gtkset_sensitive(my $check_backup_bef_restore = new Gtk2::CheckButton(N("Do new backup before restore (only for incremental backups.)")), $conf{SYS_INCREMENTAL_BACKUPS} || $conf{USER_INCREMENTAL_BACKUPS}), 0, gtkset_sensitive(my $check_remove_user_dir = new Gtk2::CheckButton(N("Remove user directories before restore.")), $user_exist), 1, new Gtk2::VBox(0,10), ), ); foreach ([$check_restore_sys, \$restore_sys], [$check_backup_bef_restore, \$backup_bef_restore], [$check_restore_user, \$restore_user], [$check_remove_user_dir, \$remove_user_before_restore], [$check_restore_other, \$restore_other]) { my $ref = $_->[1]; gtksignal_connect(gtkset_active($_->[0], $$ref), toggled => sub { $$ref = $$ref ? 0 : 1; if (!$restore_sys && !$restore_user && !$restore_other) { $next_widget = \&message_norestore_box; } elsif ($restore_sys && $conf{SYS_INCREMENTAL_BACKUPS}) { $next_widget = \&restore_step_sys; } elsif ($restore_user) { $next_widget = \&restore_step_user; } elsif ($restore_other) { $next_widget = \&restore_step_other; } else { $next_widget = \&restore_do; } }) } gtksignal_connect(gtkset_active($check_restore_other_path, $restore_other_path), toggled => sub { $restore_other_path = $restore_other_path ? 0 : 1; destroy_widget(); $current_widget->(); }); gtksignal_connect(gtkset_active($check_restore_other_src, $restore_other_src), toggled => sub { $restore_other_src = $restore_other_src ? 0 : 1; destroy_widget(); $current_widget->(); }); $central_widget = \$retore_step2; fonction_env(\$retore_step2, \&restore_step2, \&restore_box); if (!$restore_sys && !$restore_user && !$restore_other) { $next_widget = \&message_norestore_box; } elsif ($restore_sys && $conf{SYS_INCREMENTAL_BACKUPS}) { $next_widget = \&restore_step_sys; } elsif ($restore_user) { $next_widget = \&restore_step_user; } elsif ($restore_other) { $next_widget = \&restore_step_other; } else { $next_widget = \&restore_do; } $restore_path_entry->set_text($restore_path); $restore_path_entry->signal_connect('changed', sub { $restore_path = $restore_path_entry->get_text }); $up_box->show_all; } sub find_files_to_restore() { local $_; my $file_restore; my $start_restore; my $files_selected = 0; my @possible_sources; my %catalog_entries; my @files_to_restore; my $cat_entry; my @catalog = cat_("$cfg_dir/drakbackup_catalog"); #- file info in tree view my $model = Gtk2::TreeStore->new("Glib::String", "Gtk2::Gdk::Pixbuf", "Glib::Int"); my $file_list = Gtk2::TreeView->new_with_model($model); $file_list->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 0)); $file_list->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererPixbuf->new, 'pixbuf' => 1)); $file_list->append_column(my $valcolumn = Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 2)); $file_list->set_headers_visible(0); $file_list->get_selection->set_mode('browse'); $valcolumn->set_visible(0); my $unselected = gtkcreate_pixbuf('unselected'); my $selected = gtkcreate_pixbuf('selected'); my $file_wildcard_entry = new Gtk2::Entry(); gtkpack($advanced_box, $file_restore = gtkpack_(new Gtk2::VBox(0,10), 0, new Gtk2::Label(N("Filename text substring to search for (empty string matches all):")), 0, gtkpack_(new Gtk2::HBox(0,10), 1, $file_wildcard_entry, 0, gtksignal_connect(new Gtk2::Button(N("Search Backups")), clicked => sub { local $_ = $file_wildcard_entry->get_text; s|^\*|\\\*|g; my $wildcard = $_; @possible_sources = glob "$conf{PATH_TO_SAVE}/list*"; $model->clear; my $match = 0; foreach my $list (@possible_sources) { my @listing = cat_($list); my @matches = grep { /$wildcard/ } @listing; if (@matches) { my $list_entry = $model->append_set(undef, [ 0 => $list, 2 => '' ]); foreach (@matches) { chop; $model->append_set($list_entry, [ 0 => $_, 1 => $unselected, 2 => 0 ]); } $match = 1 } } show_warning("i", N("No matches found...")) if $match == 0; }), ), 1, create_scrolled_window($file_list), 0, gtkset_sensitive(gtksignal_connect($start_restore = new Gtk2::Button(N("Restore Selected")), clicked => sub { @files_to_restore = (); my $last_entry = ''; my $catalog_entry; my $restore_file; foreach (sort keys %catalog_entries) { if ($catalog_entries{$_} == 1) { ($catalog_entry, $restore_file) = split("###", $_); $last_entry = $catalog_entry if $last_entry eq ''; if ($catalog_entry ne $last_entry) { restore_catalog_entry($cat_entry, @files_to_restore); @files_to_restore = (); push @files_to_restore, $restore_file; } else { push @files_to_restore, $restore_file; } $last_entry = $catalog_entry; } } restore_catalog_entry($cat_entry, @files_to_restore); destroy_widget(); find_files_to_restore(); }), 0), ), ); $file_list->get_selection->signal_connect(changed => sub { my ($lmodel, $iter) = $_[0]->get_selected; $lmodel && $iter or return; my ($s, $val) = $lmodel->get($iter, 0, 2); if (! any { /$s/ } @possible_sources) { my $parent_iter = Gtk2::TreeModel::iter_parent($lmodel, $iter); my $parent_name = $lmodel->get($parent_iter, 0); $cat_entry = substr($parent_name, -19, 15); my @full_cat_entry = grep { /^$cat_entry/ } @catalog; chop @full_cat_entry; $cat_entry = $full_cat_entry[0]; $val ? $lmodel->set($iter, 1, $unselected, 2, 0) : $lmodel->set($iter, 1, $selected, 2, 1); $val ? $files_selected-- : $files_selected++; $catalog_entries{$cat_entry . "###" . $s} = 1 - $val; $files_selected ? gtkset_sensitive($start_restore, 1) : gtkset_sensitive($start_restore, 0); } }); $central_widget = \$file_restore; } sub catalog_restore { my ($call_method) = @_; my $catalog_box; my $cat_entry; my @restore_files; my $restore_path_entry; #- catalog info in tree view my $model = Gtk2::TreeStore->new("Glib::String"); my $tree_catalog = Gtk2::TreeView->new_with_model($model); $tree_catalog->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 0)); $tree_catalog->set_headers_visible(0); $tree_catalog->get_selection->set_mode('single'); # file details in list widget my $lmodel = Gtk2::ListStore->new("Glib::String"); my $tree_files = Gtk2::TreeView->new_with_model($lmodel); $tree_files->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 0)); $tree_files->set_headers_visible(0); $tree_files->get_selection->set_mode('multiple'); #- read the catalog my @catalog = cat_("$cfg_dir/drakbackup_catalog"); foreach (@catalog) { chop; my @line_data = split(':', $_); my $t = $line_data[0]; my $t_catalog = $model->append_set(undef, [ 0 => $t ]); my $indexer = 0; foreach (@line_data) { if ($indexer != 0) { my $m; $m = "Media: " if $indexer == 1; $m = "Label or Host: " if $indexer == 2; $m = "Device or Path: " if $indexer == 3; $m = "Type: Incremental" if $_ eq "I"; $m = "Type: Differential" if $_ eq "D"; $m = "Type: Full" if $_ eq "F"; $m .= $_ if $_ ne "I" && $_ ne "F" && $_ ne "D"; $model->append_set($t_catalog, [ 0 => $m ]); } $indexer++; } } $tree_catalog->get_selection->signal_connect(changed => sub { my ($model, $iter) = $_[0]->get_selected; $model && $iter or return; $cat_entry = $model->get($iter, 0); my $parent_iter = Gtk2::TreeModel::iter_parent($model, $iter); if ($parent_iter) { $cat_entry = ''; return; } cursor_wait(); @restore_files = (); $lmodel->clear; foreach my $filename (glob("$conf{PATH_TO_SAVE}/list*$cat_entry.txt")) { my @contents = cat_($filename); foreach (@contents) { chop; my $s = $_; $lmodel->append_set(undef, $s); } } cursor_norm(); my @full_cat_entry = grep { /^$cat_entry/ } @catalog; $cat_entry = $full_cat_entry[0]; }); $tree_files->get_selection->signal_connect(changed => sub { my (@what) = $_[0]->get_selected_rows; @restore_files = (); foreach (@what) { my $iter = $lmodel->get_iter($_); my $s = $lmodel->get($iter, 0); push @restore_files, $s; } }); gtkpack($advanced_box, $catalog_box = gtkpack_(new Gtk2::HBox(0,10), 1, gtkpack_(new Gtk2::VBox(0,5), 0, N("Click date/time to see backup files.\nCtrl-Click files to select multiple files."), 1, gtkpack_(new Gtk2::VBox(0, 10), 1, create_scrolled_window($tree_catalog), 1, create_scrolled_window($tree_files), ), 0, gtkpack_(new Gtk2::HBox(1, 10), 1, gtksignal_connect(Gtk2::Button->new(N("Restore Selected\nCatalog Entry")), clicked => sub { if ($cat_entry) { my $media_check = restore_catalog_entry($cat_entry, ()); if (! $media_check) { destroy_widget(); interactive_mode_box(); } } }), 1, gtksignal_connect(Gtk2::Button->new(N("Restore Selected\nFiles")), clicked => sub { my $files = @restore_files; #- grab the array before the widget clears it my @passed_files = @restore_files; if ($cat_entry && $files) { my $media_check = restore_catalog_entry($cat_entry, @passed_files); if (! $media_check) { destroy_widget(); interactive_mode_box(); } } }), 1, gtkpack_(new Gtk2::VBox(0, 5), 0, new Gtk2::Label("Restore To Path"), 0, $restore_path_entry = new Gtk2::Entry(), ), 1, gtksignal_connect(Gtk2::Button->new(N("Change\nRestore Path")), clicked => sub { filedialog_generic("Path To Restore To", \$restore_path_entry, \$restore_path); }), ), 0, new Gtk2::VBox(0,10), ), 0, new Gtk2::VBox(0,10), ), ); $restore_path_entry->set_text($restore_path); gtksignal_connect($restore_path_entry, changed => sub { $restore_path = $restore_path_entry->get_text }); button_box_restore(); fonction_env(\$catalog_box, \&catalog_restore, \&restore_find_media_box, \&catalog_restore) if $call_method eq "need media";; fonction_env(\$catalog_box, \&catalog_restore, \&restore_box, \&catalog_restore) if $call_method eq "button"; $central_widget = \$catalog_box; $up_box->show_all; } sub restore_catalog_entry { restore_status(); my ($cat_entry, @restore_files) = @_; my $username; my $userpass = $conf{PASSWD}; my $restore_result = 1; my @line_data = split(':', $cat_entry); my $backup_time = $line_data[0]; #- use our own variables here so we don't trash a saved config accidentally my $media = $line_data[1]; #- can be a volume name or a host name my $vol_host = $line_data[2]; #- see if we have a username embedded in the host if (index($vol_host, "@") != -1) { my @user_host = split("@", $vol_host); $username = $user_host[0]; $vol_host = $user_host[1]; } else { $username = $conf{LOGIN}; } #- create a restore work directory if we don't have one -d "$cfg_dir/restores" or mkdir_p "$cfg_dir/restores"; #- can be a device name or a path my $dev_path = $line_data[3]; if ($media eq 'HD') { #- shouldn't really happen, should have just browsed #- to the $conf{PATH_TO_SAVE} in the previous step - deal with it anyway my @restore_tar_files = glob("$dev_path/*$backup_time*$tar_ext"); my $matches = @restore_tar_files; if ($matches == 0) { show_warning("f", N("Backup files not found at %s.", $dev_path)); return 0; } else { my $save_path_org = $conf{PATH_TO_SAVE}; $conf{PATH_TO_SAVE} = $dev_path; $restore_result = restore_hd_or_cd($cat_entry, $dev_path, @restore_files); $conf{PATH_TO_SAVE} = $save_path_org; } } if ($media eq 'CD') { #- we know the cdrecord device, and the label #- prompt the user for the right CD $in->ask_okcancel(N("Restore From CD"), N("Insert the CD with volume label %s\n in the CD drive under mount point /mnt/cdrom", $vol_host) ,1) ? ($vol_name = get_cd_volname()) : return 0; if ($vol_name ne $vol_host) { show_warning("f", N("Not the correct CD label. Disk is labelled %s.", $vol_name)); return 0; } else { $restore_result = restore_hd_or_cd($cat_entry, '/mnt/cdrom', @restore_files); } } if ($media eq 'Tape') { #- a little more complicated, we need to check if other backups #- were done on this tape, and try to find the offset to this one $in->ask_okcancel(N("Restore From Tape"), N("Insert the tape with volume label %s\n in the tape drive device %s", $vol_host, $dev_path) ,1) ? ($vol_name = get_tape_label($dev_path)) : return 0; if ($vol_name ne $vol_host) { show_warning("f", N("Not the correct tape label. Tape is labelled %s.", $vol_name)); return 0; } else { $restore_result = restore_tape($cat_entry, $dev_path, @restore_files); } } if ($media eq 'ftp' || $media eq 'webdav' || $media eq 'ssh' || $media eq 'rsync') { #- show the user what we know of the connection from the catalog #- and the config file, let them override if necessary $in->ask_from(N("Restore Via Network"), N("Restore Via Network Protocol: %s", $media), [ { label => N("Host Name"), val => \$vol_host }, { label => N("Host Path or Module"), val => \$dev_path }, { label => N("Username"), val => \$username }, { label => N("Password"), val => \$userpass, hidden => 1 }, ]) or goto return 0; if ($media eq 'ftp' || $media eq 'rsync') { if ($userpass eq '') { show_warning("f", N("Password required")); return 0; } } if ($media eq 'ftp' || $media eq 'rsync' || $media eq 'ssh') { if ($username eq '') { show_warning("f", N("Username required")); return 0; } elsif ($vol_host eq '') { show_warning("f", N("Hostname required")); return 0; } } if ($dev_path eq '') { show_warning("f", N("Path or Module required")); return 0; } $restore_result = restore_ftp($cat_entry, $vol_host, $dev_path, $username, $userpass, @restore_files) if $media eq 'ftp'; $restore_result = restore_rsync_ssh_webdav($cat_entry, $vol_host, $dev_path, $username, $media, @restore_files) if $media eq 'rsync' || $media eq 'ssh' || $media eq 'webdav'; } # cleanup our restore dir - unlink fails here? system("rm -fr $cfg_dir/restores/*"); if (!$restore_result) { show_warning("i", N("Files Restored...")); return 0; } else { show_warning("f", N("Restore Failed...")); return 1; } } sub restore_hd_or_cd { my ($cat_entry, $tarfile_dir, @restore_files) = @_; my $indv_files = @restore_files; my $command; my $wild_card = catalog_to_wildcard($cat_entry); if ($indv_files == 0) { #- full catalog specified foreach (wildcard_to_tarfile($wild_card)) { $command = "tar -C $restore_path -xzf $tarfile_dir/$_"; spawn_progress($command, "Untarring from \n$_ \nto $restore_path."); } } else { #- individual files - pull from appropriate catalog foreach (@restore_files) { my $tarfile = file_to_tarfile($_, $wild_card); $_ = substr($_, 1); $command = "tar -C $restore_path -xzf $tarfile_dir/$tarfile $_"; spawn_progress($command, "Untarring \n$_ from \n$tarfile \nto $restore_path."); } } return 0; } sub restore_tape { my ($cat_entry, $dev_path, @restore_files) = @_; my $indv_files = @restore_files; my $command; my $wild_card = catalog_to_wildcard($cat_entry); $dev_path =~ s|/st|/nst|; if ($indv_files == 0) { #- full catalog specified foreach (wildcard_to_tarfile($wild_card)) { my $offset = find_tape_offset($cat_entry); $command = "mt -f $dev_path rewind"; spawn_progress($command, "Rewinding tape on $dev_path."); $command = "mt -f $dev_path fsf $offset"; spawn_progress($command, "Moving forward $offset file records."); $command = "tar -C $cfg_dir/restores -xf $dev_path"; spawn_progress($command, "Untarring from $dev_path to work directory."); if (-e "$cfg_dir/restores/$conf{PATH_TO_SAVE}/$_") { $command = "tar -C $restore_path -xzf $cfg_dir/restores/$conf{PATH_TO_SAVE}/$_"; spawn_progress($command, "Untarring \n$_ \nto $restore_path."); } else { return 1; } } } else { #- individual files - pull from appropriate catalog foreach (@restore_files) { my $tarfile = file_to_tarfile($_, $wild_card); $_ = substr($_, 1); if (!-e "$cfg_dir/restores/$tarfile") { my $offset = find_tape_offset($cat_entry); $command = "mt -f $dev_path rewind"; spawn_progress($command, "Rewinding tape on $dev_path."); $command = "mt -f $dev_path fsf $offset"; spawn_progress($command, "Moving forward $offset file records."); $command = "tar -C cfg_dir/restores -xf $dev_path"; spawn_progress($command, "Untarring from $dev_path to work directory."); } if (-e "$cfg_dir/restores/$tarfile") { $command = "tar -C $restore_path -xzf $cfg_dir/restores/$tarfile $_"; spawn_progress($command, "Untarring \n$_ from \n$tarfile \nto $restore_path."); } else { return 1; } } } return 0; } sub restore_ftp { use Net::FTP; my $ftp; my ($cat_entry, $hostname, $hostpath, $username, $userpass, @restore_files) = @_; my $indv_files = @restore_files; my $command; $DEBUG and print "file list to retrieve: $cat_entry\n "; if ($DEBUG && $interactive) { $ftp = Net::FTP->new($hostname, Debug => 1) or return 1 } elsif ($interactive) { $ftp = Net::FTP->new($hostname, Debug => 0) or return 1 } else { $ftp = Net::FTP->new($hostname, Debug => 0) or return 1 } $ftp->login($username, $userpass); $ftp->cwd($hostpath); my $wild_card = catalog_to_wildcard($cat_entry); if ($indv_files == 0) { #- full catalog specified foreach (wildcard_to_tarfile($wild_card)) { $ftp->get($_, "$cfg_dir/restores/$_"); if (-e "$cfg_dir/restores/$_") { $command = "tar -C $restore_path -xzf $cfg_dir/restores/$_"; spawn_progress($command, "Untarring \n$_ \nto $restore_path."); } else { show_warning("f", N("%s not retrieved...", $_)); return 1; } } } else { #- individual files - pull from appropriate catalog foreach (@restore_files) { my $tarfile = file_to_tarfile($_, $wild_card); $_ = substr($_, 1); if (!-e "$cfg_dir/restores/$tarfile") { $ftp->get($tarfile, "$cfg_dir/restores/$tarfile"); } if (-e "$cfg_dir/restores/$tarfile") { $command = "tar -C $restore_path -xzf $cfg_dir/restores/$tarfile $_"; spawn_progress($command, "Untarring \n$_ from \n$tarfile \nto $restore_path."); } else { show_warning("f", N("%s not retrieved...", $tarfile)); return 1; } } } $ftp->quit; return 0; } sub restore_rsync_ssh_webdav { my ($cat_entry, $hostname, $hostpath, $username, $mode, @restore_files) = @_; my $indv_files = @restore_files; my $command; my $wild_card = catalog_to_wildcard($cat_entry); if ($indv_files == 0) { #- full catalog specified foreach (wildcard_to_tarfile($wild_card)) { if ($mode eq 'ssh') { $command = "scp $username\@$hostname:$hostpath/$_ $cfg_dir/restores/"; } elsif ($mode eq 'rsync') { $command = "rsync --password-file=$cfg_dir/rsync.user $username\@$hostname" . "::" . "$hostpath/$_ $cfg_dir/restores/"; } else { $command = "wget http://$hostname/$hostpath/$_ -P $cfg_dir/restores/"; } spawn_progress($command, "Retrieving backup file \n$_ \nvia $mode."); if (-e "$cfg_dir/restores/$_") { $command = "tar -C $restore_path -xzf $cfg_dir/restores/$_"; spawn_progress($command, "Untarring \n$_ \nto $restore_path."); } else { return 1; } } } else { #- individual files - pull from appropriate catalog foreach (@restore_files) { my $tarfile = file_to_tarfile($_, $wild_card); $_ = substr($_, 1); if (!-e "$cfg_dir/restores/$tarfile") { if ($mode eq 'ssh') { $command = "scp $username\@$hostname:$hostpath/$tarfile $cfg_dir/restores/"; } elsif ($mode eq 'rsync') { $command = "rsync --password-file=$cfg_dir/rsync.user $username\@$hostname" . "::" . "$hostpath/$tarfile $cfg_dir/restores/"; } else { $command = "wget http://$hostname/$hostpath/$tarfile -P $cfg_dir/restores/"; } spawn_progress($command, "Retrieving backup file \n$tarfile \nvia $mode."); } if (-e "$cfg_dir/restores/$tarfile") { $command = "tar -C $restore_path -xzf $cfg_dir/restores/$tarfile $_"; spawn_progress($command, "Untarring \n$_ from \n$tarfile \nto $restore_path."); } else { return 1; } } } return 0; } sub catalog_to_wildcard { my ($cat_entry) = @_; my @line_data = split(':', $cat_entry); my $wildcard = $line_data[0]; $wildcard; } sub wildcard_to_tarfile { my ($wildcard) = @_; my (@tarfile) = glob("$conf{PATH_TO_SAVE}/*$wildcard.txt"); foreach (@tarfile) { $_ = basename($_); s/txt/$tar_ext/; s/list/backup/; } @tarfile; } sub file_to_tarfile { my ($restore_file, $wildcard) = @_; my $tarfile = `grep -l $restore_file $conf{PATH_TO_SAVE}/*$wildcard.txt`; chop $tarfile; $tarfile = basename($tarfile); $tarfile =~ s/txt/$tar_ext/; $tarfile =~ s/list/backup/; $tarfile; } sub find_tape_offset { my ($cat_entry) = @_; my @line_data = split(':', $cat_entry); my $label = $line_data[2]; my @catalog = cat_("$cfg_dir/drakbackup_catalog"); # always off by 1 for tape label. my $offset = 1; foreach (@catalog) { if (index($_, $label)) { if (!index($_, $cat_entry)) { # tar seems to need 2 of these to get located $offset++; $offset++; } else { return $offset; } } } } sub restore_box() { my $retore_box; if ($good_restore_path) { $path_to_find_restore = $conf{PATH_TO_SAVE} if $conf{USE_HD}; $path_to_find_restore = "/mnt/cdrom" if $conf{USE_CD}; } find_backup_to_restore(); button_box_restore_main(); if (@other_backuped || @sys_backuped || @user_backuped) { gtkpack($advanced_box, $retore_box = gtkpack_(new Gtk2::HBox(0,1), 1, new Gtk2::VBox(0,10), 1, gtkpack_(new Gtk2::VBox(0,10), 1, new Gtk2::VBox(0,10), 1, new Gtk2::VBox(0,10), 1, gtksignal_connect(Gtk2::Button->new(N("Search for files to restore")), clicked => sub { $retore_box->destroy; button_box_file_restore(); find_files_to_restore() }), 1, gtksignal_connect(Gtk2::Button->new(N("Restore all backups")), clicked => sub { $retore_box->destroy; button_box_restore(); @user_list_to_restore2 = sort @user_backuped; $restore_sys = 1; $restore_other = 1; $restore_user = 1; restore_do() }), 1, gtksignal_connect(Gtk2::Button->new(N("Custom Restore")), clicked => sub { $retore_box->destroy; button_box_restore(); restore_step2(); }), 1, gtksignal_connect(Gtk2::Button->new(N("Restore From Catalog")), clicked => sub { $retore_box->destroy; catalog_restore("button"); }), 1, new Gtk2::VBox(0,10), 1, new Gtk2::VBox(0,10), ), 1, new Gtk2::HBox(0,10), ), ); } else { destroy_widget(); restore_find_media_box(), } fonction_env(\$retore_box, \&restore_box, \&interactive_mode_box); $central_widget = \$retore_box; $up_box->show_all; } sub restore_find_media_box() { my $mount_media = 1; $good_restore_path = 0; my $message = N("Unable to find backups to restore...\n"); $message .= N("Verify that %s is the correct path", $path_to_find_restore) if $conf{USE_HD} && $conf{USE_CD}; $message .= N(" and the CD is in the drive") if $conf{USE_CD}; if ($conf{USE_TAPE} || $conf{NET_PROTO}) { $message .= N("Backups on unmountable media - Use Catalog to restore"); $mount_media = 0; } $message .= "."; gtkpack($advanced_box, $box2 = gtkpack_(new Gtk2::VBox(0, 5), 1, gtkpack(new Gtk2::HBox(0, 15), new Gtk2::VBox(0, 5), gtkcreate_img('warning'), translate($message), new Gtk2::VBox(0, 5), ), 1, gtkpack(new Gtk2::HBox(0, 15), new Gtk2::VBox(0, 5), gtkpack(new Gtk2::VBox(0, 10), gtkset_sensitive(gtksignal_connect(Gtk2::Button->new(N("CD in place - continue.")), clicked => sub { $good_restore_path = 1; interactive_mode_box("restore"); }), $mount_media), $new_path_entry = gtkset_sensitive(new Gtk2::Entry(), $mount_media), gtkset_sensitive(gtksignal_connect(Gtk2::Button->new(N("Browse to new restore repository.")), clicked => sub { filedialog_generic("Directory To Restore From", \$new_path_entry, \$path_to_find_restore); }), $mount_media), gtksignal_connect(Gtk2::Button->new(N("Restore From Catalog")), clicked => sub { $box2->destroy; catalog_restore("need media"); }), gtksignal_connect(Gtk2::Button->new(N("Search for files to restore")), clicked => sub { $box2->destroy; button_box_file_restore(); find_files_to_restore() }), ), new Gtk2::VBox(0, 5), ), 1, new Gtk2::VBox(0, 5), ), ); $new_path_entry->set_text($path_to_find_restore); $central_widget = \$box2; button_box_find_media($mount_media); $up_box->show_all; } sub restore_status() { destroy_widget(); $pbar3 = new Gtk2::ProgressBar; $stext = new Gtk2::Label(""); gtkpack($advanced_box, $table = gtkpack(new Gtk2::VBox(0, 5), new Gtk2::HBox(0,5), create_packtable({ col_spacings => 10, row_spacings => 5 }, [""], [""], [""], [""], [N("Restore Progress")], [""], [""], [$pbar3], [""], [""], [$plabel3 = new Gtk2::Label(' ')], [""], ), $stext, ), ); $central_widget = \$table; $up_box->show_all; gtkflush(); } ################################################ BUTTON_BOX ################################################ sub button_box_adv() { $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack_(new Gtk2::HButtonBox, 0, gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => \&interactive_mode_box), 0, gtksignal_connect(Gtk2::Button->new(N("Help")), clicked => \&adv_help), 1, new Gtk2::HBox(0, 1), 0, gtksignal_connect(Gtk2::Button->new(N("Previous")), clicked => sub { destroy_widget(); $previous_widget->(); }), 0, gtksignal_connect(Gtk2::Button->new(N("Save")), clicked => sub { unless (check_pkg_needs()) { if (!save_conf_file()) { destroy_widget(); $previous_widget->(); } } }), ), ); } sub button_box_restore_main() { $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack_(gtkpack_(new Gtk2::HButtonBox, 0, gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => sub { destroy_widget(); interactive_mode_box(); }), 0, gtksignal_connect(Gtk2::Button->new(N("Help")), clicked => \&adv_help), 1, new Gtk2::HBox(0, 1), 0, gtksignal_connect(Gtk2::Button->new(N("Previous")), clicked => sub { destroy_widget(); interactive_mode_box() }), 0, gtksignal_connect(Gtk2::Button->new(N("Ok")), clicked => sub { destroy_widget(); interactive_mode_box() }), ), ), ); } sub button_box_file_restore() { $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack_(gtkpack_(new Gtk2::HButtonBox, 0, gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => sub { destroy_widget(); restore_box(); }), 0, gtksignal_connect(Gtk2::Button->new(N("Help")), clicked => \&adv_help), 1, new Gtk2::HBox(0, 1), ), ), ); } sub button_box_log_main() { $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack_(gtkpack_(new Gtk2::HButtonBox, 1, new Gtk2::HBox(0, 1), 0, gtksignal_connect(Gtk2::Button->new(N("Ok")), clicked => sub { destroy_widget(); interactive_mode_box() }), ), ), ); } sub button_box_backup_end() { $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack_(new Gtk2::HButtonBox, 0, gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => sub { destroy_widget(); interactive_mode_box() }), 0, gtksignal_connect(Gtk2::Button->new(N("Help")), clicked => \&adv_help), 1, new Gtk2::HBox(0, 1), 0, gtksignal_connect(Gtk2::Button->new(N("Previous")), clicked => sub { destroy_widget(); $previous_widget->() }), 0, gtksignal_connect(Gtk2::Button->new(N("Build Backup")), clicked => sub { destroy_widget(); build_backup_status(); build_backup_files(); }), ), ); } sub button_box_wizard_end() { $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack_(new Gtk2::HButtonBox, 0, gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => sub { destroy_widget(); interactive_mode_box(); }), 0, gtksignal_connect(Gtk2::Button->new(N("Help")), clicked => \&adv_help), 1, new Gtk2::HBox(0, 1), 0, gtksignal_connect(Gtk2::Button->new(N("Previous")), clicked => sub { destroy_widget(); $previous_widget->(); }), 0, gtksignal_connect(Gtk2::Button->new(N("Save")), clicked => sub { destroy_widget(); save_conf_file(); interactive_mode_box(); }), ), ); } sub button_box_restore_end() { $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack_(new Gtk2::HButtonBox, 0, gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => sub { destroy_widget(); interactive_mode_box(); }), 0, gtksignal_connect(Gtk2::Button->new(N("Help")), clicked => \&adv_help), 1, new Gtk2::HBox(0, 1), 0, gtksignal_connect(Gtk2::Button->new(N("Previous")), clicked => sub { destroy_widget(); $previous_widget->(); }), 0, gtksignal_connect(Gtk2::Button->new(N("Restore")), clicked => sub { destroy_widget(); restore_backend(); }), ), ); } sub button_box_build_backup_end() { $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack_(new Gtk2::HButtonBox, 1, new Gtk2::HBox(0, 5), 1, new Gtk2::HBox(0, 5), 0, gtksignal_connect(Gtk2::Button->new(N("Ok")), clicked => \&interactive_mode_box), ), ); } sub button_box_restore_pbs_end() { $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack_(new Gtk2::HButtonBox, 1, new Gtk2::HBox(0, 5), 1, new Gtk2::HBox(0, 5), 1, gtksignal_connect(Gtk2::Button->new(N("Help")), clicked => \&adv_help), 0, gtksignal_connect(Gtk2::Button->new(N("Ok")), clicked => sub { destroy_widget(); interactive_mode_box(); }), ), ); } sub button_box_restore() { $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack_(new Gtk2::HButtonBox, 1, gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => sub { destroy_widget(); interactive_mode_box(); }), 1, gtksignal_connect(Gtk2::Button->new(N("Help")), clicked => \&adv_help), 1, new Gtk2::HBox(0, 0), 0, gtksignal_connect(Gtk2::Button->new(N("Previous")), clicked => sub { destroy_widget(); $previous_widget->(); }), 1, gtksignal_connect(Gtk2::Button->new(N("Next")), clicked => sub { destroy_widget(); $next_widget->(); }), ), ); } sub button_box_find_media { my ($mount_media) = @_; #- $central_widget is not known yet? $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack_(new Gtk2::HButtonBox, 1, gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => sub { $central_widget = \$box2; interactive_mode_box(); }), 1, gtksignal_connect(Gtk2::Button->new(N("Help")), clicked => sub { $central_widget = \$box2; adv_help(); }), 1, new Gtk2::HBox(0, 0), 0, gtksignal_connect(Gtk2::Button->new(N("Previous")), clicked => sub { $central_widget = \$box2; interactive_mode_box(); }), 1, gtkset_sensitive(gtksignal_connect(Gtk2::Button->new(N("Next")), clicked => sub { interactive_mode_box("restore"); }), $mount_media), ), ); } sub button_box_wizard() { $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack_(new Gtk2::HButtonBox, 1, gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => sub { destroy_widget(); interactive_mode_box() }), 1, gtksignal_connect(Gtk2::Button->new(N("Help")), clicked => \&adv_help), 1, new Gtk2::HBox(0, 0), 0, gtksignal_connect(Gtk2::Button->new($previous_widget ? N("Previous") : N("Ok")), clicked => sub { destroy_widget(); $previous_widget ? $previous_widget->() : $next_widget->(); }), if_($next_widget, 1, gtksignal_connect(Gtk2::Button->new(N("Next")), clicked => sub { destroy_widget(); $next_widget ? $next_widget->() : $previous_widget->(); })), ), ); } sub button_box_main() { $button_box_tmp->destroy; gtkpack($button_box, $button_box_tmp = gtkpack(Gtk2::HButtonBox->new, gtksignal_connect(Gtk2::Button->new(N("Help")), clicked => \&adv_help), gtksignal_connect(Gtk2::Button->new(N("Close")), clicked => sub { ugtk2->exit(0) }), ), ); } ################################################ MESSAGES ################################################ sub install_rpm { my ($previous_function) = @_; #- catch a crash when calling help #- this GUI control technique is kind of funky if ($previous_function eq '') { $previous_function = \&advanced_where; } destroy_widget(); gtkpack($advanced_box, my $box_what_user = gtkpack_(new Gtk2::VBox(0, 15), 0, N("The following packages need to be installed:\n") . join(' ', @list_of_rpm_to_install), 0, new Gtk2::HSeparator, 0, gtksignal_connect(Gtk2::Button->new(N("Install")), clicked => sub { system("/usr/sbin/urpmi --X @list_of_rpm_to_install"); destroy_widget(); $previous_widget->(); }), ), ); fonction_env(\$box_what_user, \&install_rpm, \&$previous_function); $up_box->show_all; } sub message_norestore_box() { $box2->destroy; gtkadd($advanced_box, $box2 = gtkpack_(new Gtk2::HBox(0, 15), 1, new Gtk2::VBox(0, 5), 1, gtkpack(new Gtk2::HBox(0, 15), new Gtk2::VBox(0, 5), gtkcreate_img('warning'), N("Please select data to restore..."), new Gtk2::VBox(0, 5), ), 1, new Gtk2::VBox(0, 5), ), ); button_box_restore_main(); $central_widget = \$box2; $up_box->show_all; } ################################################ BUILD_BACKUP ################################################ sub progress { my ($progressbar, $plabel, $incr, $label_text) = @_; my ($new_val) = $progressbar->get_fraction; $new_val += $incr; if ($new_val > 1) { $new_val = 1 } $progressbar->set_fraction($new_val); $plabel->set_text($label_text); gtkflush(); } sub build_backup_status() { $pbar = new Gtk2::ProgressBar; $pbar1 = new Gtk2::ProgressBar; $pbar2 = new Gtk2::ProgressBar; $pbar3 = new Gtk2::ProgressBar; $plabel = new Gtk2::Label(" "); $plabel1 = new Gtk2::Label(" "); $plabel2 = new Gtk2::Label(" "); $plabel3 = new Gtk2::Label(" "); $stext = new Gtk2::Label(""); button_box_build_backup_end(); my $table = Gtk2::Table->new(10, 2, 1); $table->set_row_spacings(5); $table->set_col_spacings(10); $table->attach_defaults(new Gtk2::Label(N("Backup system files")), 0, 1, 0, 1); $table->attach_defaults($pbar, 0, 1, 1, 2); $table->attach_defaults($plabel, 1, 2, 1, 2); $table->attach_defaults(new Gtk2::Label(N("Backup user files")), 0, 1, 2, 3); $table->attach_defaults($pbar1, 0, 1, 3, 4); $table->attach_defaults($plabel1, 1, 2, 3, 4); $table->attach_defaults(new Gtk2::Label(N("Backup other files")), 0, 1, 4, 5); $table->attach_defaults($pbar2, 0, 1, 5, 6); $table->attach_defaults($plabel2, 1, 2, 5, 6); $table->attach_defaults(new Gtk2::Label(N("Total Progress")), 0, 1, 6, 7); $table->attach_defaults($pbar3, 0, 1, 7, 8); $table->attach_defaults($plabel3, 1, 2, 7, 8); gtkpack($advanced_box, my $tbox = gtkpack(new Gtk2::VBox(0, 5), $table, $stext, ), ); $central_widget = \$tbox; $up_box->show_all; gtkflush(); } sub build_backup_ftp_status() { $pbar = new Gtk2::ProgressBar; $pbar3 = new Gtk2::ProgressBar; destroy_widget(); button_box_build_backup_end(); $pbar->set_fraction(0); $pbar3->set_fraction(0); gtkpack($advanced_box, $table = gtkpack_(new Gtk2::VBox(0, 15), 1, N("Sending files by FTP"), 1, new Gtk2::VBox(0, 15), 1, create_packtable ({ col_spacings => 10, row_spacings => 5 }, [N("Sending files...")], [""], [ $plabel = new Gtk2::Label(' ') ], [ $pbar ], [""], [N("Total Progress")], [ $plabel3 = new Gtk2::Label(' ') ], [$pbar3], ), 1, new Gtk2::VBox(0, 15), ), ); $central_widget = \$table; $up_box->show_all; gtkflush(); } sub build_backup_box_see_conf { my ($caller) = @_; my $box2; my $text = new Gtk2::TextView; read_conf_file(); system_state(); gtktext_insert($text, [ [ $system_state ] ]); button_box_restore_main(); gtkpack($advanced_box, $box2 = gtkpack_(new Gtk2::HBox(0, 15), 1, gtkpack_(new Gtk2::VBox(0,10), 0, N("Drakbackup Configuration"), 1, create_scrolled_window($text), ), ), ); button_box_backup_end(); $central_widget = \$box2; $current_widget = \&build_backup_box_see_conf; if ($caller eq "interactive") { $previous_widget = \&interactive_mode_box; } else { $previous_widget = \&build_backup_box; } $up_box->show_all; } sub build_backup_box() { destroy_widget(); gtkadd($advanced_box, $box2 = gtkpack_(new Gtk2::HBox(0, 15), 1, new Gtk2::VBox(0, 5), 1, gtkpack_(new Gtk2::VBox(0, 15), 1, new Gtk2::VBox(0, 5), 1, gtksignal_connect(my $button_from_conf_file = Gtk2::Button->new, clicked => sub { destroy_widget(); build_backup_box_see_conf(); }), 0, new Gtk2::VBox(0, 5), 1, gtksignal_connect(my $button_see_conf = Gtk2::Button->new, clicked => sub { destroy_widget(); build_backup_box_see_conf(); }), 1, new Gtk2::VBox(0, 5), ), 1, new Gtk2::VBox(0, 5), ), ); $button_from_conf_file->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-discdurwhat-40"), new Gtk2::Label(N("Backup Now from configuration file")), new Gtk2::HBox(0, 5) )); $button_see_conf->add(gtkpack(new Gtk2::HBox(0,10), gtkcreate_img("ic82-moreoption-40"), new Gtk2::Label(N("View Backup Configuration.")), new Gtk2::HBox(0, 5) )); button_box_restore_main(); fonction_env(\$box2, \&build_backup_box, \&interactive_mode_box); $up_box->show_all; } ################################################ INTERACTIVE ################################################ sub interactive_mode_box { my ($mode) = @_; if ($mode eq "restore") { $central_widget = \$box2; restore_box(); return 0; } destroy_widget(); gtkadd($advanced_box, $box2 = gtkpack_(new Gtk2::HBox(0, 15), 1, new Gtk2::VBox(0, 5), 1, gtkpack_(new Gtk2::VBox(0, 5), 1, new Gtk2::VBox(0, 5), 0, gtksignal_connect(Gtk2::Button->new(N("Wizard Configuration")), clicked => sub { destroy_widget(); read_conf_file(); wizard(); }), 0, gtksignal_connect(Gtk2::Button->new(N("Advanced Configuration")), clicked => sub { button_box_adv(); destroy_widget(); advanced_box(); }), 0, gtksignal_connect(Gtk2::Button->new(N("View Configuration")), clicked => sub { destroy_widget(); build_backup_box_see_conf("interactive"); }), 0, gtksignal_connect(Gtk2::Button->new(N("View Last Log")), clicked => sub { $results = cat_($log_file); button_box_log_main(); show_status(); }), 0, gtksignal_connect(Gtk2::Button->new(N("Backup Now")), clicked => sub { if ($cfg_file_exist) { build_backup_box(); } else { $in->ask_warn(N("Error"), N("No configuration file found \nplease click Wizard or Advanced.")); } }), 0, gtksignal_connect(Gtk2::Button->new(N("Restore")), clicked => sub { destroy_widget(); restore_box(); }), 1, new Gtk2::VBox(0, 5), ), 1, new Gtk2::VBox(0, 5), ), ); $central_widget = \$box2; button_box_main(); $up_box->show_all; } sub interactive_mode() { $interactive = 1; $in = 'interactive'->vnew; $::Wizard_title = N("Drakbackup"); $::Wizard_pix_up = "ic82-back-up-48.png"; $in->isa('interactive::gtk') and $::isWizard = 1; $my_win = ugtk2->new(N("Drakbackup")); $window1 = $my_win->{window}; $my_win->{rwindow}->signal_connect(delete_event => sub { ugtk2->exit(0) }); read_conf_file(); gtkadd($window1, gtkpack(new Gtk2::VBox(0,0), gtkpack($up_box = new Gtk2::VBox(0, 5), gtkpack_(new Gtk2::VBox(0, 3), 1, gtkpack_(new Gtk2::HBox(0, 3), 1, $advanced_box = new Gtk2::HBox(0, 15), ), 0, new Gtk2::HSeparator, 0, $button_box = gtkpack(new Gtk2::VBox(0, 15), $button_box_tmp = gtkpack(new Gtk2::VBox(0, 0),), ), ), ), ), ); setup_tooltips(); interactive_mode_box(); button_box_main(); $central_widget = \$box2; $window1->realize; $window1->show_all; $my_win->main; $my_win->exit(0); } ################################################ HELP & ABOUT ################################################ sub adv_help() { exec("drakhelp --id drakbackup") unless fork(); } sub destroy_widget() { if ($central_widget ne '') { $$central_widget->destroy; $central_widget = ''; } }