X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=resize%2Fresize.ml;h=ef279cdafc87d94d6718e727f526f48ff9279d0a;hb=025dba7f803419f510fd8f085ce693838af82878;hp=c53232774c7d80803f5de1a54d1e9ed25d59885d;hpb=9f198956047583e506713a4472117922f8b27b2e;p=libguestfs.git diff --git a/resize/resize.ml b/resize/resize.ml index c532327..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, 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,8 @@ let infile, outfile, 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 let deletes = ref [] in @@ -71,6 +75,8 @@ let infile, outfile, 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"; "--debug", Arg.Set debug, " -\"-"; @@ -118,6 +124,7 @@ read the man page virt-resize(1). ); (* Dereference the rest of the args. *) + let alignment = !alignment in let copy_boot_loader = !copy_boot_loader in let deletes = List.rev !deletes in let dryrun = !dryrun in @@ -135,6 +142,18 @@ read the man page virt-resize(1). let resizes_force = List.rev !resizes_force in let shrink = match !shrink with "" -> None | str -> Some str in + if alignment < 1 then + 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 @@ -145,6 +164,8 @@ read the man page virt-resize(1). printf "ntfsresize-force\n"; 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 (); @@ -162,8 +183,8 @@ read the man page virt-resize(1). | _ -> error "usage is: %s [--options] indisk outdisk" prog in - infile, outfile, 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 @@ -240,12 +261,16 @@ let () = (* Build a data structure describing the source disk's partition layout. *) type partition = { p_name : string; (* Device name, like /dev/sda1. *) - p_part : G.partition; (* Partition data from libguestfs. *) + p_part : G.partition; (* SOURCE partition data from libguestfs. *) p_bootable : bool; (* Is it bootable? *) p_mbr_id : int option; (* MBR ID, if it has one. *) p_type : partition_content; (* Content type and content size. *) - mutable p_operation : partition_operation; (* What we're going to do. *) - mutable p_target_partnum : int; (* Partition number on target. *) + + (* What we're going to do: *) + mutable p_operation : partition_operation; + p_target_partnum : int; (* TARGET partition number. *) + p_target_start : int64; (* TARGET partition start (sector num). *) + p_target_end : int64; (* TARGET partition end (sector num). *) } and partition_content = | ContentUnknown (* undetermined *) @@ -322,7 +347,8 @@ let partitions : partition list = { p_name = name; p_part = part; p_bootable = bootable; p_mbr_id = mbr_id; p_type = typ; - p_operation = OpCopy; p_target_partnum = 0 } + p_operation = OpCopy; p_target_partnum = 0; + p_target_start = 0L; p_target_end = 0L } ) parts in if debug then ( @@ -565,7 +591,7 @@ let calculate_surplus () = let nr_partitions = List.length partitions in let overhead = (Int64.of_int sectsize) *^ ( 2L *^ 64L +^ (* GPT start and end *) - (128L *^ (Int64.of_int (nr_partitions + 1))) (* Maximum alignment *) + (alignment *^ (Int64.of_int (nr_partitions + 1))) (* Maximum alignment *) ) +^ (Int64.of_int (max_bootloader - 64 * 512)) in (* Bootloader *) @@ -794,117 +820,177 @@ 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. *) -let () = - (* 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. - *) + +(* Calculate the location of the partitions on the target disk. This + * also removes from the list any partitions that will be deleted, so + * the final list just contains partitions that need to be created + * on the target. + *) +let partitions = let sectsize = Int64.of_int sectsize in - let start = ref ((List.hd partitions).p_part.G.part_start /^ sectsize) in - (* This counts the partition numbers on the target disk. *) - let nextpart = ref 1 in + (* Return 'i' rounded up to the next multiple of 'a'. *) + let roundup64 i a = let a = a -^ 1L in (i +^ a) &^ (~^ a) in - let rec repartition = function - | [] -> () + let rec loop partnum start = function | p :: ps -> - let target_partnum = - match p.p_operation with - | OpDelete -> None (* do nothing *) - | OpIgnore | OpCopy -> (* new partition, same size *) - (* Size in sectors. *) - let size = (p.p_part.G.part_size +^ sectsize -^ 1L) /^ sectsize in - Some (add_partition size) - | OpResize newsize -> (* new partition, resized *) - (* Size in sectors. *) - let size = (newsize +^ sectsize -^ 1L) /^ sectsize in - Some (add_partition size) in - - (match target_partnum with - | None -> (* OpDelete *) - () - | Some target_partnum -> (* not OpDelete *) - p.p_target_partnum <- target_partnum; - - (* Set bootable and MBR IDs *) - if p.p_bootable then - g#part_set_bootable "/dev/sdb" target_partnum true; - - (match p.p_mbr_id with - | None -> () - | Some mbr_id -> - g#part_set_mbr_id "/dev/sdb" target_partnum mbr_id - ); - ); - - repartition ps - - (* Add a partition, returns the partition number on the target. *) - and add_partition size (* in SECTORS *) = - let target_partnum, end_ = - if !nextpart <= 3 || parttype <> "msdos" then ( - let target_partnum = !nextpart in - let end_ = !start +^ size -^ 1L in - g#part_add "/dev/sdb" "primary" !start end_; - incr nextpart; - target_partnum, end_ - ) else ( - if !nextpart = 4 then ( - g#part_add "/dev/sdb" "extended" !start (-1L); - incr nextpart; - start := !start +^ 128L - ); - let target_partnum = !nextpart in - let end_ = !start +^ size -^ 1L in - g#part_add "/dev/sdb" "logical" !start end_; - incr nextpart; - target_partnum, end_ - ) in - - (* Start of next partition + alignment to 128 sectors. *) - start := ((end_ +^ 1L) +^ 127L) &^ (~^ 127L); + (match p.p_operation with + | OpDelete -> loop partnum start ps (* skip p *) + + | OpIgnore | OpCopy -> (* same size *) + (* Size in sectors. *) + let size = (p.p_part.G.part_size +^ sectsize -^ 1L) /^ sectsize in + (* Start of next partition + alignment. *) + let end_ = start +^ size in + let next = roundup64 end_ alignment in + + { p with p_target_start = start; p_target_end = end_ -^ 1L; + p_target_partnum = partnum } :: loop (partnum+1) next ps + + | OpResize newsize -> (* resized partition *) + (* New size in sectors. *) + let size = (newsize +^ sectsize -^ 1L) /^ sectsize in + (* Start of next partition + alignment. *) + let next = start +^ size in + let next = roundup64 next alignment in + + { p with p_target_start = start; p_target_end = next -^ 1L; + p_target_partnum = partnum } :: loop (partnum+1) next ps + ) - target_partnum + | [] -> + (* Create the surplus partition if there is room for it. *) + if extra_partition && surplus >= min_extra_partition then ( + [ { + (* Since this partition has no source, this data is + * meaningless and not used since the operation is + * OpIgnore. + *) + p_name = ""; + p_part = { G.part_num = 0l; part_start = 0L; part_end = 0L; + part_size = 0L }; + p_bootable = false; p_mbr_id = None; p_type = ContentUnknown; + + (* Target information is meaningful. *) + p_operation = OpIgnore; + p_target_partnum = partnum; + p_target_start = start; p_target_end = ~^ 64L + } ] + ) + else + [] in - repartition partitions; + (* 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 = + 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 - (* Create the surplus partition. *) - if extra_partition && surplus >= min_extra_partition then ( - let size = outsize /^ sectsize -^ 64L -^ !start in - ignore (add_partition size) - ) + loop 1 start partitions + +(* Now partition the target disk. *) +let () = + List.iter ( + fun p -> + g#part_add "/dev/sdb" "primary" p.p_target_start p.p_target_end; + + (* Set bootable and MBR IDs *) + if p.p_bootable then + g#part_set_bootable "/dev/sdb" p.p_target_partnum true; + + (match p.p_mbr_id with + | None -> () + | Some mbr_id -> + g#part_set_mbr_id "/dev/sdb" p.p_target_partnum mbr_id + ); + ) partitions (* Copy over the data. *) let () = - let rec copy_data = function - | [] -> () + List.iter ( + fun p -> + match p.p_operation with + | OpCopy | OpResize _ -> + (* XXX Old code had 'when target_partnum > 0', but it appears + * to have served no purpose since the field could never be 0 + * at this point. + *) - | ({ p_name = source; p_target_partnum = target_partnum; - p_operation = (OpCopy | OpResize _) } as p) :: ps - when target_partnum > 0 -> let oldsize = p.p_part.G.part_size in let newsize = match p.p_operation with OpResize s -> s | _ -> oldsize in let copysize = if newsize < oldsize then newsize else oldsize in - let target = sprintf "/dev/sdb%d" target_partnum in + let source = p.p_name in + let target = sprintf "/dev/sdb%d" p.p_target_partnum in if not quiet then printf "Copying %s ...\n%!" source; g#copy_size source target copysize; - copy_data ps + | _ -> () + ) partitions - | _ :: ps -> - copy_data ps - in +(* 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%!"; - copy_data partitions + 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