ACC SHELL
Path : /usr/lib/YaST2/bin/ |
|
Current File : //usr/lib/YaST2/bin/backup_archive.pl |
#!/usr/bin/perl -w
#
# File:
# backup_archive.pl
#
# Module:
# Backup module
#
# Authors:
# Ladislav Slezak <lslezak@suse.cz>
#
# Description:
# This script creates backup archive as specified with command
# line parameters.
#
# $Id: backup_archive.pl 45208 2008-03-05 12:09:30Z locilka $
#
use Getopt::Long;
use strict;
use File::Temp qw( tempdir );
use POSIX qw( strftime );
# command line options
my $archive_name = '';
my $archive_type = '';
my $help = '0';
my $store_pt = '0';
my @ext2_parts = ();
my $verbose = '0';
my $files_info = '';
my $comment_file = '';
my $complete_backup = '';
my $multi_volume = undef;
my $temp_dir = '/tmp';
# return all harddisks present in system
sub harddisks($)
{
my @disks = ();
my ($verbose) = @_;
if (defined open(SFD, '/sbin/sfdisk -s 2> /dev/null |'))
{
while (my $sfline = <SFD>)
{
chomp($sfline);
if ($sfline =~ /^\/dev\/(.d.):\s+\d+$/)
{
push(@disks, $1);
}
}
close(SFD);
if ($verbose)
{
print "Partition tables info read\n";
}
}
return @disks;
}
# create parent directories for new file
sub create_dirs($)
{
my ($f) = @_;
my $ix = rindex($f, '/');
if ($ix > 0)
{
my $dirs = substr($f, 0, $ix);
if ($verbose) {
print "Running: /bin/mkdir -p $dirs 2> /dev/null\n";
}
system("/bin/mkdir -p $dirs 2> /dev/null"); # create directory with parents
}
}
sub remove_escape($)
{
my ($in) = @_;
my $num_bkslh = 0;
my $len = length($in);
my $idx = 0;
my $result = '';
while($idx < $len)
{
my $char = substr($in, $idx, 1);
if ($char eq "\\")
{
$num_bkslh = $num_bkslh + 1;
}
else
{
if ($num_bkslh > 0)
{
$result .= "\\" x ($num_bkslh >> 1);
if (($char eq "n") and ($num_bkslh % 2 != 0))
{
$result .= "\n";
}
else
{
$result .= $char;
}
}
else
{
$result .= $char;
}
$num_bkslh = 0;
}
$idx += 1;
}
# add trailing backslashes
$result .= "\\" x ($num_bkslh >> 1);
return $result;
}
# parse command line options
GetOptions('archive-name=s' => \$archive_name,
'archive-type=s' => \$archive_type, 'help' => \$help,
'store-ptable' => \$store_pt, 'store-ext2=s' => \@ext2_parts,
'verbose' => \$verbose, 'files-info=s' => \$files_info,
'comment-file=s'=> \$comment_file, 'multi-volume=i' => \$multi_volume,
'complete-backup=s' => \$complete_backup, 'tmp-dir=s' => \$temp_dir
);
if ($help or $files_info eq '' or $archive_name eq '')
{
print "Usage: $0 [options]\n\n";
print "This script creates backup archive as specified in input file and in command line options.\n";
print "Options --archive-name and --files-info are mandatory.\n\n";
print "Options:\n\n";
print " --help Display this help\n\n";
print " --archive-name <file> Target archive file name\n";
print " --archive-type <type> Type of compression used by tar, type can be 'tgz' - compressed by gzip, 'tbz2' - compressed by bzip2, 'tar' - no compression or 'txt' - only list of files is generated instead crating archive. Default is 'tgz'\n";
print " --multi-volume <size> Create multiple volume archive, size is in kiB (1kiB = 1024B)\n";
print " --verbose Print progress information\n";
print " --store-ptable Add partition tables information to archive\n";
print " --store-ext2 <device> Store Ext2 system area from device\n";
print " --files-info <file> Data file from backup_search script\n";
print " --complete-backup <file> Read list of completly backed up packages from a file\n";
print " --temp-dir <directory> Temporary directory to use (default is /tmp)\n";
print " --comment-file <file> Use comment stored in file\n\n";
exit 0;
}
$| = 1;
# archive type option
if ($archive_type ne 'tgz' && $archive_type ne 'tbz2' && $archive_type ne 'tar'
&& $archive_type ne 'txt' && $archive_type ne 'stgz' && $archive_type ne 'stbz2'
&& $archive_type ne 'star')
{
$archive_type = 'tgz';
}
# for security reasons set permissions only to owner
umask(0077);
# only store list of files - filter input list
if ($archive_type eq 'txt')
{
print "Storing list\n";
create_dirs($archive_name);
open(OUT, '>', $archive_name)
or die "Error storing file list\n";
if (defined open(FILES, $files_info))
{
while (my $line = <FILES>)
{
chomp($line);
if (substr($line, 0, 1) eq "/")
{
print OUT $line."\n";
}
}
close(FILES);
}
close(FILES_INFO);
print "File list stored\n";
exit 0;
}
# create parent temporary directory
system("/bin/mkdir -p '$temp_dir'");
if (! -d $temp_dir) {
die "Cannot create directory $temp_dir: ".$!;
}
my $tmp_dir_root = tempdir($temp_dir."/backup_tmp_XXXXXXXX", CLEANUP => 1); # remove directory content at exit
system("/bin/mkdir -p '$tmp_dir_root'");
if (! -d $tmp_dir_root) {
die "Cannot create directory $tmp_dir_root: ".$!;
}
my $tmp_dir = $tmp_dir_root."/tmp";
if (!mkdir($tmp_dir))
{
die "Can not create directory $tmp_dir\n";
}
$tmp_dir .= "/info";
if (!mkdir($tmp_dir))
{
die "Can not create directory $tmp_dir\n";
}
my $tmp_dir_sys = $tmp_dir_root."/tmp/system";
if (!mkdir($tmp_dir_sys))
{
die "Can not create directory $tmp_dir_sys\n";
}
my $files_num = 0;
open(OUT, '>', $tmp_dir."/files")
or die "Can not open file $tmp_dir/files\n";
print OUT "info/files\n";
$files_num++;
print OUT "info/packages_info.gz\n";
$files_num++;
# store host name
use Sys::Hostname;
my $host = hostname();
if ($verbose)
{
print "Storing hostname: ";
}
open(HOST, '>', $tmp_dir.'/hostname');
print HOST $host;
close(HOST);
if (-s $tmp_dir.'/hostname')
{
if ($verbose)
{
print "Success\n";
}
print OUT "info/hostname\n";
$files_num++;
}
else
{
if ($verbose)
{
print "Failed\n";
}
}
# store date
if ($verbose)
{
print "Storing date: ";
}
my $date = strftime('%d.%m.%Y %H:%M', localtime());
open(DATE, '>', $tmp_dir.'/date');
print DATE $date;
close(DATE);
if (-s $tmp_dir.'/date')
{
if ($verbose)
{
print "Success\n";
}
print OUT "info/date\n";
$files_num++;
}
else
{
if ($verbose)
{
print "Failed\n";
}
}
my @disks = ();
my @disks_results = ();
# store partition table info
if ($store_pt)
{
if ($verbose)
{
print "Storing partition table\n";
}
@disks = harddisks($verbose);
foreach my $disk (@disks)
{
my $stored = 0;
if (system("/sbin/sfdisk -d /dev/$disk > $tmp_dir_sys/partition_table_$disk.txt 2> /dev/null") >> 8 == 0)
{
if (system("dd if=/dev/$disk of=$tmp_dir_sys/partition_table_$disk bs=512 count=1 2> /dev/null") >> 8 == 0)
{
print OUT "system/partition_table_$disk.txt\n";
print OUT "system/partition_table_$disk\n";
$files_num = $files_num + 2;
$stored = 1;
if ($verbose)
{
print "Stored partition: $disk\n";
}
}
else
{
unlink("$tmp_dir_sys/partition_table_$disk.txt");
if ($verbose)
{
print "Error storing partition: $disk\n";
}
}
}
else
{
if ($verbose)
{
print "Error storing partition: $disk\n";
}
}
push(@disks_results, $stored);
}
}
# copy comment
if (length($comment_file) > 0)
{
system("cp $comment_file $tmp_dir/comment");
if ($? == 0)
{
print OUT "info/comment\n";
$files_num++;
}
}
# copy completely backed up packages
if (length($complete_backup) > 0)
{
system("cp $complete_backup $tmp_dir/complete_backup");
if ($? == 0)
{
print OUT "info/complete_backup\n";
$files_num++;
}
}
if ($verbose)
{
print "Storing list of installed packages\n";
}
system("rpm -qa > $tmp_dir/installed_packages 2> /dev/null");
if ($verbose)
{
if ($? == 0)
{
print "Packages stored: Success\n";
print OUT "info/installed_packages\n";
$files_num = $files_num + 1;
}
else
{
print "Packages stored: Failed\n";
}
}
# store ext2 system area
foreach my $part (@ext2_parts)
{
if ($verbose)
{
print "Storing ext2 area: $part\n";
}
# transliterate all '/' characters to '_' in device name
my $tr_dev_name = $part;
$tr_dev_name =~ tr/\//_/;
my $output_name = $tmp_dir_sys."/e2image".$tr_dev_name;
system("/sbin/e2image $part $output_name 2> /dev/null");
if (-s $output_name)
{
# compress e2image, tar is used because e2image is sparse file
system("tar -j -C $tmp_dir_sys -S -c -f $tmp_dir_sys/e2image$tr_dev_name.tar.bz2 e2image$tr_dev_name");
if ($? == 0)
{
print OUT "system/e2image$tr_dev_name.tar.bz2\n";
$files_num++;
}
if ($verbose)
{
if ($? == 0)
{
print "Success\n";
}
else
{
print "Failed\n";
}
}
}
else
{
if ($verbose)
{
print "Failed\n";
}
}
}
# filter files_info file, output only file names
open(FILES_INFO, "> $tmp_dir/packages_info")
or die "Can not create file $tmp_dir/packages_info\n";
my $package_name;
my $install_prefix;
my $opened;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
$year += 1900;
$mon++;
my $date_str = sprintf("%04d%02d%02d", $year, $mon, $mday);
if ($verbose)
{
print "Creating archive:\n";
}
if (open(FILES, $files_info))
{
while (my $line = <FILES>)
{
chomp($line);
if ($line =~ /^\/.+/)
{
my $esc_line = remove_escape($line);
# if (-r $esc_line or -l $esc_line) # output only readable files from files-info
# # symlinked files need not to be readable
# {
print FILES_INFO $line."\n";
if (defined $opened)
{
if ($archive_type eq 'tgz' || $archive_type eq 'tbz2' || $archive_type eq 'tar')
{
print PKGLIST $line."\n";
}
else
{
# star doesn't use escape sequences
print PKGLIST $esc_line."\n";
}
}
# }
# else
# {
# print "/File not readable: $line\n";
# }
}
else
{
if ($line =~ /Package: (.*)/ || $line eq "Nopackage:")
{
if (defined $opened)
{
close(PKGLIST);
my $command = "/bin/tar -c -v --no-recursion --files-from $tmp_dir_root/$package_name --ignore-failed-read -S -f $tmp_dir_root/tmp/$package_name-$date_str-0.tar";
if ($archive_type eq 'stgz' || $archive_type eq 'stbz2' || $archive_type eq 'star')
{
# set exustar archive type, enable ACLs
$command = "/usr/bin/star -c -v -D list=$tmp_dir_root/$package_name -sparse -acl H=exustar -f $tmp_dir_root/tmp/$package_name-$date_str-0.star";
print OUT "$package_name-$date_str-0.star";
}
else
{
print OUT "$package_name-$date_str-0.tar";
}
$files_num++;
if ($archive_type eq 'tgz' || $archive_type eq 'stgz')
{
$command .= '.gz -z';
print OUT ".gz\n";
}
else
{
if ($archive_type eq 'tbz2')
{
$command .= '.bz2 -j';
print OUT ".bz2\n";
}
elsif ($archive_type eq 'stbz2')
{
$command .= ".bz2 -bz";
print OUT ".bz2\n";
}
else
{
print OUT "\n";
}
}
$command .= " 2> /dev/null";
system($command);
}
$package_name = ($line eq "Nopackage:") ? "NOPACKAGE" : $1;
$opened = open(PKGLIST, ">$tmp_dir_root/$package_name");
print FILES_INFO $line."\n";
}
else
{ if ($line =~ /Installed: (.*)/)
{
$install_prefix = $1;
print FILES_INFO $line."\n";
}
else
{
print STDERR "Unknown text in input file: $line\n";
}
}
}
}
close(FILES);
}
else
{
print STDERR "Cannot open list of files to backup";
}
#last package
if (defined $opened)
{
close(PKGLIST);
my $command = "/bin/tar -c -v --no-recursion --files-from $tmp_dir_root/$package_name --ignore-failed-read -S -f $tmp_dir_root/tmp/$package_name-$date_str-0.tar";
if ($archive_type eq 'stgz' || $archive_type eq 'stbz2' || $archive_type eq 'star')
{
$command = "/usr/bin/star -c -v -D list=$tmp_dir_root/$package_name -sparse -acl H=exustar -f $tmp_dir_root/tmp/$package_name-$date_str-0.star";
print OUT "$package_name-$date_str-0.star";
}
else
{
print OUT "$package_name-$date_str-0.tar";
}
$files_num++;
if ($archive_type eq 'tgz' || $archive_type eq 'stgz')
{
$command .= '.gz -z';
print OUT ".gz\n";
}
else
{
if ($archive_type eq 'tbz2')
{
$command .= '.bz2 -j';
print OUT ".bz2\n";
}
elsif ($archive_type eq 'stbz2')
{
$command .= ".bz2 -bz";
print OUT ".bz2\n";
}
else
{
print OUT "\n";
}
}
$command .= " 2> /dev/null";
system($command);
}
close(FILES_INFO);
# compress file packages_info (avg. ratio is ~10:1)
my $wait_sec = 60;
if (-e "$tmp_dir/packages_info") {
print "Gzipping $tmp_dir/packages_info\n";
system("/usr/bin/gzip -9 $tmp_dir/packages_info");
while ($wait_sec > 0 && ! -e "$tmp_dir/packages_info.gz") {
--$wait_sec;
sleep(1);
}
} elsif (! -e "$tmp_dir/packages_info.gz") {
print ("No such file : $tmp_dir/packages_info");
}
if (! -e "$tmp_dir/packages_info.gz") {
print 'Cannot create '.$tmp_dir.'/packages_info.gz: '.$!."\n";
}
close(OUT);
if ($verbose)
{
print "Creating target archive file...\n";
}
# create required subdirs
create_dirs($archive_name);
# used tar options:
# -c create archive
# -f <file> archive file name
# --files-from <file> read list from file
# -z pack archive by gzip
# -j pack archive by bzip2
# -v verbose output
# --ignore-failed-read continue after read error
# -C <dir> change to dir befor archiving
# -S store sparse files efficiently (for e2images)
# -M multi volume archive
# -L <size> volume size in kiB
# -V <str> volume prefix label
my $tar_command = "(export LC_ALL=C; cd $tmp_dir_root/tmp; tar -c --files-from $tmp_dir/files --ignore-failed-read -C $tmp_dir_root/tmp -S";
if ($verbose)
{
$tar_command .= ' -v';
}
if (defined $multi_volume && $multi_volume >= 0)
{
my $output_directory;
my $output_filename;
my $volume_num = 1;
use File::Spec::Functions "splitpath";
(my $dummy, $output_directory, $output_filename) = File::Spec->splitpath($archive_name);
use Cwd;
# if directory part is empty set it to current dir
if ($output_directory eq "")
{
$output_directory = cwd();
}
if (substr($output_directory, 0, 1) eq ".")
{
my $d = substr($output_directory, 1);
$output_directory = cwd().$d;
}
# delete ending '/' if present
if (substr($output_directory, -1, 1) eq "/")
{
chop($output_directory);
}
my $num_string = sprintf("%02d", $volume_num);
$tar_command .= " -M -V 'YaST2 backup:' -f $output_directory/${num_string}_$output_filename";
if ($multi_volume > 0)
{
my $num_blocks = 20; # default block size is 20 * 512B
# round size down: subtract block size
if ($multi_volume > ($num_blocks / 2) && $multi_volume % ($num_blocks / 2) != 0)
{
$multi_volume -= $num_blocks / 2;
}
$tar_command .= " -L $multi_volume";
}
# redirect STDERR to STDOUT
$tar_command .= " 2>&1)";
use FileHandle;
use IPC::Open2;
# start subprocess
my $pid = open2(*Reader, *Writer, $tar_command );
my $buffer = "";
my $char;
# output from tar contains strings: name of file added to archive or prompt for next volume
while(read(Reader, $char, 1) != 0)
{
if ($char eq "\n")
{
print "$buffer\n";
$buffer = "";
}
else
{
$buffer .= $char; # add character to buffer
if ($buffer =~ /Prepare volume #(\d+) for `.*' and hit return: /)
{
if ($1 == $volume_num)
{
print Writer "y\n";
}
else
{
print "/Volume created: $output_directory/${num_string}_$output_filename\n";
$volume_num++;
$num_string = sprintf("%02d", $volume_num);
print Writer "n $output_directory/${num_string}_$output_filename\n";
}
$buffer = ""; # clear buffer for next file name or tar prompt
}
}
}
print "/Volume created: $output_directory/${num_string}_$output_filename\n";
}
else
{
# create standard (no multi volume) archive
$tar_command .= " -f $archive_name 2> /dev/null)";
print "Tar command: $tar_command\n";
system($tar_command);
}
if ($verbose)
{
print "/Tar result: $?\n";
}
ACC SHELL 2018