@@ -277,6 +277,38 @@ def ordered_qmp(qmsg, conv_keys=True):
def qemu_img_create(*args: str) -> 'subprocess.CompletedProcess[str]':
return qemu_img('create', *args)
+def qemu_img_json(*args: str) -> Any:
+ """
+ Run qemu-img and return its output as deserialized JSON.
+
+ :raise CalledProcessError:
+ When qemu-img crashes, or returns a non-zero exit code without
+ producing a valid JSON document to stdout.
+ :raise JSONDecoderError:
+ When qemu-img returns 0, but failed to produce a valid JSON document.
+
+ :return: A deserialized JSON object; probably a dict[str, Any].
+ """
+ try:
+ res = qemu_img(*args, combine_stdio=False)
+ except subprocess.CalledProcessError as exc:
+ # Terminated due to signal. Don't bother.
+ if exc.returncode < 0:
+ raise
+
+ # Commands like 'check' can return failure (exit codes 2 and 3)
+ # to indicate command completion, but with errors found. For
+ # multi-command flexibility, ignore the exact error codes and
+ # *try* to load JSON.
+ try:
+ return json.loads(exc.stdout)
+ except json.JSONDecodeError:
+ # Nope. This thing is toast. Raise the /process/ error.
+ pass
+ raise
+
+ return json.loads(res.stdout)
+
def qemu_img_measure(*args):
return json.loads(qemu_img_pipe("measure", "--output", "json", *args))