From 5f3f415247a8769e6002228a2b7734779914a408 Mon Sep 17 00:00:00 2001 From: ccardenas Date: Tue, 11 Sep 2018 04:03:16 +0000 Subject: [PATCH] Add ability to create qcow2 disk. vmctl create now takes an optional disk format parameter: raw or qcow2. If format is omitted, raw is used. Many thanks to Ori Bernstein. --- usr.sbin/vmctl/main.c | 20 +++++-- usr.sbin/vmctl/vmctl.8 | 6 +- usr.sbin/vmctl/vmctl.c | 126 ++++++++++++++++++++++++++++++++++++++++- usr.sbin/vmctl/vmctl.h | 5 +- 4 files changed, 144 insertions(+), 13 deletions(-) diff --git a/usr.sbin/vmctl/main.c b/usr.sbin/vmctl/main.c index 0652490cdb3..49d982f2ad0 100644 --- a/usr.sbin/vmctl/main.c +++ b/usr.sbin/vmctl/main.c @@ -1,4 +1,4 @@ -/* $OpenBSD: main.c,v 1.40 2018/09/09 04:09:32 ccardenas Exp $ */ +/* $OpenBSD: main.c,v 1.41 2018/09/11 04:03:16 ccardenas Exp $ */ /* * Copyright (c) 2015 Reyk Floeter @@ -63,7 +63,8 @@ int ctl_receive(struct parse_result *, int, char *[]); struct ctl_command ctl_commands[] = { { "console", CMD_CONSOLE, ctl_console, "id" }, - { "create", CMD_CREATE, ctl_create, "\"path\" -s size", 1 }, + { "create", CMD_CREATE, ctl_create, + "\"path\" -s size [-f fmt]", 1 }, { "load", CMD_LOAD, ctl_load, "\"path\"" }, { "log", CMD_LOG, ctl_log, "(verbose|brief)" }, { "reload", CMD_RELOAD, ctl_reload, "" }, @@ -497,24 +498,28 @@ int ctl_create(struct parse_result *res, int argc, char *argv[]) { int ch, ret; - const char *paths[2]; + const char *paths[2], *format; if (argc < 2) ctl_usage(res->ctl); paths[0] = argv[1]; paths[1] = NULL; + format = "raw"; if (pledge("stdio rpath wpath cpath", NULL) == -1) err(1, "pledge"); argc--; argv++; - while ((ch = getopt(argc, argv, "s:")) != -1) { + while ((ch = getopt(argc, argv, "s:f:")) != -1) { switch (ch) { case 's': if (parse_size(res, optarg, 0) != 0) errx(1, "invalid size: %s", optarg); break; + case 'f': + format = optarg; + break; default: ctl_usage(res->ctl); /* NOTREACHED */ @@ -525,7 +530,12 @@ ctl_create(struct parse_result *res, int argc, char *argv[]) fprintf(stderr, "missing size argument\n"); ctl_usage(res->ctl); } - ret = create_imagefile(paths[0], res->size); + if (strcmp(format, "raw") == 0) + ret = create_raw_imagefile(paths[0], res->size); + else if (strcmp(format, "qcow2") == 0) + ret = create_qc2_imagefile(paths[0], res->size); + else + errx(1, "unknown image format %s", format); if (ret != 0) { errno = ret; err(1, "create imagefile operation failed"); diff --git a/usr.sbin/vmctl/vmctl.8 b/usr.sbin/vmctl/vmctl.8 index 1ca6a0a5c84..d4b06f36a9e 100644 --- a/usr.sbin/vmctl/vmctl.8 +++ b/usr.sbin/vmctl/vmctl.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: vmctl.8,v 1.46 2018/09/09 06:36:43 jmc Exp $ +.\" $OpenBSD: vmctl.8,v 1.47 2018/09/11 04:03:16 ccardenas Exp $ .\" .\" Copyright (c) 2015 Mike Larkin .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: September 9 2018 $ +.Dd $Mdocdate: September 11 2018 $ .Dt VMCTL 8 .Os .Sh NAME @@ -50,7 +50,7 @@ Using .Xr cu 1 connect to the console of the VM with the specified .Ar id . -.It Cm create Ar path Fl s Ar size +.It Cm create Ar path Fl s Ar size Op Fl f Ar format Creates a VM disk image file with the specified .Ar path and diff --git a/usr.sbin/vmctl/vmctl.c b/usr.sbin/vmctl/vmctl.c index 3810bd778d1..0ef71a55890 100644 --- a/usr.sbin/vmctl/vmctl.c +++ b/usr.sbin/vmctl/vmctl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: vmctl.c,v 1.56 2018/09/09 04:09:32 ccardenas Exp $ */ +/* $OpenBSD: vmctl.c,v 1.57 2018/09/11 04:03:16 ccardenas Exp $ */ /* * Copyright (c) 2014 Mike Larkin @@ -737,7 +737,7 @@ vm_console(struct vmop_info_result *list, size_t ct) } /* - * create_imagefile + * create_raw_imagefile * * Create an empty imagefile with the specified path and size. * @@ -751,7 +751,7 @@ vm_console(struct vmop_info_result *list, size_t ct) * Exxxx : Various other Exxxx errno codes due to other I/O errors */ int -create_imagefile(const char *imgfile_path, long imgsize) +create_raw_imagefile(const char *imgfile_path, long imgsize) { int fd, ret; @@ -772,3 +772,123 @@ create_imagefile(const char *imgfile_path, long imgsize) ret = close(fd); return (ret); } + +/* + * create_imagefile + * + * Create an empty qcow2 imagefile with the specified path and size. + * + * Parameters: + * imgfile_path: path to the image file to create + * imgsize : size of the image file to create (in MB) + * + * Return: + * EEXIST: The requested image file already exists + * 0 : Image file successfully created + * Exxxx : Various other Exxxx errno codes due to other I/O errors + */ +#define ALIGN(sz, align) \ + ((sz + align - 1) & ~(align - 1)) +int +create_qc2_imagefile(const char *imgfile_path, long imgsize) +{ + struct qcheader { + char magic[4]; + uint32_t version; + uint64_t backingoff; + uint32_t backingsz; + uint32_t clustershift; + uint64_t disksz; + uint32_t cryptmethod; + uint32_t l1sz; + uint64_t l1off; + uint64_t refoff; + uint32_t refsz; + uint32_t snapcount; + uint64_t snapsz; + /* v3 additions */ + uint64_t incompatfeatures; + uint64_t compatfeatures; + uint64_t autoclearfeatures; + uint32_t reforder; + uint32_t headersz; + } __packed hdr; + int fd, ret; + uint64_t l1sz, refsz, disksz, initsz, clustersz; + uint64_t l1off, refoff, v, i; + uint16_t refs; + + disksz = 1024*1024*imgsize; + clustersz = (1<<16); + l1off = ALIGN(sizeof hdr, clustersz); + l1sz = disksz / (clustersz*clustersz/8); + if (l1sz == 0) + l1sz = 1; + + refoff = ALIGN(l1off + 8*l1sz, clustersz); + refsz = disksz / (clustersz*clustersz*clustersz/2); + if (refsz == 0) + refsz = 1; + + initsz = ALIGN(refoff + refsz*clustersz, clustersz); + + memcpy(hdr.magic, "QFI\xfb", 4); + hdr.version = htobe32(3); + hdr.backingoff = htobe64(0); + hdr.backingsz = htobe32(0); + hdr.clustershift = htobe32(16); + hdr.disksz = htobe64(disksz); + hdr.cryptmethod = htobe32(0); + hdr.l1sz = htobe32(l1sz); + hdr.l1off = htobe64(l1off); + hdr.refoff = htobe64(refoff); + hdr.refsz = htobe32(refsz); + hdr.snapcount = htobe32(0); + hdr.snapsz = htobe64(0); + hdr.incompatfeatures = htobe64(0); + hdr.compatfeatures = htobe64(0); + hdr.autoclearfeatures = htobe64(0); + hdr.reforder = htobe32(4); + hdr.headersz = htobe32(sizeof hdr); + + /* Refuse to overwrite an existing image */ + fd = open(imgfile_path, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, + S_IRUSR | S_IWUSR); + if (fd == -1) + return (errno); + + /* Write out the header */ + if (write(fd, &hdr, sizeof hdr) != sizeof hdr) + goto error; + + /* Extend to desired size, and add one refcount cluster */ + if (ftruncate(fd, (off_t)initsz + clustersz) == -1) + goto error; + + /* + * Paranoia: if our disk image takes more than one cluster + * to refcount the initial image, fail. + */ + if (initsz/clustersz > clustersz/2) { + errno = ERANGE; + goto error; + } + + /* Add a refcount block, and refcount ourselves. */ + v = htobe64(initsz); + if (pwrite(fd, &v, 8, refoff) != 8) + goto error; + for (i = 0; i < initsz/clustersz + 1; i++) { + refs = htobe16(1); + if (pwrite(fd, &refs, 2, initsz + 2*i) != 2) + goto error; + } + + ret = close(fd); + return (ret); +error: + ret = errno; + close(fd); + unlink(imgfile_path); + return (errno); +} diff --git a/usr.sbin/vmctl/vmctl.h b/usr.sbin/vmctl/vmctl.h index 92be581c0c5..593108f1f70 100644 --- a/usr.sbin/vmctl/vmctl.h +++ b/usr.sbin/vmctl/vmctl.h @@ -1,4 +1,4 @@ -/* $OpenBSD: vmctl.h,v 1.22 2018/09/09 04:09:32 ccardenas Exp $ */ +/* $OpenBSD: vmctl.h,v 1.23 2018/09/11 04:03:16 ccardenas Exp $ */ /* * Copyright (c) 2015 Reyk Floeter @@ -85,7 +85,8 @@ __dead void ctl_openconsole(const char *); /* vmctl.c */ -int create_imagefile(const char *, long); +int create_raw_imagefile(const char *, long); +int create_qc2_imagefile(const char *, long); int vm_start(uint32_t, const char *, int, int, char **, int, char **, int *, char *, char *, char *); int vm_start_complete(struct imsg *, int *, int); -- 2.20.1