From 2910413850c7d9e8df753afad179e415f0638d6d Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 20 Oct 2011 22:06:33 +0100 Subject: [PATCH] resize: Add --align-first auto|never|always option. The first partition can now be aligned. We fix the bootloader correctly for Windows by adjusting the "Hidden Sectors" field. --- align/virt-alignment-scan.pod | 27 +++++-------- resize/resize.ml | 88 ++++++++++++++++++++++++++++++++++++++----- resize/utils.ml | 23 +++++++++++ resize/virt-resize.pod | 34 +++++++++++++---- 4 files changed, 138 insertions(+), 34 deletions(-) diff --git a/align/virt-alignment-scan.pod b/align/virt-alignment-scan.pod index dcc71b4..3b6828b 100755 --- a/align/virt-alignment-scan.pod +++ b/align/virt-alignment-scan.pod @@ -275,24 +275,15 @@ will start at a multiple of 2048 sectors. =head2 SETTING ALIGNMENT -Currently there is no virt tool for fixing alignment problems in -guests. This is a difficult problem to fix because simply moving -partitions around breaks the bootloader, necessitating either manual -reinstallation of the bootloader using a rescue disk, or complex and -error-prone hacks. - -L does not change the alignment of the first -partition, but it does align the second and subsequent partitions to a -multiple of 64 or 128 sectors (depending on the version of -virt-resize, 128 in virt-resize E 1.13.19). For operating systems -that have a separate boot partition, virt-resize could be used to -align the main OS partition, so that the majority of OS accesses -except at boot will be aligned. - -The easiest way to correct partition alignment problems is to -reinstall your guest operating systems. If you install operating -systems from templates, ensure these have correct partition alignment -too. +L can change the alignment of the partitions of some +guests. Currently it can fully align all the partitions of all +Windows guests, and it will fix the bootloader where necessary. For +Linux guests, it can align the second and subsequent partitions, so +the majority of OS accesses except at boot will be aligned. + +Another way to correct partition alignment problems is to reinstall +your guest operating systems. If you install operating systems from +templates, ensure these have correct partition alignment too. For older versions of Windows, the following NetApp document contains useful information: L diff --git a/resize/resize.ml b/resize/resize.ml index aed0e43..ef279cd 100644 --- a/resize/resize.ml +++ b/resize/resize.ml @@ -28,8 +28,10 @@ let min_extra_partition = 10L *^ 1024L *^ 1024L (* Command line argument parsing. *) let prog = Filename.basename Sys.executable_name -let infile, outfile, alignment, copy_boot_loader, debug, deletes, dryrun, - expand, expand_content, extra_partition, format, ignores, +type align_first_t = [ `Never | `Always | `Auto ] + +let infile, outfile, align_first, alignment, copy_boot_loader, debug, deletes, + dryrun, expand, expand_content, extra_partition, format, ignores, lv_expands, machine_readable, ntfsresize_force, output_format, quiet, resizes, resizes_force, shrink = let display_version () = @@ -42,6 +44,7 @@ let infile, outfile, alignment, copy_boot_loader, debug, deletes, dryrun, let add xs s = xs := s :: !xs in + let align_first = ref "auto" in let alignment = ref 128 in let copy_boot_loader = ref true in let debug = ref false in @@ -72,6 +75,7 @@ let infile, outfile, alignment, copy_boot_loader, debug, deletes, dryrun, in let argspec = Arg.align [ + "--align-first", Arg.Set_string align_first, "never|always|auto Align first partition (default: auto)"; "--alignment", Arg.Set_int alignment, "sectors Set partition alignment (default: 128 sectors)"; "--no-copy-boot-loader", Arg.Clear copy_boot_loader, " Don't copy boot loader"; "-d", Arg.Set debug, " Enable debugging messages"; @@ -142,6 +146,14 @@ read the man page virt-resize(1). error "alignment cannot be < 1"; let alignment = Int64.of_int alignment in + let align_first = + match !align_first with + | "never" -> `Never + | "always" -> `Always + | "auto" -> `Auto + | _ -> + error "unknown --align-first option: use never|always|auto" in + (* No arguments and machine-readable mode? Print out some facts * about what this binary supports. We only need to print out new * things added since this option, or things which depend on features @@ -153,6 +165,7 @@ read the man page virt-resize(1). printf "32bitok\n"; printf "128-sector-alignment\n"; printf "alignment\n"; + printf "align-first\n"; let g = new G.guestfs () in g#add_drive_opts "/dev/null"; g#launch (); @@ -170,8 +183,8 @@ read the man page virt-resize(1). | _ -> error "usage is: %s [--options] indisk outdisk" prog in - infile, outfile, alignment, copy_boot_loader, debug, deletes, dryrun, - expand, expand_content, extra_partition, format, ignores, + infile, outfile, align_first, alignment, copy_boot_loader, debug, deletes, + dryrun, expand, expand_content, extra_partition, format, ignores, lv_expands, machine_readable, ntfsresize_force, output_format, quiet, resizes, resizes_force, shrink @@ -807,6 +820,22 @@ let () = ignore (g#pwrite_device "/dev/sdb" loader start) ) +(* Are we going to align the first partition and fix the bootloader? *) +let align_first_partition_and_fix_bootloader = + (* Bootloaders that we know how to fix. *) + let can_fix_boot_loader = + match partitions with + | { p_type = ContentFS ("ntfs", _); p_bootable = true; + p_operation = OpCopy | OpIgnore | OpResize _ } :: _ -> true + | _ -> false + in + + match align_first, can_fix_boot_loader with + | `Never, _ + | `Auto, false -> false + | `Always, _ + | `Auto, true -> true + (* Repartition the target disk. *) (* Calculate the location of the partitions on the target disk. This @@ -869,12 +898,18 @@ let partitions = [] in - (* The first partition must start at the same position as the old - * first partition. Old virt-resize used to align this to 64 - * sectors, but I suspect this is the cause of boot failures, so - * let's not do this. + (* Choose the alignment of the first partition based on the + * '--align-first' option. Old virt-resize used to always align this + * to 64 sectors, but this causes boot failures unless we are able to + * adjust the bootloader accordingly. *) - let start = (List.hd partitions).p_part.G.part_start /^ sectsize in + let start = + if align_first_partition_and_fix_bootloader then + alignment + else + (* Preserve the existing start, but convert to sectors. *) + (List.hd partitions).p_part.G.part_start /^ sectsize in + loop 1 start partitions (* Now partition the target disk. *) @@ -922,6 +957,41 @@ let () = | _ -> () ) partitions +(* Fix the bootloader if we aligned the first partition. *) +let () = + if align_first_partition_and_fix_bootloader then ( + (* See can_fix_boot_loader above. *) + match partitions with + | { p_type = ContentFS ("ntfs", _); p_bootable = true; + p_target_partnum = partnum; p_target_start = start } :: _ -> + (* If the first partition is NTFS and bootable, set the "Number of + * Hidden Sectors" field in the NTFS Boot Record so that the + * filesystem is still bootable. + *) + + (* Should always be /dev/sdb1? *) + let target = sprintf "/dev/sdb%d" partnum in + + (* Sanity check: it contains the NTFS magic. *) + let magic = g#pread_device target 8 3L in + if magic <> "NTFS " then + eprintf "warning: first partition is NTFS but does not contain NTFS boot loader magic\n%!" + else ( + if not quiet then + printf "Fixing first NTFS partition boot record ...\n%!"; + + if debug then ( + let old_hidden = int_of_le32 (g#pread_device target 4 0x1c_L) in + eprintf "old hidden sectors value: 0x%Lx\n%!" old_hidden + ); + + let new_hidden = le32_of_int start in + ignore (g#pwrite_device target new_hidden 0x1c_L) + ) + + | _ -> () + ) + (* After copying the data over we must shut down and restart the * appliance in order to expand the content. The reason for this may * not be obvious, but it's because otherwise we'll have duplicate VGs diff --git a/resize/utils.ml b/resize/utils.ml index 3851975..dc134be 100644 --- a/resize/utils.ml +++ b/resize/utils.ml @@ -27,6 +27,29 @@ let ( /^ ) = Int64.div let ( &^ ) = Int64.logand let ( ~^ ) = Int64.lognot +let int_of_le32 str = + assert (String.length str = 4); + let c0 = Char.code (String.unsafe_get str 0) in + let c1 = Char.code (String.unsafe_get str 1) in + let c2 = Char.code (String.unsafe_get str 2) in + let c3 = Char.code (String.unsafe_get str 3) in + Int64.of_int c0 +^ + (Int64.shift_left (Int64.of_int c1) 8) +^ + (Int64.shift_left (Int64.of_int c2) 16) +^ + (Int64.shift_left (Int64.of_int c3) 24) + +let le32_of_int i = + let c0 = i &^ 0xffL in + let c1 = Int64.shift_right (i &^ 0xff00L) 8 in + let c2 = Int64.shift_right (i &^ 0xff0000L) 16 in + let c3 = Int64.shift_right (i &^ 0xff000000L) 24 in + let s = String.create 4 in + String.unsafe_set s 0 (Char.unsafe_chr (Int64.to_int c0)); + String.unsafe_set s 1 (Char.unsafe_chr (Int64.to_int c1)); + String.unsafe_set s 2 (Char.unsafe_chr (Int64.to_int c2)); + String.unsafe_set s 3 (Char.unsafe_chr (Int64.to_int c3)); + s + let output_spaces chan n = for i = 0 to n-1 do output_char chan ' ' done let wrap ?(chan = stdout) ?(hanging = 0) str = diff --git a/resize/virt-resize.pod b/resize/virt-resize.pod index 8ae4894..4ce3a4e 100644 --- a/resize/virt-resize.pod +++ b/resize/virt-resize.pod @@ -246,6 +246,28 @@ C
) Display help. +=item B<--align-first auto> + +=item B<--align-first never> + +=item B<--align-first always> + +Align the first partition for improved performance (see also the +I<--alignment> option). + +The default is I<--align-first auto> which only aligns the first +partition if it is safe to do so. That is, only when we know how to +fix the bootloader automatically, and at the moment that can only be +done for Windows guests. + +I<--align-first never> means we never move the first partition. +This is the safest option. Try this if the guest does not boot +after resizing. + +I<--align-first always> means we always align the first partition (if +it needs to be aligned). For some guests this will break the +bootloader, making the guest unbootable. + =item B<--alignment N> Set the alignment of partitions to C sectors. The default in @@ -590,10 +612,10 @@ not required by any modern operating system. In Windows Vista and later versions, Microsoft switched to using a separate boot partition. In these VMs, typically C is the -boot partition and C is the main (C:) drive. We have not -had any luck resizing the boot partition. Doing so seems to break the -guest completely. However expanding the second partition (ie. C: -drive) should work. +boot partition and C is the main (C:) drive. Resizing the +first (boot) partition causes the bootloader to fail with +C<0xC0000225> error. Resizing the second partition (ie. C: drive) +should work. Windows may initiate a lengthy "chkdsk" on first boot after a resize, if NTFS partitions have been expanded. This is just a safety check @@ -602,9 +624,7 @@ and (unless it find errors) is nothing to worry about. =head2 GUEST BOOT STUCK AT "GRUB" If a Linux guest does not boot after resizing, and the boot is stuck -after printing C on the console, try reinstalling grub. This -sometimes happens on older (RHEL 5-era) guests, for reasons we don't -fully understand, although we think is to do with partition alignment. +after printing C on the console, try reinstalling grub. guestfish -i -a newdisk > cat /boot/grub/device.map -- 1.8.3.1