diff options
Diffstat (limited to 'rescue/sbin')
| -rwxr-xr-x | rescue/sbin/partimage_whole_disk | 264 | 
1 files changed, 264 insertions, 0 deletions
| diff --git a/rescue/sbin/partimage_whole_disk b/rescue/sbin/partimage_whole_disk new file mode 100755 index 000000000..ed0653a95 --- /dev/null +++ b/rescue/sbin/partimage_whole_disk @@ -0,0 +1,264 @@ +#!/usr/bin/perl + +use lib qw(/usr/lib/libDrakX); +use standalone; +use fsedit; +use fs::format; +use fs::type; +use resize_fat::main; +use diskdrake::resize_ntfs; +use diskdrake::resize_ext2; +use common; +use partition_table::empty; +use Carp::Heavy; + +my %options = ( +    save_home_directory => 1, +    empty_space_at_end_of_disk => 0, # 300 * 1024 * 2, # 300MB +    ask_before_modifying_home => 1, +    bzip2 => 1, +); + +my ($server); +if ($ARGV[0] eq '-s') { +    (undef, $server, @ARGV) = @ARGV; +}  +my $action = shift @ARGV; + +sub usage() { die "partimage_whole_disk [-s <server>] (save_all <dir> | rest_all <dir>)\n" } + +$ENV{PATH} = "/sbin:/usr/sbin:$ENV{PATH}"; +$ENV{HOME} = '/'; +log::openLog("/var/log/partimage_whole_disk.log"); +my @partimage_cmd = ('partimage', if_($server, '-s', $server, '-n', '-L')); + +@ARGV == 1 or usage(); + +if ($server && !is_network_configured()) { +    run_program::run('drvinst', 'NETWORK');  +    run_program::run('dhcp-client'); +} + +run_program::run('drvinst', 'STORAGE_SCSI', 'STORAGE_IDE'); + +my $all_hds = fsedit::get_hds({}); + +if ($action eq 'save_all') { +    save_all($ARGV[0]); +} elsif ($action eq 'rest_all') { +    rest_all($ARGV[0]); +} + +sub save_all { +    my ($dir) = @_; + +    my $base_dir = $dir; +    for (my $i = 0; read_part_list($dir); $i++) { +	#- find a free dir +	$dir = sprintf("$base_dir.%03d", $i); +    } + +    my $hd = $all_hds->{hds}[0] or die "no main hard drive\n"; +    log::l("save_all on $hd->{device}"); +    my $part_list = [ partition_table::get_normal_parts($hd) ]; + +    foreach (@$part_list) { +	$_->{saved} = !member($_->{fs_type}, 'ntfs', 'ntfs-3g', 'vfat', 'swap'); +    } + +    if (!$options{save_home_directory}) { +	#- shrink and don't save the last ext3 partition (which is the /home partition) +	if (my $part = find { isTrueLocalFS($_) } reverse @$part_list) { +	    $part->{size} = min($part->{size}, 1024 * 1024 * 2); # not greater than 1GB +	    $part->{saved} = 0; +        } +    } + +    foreach (grep { $_->{saved} } @$part_list) { +	run_or_die(@partimage_cmd, +		   if_($options{bzip2}, '-z', 2), +		   '-V', 0, '--nombr', '--nodesc', '--nocheck', '-b', '-o', +		   'save', devices::make($_->{device}), "$dir/$_->{device}"); +    } +    save_part_list($dir, $hd->{geom}, $part_list); +} +sub rest_all { +    my ($dir) = @_; + +    my ($forced_geom, $part_list) = read_part_list($dir) or error("read_part_list $dir failed"); + +	(my $hd) = my @used_hds = uniq(map { +	    my $part = $_; +	    find { $part->{device} =~ /^\Q$_->{device}\E./ } fs::get::hds($all_hds)  +	      or error("can't find hard drive for partition $part->{device}"); +	} @$part_list); + +	@used_hds >= 1 or error("no matching hd"); +	@used_hds <= 1 or error("multiple hds: " . join(' ', map { $_->{device} } @used_hds)); + +	fs::type::set_fs_type($_, $_->{fs_type}) foreach @$part_list; +	put_in_hash($_, partition_table::hd2minimal_part($hd)) foreach @$part_list; + +	my ($from_partimage, $other) = partition { $_->{saved} } @$part_list; +	my ($from_resize, $created) = partition { member($_->{fs_type}, 'vfat', 'ntfs', 'ntfs-3g') } @$other; +	 +	my $total = sum(map { $_->{size} } @$part_list); +	if ($total > $hd->{totalsectors}) { +	    error("$dir doesn't fit: $total > $hd->{totalsectors}"); +	} + + +    foreach (@$from_resize) { +	#- resize first +	my $part = fs::get::device2part($_->{device}, [ fs::get::fstab($all_hds) ]); +	if (!$part) { +	    log::l("partition to resize is missing ($_->{device})"); +	    $_->{missing} = 1; +	    next; +	} +	if ($part->{fs_type} ne $_->{fs_type}) { +	    log::l("partition $_->{device} doesn't have the right filesystem ($part->{fs_type} != $_->{fs_type})"); +	    $_->{missing} = 1; +	    next; +	} + +	if (@$from_resize == 1) { +	    my $half_size = int($hd->{totalsectors} / 2) - 2 * $hd->cylinder_size; +	    my $suggested_total = $total - $_->{size} + $half_size; +	    log::l("resizing bigger? (size $_->{size}, half_size $half_size, total $total, suggested_total $suggested_total)"); +	    if ($half_size > $_->{size} && $suggested_total < $hd->{totalsectors}) { +		log::l("prefering to resize $_->{device} to size $half_size instead of $_->{size}"); +		$_->{size} = $half_size; +	    } +	} + +	$_->{start} = $part->{start}; +	if ($_->{size} < $part->{size}) { +	    log::l("resizing $_->{device} to $_->{size} (it is $part->{size})"); +	    my $resize_pkg = $_->{fs_type} eq 'vfat' ? 'resize_fat::main' : 'diskdrake::resize_ntfs'; +	    my $resize = $resize_pkg->new($_->{device}, devices::make($_->{device})); +	    $resize->resize($_->{size}); +	} else { +	    log::l("no need to resize, instead setting $_->{device}'s size to $part->{size} instead of $_->{size}"); +	    $_->{size} = $part->{size}; +	} +    } + +    put_in_hash($hd->{geom}, $forced_geom); +    log::l("totalsectors $hd->{totalsectors} heads $hd->{geom}{heads} sectors $hd->{geom}{sectors}"); +    partition_table::raw::compute_nb_cylinders($hd->{geom}, $hd->{totalsectors}); + +    #- grow the last ext3 partition +    if (my $part = find { isTrueLocalFS($_) } reverse @$part_list) { +	$part->{ratio} = 1; + +	if ($options{ask_before_modifying_home}) { +	    print "\nkeep existing /home? (Y/n) "; +	    if (<STDIN> !~ /n/i) { +		my $l = @$from_partimage > 1 ? $from_partimage : $created; +		#- it was meant to be restored or formatted +		my $p = pop @$l; +		log::l("keeping existing /home: removing $p->{device}"); +	    } +	} +    } + +    #- write the partition table +    partition_table::raw::zero_MBR($hd); +    foreach my $part (grep { $_->{rootDevice} eq $hd->{device} } @$part_list) { +	next if $part->{missing}; + +	my $hole = find { isEmpty($_) && $_->{size} >= $part->{size} } partition_table::get_normal_parts_and_holes($hd) or die "not enough room for $part->{device}"; +	$part->{start} = $hole->{start}; +	     +	log::l("handling $part->{device}"); +	my $extended = $part->{device} =~ /(\d+)$/ && $1 > 4 && $hd->hasExtended; + +	my %wanted_part = %$part; +	if ($part->{ratio}) { +	    $part->{size} = $hole->{size} - ($options{empty_space_at_end_of_disk} || 0); +	} else { +	    $part->{size} += $hd->{geom}{sectors} if $extended; +	    $part->{size} += $hd->cylinder_size if $part->{start} == 1; +	} +	log::l("adding $part->{device} with size $part->{size}"); +	partition_table::add($hd, $part, $extended ? 'Extended' : 'Primary'); +	foreach ('device', if_(!$part->{ratio}, 'size')) { +	    $part->{$_} eq $wanted_part{$_} or log::l("bad $_ for $part->{device}: $part->{$_} != $wanted_part{$_}"); +	} +    } +    partition_table::write($hd); + +    #- restore from partimage +    foreach (@$from_partimage) { +	run_or_die(@partimage_cmd, 'restore', '-b', devices::make($_->{device}), "$dir/$_->{device}"); + +	if ($_->{ratio}) { +	    my $resize = diskdrake::resize_ext2->new($_->{device}, devices::make($_->{device})); +	    $resize->resize($_->{size}); +	} +    } + +    foreach (@$created) { +	fs::format::part_raw($_, undef); +    } + +    run_program::run('guessmounts'); + +    if (my @missing = grep { $_->{missing} } @$part_list) { +	my $missing = join('|', map { quotemeta($_->{device}) } @missing); +	log::l("drop missing devices from fstab and lilo.conf: $missing"); +	$::prefix = '/mnt'; +	substInFile { $_ = '' if m!^/dev/($missing)\s! } "$::prefix/etc/fstab"; + +	my $match; +	substInFile {  +	    /^\S/ and $match = m!^other=/dev/($missing)$!;  +	    $_ = '' if $match; +	} "$::prefix/etc/lilo.conf"; +    } +     +    run_or_die('install_bootloader', '--auto'); + +    print "\n", "Your system is ready, press enter to reboot (Y/n) "; +    if (<STDIN> !~ /n/i) { +	run_program::run('reboot'); +    } +} + +sub lst_fields() { qw(device size fs_type saved) } +sub save_part_list { +    my ($dir, $geom, $part_list) = @_; +    my @l = map { join(' ', @$_{lst_fields()}) } @$part_list; +    log::l("save_part_list $dir: $_") foreach @l; +    my $partimage = join(' ', @partimage_cmd); +    open(my $F, "| $partimage -z0 -Bfoo=bar -o save_file $dir/lst"); +    print $F join("/", $geom->{heads}, $geom->{sectors}), "\n"; +    print $F "$_\n" foreach @l; +} +sub read_part_list { +    my ($dir) = @_; +    my $partimage = join(' ', @partimage_cmd); +    open(my $F, "$partimage -z0 -Bfoo=bar rest_file $dir/lst |"); +    my $geom_string = <$F> or return; +    my %geom; @geom{'heads', 'sectors'} = split('/', chomp_($geom_string)); +    my @l = chomp_(cat__($F)); +    log::l("read_part_list $dir: $_") foreach @l; +    \%geom, [ map { my %l; @l{lst_fields()} = split; \%l } @l ]; +} + +sub run_or_die { +    my (@l) = @_; +    run_program::raw({ timeout => 4 * 60 * 60 }, @l) or die join(' ', @l) . " failed\n"; +} + +sub error { +    my ($msg) = @_; +    log::l($msg); +    die "$msg\n"; +} + +sub is_network_configured() { +    my (undef, @l) = cat_('/proc/net/route'); +    find { /^(\S+)/ && $1 ne 'lo' } @l; +} | 
