On Sun, 12 Aug 2018 22:51:24 -0700, Ori Bernstein <[email protected]> wrote:
> <qcow2 patch>
This patch adds preliminary support for creating qcow2 disk images. It gives
the 'vmctl create' command an option '-f', which can be given arguments of
either 'raw' (defualt) or 'qcow2', and it creates a disk of the appropriate
format.
The qcow2 header is duplicated inline, since it seemed better than the other
options (putting it into a header and messing around with include/source paths
so
that we could share it with vmd, or creating a libqcow2)
That decision may get revisited when I find time to implement snapshotting,
since creating a snapshot will involve relatively deep modifications to the
disk (adjusting refcounts, rewriting L1/L2 tables, etc).
---
usr.sbin/vmctl/main.c | 18 +++++--
usr.sbin/vmctl/vmctl.8 | 7 ++-
usr.sbin/vmctl/vmctl.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++-
usr.sbin/vmctl/vmctl.h | 3 +-
4 files changed, 143 insertions(+), 9 deletions(-)
diff --git usr.sbin/vmctl/main.c usr.sbin/vmctl/main.c
index b7674d0c980..f39ccbdbc1f 100644
--- usr.sbin/vmctl/main.c
+++ usr.sbin/vmctl/main.c
@@ -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, "" },
@@ -470,24 +471,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 */
@@ -498,7 +503,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 usr.sbin/vmctl/vmctl.8 usr.sbin/vmctl/vmctl.8
index 81ecbeb6c1d..358928c7ff6 100644
--- usr.sbin/vmctl/vmctl.8
+++ usr.sbin/vmctl/vmctl.8
@@ -50,12 +50,15 @@ 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
.Ar size ,
-rounded to megabytes.
+rounded to megabytes. The disk
+.Ar format
+may be specified as either 'raw' or 'qcow2', defaulting to 'raw'
+if left unspecified.
.It Cm load Ar filename
Load additional configuration from the specified file.
.It Cm log brief
diff --git usr.sbin/vmctl/vmctl.c usr.sbin/vmctl/vmctl.c
index 7ab9a9bc60c..b3a4e6d7b14 100644
--- usr.sbin/vmctl/vmctl.c
+++ usr.sbin/vmctl/vmctl.c
@@ -735,7 +735,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.
*
@@ -749,7 +749,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;
@@ -770,3 +770,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 empty image, the disk too big.
+ */
+ 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 usr.sbin/vmctl/vmctl.h usr.sbin/vmctl/vmctl.h
index 91ade10b7d8..87097446a18 100644
--- usr.sbin/vmctl/vmctl.h
+++ usr.sbin/vmctl/vmctl.h
@@ -83,7 +83,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 **, char *, char *, char *);
int vm_start_complete(struct imsg *, int *, int);
--
2.16.4
--
Ori Bernstein