1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
|
(* virt-sparsify
* Copyright (C) 2011-2023 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*)
(* This is the traditional virt-sparsify mode: We copy from a
* source disk to a destination disk.
*)
open Unix
open Printf
open Std_utils
open Tools_utils
open Common_gettext.Gettext
open Unix_utils
open Utils
open Cmdline
module G = Guestfs
type tmp_place =
| Directory of string | Block_device of string | Prebuilt_file of string
let run indisk outdisk check_tmpdir compress convert
format ignores option tmp_param zeroes ks =
(* Once we have got past argument parsing and start to create
* temporary files (including the potentially massive overlay file), we
* need to catch SIGINT (^C) and exit cleanly so the temporary file
* goes away. Note that we don't delete temporaries in the signal
* handler.
*
* To safely set a signal handler, we must call [On_exit.register]
* because that may register signal handlers which we override.
*)
On_exit.register ();
let do_sigint _ = exit 1 in
Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint);
(* What should the output format be? If the user specified an
* input format, use that, else detect it from the source image.
*)
let output_format =
match convert with
| Some fmt -> fmt (* user specified output conversion *)
| None ->
match format with
| Some fmt -> fmt (* user specified input format, use that *)
| None ->
(* Don't know, so we must autodetect. *)
match (open_guestfs ())#disk_format indisk with
| "unknown" ->
error (f_"cannot detect input disk format; use the \
--format parameter")
| fmt -> fmt in
(* Compression is not supported by raw output (RHBZ#852194). *)
if output_format = "raw" && compress then
error (f_"--compress cannot be used for raw output. \
Remove this option or use --convert qcow2.");
(* Use TMPDIR or --tmp parameter? *)
let tmp_place =
match tmp_param with
| None -> Directory (Filename.get_temp_dir_name ()) (* $TMPDIR or /tmp *)
| Some dir when is_directory dir -> Directory dir
| Some dev when is_block_device dev -> Block_device dev
| Some file when String.is_prefix file "prebuilt:" ->
let file = String.sub file 9 (String.length file - 9) in
if not (Sys.file_exists file) then
error (f_"--tmp prebuilt:file: %s: file does not exist") file;
let g = open_guestfs () in
if g#disk_format file <> "qcow2" then
error (f_"--tmp prebuilt:file: %s: file format is not qcow2") file;
if not (g#disk_has_backing_file file) then
error (f_"--tmp prebuilt:file: %s: file does not have backing file")
file;
Prebuilt_file file
| Some path ->
error (f_"--tmp parameter must point to a directory, block device \
or prebuilt file") in
(* Check there is enough space in temporary directory. *)
(match tmp_place with
| Block_device _
| Prebuilt_file _ -> ()
| Directory tmpdir ->
(* Get virtual size of the input disk. *)
let virtual_size = (open_guestfs ())#disk_virtual_size indisk in
debug "input disk virtual size is %Ld bytes (%s)"
virtual_size (human_size virtual_size);
let print_warning () =
let free_space = StatVFS.free_space (StatVFS.statvfs tmpdir) in
let extra_needed = virtual_size -^ free_space in
if extra_needed > 0L then (
warning (f_"\
There may not be enough free space on %s.
You may need to set TMPDIR to point to a directory with more free space.
Max needed: %s. Free: %s. May need another %s.
Note this is an overestimate. If the guest disk is full of data
then not as much free space would be required.
You can ignore this warning or change it to a hard failure using the
--check-tmpdir=(ignore|continue|warn|fail) option. See virt-sparsify(1).
%!")
tmpdir (human_size virtual_size)
(human_size free_space) (human_size extra_needed);
true
) else false
in
match check_tmpdir with
| `Ignore -> ()
| `Continue -> ignore (print_warning ())
| `Warn ->
if print_warning () then (
eprintf "Press RETURN to continue or ^C to quit.\n%!";
ignore (read_line ())
);
| `Fail ->
if print_warning () then (
eprintf "Exiting because --check-tmpdir=fail was set.\n%!";
exit 2
)
);
(* Create the temporary overlay file. *)
let overlaydisk =
(match tmp_place with
| Directory tmpdir ->
message (f_"Create overlay file in %s to protect source disk") tmpdir
| Block_device device ->
message (f_"Create overlay device %s to protect source disk") device
| Prebuilt_file file ->
message (f_"Using prebuilt file %s as overlay") file
);
(* Create 'tmp' with the indisk as the backing file. *)
let create tmp =
let g = open_guestfs () in
g#disk_create
~backingfile:indisk ?backingformat:format ~compat:"1.1"
tmp "qcow2" Int64.minus_one
in
match tmp_place with
| Directory temp_dir ->
let tmp = Filename.temp_file ~temp_dir "sparsify" ".qcow2" in
On_exit.unlink tmp;
create tmp;
tmp
| Block_device device ->
create device;
device
| Prebuilt_file file ->
(* Don't create anything, use the prebuilt file as overlay. *)
file in
message (f_"Examine source disk");
(* Connect to libguestfs. *)
let g =
let g = open_guestfs () in
(* Note that the temporary overlay disk is always qcow2 format. *)
g#add_drive ~format:"qcow2" ~readonly:false ~cachemode:"unsafe" overlaydisk;
if not (quiet ()) then (
let machine_readable = machine_readable () <> None in
Progress.set_up_progress_bar ~machine_readable g
);
g#set_network (key_store_requires_network ks);
g#launch ();
g in
(* Decrypt the disks. *)
inspect_decrypt g ks;
(* Modify SIGINT handler (set first above) to cancel the handle. *)
let do_sigint _ =
g#user_cancel ();
exit 1
in
Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint);
(* Write zeroes for non-ignored filesystems that we are able to mount,
* and selected swap partitions.
*)
let filesystems = g#list_filesystems () in
let btrfs_filesystems = List.filter (
fun (fs, fstype) ->
fstype = "btrfs"
) filesystems in
let btrfs_filesystems = List.map fst btrfs_filesystems in
let filesystems = List.map fst filesystems in
let filesystems = List.sort compare filesystems in
let is_ignored fs =
let fs = g#canonical_device_name fs in
List.exists (fun fs' -> fs = g#canonical_device_name fs') ignores
in
let is_read_only_lv = is_read_only_lv g in
let is_readonly_btrfs_snapshot fs mp =
try
let is_btrfs = List.mem fs btrfs_filesystems in
if is_btrfs then (
try
let vol_info = g#btrfs_subvolume_show mp in
String.find (List.assoc "Flags" vol_info) "readonly" <> -1
with G.Error _ -> false
) else false
with Not_found -> false
in
let is_readonly_device mp =
let statvfs = g#statvfs mp in
let flags = statvfs.G.flag in
(* 0x01 is ST_RDONLY in Linux' GNU libc. *)
flags <> -1_L && (flags &^ 0x1_L) <> 0_L
in
List.iter (
fun fs ->
if not (is_ignored fs) && not (is_read_only_lv fs) then (
if List.mem fs zeroes then (
message (f_"Zeroing %s") fs;
g#zero_device fs
) else (
let mounted =
try g#mount fs "/"; true
with _ -> false in
if mounted then (
if is_readonly_btrfs_snapshot fs "/" then (
info (f_"Skipping %s, as it is a read-only btrfs snapshot.") fs;
) else if is_readonly_device "/" then (
info (f_"Skipping %s, as it is a read-only device.") fs;
) else (
message (f_"Fill free space in %s with zero") fs;
g#zero_free_space "/"
)
) else (
let is_linux_x86_swap =
(* Look for the signature for Linux swap on i386.
* Location depends on page size, so it definitely won't
* work on non-x86 architectures (eg. on PPC, page size is
* 64K). Also this avoids hibernated swap space: in those,
* the signature is moved to a different location.
*)
try g#pread_device fs 10 4086L = "SWAPSPACE2"
with _ -> false in
if is_linux_x86_swap then (
message (f_"Clearing Linux swap on %s") fs;
(* Don't use mkswap. Just preserve the header containing
* the label, UUID and swap format version (libguestfs
* mkswap may differ from guest's own).
*)
let header = g#pread_device fs 4096 0L in
g#zero_device fs;
if g#pwrite_device fs header 0L <> 4096 then
error (f_"pwrite: short write restoring swap partition header")
)
)
);
g#umount_all ()
)
) filesystems;
(* Fill unused space in volume groups. *)
let vgs = g#vgs () in
let vgs = Array.to_list vgs in
let vgs = List.sort compare vgs in
List.iter (
fun vg ->
if not (List.mem vg ignores) then (
let lvname = String.random8 () in
let lvdev = "/dev/" ^ vg ^ "/" ^ lvname in
let created =
try g#lvcreate_free lvname vg 100; true
with _ -> false in
if created then (
message (f_"Fill free space in volgroup %s with zero") vg;
g#zero_device lvdev;
g#sync ();
g#lvremove lvdev
)
)
) vgs;
(* Don't need libguestfs now. *)
g#shutdown ();
g#close ();
(* Modify SIGINT handler (set first above) to just exit. *)
let do_sigint _ = exit 1 in
Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint);
(* Now run qemu-img convert which copies the overlay to the
* destination and automatically does sparsification.
*)
message ("Copy to destination and make sparse");
let cmd =
sprintf "qemu-img convert -f qcow2 -O %s%s%s %s %s"
(quote output_format)
(if compress then " -c" else "")
(match option with
| None -> ""
| Some option -> " -o " ^ quote option)
(quote overlaydisk) (quote (qemu_input_filename outdisk)) in
if shell_command cmd <> 0 then
error (f_"external command failed: %s") cmd;
(* Finished. *)
message (f_"Sparsify operation completed with no errors.");
if not (quiet ()) then
info (f_"Before deleting the old disk, carefully check that the \
target disk boots and works correctly.")
|