-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathcopying.ml
349 lines (299 loc) · 11.2 KB
/
copying.ml
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-2025 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.")