summaryrefslogtreecommitdiffstats
path: root/rescue/sbin/partimage_whole_disk
diff options
context:
space:
mode:
Diffstat (limited to 'rescue/sbin/partimage_whole_disk')
-rwxr-xr-xrescue/sbin/partimage_whole_disk264
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;
+}