2 * Copyright (C) 2010-2011 Red Hat Inc.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 (* Minimum surplus before we create an extra partition. *)
26 let min_extra_partition = 10L *^ 1024L *^ 1024L
28 (* Command line argument parsing. *)
29 let prog = Filename.basename Sys.executable_name
31 let infile, outfile, copy_boot_loader, debug, deletes, dryrun,
32 expand, expand_content, extra_partition, format, ignores,
33 lv_expands, ntfsresize_force, output_format,
34 quiet, resizes, resizes_force, shrink =
35 let display_version () =
36 let g = new G.guestfs () in
37 let version = g#version () in
38 printf "virt-resize %Ld.%Ld.%Ld%s\n"
39 version.G.major version.G.minor version.G.release version.G.extra;
43 let add xs s = xs := s :: !xs in
45 let copy_boot_loader = ref true in
46 let debug = ref false in
47 let deletes = ref [] in
48 let dryrun = ref false in
49 let expand = ref "" in
51 if s = "" then error "%s: empty --expand option" prog
52 else if !expand <> "" then error "--expand option given twice"
55 let expand_content = ref true in
56 let extra_partition = ref true in
57 let format = ref "" in
58 let ignores = ref [] in
59 let lv_expands = ref [] in
60 let machine_readable = ref false in
61 let ntfsresize_force = ref false in
62 let output_format = ref "" in
63 let quiet = ref false in
64 let resizes = ref [] in
65 let resizes_force = ref [] in
66 let shrink = ref "" in
68 if s = "" then error "empty --shrink option"
69 else if !shrink <> "" then error "--shrink option given twice"
73 let argspec = Arg.align [
74 "--no-copy-boot-loader", Arg.Clear copy_boot_loader, " Don't copy boot loader";
75 "-d", Arg.Set debug, " Enable debugging messages";
76 "--debug", Arg.Set debug, " -\"-";
77 "--delete", Arg.String (add deletes), "part Delete partition";
78 "--expand", Arg.String set_expand, "part Expand partition";
79 "--no-expand-content", Arg.Clear expand_content, " Don't expand content";
80 "--no-extra-partition", Arg.Clear extra_partition, " Don't create extra partition";
81 "--format", Arg.Set_string format, "format Format of input disk";
82 "--ignore", Arg.String (add ignores), "part Ignore partition";
83 "--lv-expand", Arg.String (add lv_expands), "lv Expand logical volume";
84 "--LV-expand", Arg.String (add lv_expands), "lv -\"-";
85 "--lvexpand", Arg.String (add lv_expands), "lv -\"-";
86 "--LVexpand", Arg.String (add lv_expands), "lv -\"-";
87 "--machine-readable", Arg.Set machine_readable, " Make output machine readable";
88 "-n", Arg.Set dryrun, " Don't perform changes";
89 "--dryrun", Arg.Set dryrun, " -\"-";
90 "--dry-run", Arg.Set dryrun, " -\"-";
91 "--ntfsresize-force", Arg.Set ntfsresize_force, " Force ntfsresize";
92 "--output-format", Arg.Set_string format, "format Format of output disk";
93 "-q", Arg.Set quiet, " Don't print the summary";
94 "--quiet", Arg.Set quiet, " -\"-";
95 "--resize", Arg.String (add resizes), "part=size Resize partition";
96 "--resize-force", Arg.String (add resizes_force), "part=size Forcefully resize partition";
97 "--shrink", Arg.String set_shrink, "part Shrink partition";
98 "-V", Arg.Unit display_version, " Display version and exit";
99 "--version", Arg.Unit display_version, " -\"-";
101 let disks = ref [] in
102 let anon_fun s = disks := s :: !disks in
105 %s: resize a virtual machine disk
107 A short summary of the options is given below. For detailed help please
108 read the man page virt-resize(1).
111 Arg.parse argspec anon_fun usage_msg;
113 let debug = !debug in
115 eprintf "command line:";
116 List.iter (eprintf " %s") (Array.to_list Sys.argv);
120 (* Dereference the rest of the args. *)
121 let copy_boot_loader = !copy_boot_loader in
122 let deletes = List.rev !deletes in
123 let dryrun = !dryrun in
124 let expand = match !expand with "" -> None | str -> Some str in
125 let expand_content = !expand_content in
126 let extra_partition = !extra_partition in
127 let format = match !format with "" -> None | str -> Some str in
128 let ignores = List.rev !ignores in
129 let lv_expands = List.rev !lv_expands in
130 let machine_readable = !machine_readable in
131 let ntfsresize_force = !ntfsresize_force in
132 let output_format = match !output_format with "" -> None | str -> Some str in
133 let quiet = !quiet in
134 let resizes = List.rev !resizes in
135 let resizes_force = List.rev !resizes_force in
136 let shrink = match !shrink with "" -> None | str -> Some str in
138 (* No arguments and machine-readable mode? Print out some facts
139 * about what this binary supports. We only need to print out new
140 * things added since this option, or things which depend on features
143 if !disks = [] && machine_readable then (
144 printf "virt-resize\n";
145 printf "ntfsresize-force\n";
147 let g = new G.guestfs () in
148 g#add_drive_opts "/dev/null";
150 if feature_available g [| "ntfsprogs"; "ntfs3g" |] then
152 if feature_available g [| "btrfs" |] then
157 (* Verify we got exactly 2 disks. *)
158 let infile, outfile =
159 match List.rev !disks with
160 | [infile; outfile] -> infile, outfile
162 error "usage is: %s [--options] indisk outdisk" prog in
164 infile, outfile, copy_boot_loader, debug, deletes, dryrun,
165 expand, expand_content, extra_partition, format, ignores,
166 lv_expands, ntfsresize_force, output_format,
167 quiet, resizes, resizes_force, shrink
169 (* Default to true, since NTFS and btrfs support are usually available. *)
170 let ntfs_available = ref true
171 let btrfs_available = ref true
173 (* Add in and out disks to the handle and launch. *)
174 let connect_both_disks () =
175 let g = new G.guestfs () in
176 if debug then g#set_trace true;
177 g#add_drive_opts ?format ~readonly:true infile;
178 g#add_drive_opts ?format:output_format ~readonly:false outfile;
179 if not quiet then Progress.set_up_progress_bar g;
182 (* Set the filter to /dev/sda, in case there are any rogue
183 * PVs lying around on the target disk.
185 g#lvm_set_filter [|"/dev/sda"|];
187 (* Update features available in the daemon. *)
188 ntfs_available := feature_available g [|"ntfsprogs"; "ntfs3g"|];
189 btrfs_available := feature_available g [|"btrfs"|];
195 printf "Examining %s ...\n%!" infile;
197 let g = connect_both_disks () in
201 (* Get the size in bytes of each disk.
203 * Originally we computed this by looking at the same of the host file,
204 * but of course this failed for qcow2 images (RHBZ#633096). The right
205 * way to do it is with g#blockdev_getsize64.
207 let sectsize, insize, outsize =
208 let sectsize = g#blockdev_getss "/dev/sdb" in
209 let insize = g#blockdev_getsize64 "/dev/sda" in
210 let outsize = g#blockdev_getsize64 "/dev/sdb" in
212 eprintf "%s size %Ld bytes\n" infile insize;
213 eprintf "%s size %Ld bytes\n" outfile outsize
215 sectsize, insize, outsize
218 (* In reality the number of sectors containing boot loader data will be
219 * less than this (although Windows 7 defaults to putting the first
220 * partition on sector 2048, and has quite a large boot loader).
222 * However make this large enough to be sure that we have copied over
223 * the boot loader. We could also do this by looking for the sector
224 * offset of the first partition.
226 * It doesn't matter if we copy too much.
230 (* Check the disks are at least as big as the bootloader. *)
232 if insize < Int64.of_int max_bootloader then
233 error "%s: file is too small to be a disk image (%Ld bytes)"
235 if outsize < Int64.of_int max_bootloader then
236 error "%s: file is too small to be a disk image (%Ld bytes)"
239 (* Build a data structure describing the source disk's partition layout. *)
241 p_name : string; (* Device name, like /dev/sda1. *)
242 p_part : G.partition; (* Partition data from libguestfs. *)
243 p_bootable : bool; (* Is it bootable? *)
244 p_mbr_id : int option; (* MBR ID, if it has one. *)
245 p_type : partition_content; (* Content type and content size. *)
246 mutable p_operation : partition_operation; (* What we're going to do. *)
247 mutable p_target_partnum : int; (* Partition number on target. *)
249 and partition_content =
250 | ContentUnknown (* undetermined *)
251 | ContentPV of int64 (* physical volume (size of PV) *)
252 | ContentFS of string * int64 (* mountable filesystem (FS type, FS size) *)
253 and partition_operation =
254 | OpCopy (* copy it as-is, no resizing *)
255 | OpIgnore (* ignore it (create on target, but don't
257 | OpDelete (* delete it *)
258 | OpResize of int64 (* resize it to the new size *)
260 let rec debug_partition p =
261 eprintf "%s:\n" p.p_name;
262 eprintf "\tpartition data: %ld %Ld-%Ld (%Ld bytes)\n"
263 p.p_part.G.part_num p.p_part.G.part_start p.p_part.G.part_end
264 p.p_part.G.part_size;
265 eprintf "\tbootable: %b\n" p.p_bootable;
266 eprintf "\tpartition ID: %s\n"
267 (match p.p_mbr_id with None -> "(none)" | Some i -> sprintf "0x%x" i);
268 eprintf "\tcontent: %s\n" (string_of_partition_content p.p_type)
269 and string_of_partition_content = function
270 | ContentUnknown -> "unknown data"
271 | ContentPV sz -> sprintf "LVM PV (%Ld bytes)" sz
272 | ContentFS (fs, sz) -> sprintf "filesystem %s (%Ld bytes)" fs sz
273 and string_of_partition_content_no_size = function
274 | ContentUnknown -> "unknown data"
275 | ContentPV _ -> sprintf "LVM PV"
276 | ContentFS (fs, _) -> sprintf "filesystem %s" fs
278 let get_partition_content =
279 let pvs_full = Array.to_list (g#pvs_full ()) in
282 let fs = g#vfs_type dev in
283 if fs = "unknown" then
285 else if fs = "LVM2_member" then (
286 let rec loop = function
288 error "%s: physical volume not returned by pvs_full"
290 | pv :: _ when canonicalize pv.G.pv_name = dev ->
291 ContentPV pv.G.pv_size
292 | _ :: pvs -> loop pvs
298 let stat = g#statvfs "/" in
299 let size = stat.G.bsize *^ stat.G.blocks in
303 G.Error _ -> ContentUnknown
305 let partitions : partition list =
306 let parts = Array.to_list (g#part_list "/dev/sda") in
308 if List.length parts = 0 then
309 error "the source disk has no partitions";
313 fun ({ G.part_num = part_num } as part) ->
314 let part_num = Int32.to_int part_num in
315 let name = sprintf "/dev/sda%d" part_num in
316 let bootable = g#part_get_bootable "/dev/sda" part_num in
318 try Some (g#part_get_mbr_id "/dev/sda" part_num)
319 with G.Error _ -> None in
320 let typ = get_partition_content name in
322 { p_name = name; p_part = part;
323 p_bootable = bootable; p_mbr_id = mbr_id; p_type = typ;
324 p_operation = OpCopy; p_target_partnum = 0 }
328 eprintf "%d partitions found\n" (List.length partitions);
329 List.iter debug_partition partitions
332 (* Check content isn't larger than partitions. If it is then
333 * something has gone wrong and we shouldn't continue. Old
334 * virt-resize didn't do these checks.
338 | { p_name = name; p_part = { G.part_size = size };
339 p_type = ContentPV pv_size }
340 when size < pv_size ->
341 error "%s: partition size %Ld < physical volume size %Ld"
343 | { p_name = name; p_part = { G.part_size = size };
344 p_type = ContentFS (_, fs_size) }
345 when size < fs_size ->
346 error "%s: partition size %Ld < filesystem size %Ld"
351 (* Check partitions don't overlap. *)
352 let rec loop end_of_prev = function
354 | { p_name = name; p_part = { G.part_start = part_start } } :: _
355 when end_of_prev > part_start ->
356 error "%s: this partition overlaps the previous one" name
357 | { p_part = { G.part_end = part_end } } :: parts -> loop part_end parts
363 (* Build a data structure describing LVs on the source disk.
364 * This is only used if the user gave the --lv-expand option.
368 lv_type : logvol_content;
369 mutable lv_operation : logvol_operation
371 and logvol_content = partition_content (* except ContentPV cannot occur *)
372 and logvol_operation =
373 | LVOpNone (* nothing *)
374 | LVOpExpand (* expand it *)
376 let debug_logvol lv =
377 eprintf "%s:\n" lv.lv_name;
378 eprintf "\tcontent: %s\n" (string_of_partition_content lv.lv_type)
381 let lvs = Array.to_list (g#lvs ()) in
385 let typ = get_partition_content name in
386 assert (match typ with ContentPV _ -> false | _ -> true);
388 { lv_name = name; lv_type = typ; lv_operation = LVOpNone }
392 eprintf "%d logical volumes found\n" (List.length lvs);
393 List.iter debug_logvol lvs
398 (* These functions tell us if we know how to expand the content of
399 * a particular partition or LV, and what method to use.
401 type expand_content_method =
402 | PVResize | Resize2fs | NTFSResize | BtrfsFilesystemResize
404 let string_of_expand_content_method = function
405 | PVResize -> "pvresize"
406 | Resize2fs -> "resize2fs"
407 | NTFSResize -> "ntfsresize"
408 | BtrfsFilesystemResize -> "btrfs-filesystem-resize"
410 let can_expand_content =
411 if expand_content then
413 | ContentUnknown -> false
414 | ContentPV _ -> true
415 | ContentFS (("ext2"|"ext3"|"ext4"), _) -> true
416 | ContentFS (("ntfs"), _) when !ntfs_available -> true
417 | ContentFS (("btrfs"), _) when !btrfs_available -> true
418 | ContentFS (_, _) -> false
422 let expand_content_method =
423 if expand_content then
425 | ContentUnknown -> assert false
426 | ContentPV _ -> PVResize
427 | ContentFS (("ext2"|"ext3"|"ext4"), _) -> Resize2fs
428 | ContentFS (("ntfs"), _) when !ntfs_available -> NTFSResize
429 | ContentFS (("btrfs"), _) when !btrfs_available -> BtrfsFilesystemResize
430 | ContentFS (_, _) -> assert false
432 fun _ -> assert false
434 (* Helper function to locate a partition given what the user might
435 * type on the command line. It also gives errors for partitions
436 * that the user has asked to be ignored or deleted.
439 let hash = Hashtbl.create 13 in
440 List.iter (fun ({ p_name = name } as p) -> Hashtbl.add hash name p)
444 if String.length name < 5 || String.sub name 0 5 <> "/dev/" then
448 let name = canonicalize name in
451 try Hashtbl.find hash name
453 error "%s: partition not found in the source disk image (this error came from '%s' option on the command line). Try running this command: virt-filesystems --partitions --long -a %s"
454 name option infile in
456 if partition.p_operation = OpIgnore then
457 error "%s: partition already ignored, you cannot use it in '%s' option"
460 if partition.p_operation = OpDelete then
461 error "%s: partition already deleted, you cannot use it in '%s' option"
466 (* Handle --ignore option. *)
470 let p = find_partition ~option:"--ignore" dev in
471 p.p_operation <- OpIgnore
474 (* Handle --delete option. *)
478 let p = find_partition ~option:"--delete" dev in
479 p.p_operation <- OpDelete
482 (* Helper function to mark a partition for resizing. It prevents the
483 * user from trying to mark the same partition twice. If the force
484 * flag is given, then we will allow the user to shrink the partition
485 * even if we think that would destroy the content.
487 let mark_partition_for_resize ~option ?(force = false) p newsize =
488 let name = p.p_name in
489 let oldsize = p.p_part.G.part_size in
491 (match p.p_operation with
493 error "%s: this partition has already been marked for resizing"
495 | OpIgnore | OpDelete ->
496 (* This error should have been caught already by find_partition ... *)
497 error "%s: this partition has already been ignored or deleted"
502 (* Only do something if the size will change. *)
503 if oldsize <> newsize then (
504 let bigger = newsize > oldsize in
506 if not bigger && not force then (
507 (* Check if this contains filesystem content, and how big that is
508 * and whether we will destroy any content by shrinking this.
512 error "%s: This partition has unknown content which might be damaged by shrinking it. If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy any data on this partition. (This error came from '%s' option on the command line.)"
514 | ContentPV size when size > newsize ->
515 error "%s: This partition has contains an LVM physical volume which will be damaged by shrinking it below %Ld bytes (user asked to shrink it to %Ld bytes). If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy any data on this partition. (This error came from '%s' option on the command line.)"
516 name size newsize option
518 | ContentFS (fstype, size) when size > newsize ->
519 error "%s: This partition has contains a %s filesystem which will be damaged by shrinking it below %Ld bytes (user asked to shrink it to %Ld bytes). If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy any data on this partition. (This error came from '%s' option on the command line.)"
520 name fstype size newsize option
524 p.p_operation <- OpResize newsize
527 (* Handle --resize and --resize-force options. *)
529 let do_resize ~option ?(force = false) arg =
530 (* Argument is "dev=size". *)
533 let i = String.index arg '=' in
534 let n = String.length arg - (i+1) in
535 if n == 0 then raise Not_found;
536 String.sub arg 0 i, String.sub arg (i+1) n
538 error "%s: missing size field in '%s' option" arg option in
540 let p = find_partition ~option dev in
542 (* Parse the size field. *)
543 let oldsize = p.p_part.G.part_size in
544 let newsize = parse_size oldsize sizefield in
546 if newsize <= 0L then
547 error "%s: new partition size is zero or negative" dev;
549 mark_partition_for_resize ~option ~force p newsize
552 List.iter (do_resize ~option:"--resize") resizes;
553 List.iter (do_resize ~option:"--resize-force" ~force:true) resizes_force
555 (* Helper function calculates the surplus space, given the total
556 * required so far for the current partition layout, compared to
557 * the size of the target disk. If the return value >= 0 then it's
558 * a surplus, if it is < 0 then it's a deficit.
560 let calculate_surplus () =
561 (* We need some overhead for partitioning. Worst case would be for
562 * EFI partitioning + massive per-partition alignment.
564 let nr_partitions = List.length partitions in
565 let overhead = (Int64.of_int sectsize) *^ (
566 2L *^ 64L +^ (* GPT start and end *)
567 (64L *^ (Int64.of_int (nr_partitions + 1))) (* Maximum alignment *)
569 (Int64.of_int (max_bootloader - 64 * 512)) in (* Bootloader *)
571 let required = List.fold_left (
574 match p.p_operation with
575 | OpCopy | OpIgnore -> p.p_part.G.part_size
577 | OpResize newsize -> newsize in
581 outsize -^ (required +^ overhead)
583 (* Handle --expand and --shrink options. *)
585 if expand <> None && shrink <> None then
586 error "you cannot use options --expand and --shrink together";
588 if expand <> None || shrink <> None then (
589 let surplus = calculate_surplus () in
592 eprintf "surplus before --expand or --shrink: %Ld\n" surplus;
598 error "You cannot use --expand when there is no surplus space to expand into. You need to make the target disk larger by at least %s."
599 (human_size (Int64.neg surplus));
601 let option = "--expand" in
602 let p = find_partition ~option dev in
603 let oldsize = p.p_part.G.part_size in
604 mark_partition_for_resize ~option p (oldsize +^ surplus)
610 error "You cannot use --shrink when there is no deficit (see 'deficit' in the virt-resize(1) man page).";
612 let option = "--shrink" in
613 let p = find_partition ~option dev in
614 let oldsize = p.p_part.G.part_size in
615 mark_partition_for_resize ~option p (oldsize +^ surplus)
619 (* Calculate the final surplus.
620 * At this point, this number must be >= 0.
623 let surplus = calculate_surplus () in
625 if surplus < 0L then (
626 let deficit = Int64.neg surplus in
627 error "There is a deficit of %Ld bytes (%s). You need to make the target disk larger by at least this amount or adjust your resizing requests."
628 deficit (human_size deficit)
633 (* Mark the --lv-expand LVs. *)
635 let hash = Hashtbl.create 13 in
636 List.iter (fun ({ lv_name = name } as lv) -> Hashtbl.add hash name lv) lvs;
641 try Hashtbl.find hash name
643 error "%s: logical volume not found in the source disk image (this error came from '--lv-expand' option on the command line). Try running this command: virt-filesystems --logical-volumes --long -a %s"
645 lv.lv_operation <- LVOpExpand
648 (* Print a summary of what we will do. *)
653 printf "**********\n\n";
654 printf "Summary of changes:\n\n";
657 fun ({ p_name = name; p_part = { G.part_size = oldsize }} as p) ->
659 match p.p_operation with
661 sprintf "%s: This partition will be left alone." name
663 sprintf "%s: This partition will be created, but the contents will be ignored (ie. not copied to the target)." name
665 sprintf "%s: This partition will be deleted." name
666 | OpResize newsize ->
667 sprintf "%s: This partition will be resized from %s to %s."
668 name (human_size oldsize) (human_size newsize) ^
669 if can_expand_content p.p_type then (
670 sprintf " The %s on %s will be expanded using the '%s' method."
671 (string_of_partition_content_no_size p.p_type)
673 (string_of_expand_content_method
674 (expand_content_method p.p_type))
677 wrap ~hanging:4 (text ^ "\n\n")
681 fun ({ lv_name = name } as lv) ->
682 match lv.lv_operation with
686 sprintf "%s: This logical volume will be expanded to maximum size."
688 if can_expand_content lv.lv_type then (
689 sprintf " The %s on %s will be expanded using the '%s' method."
690 (string_of_partition_content_no_size lv.lv_type)
692 (string_of_expand_content_method
693 (expand_content_method lv.lv_type))
696 wrap ~hanging:4 (text ^ "\n\n")
699 if surplus > 0L then (
701 sprintf "There is a surplus of %s." (human_size surplus) ^
702 if extra_partition then (
703 if surplus >= min_extra_partition then
704 sprintf " An extra partition will be created for the surplus."
706 sprintf " The surplus space is not large enough for an extra partition to be created and so it will just be ignored."
708 sprintf " The surplus space will be ignored. Run a partitioning program in the guest to partition this extra space if you want." in
713 printf "**********\n";
717 if dryrun then exit 0
719 (* Create a partition table.
721 * We *must* do this before copying the bootloader across, and copying
722 * the bootloader must be careful not to disturb this partition table
723 * (RHBZ#633766). There are two reasons for this:
725 * (1) The 'parted' library is stupid and broken. In many ways. In
726 * this particular instance the stupid and broken bit is that it
727 * overwrites the whole boot sector when initializating a partition
728 * table. (Upstream don't consider this obvious problem to be a bug).
730 * (2) GPT has a backup partition table located at the end of the disk.
731 * It's non-movable, because the primary GPT contains fixed references
732 * to both the size of the disk and the backup partition table at the
733 * end. This would be a problem for any resize that didn't either
734 * carefully move the backup GPT (and rewrite those references) or
735 * recreate the whole partition table from scratch.
738 let parttype = g#part_get_parttype "/dev/sda" in
739 if debug then eprintf "partition table type: %s\n%!" parttype;
741 (* Try hard to initialize the partition table. This might involve
742 * relaunching another handle.
745 printf "Setting up initial partition table on %s ...\n%!" outfile;
747 let last_error = ref "" in
748 let rec initialize_partition_table g attempts =
750 try g#part_init "/dev/sdb" parttype; true
751 with G.Error error -> last_error := error; false in
753 else if attempts > 0 then (
758 let g = connect_both_disks () in
759 initialize_partition_table g (attempts-1)
764 let g, ok = initialize_partition_table g 5 in
766 error "Failed to initialize the partition table on the target disk. You need to wipe or recreate the target disk and then run virt-resize again.\n\nThe underlying error was: %s" !last_error;
770 (* Copy the bootloader across.
771 * Don't disturb the partition table that we just wrote.
772 * https://secure.wikimedia.org/wikipedia/en/wiki/Master_Boot_Record
773 * https://secure.wikimedia.org/wikipedia/en/wiki/GUID_Partition_Table
776 if copy_boot_loader then (
777 let bootsect = g#pread_device "/dev/sda" 446 0L in
778 if String.length bootsect < 446 then
779 error "pread-device: short read";
780 ignore (g#pwrite_device "/dev/sdb" bootsect 0L);
783 if parttype <> "gpt" then 512L
785 (* XXX With 4K sectors does GPT just fit more entries in a
786 * sector, or does it always use 34 sectors?
790 let loader = g#pread_device "/dev/sda" max_bootloader start in
791 if String.length loader < max_bootloader then
792 error "pread-device: short read";
793 ignore (g#pwrite_device "/dev/sdb" loader start)
796 (* Repartition the target disk. *)
798 (* The first partition must start at the same position as the old
799 * first partition. Old virt-resize used to align this to 64
800 * sectors, but I suspect this is the cause of boot failures, so
803 let sectsize = Int64.of_int sectsize in
804 let start = ref ((List.hd partitions).p_part.G.part_start /^ sectsize) in
806 (* This counts the partition numbers on the target disk. *)
807 let nextpart = ref 1 in
809 let rec repartition = function
813 match p.p_operation with
814 | OpDelete -> None (* do nothing *)
815 | OpIgnore | OpCopy -> (* new partition, same size *)
816 (* Size in sectors. *)
817 let size = (p.p_part.G.part_size +^ sectsize -^ 1L) /^ sectsize in
818 Some (add_partition size)
819 | OpResize newsize -> (* new partition, resized *)
820 (* Size in sectors. *)
821 let size = (newsize +^ sectsize -^ 1L) /^ sectsize in
822 Some (add_partition size) in
824 (match target_partnum with
825 | None -> (* OpDelete *)
827 | Some target_partnum -> (* not OpDelete *)
828 p.p_target_partnum <- target_partnum;
830 (* Set bootable and MBR IDs *)
832 g#part_set_bootable "/dev/sdb" target_partnum true;
834 (match p.p_mbr_id with
837 g#part_set_mbr_id "/dev/sdb" target_partnum mbr_id
843 (* Add a partition, returns the partition number on the target. *)
844 and add_partition size (* in SECTORS *) =
845 let target_partnum, end_ =
846 if !nextpart <= 3 || parttype <> "msdos" then (
847 let target_partnum = !nextpart in
848 let end_ = !start +^ size -^ 1L in
849 g#part_add "/dev/sdb" "primary" !start end_;
853 if !nextpart = 4 then (
854 g#part_add "/dev/sdb" "extended" !start (-1L);
856 start := !start +^ 64L
858 let target_partnum = !nextpart in
859 let end_ = !start +^ size -^ 1L in
860 g#part_add "/dev/sdb" "logical" !start end_;
865 (* Start of next partition + alignment to 64 sectors. *)
866 start := ((end_ +^ 1L) +^ 63L) &^ (~^ 63L);
871 repartition partitions;
873 (* Create the surplus partition. *)
874 if extra_partition && surplus >= min_extra_partition then (
875 let size = outsize /^ sectsize -^ 64L -^ !start in
876 ignore (add_partition size)
879 (* Copy over the data. *)
881 let rec copy_data = function
884 | ({ p_name = source; p_target_partnum = target_partnum;
885 p_operation = (OpCopy | OpResize _) } as p) :: ps
886 when target_partnum > 0 ->
887 let oldsize = p.p_part.G.part_size in
889 match p.p_operation with OpResize s -> s | _ -> oldsize in
891 let copysize = if newsize < oldsize then newsize else oldsize in
893 let target = sprintf "/dev/sdb%d" target_partnum in
896 printf "Copying %s ...\n%!" source;
898 g#copy_size source target copysize;
908 (* After copying the data over we must shut down and restart the
909 * appliance in order to expand the content. The reason for this may
910 * not be obvious, but it's because otherwise we'll have duplicate VGs
911 * (the old VG(s) and the new VG(s)) which breaks LVM.
913 * The restart is only required if we're going to expand something.
918 | ({ p_operation = OpResize _ } as p) -> can_expand_content p.p_type
923 | ({ lv_operation = LVOpExpand } as lv) -> can_expand_content lv.lv_type
928 if to_be_expanded then (
933 let g = new G.guestfs () in
934 if debug then g#set_trace true;
935 g#add_drive_opts ?format:output_format ~readonly:false outfile;
936 if not quiet then Progress.set_up_progress_bar g;
939 g (* Return new handle. *)
941 else g (* Return existing handle. *)
944 if to_be_expanded then (
945 (* Helper function to expand partition or LV content. *)
946 let do_expand_content target = function
947 | PVResize -> g#pvresize target
951 | NTFSResize -> g#ntfsresize_opts ~force:ntfsresize_force target
952 | BtrfsFilesystemResize ->
953 (* Complicated ... Btrfs forces us to mount the filesystem
954 * in order to resize it.
956 assert (Array.length (g#mounts ()) = 0);
957 g#mount_options "" target "/";
958 g#btrfs_filesystem_resize "/";
962 (* Expand partition content as required. *)
965 | ({ p_operation = OpResize _ } as p) when can_expand_content p.p_type ->
966 let source = p.p_name in
967 let target = sprintf "/dev/sda%d" p.p_target_partnum in
968 let meth = expand_content_method p.p_type in
971 printf "Expanding %s%s using the '%s' method ...\n%!"
973 (if source <> target then sprintf " (now %s)" target else "")
974 (string_of_expand_content_method meth);
976 do_expand_content target meth
980 (* Expand logical volume content as required. *)
983 | ({ lv_operation = LVOpExpand } as lv) when can_expand_content lv.lv_type ->
984 let name = lv.lv_name in
985 let meth = expand_content_method lv.lv_type in
988 printf "Expanding %s using the '%s' method ...\n%!"
990 (string_of_expand_content_method meth);
992 (* First expand the LV itself to maximum size. *)
993 g#lvresize_free name 100;
995 (* Then expand the content in the LV. *)
996 do_expand_content name meth
1001 (* Finished. Unmount disks and exit. *)
1009 wrap "Resize operation completed with no errors. Before deleting the old disk, carefully check that the resized disk boots and works correctly.\n";