Skip to content

Commit e93391f

Browse files
committed
qcow_tool-wrapper: Call qemu-img instead of qcow-stream-tool
This proves much more reliable than code based on ocaml-qcow. Since qemu-img has a different format (with the needed information spread across two files resulting from calls to 'qemu-img info' and 'qemu-img map'), change the parsing code in vhd_qcow_parsing and qcow2-to-stdout. Signed-off-by: Andrii Sultanov <andriy.sultanov@vates.tech>
1 parent 01153f4 commit e93391f

4 files changed

Lines changed: 154 additions & 54 deletions

File tree

ocaml/xapi/qcow_tool_wrapper.ml

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,43 @@ let receive (progress_cb : int -> unit) (unix_fd : Unix.file_descr)
2222
Vhd_qcow_parsing.run_tool qcow_tool progress_cb args ~input_fd:unix_fd
2323

2424
let read_header qcow_path =
25-
let args = ["read_headers"; qcow_path] in
26-
let qcow_tool = !Xapi_globs.qcow_stream_tool in
27-
let pipe_reader, pipe_writer = Unix.pipe ~cloexec:true () in
28-
2925
let progress_cb _ = () in
30-
Xapi_stdext_pervasives.Pervasiveext.finally
31-
(fun () ->
32-
Vhd_qcow_parsing.run_tool qcow_tool progress_cb args
33-
~output_fd:pipe_writer
34-
)
35-
(fun () -> Unix.close pipe_writer) ;
36-
pipe_reader
26+
let run_in_thread tool args pipe_writer replace_fds =
27+
Thread.create
28+
(fun () ->
29+
Xapi_stdext_pervasives.Pervasiveext.finally
30+
(fun () ->
31+
Vhd_qcow_parsing.run_tool tool progress_cb args
32+
~output_fd:pipe_writer ~replace_fds
33+
)
34+
(fun () -> Unix.close pipe_writer)
35+
)
36+
()
37+
in
38+
39+
let map_pipe_reader, map_pipe_writer = Unix.pipe ~cloexec:true () in
40+
let (_ : Thread.t) =
41+
run_in_thread "/usr/bin/qemu-img"
42+
["map"; qcow_path; "--output=json"]
43+
map_pipe_writer []
44+
in
45+
46+
let info_pipe_reader, info_pipe_writer = Unix.pipe ~cloexec:true () in
47+
let (_ : Thread.t) =
48+
run_in_thread "/usr/bin/qemu-img"
49+
["info"; qcow_path; "--output=json"]
50+
info_pipe_writer []
51+
in
52+
53+
(map_pipe_reader, info_pipe_reader)
3754

3855
let parse_header qcow_path =
39-
let pipe_reader = read_header qcow_path in
40-
Vhd_qcow_parsing.parse_header pipe_reader
56+
let pipe, _ = read_header qcow_path in
57+
Vhd_qcow_parsing.parse_header pipe
4158

4259
let parse_header_interval qcow_path =
43-
let pipe_reader = read_header qcow_path in
44-
Vhd_qcow_parsing.parse_header_interval pipe_reader
60+
let pipes = read_header qcow_path in
61+
Vhd_qcow_parsing.parse_header_qemu_img pipes
4562

4663
let send ?relative_to (progress_cb : int -> unit) (unix_fd : Unix.file_descr)
4764
(path : string) (_size : Int64.t) =
@@ -52,7 +69,7 @@ let send ?relative_to (progress_cb : int -> unit) (unix_fd : Unix.file_descr)
5269

5370
(* If VDI is backed by QCOW, parse the header to determine nonzero clusters
5471
to avoid reading all of the raw disk *)
55-
let input_fd = Result.map read_header qcow_path |> Result.to_option in
72+
let input_fds = Result.map read_header qcow_path |> Result.to_option in
5673

5774
(* TODO: If VHD headers are to be consulted as well, qcow2-to-stdout
5875
needs to properly account for cluster_bits. Currently QCOW2 export
@@ -67,28 +84,60 @@ let send ?relative_to (progress_cb : int -> unit) (unix_fd : Unix.file_descr)
6784
| None ->
6885
None
6986
in
70-
let diff_fd = Option.map read_header relative_to_qcow_path in
87+
let diff_fds = Option.map read_header relative_to_qcow_path in
88+
89+
let map_fd_string = Uuidx.(to_string (make ())) in
90+
let info_fd_string = Uuidx.(to_string (make ())) in
91+
let diff_map_fd_string = Uuidx.(to_string (make ())) in
92+
let diff_info_fd_string = Uuidx.(to_string (make ())) in
7193

72-
let unique_string = Uuidx.(to_string (make ())) in
7394
let args =
7495
[path]
7596
@ (match relative_to with None -> [] | Some vdi -> ["--diff"; vdi])
7697
@ ( match relative_to_qcow_path with
7798
| None ->
7899
[]
79100
| Some _ ->
80-
["--json-header-diff"; unique_string]
101+
[
102+
"--json-header-diff-map"
103+
; diff_map_fd_string
104+
; "--json-header-diff-info"
105+
; diff_info_fd_string
106+
]
81107
)
82-
@ match qcow_path with Error _ -> [] | Ok _ -> ["--json-header"]
108+
@
109+
match qcow_path with
110+
| Error _ ->
111+
[]
112+
| Ok _ ->
113+
[
114+
"--json-header-map"
115+
; map_fd_string
116+
; "--json-header-info"
117+
; info_fd_string
118+
]
83119
in
84120
let qcow_tool = !Xapi_globs.qcow_to_stdout in
85-
let replace_fds = Option.map (fun fd -> [(unique_string, fd)]) diff_fd in
121+
let replace_fds =
122+
Option.map
123+
(fun (map_fd, info_fd) ->
124+
let rfds = [(map_fd_string, map_fd); (info_fd_string, info_fd)] in
125+
match diff_fds with
126+
| Some (diff_map_fd, diff_info_fd) ->
127+
(diff_map_fd_string, diff_map_fd)
128+
:: (diff_info_fd_string, diff_info_fd)
129+
:: rfds
130+
| None ->
131+
rfds
132+
)
133+
input_fds
134+
in
86135
Xapi_stdext_pervasives.Pervasiveext.finally
87136
(fun () ->
88-
Vhd_qcow_parsing.run_tool qcow_tool progress_cb args ?input_fd
89-
~output_fd:unix_fd ?replace_fds
137+
Vhd_qcow_parsing.run_tool qcow_tool progress_cb args ~output_fd:unix_fd
138+
?replace_fds
90139
)
91140
(fun () ->
92-
Option.iter Unix.close input_fd ;
93-
Option.iter Unix.close diff_fd
141+
Option.iter (fun (x, y) -> Unix.close x ; Unix.close y) input_fds ;
142+
Option.iter (fun (x, y) -> Unix.close x ; Unix.close y) diff_fds
94143
)

ocaml/xapi/vhd_qcow_parsing.ml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,14 @@ let run_tool tool ?(replace_fds = []) ?input_fd ?output_fd
4444
error "%s output: %s" tool out ;
4545
raise (Api_errors.Server_error (Api_errors.vdi_io_error, [out]))
4646

47-
let parse_header_aux pipe_reader =
47+
let read_json pipe_reader =
4848
let ic = Unix.in_channel_of_descr pipe_reader in
4949
let buf = Buffer.create 4096 in
5050
let json = Yojson.Basic.from_channel ~buf ~fname:"header.json" ic in
51-
In_channel.close ic ;
51+
In_channel.close ic ; json
52+
53+
let parse_header_aux pipe_reader =
54+
let json = read_json pipe_reader in
5255
let cluster_size =
5356
1 lsl Yojson.Basic.Util.(member "cluster_bits" json |> to_int)
5457
in
@@ -61,6 +64,31 @@ let parse_header pipe_reader =
6164
in
6265
(cluster_size, cluster_list)
6366

67+
let parse_header_qemu_img (map_pipe_reader, info_pipe_reader) =
68+
let info_json = read_json info_pipe_reader in
69+
let cluster_size =
70+
Yojson.Basic.Util.(member "cluster-size" info_json |> to_int)
71+
in
72+
let map_json = read_json map_pipe_reader in
73+
let cluster_list =
74+
Yojson.Basic.Util.(
75+
map_json
76+
|> to_list
77+
|> List.filter_map (fun i ->
78+
let present = member "data" i |> to_bool in
79+
if present then
80+
let start_cluster = (member "start" i |> to_int) / cluster_size in
81+
let end_cluster =
82+
start_cluster + ((member "length" i |> to_int) / cluster_size) - 1
83+
in
84+
Some (start_cluster, end_cluster)
85+
else
86+
None
87+
)
88+
)
89+
in
90+
(cluster_size, cluster_list)
91+
6492
let parse_header_interval pipe_reader =
6593
let cluster_size, json = parse_header_aux pipe_reader in
6694
let cluster_list =

ocaml/xapi/vhd_qcow_parsing.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ val run_tool :
2424
val parse_header : Unix.file_descr -> int * int list
2525

2626
val parse_header_interval : Unix.file_descr -> int * (int * int) list
27+
28+
val parse_header_qemu_img :
29+
Unix.file_descr * Unix.file_descr -> int * (int * int) list

python3/libexec/qcow2-to-stdout.py

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -489,18 +489,36 @@ def main():
489489
action="store_true",
490490
)
491491
parser.add_argument(
492-
"--json-header",
493-
dest="json_header",
494-
help="stdin contains a JSON of pre-parsed QCOW2 information"
495-
"(virtual_size, data_clusters, cluster_bits)",
496-
action="store_true",
492+
"--json-header-map",
493+
dest="json_header_map",
494+
help="File descriptor that contains a JSON of pre-parsed QCOW2"
495+
"data clusters information for input_file",
496+
type=int,
497+
default=None,
497498
)
498499
parser.add_argument(
499-
"--json-header-diff",
500-
dest="json_header_diff",
501-
metavar="json_header_diff",
502-
help="File descriptor that contains a JSON of pre-parsed QCOW2 "
503-
"information for the diff_file_name",
500+
"--json-header-info",
501+
dest="json_header_info",
502+
help="File descriptor that contains a JSON of pre-parsed QCOW2"
503+
"virtual size, cluster size information for input_file",
504+
type=int,
505+
default=None,
506+
)
507+
parser.add_argument(
508+
"--json-header-diff-map",
509+
dest="json_header_diff_map",
510+
metavar="json_header_diff_map",
511+
help="File descriptor that contains a JSON of pre-parsed QCOW2"
512+
"data clusters for diff_file_name",
513+
type=int,
514+
default=None,
515+
)
516+
parser.add_argument(
517+
"--json-header-diff-info",
518+
dest="json_header_diff_info",
519+
metavar="json_header_diff_info",
520+
help="File descriptor that contains a JSON of pre-parsed QCOW2"
521+
"virtual size, cluster size information for diff_file_name",
504522
type=int,
505523
default=None,
506524
)
@@ -513,29 +531,31 @@ def main():
513531
nonzero_clusters = None
514532
diff_virtual_size = None
515533
diff_nonzero_clusters = None
516-
if args.json_header:
517-
json_header = json.load(sys.stdin)
518-
try:
519-
virtual_size = json_header['virtual_size']
520-
source_cluster_size = 2 ** json_header['cluster_bits']
521-
if source_cluster_size != args.cluster_size:
522-
args.cluster_size = source_cluster_size
523-
nonzero_clusters = json_header['data_clusters']
524-
except KeyError as e:
525-
raise RuntimeError(f'Incomplete JSON - missing value for {str(e)}') from e
526-
if args.json_header_diff:
527-
f = os.fdopen(args.json_header_diff)
528-
json_header = json.load(f)
534+
535+
def parse_json_files(info_fd, map_fd):
536+
map_f = os.fdopen(map_fd)
537+
info_f = os.fdopen(info_fd)
538+
map_json = json.load(map_f)
539+
info_json = json.load(info_f)
540+
529541
try:
530-
diff_virtual_size = json_header['virtual_size']
531-
if 2 ** json_header['cluster_bits'] == args.cluster_size:
532-
diff_nonzero_clusters = json_header['data_clusters']
542+
virt_size = info_json['virtual-size']
543+
cluster_size = info_json['cluster-size']
544+
if cluster_size == args.cluster_size:
545+
clusters = [ [int(el["start"] / cluster_size), int((el["start"] + el["length"]) / cluster_size) - 1] for el in map_json if el["data"] ]
533546
else:
534547
sys.exit(f"[Error] Cluster size in the files being compared are "
535-
f"different: {2**json_header['cluster_bits']} vs. {args.cluster_size}")
548+
f"different: {info_json['cluster-size']} vs. {args.cluster_size}")
549+
return virt_size, clusters
536550
except KeyError as e:
537551
raise RuntimeError(f'Incomplete JSON for the diff - missing value for {str(e)}') from e
538552

553+
554+
if args.json_header_info and args.json_header_map:
555+
virtual_size, nonzero_clusters = parse_json_files(args.json_header_info, args.json_header_map)
556+
if args.json_header_diff_info and args.json_header_diff_map:
557+
diff_virtual_size, diff_nonzero_clusters = parse_json_files(args.json_header_diff_info, args.json_header_diff_map)
558+
539559
if not os.path.exists(args.input_file):
540560
sys.exit(f"[Error] {args.input_file} does not exist.")
541561

0 commit comments

Comments
 (0)