[pve-devel] [PATCH 11/31] PVE: [Up] qemu-img dd: add osize and read from/to stdin/stdout

Dietmar Maurer dietmar at proxmox.com
Fri Mar 6 12:29:51 CET 2020


From: Wolfgang Bumiller <w.bumiller at proxmox.com>

Neither convert nor dd were previously able to write to or
read from a pipe. Particularly serializing an image file
into a raw stream or vice versa can be useful, but using
`qemu-img convert -f qcow2 -O raw foo.qcow2 /dev/stdout` in
a pipe will fail trying to seek.

While dd and convert have overlapping use cases, `dd` is a
simple read/write loop while convert is much more
sophisticated and has ways to dealing with holes and blocks
of zeroes.
Since these typically can't be detected in pipes via
SEEK_DATA/HOLE or skipped while writing, dd seems to be the
better choice for implementing stdin/stdout streams.

This patch causes "if" and "of" to default to stdin and
stdout respectively, allowing only the "raw" format to be
used in these cases.
Since the input can now be a pipe we have no way of
detecting the size of the output image to create. Since we
also want to support images with a size not matching the
dd command's "bs" parameter (which, together with "count"
could be used to calculate the desired size, and is already
used to limit it), the "osize" option is added to explicitly
override the output file's size.

Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht at proxmox.com>
---
 qemu-img-cmds.hx |   4 +-
 qemu-img.c       | 192 +++++++++++++++++++++++++++++------------------
 2 files changed, 122 insertions(+), 74 deletions(-)

diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx
index 1c93e6d185..8094abb3ee 100644
--- a/qemu-img-cmds.hx
+++ b/qemu-img-cmds.hx
@@ -56,9 +56,9 @@ STEXI
 ETEXI
 
 DEF("dd", img_dd,
-    "dd [--image-opts] [-U] [-f fmt] [-O output_fmt] [bs=block_size] [count=blocks] [skip=blocks] if=input of=output")
+    "dd [--image-opts] [-U] [-f fmt] [-O output_fmt] [bs=block_size] [count=blocks] [skip=blocks] [osize=output_size] if=input of=output")
 STEXI
- at item dd [--image-opts] [-U] [-f @var{fmt}] [-O @var{output_fmt}] [bs=@var{block_size}] [count=@var{blocks}] [skip=@var{blocks}] if=@var{input} of=@var{output}
+ at item dd [--image-opts] [-U] [-f @var{fmt}] [-O @var{output_fmt}] [bs=@var{block_size}] [count=@var{blocks}] [skip=@var{blocks}] [osize=output_size] if=@var{input} of=@var{output}
 ETEXI
 
 DEF("info", img_info,
diff --git a/qemu-img.c b/qemu-img.c
index 12211bed76..d2516968c6 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -4405,10 +4405,12 @@ out:
 #define C_IF      04
 #define C_OF      010
 #define C_SKIP    020
+#define C_OSIZE   040
 
 struct DdInfo {
     unsigned int flags;
     int64_t count;
+    int64_t osize;
 };
 
 struct DdIo {
@@ -4487,6 +4489,20 @@ static int img_dd_skip(const char *arg,
     return 0;
 }
 
+static int img_dd_osize(const char *arg,
+                        struct DdIo *in, struct DdIo *out,
+                        struct DdInfo *dd)
+{
+    dd->osize = cvtnum(arg);
+
+    if (dd->osize < 0) {
+        error_report("invalid number: '%s'", arg);
+        return 1;
+    }
+
+    return 0;
+}
+
 static int img_dd(int argc, char **argv)
 {
     int ret = 0;
@@ -4527,6 +4543,7 @@ static int img_dd(int argc, char **argv)
         { "if", img_dd_if, C_IF },
         { "of", img_dd_of, C_OF },
         { "skip", img_dd_skip, C_SKIP },
+        { "osize", img_dd_osize, C_OSIZE },
         { NULL, NULL, 0 }
     };
     const struct option long_options[] = {
@@ -4605,8 +4622,13 @@ static int img_dd(int argc, char **argv)
         arg = NULL;
     }
 
-    if (!(dd.flags & C_IF && dd.flags & C_OF)) {
-        error_report("Must specify both input and output files");
+    if (!(dd.flags & C_IF) && (!fmt || strcmp(fmt, "raw") != 0)) {
+        error_report("Input format must be raw when readin from stdin");
+        ret = -1;
+        goto out;
+    }
+    if (!(dd.flags & C_OF) && strcmp(out_fmt, "raw") != 0) {
+        error_report("Output format must be raw when writing to stdout");
         ret = -1;
         goto out;
     }
@@ -4618,85 +4640,101 @@ static int img_dd(int argc, char **argv)
         goto out;
     }
 
-    blk1 = img_open(image_opts, in.filename, fmt, 0, false, false,
-                    force_share);
+    if (dd.flags & C_IF) {
+        blk1 = img_open(image_opts, in.filename, fmt, 0, false, false,
+                        force_share);
 
-    if (!blk1) {
-        ret = -1;
-        goto out;
+        if (!blk1) {
+            ret = -1;
+            goto out;
+        }
     }
 
-    drv = bdrv_find_format(out_fmt);
-    if (!drv) {
-        error_report("Unknown file format");
+    if (dd.flags & C_OSIZE) {
+        size = dd.osize;
+    } else if (dd.flags & C_IF) {
+        size = blk_getlength(blk1);
+        if (size < 0) {
+            error_report("Failed to get size for '%s'", in.filename);
+            ret = -1;
+            goto out;
+        }
+    } else if (dd.flags & C_COUNT) {
+        size = dd.count * in.bsz;
+    } else {
+        error_report("Output size must be known when reading from stdin");
         ret = -1;
         goto out;
     }
-    proto_drv = bdrv_find_protocol(out.filename, true, &local_err);
 
-    if (!proto_drv) {
-        error_report_err(local_err);
-        ret = -1;
-        goto out;
-    }
-    if (!drv->create_opts) {
-        error_report("Format driver '%s' does not support image creation",
-                     drv->format_name);
-        ret = -1;
-        goto out;
-    }
-    if (!proto_drv->create_opts) {
-        error_report("Protocol driver '%s' does not support image creation",
-                     proto_drv->format_name);
-        ret = -1;
-        goto out;
+    if (!(dd.flags & C_OSIZE) && dd.flags & C_COUNT && dd.count <= INT64_MAX / in.bsz &&
+        dd.count * in.bsz < size) {
+        size = dd.count * in.bsz;
     }
-    create_opts = qemu_opts_append(create_opts, drv->create_opts);
-    create_opts = qemu_opts_append(create_opts, proto_drv->create_opts);
 
-    opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
+    if (dd.flags & C_OF) {
+        drv = bdrv_find_format(out_fmt);
+        if (!drv) {
+            error_report("Unknown file format");
+            ret = -1;
+            goto out;
+        }
+        proto_drv = bdrv_find_protocol(out.filename, true, &local_err);
 
-    size = blk_getlength(blk1);
-    if (size < 0) {
-        error_report("Failed to get size for '%s'", in.filename);
-        ret = -1;
-        goto out;
-    }
+        if (!proto_drv) {
+            error_report_err(local_err);
+            ret = -1;
+            goto out;
+        }
+        if (!drv->create_opts) {
+            error_report("Format driver '%s' does not support image creation",
+                         drv->format_name);
+            ret = -1;
+            goto out;
+        }
+        if (!proto_drv->create_opts) {
+            error_report("Protocol driver '%s' does not support image creation",
+                         proto_drv->format_name);
+            ret = -1;
+            goto out;
+        }
+        create_opts = qemu_opts_append(create_opts, drv->create_opts);
+        create_opts = qemu_opts_append(create_opts, proto_drv->create_opts);
 
-    if (dd.flags & C_COUNT && dd.count <= INT64_MAX / in.bsz &&
-        dd.count * in.bsz < size) {
-        size = dd.count * in.bsz;
-    }
+        opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
 
-    /* Overflow means the specified offset is beyond input image's size */
-    if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz ||
-                              size < in.bsz * in.offset)) {
-        qemu_opt_set_number(opts, BLOCK_OPT_SIZE, 0, &error_abort);
-    } else {
-        qemu_opt_set_number(opts, BLOCK_OPT_SIZE,
-                            size - in.bsz * in.offset, &error_abort);
-    }
+        /* Overflow means the specified offset is beyond input image's size */
+        if (dd.flags & C_OSIZE) {
+            qemu_opt_set_number(opts, BLOCK_OPT_SIZE, size, &error_abort);
+        } else if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz ||
+                                  size < in.bsz * in.offset)) {
+            qemu_opt_set_number(opts, BLOCK_OPT_SIZE, 0, &error_abort);
+        } else {
+            qemu_opt_set_number(opts, BLOCK_OPT_SIZE,
+                                size - in.bsz * in.offset, &error_abort);
+        }
 
-    ret = bdrv_create(drv, out.filename, opts, &local_err);
-    if (ret < 0) {
-        error_reportf_err(local_err,
-                          "%s: error while creating output image: ",
-                          out.filename);
-        ret = -1;
-        goto out;
-    }
+        ret = bdrv_create(drv, out.filename, opts, &local_err);
+        if (ret < 0) {
+            error_reportf_err(local_err,
+                              "%s: error while creating output image: ",
+                              out.filename);
+            ret = -1;
+            goto out;
+        }
 
-    /* TODO, we can't honour --image-opts for the target,
-     * since it needs to be given in a format compatible
-     * with the bdrv_create() call above which does not
-     * support image-opts style.
-     */
-    blk2 = img_open_file(out.filename, NULL, out_fmt, BDRV_O_RDWR,
-                         false, false, false);
+        /* TODO, we can't honour --image-opts for the target,
+         * since it needs to be given in a format compatible
+         * with the bdrv_create() call above which does not
+         * support image-opts style.
+         */
+        blk2 = img_open_file(out.filename, NULL, out_fmt, BDRV_O_RDWR,
+                             false, false, false);
 
-    if (!blk2) {
-        ret = -1;
-        goto out;
+        if (!blk2) {
+            ret = -1;
+            goto out;
+        }
     }
 
     if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz ||
@@ -4714,11 +4752,17 @@ static int img_dd(int argc, char **argv)
 
     for (out_pos = 0; in_pos < size; block_count++) {
         int in_ret, out_ret;
-
-        if (in_pos + in.bsz > size) {
-            in_ret = blk_pread(blk1, in_pos, in.buf, size - in_pos);
+        size_t in_bsz = in_pos + in.bsz > size ? size - in_pos : in.bsz;
+        if (blk1) {
+            in_ret = blk_pread(blk1, in_pos, in.buf, in_bsz);
         } else {
-            in_ret = blk_pread(blk1, in_pos, in.buf, in.bsz);
+            in_ret = read(STDIN_FILENO, in.buf, in_bsz);
+            if (in_ret == 0) {
+                /* early EOF is considered an error */
+                error_report("Input ended unexpectedly");
+                ret = -1;
+                goto out;
+            }
         }
         if (in_ret < 0) {
             error_report("error while reading from input image file: %s",
@@ -4728,9 +4772,13 @@ static int img_dd(int argc, char **argv)
         }
         in_pos += in_ret;
 
-        out_ret = blk_pwrite(blk2, out_pos, in.buf, in_ret, 0);
+        if (blk2) {
+            out_ret = blk_pwrite(blk2, out_pos, in.buf, in_ret, 0);
+        } else {
+            out_ret = write(STDOUT_FILENO, in.buf, in_ret);
+        }
 
-        if (out_ret < 0) {
+        if (out_ret != in_ret) {
             error_report("error while writing to output image file: %s",
                          strerror(-out_ret));
             ret = -1;
-- 
2.20.1




More information about the pve-devel mailing list