mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
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.
This commit is contained in:
@@ -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<virt-resize(1)> 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.
|
||||
|
||||
L<virt-resize(1)> 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<ge> 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.
|
||||
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<http://media.netapp.com/documents/tr-3747.pdf>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -246,6 +246,28 @@ C<dd if=/dev/zero of=outdisk bs=1M count=..>)
|
||||
|
||||
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<N> 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</dev/sda1> is the
|
||||
boot partition and C</dev/sda2> 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</dev/sda2> 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<GRUB> 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<GRUB> on the console, try reinstalling grub.
|
||||
|
||||
guestfish -i -a newdisk
|
||||
><fs> cat /boot/grub/device.map
|
||||
|
||||
Reference in New Issue
Block a user