>From add5e22563972210f5c8baf9e8cf651fc1a7bbd8 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Sat, 10 Apr 2010 13:35:31 +0100 Subject: [PATCH 7/9] Bugfixes for virt-resize. - copy more than 64 boot loader sectors across, since real boot loaders (eg. for Windows) can be much larger than this - copy bootable flag and ID byte to new partitions - start the first partition on the new disk at the same sector offset as on the old disk - sync the disks before existing --- tools/virt-resize | 88 +++++++++++++++++++++++++++++++++++++++++----------- 1 files changed, 69 insertions(+), 19 deletions(-) diff --git a/tools/virt-resize b/tools/virt-resize index 1c4006a..74f13b1 100755 --- a/tools/virt-resize +++ b/tools/virt-resize @@ -411,12 +411,23 @@ if ($debug) { print "$outfile size $outsize bytes\n"; } +# In reality the number of sectors containing boot loader data will be +# less than this (although Windows 7 defaults to putting the first +# partition on sector 2048, and has quite a large boot loader). +# +# However make this large enough to be sure that we have copied over +# the boot loader. We could also do this by looking for the sector +# offset of the first partition. +# +# It doesn't matter if we copy too much. +my $boot_sectors = 4096; + die __x("virt-resize: {file}: file is too small to be a disk image ({sz} bytes)\n", file => $infile, sz => $insize) - if $insize < 64 * 512; + if $insize < $boot_sectors * 512; die __x("virt-resize: {file}: file is too small to be a disk image ({sz} bytes)\n", file => $outfile, sz => $outsize) - if $outsize < 64 * 512; + if $outsize < $boot_sectors * 512; # Copy the boot loader across. do_copy_boot_loader () if $copy_boot_loader; @@ -426,12 +437,12 @@ sub do_copy_boot_loader print "copying boot loader ...\n" if $debug; open IFILE, $infile or die "$infile: $!"; my $s; - my $r = sysread (IFILE, $s, 64 * 512) or die "$infile: $!"; - die "$infile: short read" if $r < 64 * 512; + my $r = sysread (IFILE, $s, $boot_sectors * 512) or die "$infile: $!"; + die "$infile: short read" if $r < $boot_sectors * 512; open OFILE, "+<$outfile" or die "$outfile: $!"; sysseek OFILE, 0, SEEK_SET or die "$outfile: seek: $!"; - $r = syswrite (OFILE, $s, 64 * 512) or die "$outfile: $!"; - die "$outfile: short write" if $r < 64 * 512; + $r = syswrite (OFILE, $s, $boot_sectors * 512) or die "$outfile: $!"; + die "$outfile: short write" if $r < $boot_sectors * 512; } # Add them to the handle and launch the appliance. @@ -466,6 +477,8 @@ sub check_source_disk my %h = %$_; $h{name} = $name; + $h{bootable} = $g->part_get_bootable ("/dev/sda", $h{part_num}); + eval { $h{mbr_id} = $g->part_get_mbr_id ("/dev/sda", $h{part_num}); }; $partitions{$name} = \%h; } } @@ -676,7 +689,11 @@ sub calculate_surplus # We need some overhead for partitioning. Worst case would be for # EFI partitioning + massive per-partition alignment. - my $overhead = $sectsize * (2 * 64 + (64 * (@partitions + 1)) + 128); + my $overhead = $sectsize * ( + 2 * 64 + # GPT start and end + (64 * (@partitions + 1)) + # Maximum alignment + ($boot_sectors - 64) # Boot loader + ); my $required = 0; foreach (@partitions) { @@ -777,17 +794,36 @@ sub repartition if ($copy_boot_loader) { $parttype = $g->part_get_parttype ("/dev/sdb"); - print "partition table type: $parttype\n" if $debug; } else { - # Didn't copy over the initial boot loader, so we need - # to make a new partition type here. $parttype = "efi"; } + print "partition table type: $parttype\n" if $debug; - # Delete any existing partitions on the destination disk. - $g->part_init ("/dev/sdb", $parttype); + # Delete any existing partitions on the destination disk, + # but leave the bootloader that we copied over intact. + if ($copy_boot_loader) { + # Delete in reverse as an easy way to deal with extended + # partitions. + foreach (sort { $b cmp $a } $g->list_partitions ()) { + if (m{^/dev/.db(\d+)$}) { + $g->part_del ("/dev/sdb", $1); + } + } + } else { + # Didn't copy over the initial boot loader, so we need + # to make a new partition table here. + $g->part_init ("/dev/sdb", $parttype); + } + + # Work out where to start the first partition. + die __"virt-resize: source disk does not have a first partition\n" + unless exists ($partitions{"/dev/sda1"}); + my $start = $partitions{"/dev/sda1"}->{part_start} / $sectsize; - my $start = 64; + # Align to 64. + $start = ($start + 63) & ~63; + + print "starting to partition from $start\n" if $debug; # Create the new partitions. foreach my $part (@partitions) { @@ -803,9 +839,18 @@ sub repartition } # Create it. - my ($target, $end) = add_partition ($start, $size); + my ($target, $end, $part_num) = add_partition ($start, $size); $partitions{$part}->{target} = $target; + if ($partitions{$part}->{bootable}) { + $g->part_set_bootable ("/dev/sdb", $part_num, 1); + } + + if ($partitions{$part}->{mbr_id}) { + $g->part_set_mbr_id ("/dev/sdb", $part_num, + $partitions{$part}->{mbr_id}); + } + # Start of next partition + alignment. $start = $end + 1; $start = ($start + 63) & ~63; @@ -825,26 +870,26 @@ sub add_partition my $start = shift; my $size = shift; - my ($target, $end); + my ($target, $end, $part_num); if ($nextpart <= 3 || $parttype ne "msdos") { $target = "/dev/sdb$nextpart"; $end = $start + $size - 1; $g->part_add ("/dev/sdb", "primary", $start, $end); - $nextpart++; + $part_num = $nextpart++; } else { if ($nextpart == 4) { $g->part_add ("/dev/sdb", "extended", $start, -1); - $nextpart++; + $part_num = $nextpart++; $start += 64; } $target = "/dev/sdb$nextpart"; $end = $start + $size - 1; $g->part_add ("/dev/sdb", "logical", $start, $end); - $nextpart++; + $part_num = $nextpart++; } - return ($target, $end); + return ($target, $end, $part_num); } # Copy over the data. @@ -881,6 +926,11 @@ sub copy_data } } +# Sync disk and exit. +$g->umount_all (); +$g->sync (); +undef $g; + exit 0; sub sizebytes -- 1.6.6.1