diff options
-rw-r--r-- | usr.sbin/vmctl/Makefile | 7 | ||||
-rw-r--r-- | usr.sbin/vmctl/main.c | 190 | ||||
-rw-r--r-- | usr.sbin/vmctl/vmctl.8 | 102 | ||||
-rw-r--r-- | usr.sbin/vmctl/vmctl.c | 117 | ||||
-rw-r--r-- | usr.sbin/vmctl/vmctl.h | 8 | ||||
-rw-r--r-- | usr.sbin/vmd/config.c | 30 | ||||
-rw-r--r-- | usr.sbin/vmd/vioqcow2.c | 51 | ||||
-rw-r--r-- | usr.sbin/vmd/virtio.c | 6 | ||||
-rw-r--r-- | usr.sbin/vmd/virtio.h | 4 | ||||
-rw-r--r-- | usr.sbin/vmd/vmd.h | 4 |
10 files changed, 408 insertions, 111 deletions
diff --git a/usr.sbin/vmctl/Makefile b/usr.sbin/vmctl/Makefile index 61b8ca7b262..6288d045110 100644 --- a/usr.sbin/vmctl/Makefile +++ b/usr.sbin/vmctl/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.4 2017/07/15 05:05:36 pd Exp $ +# $OpenBSD: Makefile,v 1.5 2018/10/19 10:12:39 reyk Exp $ .if ${MACHINE} == "amd64" || ${MACHINE} == "i386" @@ -6,14 +6,15 @@ PROG= vmctl SRCS= vmctl.c main.c atomicio.c +SRCS+= vioqcow2.c vioraw.c log.c CFLAGS+= -Wall CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes CFLAGS+= -Wmissing-declarations CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual CFLAGS+= -Wsign-compare CFLAGS+= -I${.CURDIR} -I${.CURDIR}/../vmd -LDADD+= -lutil -DPADD+= ${LIBUTIL} +LDADD+= -lutil -lpthread +DPADD+= ${LIBUTIL} ${LIBPTHREAD} PATH+=../vmd .else diff --git a/usr.sbin/vmctl/main.c b/usr.sbin/vmctl/main.c index 45cd5cab753..da84b5d9e5a 100644 --- a/usr.sbin/vmctl/main.c +++ b/usr.sbin/vmctl/main.c @@ -1,4 +1,4 @@ -/* $OpenBSD: main.c,v 1.46 2018/10/08 16:32:01 reyk Exp $ */ +/* $OpenBSD: main.c,v 1.47 2018/10/19 10:12:39 reyk Exp $ */ /* * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org> @@ -30,12 +30,14 @@ #include <stdint.h> #include <limits.h> #include <string.h> +#include <syslog.h> #include <unistd.h> #include <fcntl.h> #include <util.h> #include <imsg.h> #include "vmd.h" +#include "virtio.h" #include "proc.h" #include "vmctl.h" @@ -52,6 +54,7 @@ __dead void ctl_usage(struct ctl_command *); int vmm_action(struct parse_result *); int ctl_console(struct parse_result *, int, char *[]); +int ctl_convert(const char *, const char *, int, size_t); int ctl_create(struct parse_result *, int, char *[]); int ctl_load(struct parse_result *, int, char *[]); int ctl_log(struct parse_result *, int, char *[]); @@ -68,7 +71,7 @@ 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] [-b base]", 1 }, + "\"path\" [-b base] [-i disk] [-s size]", 1 }, { "load", CMD_LOAD, ctl_load, "\"path\"" }, { "log", CMD_LOG, ctl_log, "[verbose|brief]" }, { "reload", CMD_RELOAD, ctl_reload, "" }, @@ -92,7 +95,7 @@ usage(void) extern char *__progname; int i; - fprintf(stderr, "usage:\t%s command [arg ...]\n", + fprintf(stderr, "usage:\t%s [-v] command [arg ...]\n", __progname); for (i = 0; ctl_commands[i].name != NULL; i++) { fprintf(stderr, "\t%s %s %s\n", __progname, @@ -106,7 +109,7 @@ ctl_usage(struct ctl_command *ctl) { extern char *__progname; - fprintf(stderr, "usage:\t%s %s %s\n", __progname, + fprintf(stderr, "usage:\t%s [-v] %s %s\n", __progname, ctl->name, ctl->usage); exit(1); } @@ -114,10 +117,13 @@ ctl_usage(struct ctl_command *ctl) int main(int argc, char *argv[]) { - int ch; + int ch, verbose = 1; - while ((ch = getopt(argc, argv, "")) != -1) { + while ((ch = getopt(argc, argv, "v")) != -1) { switch (ch) { + case 'v': + verbose = 2; + break; default: usage(); /* NOTREACHED */ @@ -131,6 +137,8 @@ main(int argc, char *argv[]) if (argc < 1) usage(); + log_init(verbose, LOG_DAEMON); + return (parse(argc, argv)); } @@ -258,6 +266,8 @@ vmmaction(struct parse_result *res) break; case CMD_CREATE: case NONE: + /* The action is not expected here */ + errx(1, "invalid action %u", res->action); break; } @@ -540,12 +550,12 @@ int ctl_create(struct parse_result *res, int argc, char *argv[]) { int ch, ret, type; - const char *disk, *format, *base; + const char *disk, *format, *base, *input; if (argc < 2) ctl_usage(res->ctl); - base = NULL; + base = input = NULL; type = parse_disktype(argv[1], &disk); if (pledge("stdio rpath wpath cpath unveil", NULL) == -1) @@ -556,22 +566,34 @@ ctl_create(struct parse_result *res, int argc, char *argv[]) argc--; argv++; - while ((ch = getopt(argc, argv, "s:b:")) != -1) { + while ((ch = getopt(argc, argv, "b:i:s:")) != -1) { switch (ch) { - case 's': - if (parse_size(res, optarg, 0) != 0) - errx(1, "invalid size: %s", optarg); - break; case 'b': base = optarg; if (unveil(base, "r") == -1) err(1, "unveil"); break; + case 'i': + input = optarg; + if (unveil(input, "r") == -1) + err(1, "unveil"); + break; + case 's': + if (parse_size(res, optarg, 0) != 0) + errx(1, "invalid size: %s", optarg); + break; default: ctl_usage(res->ctl); /* NOTREACHED */ } } + + if (input) { + if (base && input) + errx(1, "conflicting -b and -i arguments"); + return ctl_convert(input, disk, type, res->size); + } + if (unveil(NULL, NULL)) err(1, "unveil"); @@ -583,15 +605,7 @@ ctl_create(struct parse_result *res, int argc, char *argv[]) ctl_usage(res->ctl); } - if (type == VMDF_QCOW2) { - format = "qcow2"; - ret = create_qc2_imagefile(disk, base, res->size); - } else { - format = "raw"; - ret = create_raw_imagefile(disk, res->size); - } - - if (ret != 0) { + if ((ret = create_imagefile(type, disk, base, res->size, &format)) != 0) { errno = ret; err(1, "create imagefile operation failed"); } else @@ -601,6 +615,138 @@ ctl_create(struct parse_result *res, int argc, char *argv[]) } int +ctl_convert(const char *srcfile, const char *dstfile, int dsttype, size_t dstsize) +{ + struct { + int fd; + int type; + struct virtio_backing file; + const char *disk; + off_t size; + } src, dst; + int ret; + const char *format, *errstr = NULL; + uint8_t *buf = NULL, *zerobuf = NULL; + size_t buflen; + ssize_t len, rlen; + off_t off; + + memset(&src, 0, sizeof(src)); + memset(&dst, 0, sizeof(dst)); + + src.type = parse_disktype(srcfile, &src.disk); + dst.type = dsttype; + dst.disk = dstfile; + + if ((src.fd = open_imagefile(src.type, src.disk, O_RDONLY, + &src.file, &src.size)) == -1) { + errstr = "failed to open source image file"; + goto done; + } + + /* We can only lock unveil after opening the disk and all base images */ + if (unveil(NULL, NULL)) + err(1, "unveil"); + + if (dstsize == 0) + dstsize = src.size; + else + dstsize *= 1048576; + if (dstsize < (size_t)src.size) { + errstr = "size cannot be smaller than input disk size"; + goto done; + } + + /* align to megabytes */ + dst.size = ALIGN(dstsize, 1048576); + + if ((ret = create_imagefile(dst.type, dst.disk, NULL, + dst.size / 1048576, &format)) != 0) { + errno = ret; + errstr = "failed to create destination image file"; + goto done; + } + + if ((dst.fd = open_imagefile(dst.type, dst.disk, O_RDWR, + &dst.file, &dst.size)) == -1) { + errstr = "failed to open destination image file"; + goto done; + } + + if (pledge("stdio", NULL) == -1) + err(1, "pledge"); + + /* + * Use 64k buffers by default. This could also be adjusted to + * the backend cluster size. + */ + buflen = 1 << 16; + if ((buf = calloc(1, buflen)) == NULL || + (zerobuf = calloc(1, buflen)) == NULL) { + errstr = "failed to allocated buffers"; + goto done; + } + + for (off = 0; off < dst.size; off += len) { + /* Read input from the source image */ + if (off < src.size) { + len = MIN((off_t)buflen, src.size - off); + if ((rlen = src.file.pread(src.file.p, + buf, (size_t)len, off)) != len) { + errno = EIO; + errstr = "failed to read from source"; + goto done; + } + } else + len = 0; + + /* and pad the remaining bytes */ + if (len < (ssize_t)buflen) { + log_debug("%s: padding %zd zero bytes at offset %lld", + format, buflen - len, off + len); + memset(buf + len, 0, buflen - len); + len = buflen; + } + + /* + * No need to copy empty buffers. This allows the backend, + * sparse files or QCOW2 images, to save space in the + * destination file. + */ + if (memcmp(buf, zerobuf, buflen) == 0) + continue; + + log_debug("%s: writing %zd of %lld bytes at offset %lld", + format, len, dst.size, off); + + if ((rlen = dst.file.pwrite(dst.file.p, + buf, (size_t)len, off)) != len) { + errno = EIO; + errstr = "failed to write to destination"; + goto done; + } + } + + if (dstsize < (size_t)dst.size) + warnx("destination size rounded to %lld megabytes", + dst.size / 1048576); + + done: + free(buf); + free(zerobuf); + if (src.file.p != NULL) + src.file.close(src.file.p, 0); + if (dst.file.p != NULL) + dst.file.close(dst.file.p, 0); + if (errstr != NULL) + errx(1, "%s", errstr); + else + warnx("%s imagefile created", format); + + return (0); +} + +int ctl_status(struct parse_result *res, int argc, char *argv[]) { if (argc == 2) { diff --git a/usr.sbin/vmctl/vmctl.8 b/usr.sbin/vmctl/vmctl.8 index f450ae49e11..f56a1dd892c 100644 --- a/usr.sbin/vmctl/vmctl.8 +++ b/usr.sbin/vmctl/vmctl.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: vmctl.8,v 1.51 2018/10/08 16:32:01 reyk Exp $ +.\" $OpenBSD: vmctl.8,v 1.52 2018/10/19 10:12:39 reyk Exp $ .\" .\" Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org> .\" @@ -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: October 8 2018 $ +.Dd $Mdocdate: October 19 2018 $ .Dt VMCTL 8 .Os .Sh NAME @@ -22,6 +22,7 @@ .Nd control the virtual machine daemon .Sh SYNOPSIS .Nm +.Op Fl v .Ar command .Op Ar arg ... .Sh DESCRIPTION @@ -32,6 +33,9 @@ A VMM manages virtual machines (VMs) on a host. The VMM subsystem is responsible for creating, destroying, and executing VMs. .Pp +The +.Fl v +option enables verbose mode. Within the commands, the .Ar size @@ -43,6 +47,19 @@ The argument can be either a numeric, non-zero identifier or alternatively the name of a virtual machine. .Pp +The +.Ar disk +argument is used by commands that take a path to a disk image file. +It may be prefixed with a format prefix +.Pf ( raw Ns : Ns Ar disk +or +.Pf qcow2 Ns : Ns Ar disk ) +.Sm on +in order to specify the disk image format. +If left unspecified, the format defaults to +.Sq raw +if it cannot be derived automatically. +.Pp The commands are as follows: .Bl -tag -width Ds .It Cm console Ar id @@ -50,31 +67,44 @@ Using .Xr cu 1 connect to the console of the VM with the specified .Ar id . -.It Cm create Ar path Fl s Op Ar size Op Fl b Ar base +.It Cm create Ar disk Oo Fl s Ar size Oc Op Fl b Ar base | Fl i Ar disk Creates a VM disk image file with the specified -.Ar path -and -.Ar size , -rounded to megabytes. -The disk -.Ar path -may be prefixed with a format prefix -.Pf ( Pa raw : -or -.Pa qcow2 : ) -in order to specify the disk format. -If left unspecified, the format defaults to -.Pa raw -if it cannot be derived automatically. -For qcow2, a +.Ar disk +path. +.Bl -tag -width "-i input" +.It Fl b Ar base +For +.Sq qcow2 , +a .Ar base image may be specified. -The base image is not modified. -The derived image contains only the changes written by the VM. -When creating a derived image, the +The base image is not modified and the derived image contains only the +changes written by the VM. +.It Fl i Ar disk +Copy and convert the input +.Ar disk +to the newly created disk. +This option conflicts with +.Fl b Ar base . +.It Fl s Ar size +Specify the .Ar size -may be omitted, and probed from the base image. -If it is provided, it must match the base image size. +of the new disk image, rounded to megabytes. +If the +.Fl b +option is specified, the size must match the size of the +.Ar base +image. +For the +.Fl i +option, the size cannot be smaller than the input disk size. +The size can be ommitted with the +.Fl b +and +.Fl i +options and will be obtained from the base or input image respectively. +.El +.Pp .It Cm load Ar filename Load additional configuration from the specified file. .It Cm log brief @@ -114,7 +144,7 @@ command. .It Xo Cm start Ar name .Op Fl cL .Op Fl b Ar path -.Op Fl d Ar path +.Op Fl d Ar disk .Op Fl i Ar count .Op Fl m Ar size .Op Fl n Ar switch @@ -131,18 +161,10 @@ If not specified, the default is to boot using the BIOS image in .Pa /etc/firmware/vmm-bios . .It Fl c Automatically connect to the VM console. -.It Fl d Ar path -Disk image file (may be specified multiple times to add multiple disk images). -The disk -.Ar path -may be prefixed with a format prefix -.Pf ( Pa raw : -or -.Pa qcow2 : ) -in order to specify the disk format. -If left unspecified, the format defaults to -.Pa raw -if it cannot be derived automatically. +.It Fl d Ar disk +Use a disk image at the specified +.Ar disk +path (may be specified multiple times to add multiple disk images). .It Fl i Ar count Number of network interfaces to add to the VM. .It Fl L @@ -330,6 +352,14 @@ Create a 4.5 Gigabyte disk image, disk.img: $ vmctl create disk.img -s 4.5G .Ed .Pp +Convert a disk image from the +.Sq raw +format to +.Sq qcow2 : +.Bd -literal -offset indent +$ vmctl create disk.qcow2 -i disk.img +.Ed +.Pp Create a new VM with 1GB memory, one network interface, one disk image ('disk.img') and boot from kernel '/bsd': .Bd -literal -offset indent diff --git a/usr.sbin/vmctl/vmctl.c b/usr.sbin/vmctl/vmctl.c index a311ed0a512..1feca4754d4 100644 --- a/usr.sbin/vmctl/vmctl.c +++ b/usr.sbin/vmctl/vmctl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: vmctl.c,v 1.61 2018/10/08 16:32:01 reyk Exp $ */ +/* $OpenBSD: vmctl.c,v 1.62 2018/10/19 10:12:39 reyk Exp $ */ /* * Copyright (c) 2014 Mike Larkin <mlarkin@openbsd.org> @@ -39,6 +39,7 @@ #include <grp.h> #include "vmd.h" +#include "virtio.h" #include "vmctl.h" #include "atomicio.h" @@ -794,6 +795,116 @@ vm_console(struct vmop_info_result *list, size_t ct) } /* + * open_imagefile + * + * Open an imagefile with the specified type, path and size. + * + * Parameters: + * type : format of the image file + * imgfile_path: path to the image file to create + * flags : flags for open(2), e.g. O_RDONLY + * file : file structure + * sz : size of the image file + * + * Return: + * fd : Returns file descriptor of the new image file + * -1 : Operation failed. errno is set. + */ +int +open_imagefile(int type, const char *imgfile_path, int flags, + struct virtio_backing *file, off_t *sz) +{ + int fd, ret, basefd[VM_MAX_BASE_PER_DISK], nfd, i; + char path[PATH_MAX]; + + *sz = 0; + if ((fd = open(imgfile_path, flags)) == -1) + return (-1); + + basefd[0] = fd; + nfd = 1; + + errno = 0; + switch (type) { + case VMDF_QCOW2: + if (strlcpy(path, imgfile_path, sizeof(path)) >= sizeof(path)) + return (-1); + for (i = 0; i < VM_MAX_BASE_PER_DISK - 1; i++, nfd++) { + if ((ret = virtio_qcow2_get_base(basefd[i], + path, sizeof(path), imgfile_path)) == -1) { + log_debug("%s: failed to get base %d", __func__, i); + return -1; + } else if (ret == 0) + break; + + /* + * This might be called after unveil is already + * locked but it is save to ignore the EPERM error + * here as the subsequent open would fail as well. + */ + if ((ret = unveil(path, "r")) != 0 && + (ret != EPERM)) + err(1, "unveil"); + if ((basefd[i + 1] = open(path, O_RDONLY)) == -1) { + log_warn("%s: failed to open base %s", + __func__, path); + return (-1); + } + } + ret = virtio_init_qcow2(file, sz, basefd, nfd); + break; + default: + ret = virtio_init_raw(file, sz, &fd, 1); + break; + } + + if (ret == -1) { + for (i = 0; i < nfd; i++) + close(basefd[i]); + return (-1); + } + + return (fd); +} + +/* + * create_imagefile + * + * Create an empty imagefile with the specified type, path and size. + * + * Parameters: + * type : format of the image file + * imgfile_path: path to the image file to create + * base_path : path to the qcow2 base image + * imgsize : size of the image file to create (in MB) + * format : string identifying the format + * + * 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 + */ +int +create_imagefile(int type, const char *imgfile_path, const char *base_path, + long imgsize, const char **format) +{ + int ret; + + switch (type) { + case VMDF_QCOW2: + *format = "qcow2"; + ret = create_qc2_imagefile(imgfile_path, base_path, imgsize); + break; + default: + *format = "raw"; + ret = create_raw_imagefile(imgfile_path, imgsize); + break; + } + + return (ret); +} + +/* * create_raw_imagefile * * Create an empty imagefile with the specified path and size. @@ -831,7 +942,7 @@ create_raw_imagefile(const char *imgfile_path, long imgsize) } /* - * create_imagefile + * create_qc2_imagefile * * Create an empty qcow2 imagefile with the specified path and size. * @@ -844,8 +955,6 @@ create_raw_imagefile(const char *imgfile_path, long imgsize) * 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, const char *base_path, long imgsize) diff --git a/usr.sbin/vmctl/vmctl.h b/usr.sbin/vmctl/vmctl.h index 5ae88268fd7..2948a4fc0ce 100644 --- a/usr.sbin/vmctl/vmctl.h +++ b/usr.sbin/vmctl/vmctl.h @@ -1,4 +1,4 @@ -/* $OpenBSD: vmctl.h,v 1.26 2018/10/08 16:32:01 reyk Exp $ */ +/* $OpenBSD: vmctl.h,v 1.27 2018/10/19 10:12:39 reyk Exp $ */ /* * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org> @@ -71,6 +71,9 @@ struct ctl_command { struct imsgbuf *ibuf; +#define ALIGN(sz, align) ((sz + align - 1) & ~(align - 1)) +#define MIN(a,b) (((a)<(b))?(a):(b)) + /* main.c */ int vmmaction(struct parse_result *); int parse_ifs(struct parse_result *, char *, int); @@ -86,6 +89,9 @@ __dead void ctl_openconsole(const char *); /* vmctl.c */ +int open_imagefile(int, const char *, int, + struct virtio_backing *, off_t *); +int create_imagefile(int, const char *, const char *, long, const char **); int create_raw_imagefile(const char *, long); int create_qc2_imagefile(const char *, const char *, long); int vm_start(uint32_t, const char *, int, int, char **, int, diff --git a/usr.sbin/vmd/config.c b/usr.sbin/vmd/config.c index 373e027b425..a749e3595b5 100644 --- a/usr.sbin/vmd/config.c +++ b/usr.sbin/vmd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.52 2018/10/15 10:35:41 reyk Exp $ */ +/* $OpenBSD: config.c,v 1.53 2018/10/19 10:12:39 reyk Exp $ */ /* * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org> @@ -35,7 +35,6 @@ #include <util.h> #include <errno.h> #include <imsg.h> -#include <libgen.h> #include "proc.h" #include "vmd.h" @@ -191,7 +190,6 @@ config_setvm(struct privsep *ps, struct vmd_vm *vm, uint32_t peerid, uid_t uid) char ifname[IF_NAMESIZE], *s; char path[PATH_MAX]; char base[PATH_MAX]; - char expanded[PATH_MAX]; unsigned int unit; struct timeval tv, rate, since_last; @@ -357,7 +355,7 @@ config_setvm(struct privsep *ps, struct vmd_vm *vm, uint32_t peerid, uid_t uid) oflags = O_RDONLY|O_NONBLOCK; aflags = R_OK; n = virtio_get_base(diskfds[i][j], base, sizeof base, - vmc->vmc_disktypes[i]); + vmc->vmc_disktypes[i], path); if (n == 0) break; if (n == -1) { @@ -366,30 +364,6 @@ config_setvm(struct privsep *ps, struct vmd_vm *vm, uint32_t peerid, uid_t uid) base, vcp->vcp_disks[i]); goto fail; } - /* - * Relative paths should be interpreted relative - * to the disk image, rather than relative to the - * directory vmd happens to be running in, since - * this is the only userful interpretation. - */ - if (base[0] == '/') { - if (realpath(base, path) == NULL) { - log_warn("unable to resolve %s", base); - goto fail; - } - } else { - s = dirname(path); - if (snprintf(expanded, sizeof(expanded), - "%s/%s", s, base) >= (int)sizeof(expanded)) { - log_warn("path too long: %s/%s", - s, base); - goto fail; - } - if (realpath(expanded, path) == NULL) { - log_warn("unable to resolve %s", base); - goto fail; - } - } } } diff --git a/usr.sbin/vmd/vioqcow2.c b/usr.sbin/vmd/vioqcow2.c index 8f56d2172d4..3a215599d49 100644 --- a/usr.sbin/vmd/vioqcow2.c +++ b/usr.sbin/vmd/vioqcow2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: vioqcow2.c,v 1.8 2018/10/08 16:32:01 reyk Exp $ */ +/* $OpenBSD: vioqcow2.c,v 1.9 2018/10/19 10:12:39 reyk Exp $ */ /* * Copyright (c) 2018 Ori Bernstein <ori@eigenstate.org> @@ -27,6 +27,7 @@ #include <unistd.h> #include <fcntl.h> #include <assert.h> +#include <libgen.h> #include <err.h> #include "vmd.h" @@ -137,11 +138,13 @@ virtio_init_qcow2(struct virtio_backing *file, off_t *szp, int *fd, size_t nfd) } ssize_t -virtio_qcow2_get_base(int fd, char *path, size_t npath) +virtio_qcow2_get_base(int fd, char *path, size_t npath, const char *dpath) { + char expanded[PATH_MAX]; struct qcheader header; uint64_t backingoff; uint32_t backingsz; + char *s = NULL; if (pread(fd, &header, sizeof(header), 0) != sizeof(header)) { log_warnx("%s: short read on header", __func__); @@ -153,19 +156,47 @@ virtio_qcow2_get_base(int fd, char *path, size_t npath) } backingoff = be64toh(header.backingoff); backingsz = be32toh(header.backingsz); - if (backingsz != 0) { - if (backingsz >= npath - 1) { - log_warn("%s: snapshot path too long", __func__); + if (backingsz == 0) + return 0; + + if (backingsz >= npath - 1) { + log_warn("%s: snapshot path too long", __func__); + return -1; + } + if (pread(fd, path, backingsz, backingoff) != backingsz) { + log_warnx("%s: could not read snapshot base name", + __func__); + return -1; + } + path[backingsz] = '\0'; + + /* + * Relative paths should be interpreted relative to the disk image, + * rather than relative to the directory vmd happens to be running in, + * since this is the only userful interpretation. + */ + if (path[0] == '/') { + if (realpath(path, expanded) == NULL || + strlcpy(path, expanded, npath) >= npath) { + log_warn("unable to resolve %s", path); return -1; } - if (pread(fd, path, backingsz, backingoff) != backingsz) { - log_warnx("%s: could not read snapshot base name", - __func__); + } else { + s = dirname(dpath); + if (snprintf(expanded, sizeof(expanded), + "%s/%s", s, path) >= (int)sizeof(expanded)) { + log_warn("path too long: %s/%s", + s, path); + return -1; + } + if (npath < PATH_MAX || + realpath(expanded, path) == NULL) { + log_warn("unable to resolve %s", path); return -1; } - path[backingsz] = '\0'; } - return backingsz; + + return strlen(path); } static int diff --git a/usr.sbin/vmd/virtio.c b/usr.sbin/vmd/virtio.c index a0cb45aa8ad..4b84ae467fd 100644 --- a/usr.sbin/vmd/virtio.c +++ b/usr.sbin/vmd/virtio.c @@ -1,4 +1,4 @@ -/* $OpenBSD: virtio.c,v 1.72 2018/10/08 16:32:01 reyk Exp $ */ +/* $OpenBSD: virtio.c,v 1.73 2018/10/19 10:12:39 reyk Exp $ */ /* * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org> @@ -1746,13 +1746,13 @@ vmmci_io(int dir, uint16_t reg, uint32_t *data, uint8_t *intr, } int -virtio_get_base(int fd, char *path, size_t npath ,int type) +virtio_get_base(int fd, char *path, size_t npath, int type, const char *dpath) { switch (type) { case VMDF_RAW: return 0; case VMDF_QCOW2: - return virtio_qcow2_get_base(fd, path, npath); + return virtio_qcow2_get_base(fd, path, npath, dpath); } log_warnx("%s: invalid disk format", __func__); return -1; diff --git a/usr.sbin/vmd/virtio.h b/usr.sbin/vmd/virtio.h index 354d0fdbce0..28aedc93e84 100644 --- a/usr.sbin/vmd/virtio.h +++ b/usr.sbin/vmd/virtio.h @@ -1,4 +1,4 @@ -/* $OpenBSD: virtio.h,v 1.31 2018/10/08 16:32:01 reyk Exp $ */ +/* $OpenBSD: virtio.h,v 1.32 2018/10/19 10:12:39 reyk Exp $ */ /* * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org> @@ -271,7 +271,7 @@ void viornd_update_qs(void); void viornd_update_qa(void); int viornd_notifyq(void); -ssize_t virtio_qcow2_get_base(int, char *, size_t); +ssize_t virtio_qcow2_get_base(int, char *, size_t, const char *); int virtio_init_raw(struct virtio_backing *, off_t *, int*, size_t); int virtio_init_qcow2(struct virtio_backing *, off_t *, int*, size_t); diff --git a/usr.sbin/vmd/vmd.h b/usr.sbin/vmd/vmd.h index 4d7b0380294..7ae4e4bd65e 100644 --- a/usr.sbin/vmd/vmd.h +++ b/usr.sbin/vmd/vmd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: vmd.h,v 1.83 2018/10/15 10:35:41 reyk Exp $ */ +/* $OpenBSD: vmd.h,v 1.84 2018/10/19 10:12:39 reyk Exp $ */ /* * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org> @@ -426,6 +426,6 @@ int cmdline_symset(char *); int host(const char *, struct address *); /* virtio.c */ -int virtio_get_base(int, char *, size_t, int); +int virtio_get_base(int, char *, size_t, int, const char *); #endif /* VMD_H */ |