This introduces new grammar and the -t optional in vmctl start.
(For now, only root can create VM instances; but it is planned to allow
users to create their own VMs based on permissions and quota.)
OK ccardenas@ mlarkin@ jmc@
-/* $OpenBSD: main.c,v 1.37 2018/07/11 13:19:47 reyk Exp $ */
+/* $OpenBSD: main.c,v 1.38 2018/07/12 12:04:49 reyk Exp $ */
/*
* Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
{ "show", CMD_STATUS, ctl_status, "[id]" },
{ "start", CMD_START, ctl_start, "\"name\""
" [-Lc] [-b image] [-r image] [-m size]\n"
- "\t\t[-n switch] [-i count] [-d disk]*" },
+ "\t\t[-n switch] [-i count] [-d disk]* [-I name]" },
{ "status", CMD_STATUS, ctl_status, "[id]" },
{ "stop", CMD_STOP, ctl_stop, "id [-fw]" },
{ "pause", CMD_PAUSE, ctl_pause, "id" },
case CMD_START:
ret = vm_start(res->id, res->name, res->size, res->nifs,
res->nets, res->ndisks, res->disks, res->path,
- res->isopath);
+ res->isopath, res->instance);
if (ret) {
errno = ret;
err(1, "start VM operation failed");
free(res->name);
free(res->path);
free(res->isopath);
+ free(res->instance);
for (i = 0; i < res->ndisks; i++)
free(res->disks[i]);
free(res->disks);
return (0);
}
+int
+parse_instance(struct parse_result *res, char *word)
+{
+ if (strlen(word) >= VMM_MAX_NAME_LEN) {
+ warnx("instance vm name too long");
+ return (-1);
+ }
+ res->id = 0;
+ if ((res->instance = strdup(word)) == NULL)
+ errx(1, "strdup");
+
+ return (0);
+}
+
int
ctl_create(struct parse_result *res, int argc, char *argv[])
{
argc--;
argv++;
- while ((ch = getopt(argc, argv, "b:r:cLm:n:d:i:")) != -1) {
+ while ((ch = getopt(argc, argv, "b:r:cLm:n:d:I:i:")) != -1) {
switch (ch) {
case 'b':
if (res->path)
if (parse_disk(res, path) != 0)
errx(1, "invalid disk: %s", optarg);
break;
+ case 'I':
+ if (parse_instance(res, optarg) == -1)
+ errx(1, "invalid name: %s", optarg);
+ break;
case 'i':
if (res->nifs != -1)
errx(1, "interfaces specified multiple times");
-.\" $OpenBSD: vmctl.8,v 1.42 2018/07/11 17:21:57 jmc Exp $
+.\" $OpenBSD: vmctl.8,v 1.43 2018/07/12 12:04:49 reyk Exp $
.\"
.\" Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
.\"
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: July 11 2018 $
+.Dd $Mdocdate: July 12 2018 $
.Dt VMCTL 8
.Os
.Sh NAME
.Op Fl n Ar switch
.Bk -words
.Op Fl r Ar path
+.Op Fl t Ar name
.Ek
.Xc
Starts a VM defined by the specified name and parameters:
-.Bl -tag -width "-i count"
+.Bl -tag -width "-I parent"
.It Fl b Ar path
Boot the VM with the specified kernel or BIOS image.
If not specified, the default is to boot using the BIOS image in
selected VM as a SCSI CD-ROM device attached to a virtio SCSI adapter
(e.g.\&
.Xr vioscsi 4 ) .
+.It Fl t Ar name
+Use an existing VM with the specified
+.Ar name
+as a template to create a new VM instance.
+The instance will inherit settings from the parent VM,
+except for exclusive options such as disk, interface lladdr, or
+interface names.
.El
.Pp
Note that the VM name supplied to the 'start' command can only consist of
# vmctl start "myvm" -m 1G -i 1 -b /bsd -d disk.img
.Ed
.Pp
+Start a new VM instance with the name 'myvm' from a pre-configured
+VM 'openbsd.4G':
+.Bd -literal -offset indent
+# vmctl start "myvm" -t "openbsd.4G" -d mydisk.img
+.Ed
+.Pp
.Xr vmd 8
will create a new
.Xr tap 4
-/* $OpenBSD: vmctl.c,v 1.53 2018/07/11 21:29:05 reyk Exp $ */
+/* $OpenBSD: vmctl.c,v 1.54 2018/07/12 12:04:49 reyk Exp $ */
/*
* Copyright (c) 2014 Mike Larkin <mlarkin@openbsd.org>
* disks: disk image file names
* kernel: kernel image to load
* iso: iso image file
+ * instance: create instance from vm
*
* Return:
* 0 if the request to start the VM was sent successfully.
*/
int
vm_start(uint32_t start_id, const char *name, int memsize, int nnics,
- char **nics, int ndisks, char **disks, char *kernel, char *iso)
+ char **nics, int ndisks, char **disks, char *kernel, char *iso,
+ char *instance)
{
struct vmop_create_params *vmc;
struct vm_create_params *vcp;
flags |= VMOP_CREATE_KERNEL;
if (iso)
flags |= VMOP_CREATE_CDROM;
- if (flags != 0) {
+ if (instance)
+ flags |= VMOP_CREATE_INSTANCE;
+ else if (flags != 0) {
if (memsize < 1)
memsize = VM_DEFAULT_MEMORY;
if (ndisks > VMM_MAX_DISKS_PER_VM)
if (strlcpy(vcp->vcp_cdrom, iso,
sizeof(vcp->vcp_cdrom)) >= sizeof(vcp->vcp_cdrom))
errx(1, "cdrom name too long");
+ if (instance != NULL)
+ if (strlcpy(vmc->vmc_instance, instance,
+ sizeof(vmc->vmc_instance)) >= sizeof(vmc->vmc_instance))
+ errx(1, "instance vm name too long");
imsg_compose(ibuf, IMSG_VMDOP_START_VM_REQUEST, 0, 0, -1,
vmc, sizeof(struct vmop_create_params));
*ret = ENOENT;
break;
case VMD_DISK_MISSING:
- warnx("could not open specified disk image(s)");
+ warnx("could not open disk image(s)");
*ret = ENOENT;
break;
case VMD_DISK_INVALID:
-/* $OpenBSD: vmctl.h,v 1.20 2018/07/11 13:19:47 reyk Exp $ */
+/* $OpenBSD: vmctl.h,v 1.21 2018/07/12 12:04:49 reyk Exp $ */
/*
* Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
size_t ndisks;
char **disks;
int verbose;
+ char *instance;
unsigned int flags;
unsigned int mode;
struct ctl_command *ctl;
int parse_size(struct parse_result *, char *, long long);
int parse_disk(struct parse_result *, char *);
int parse_vmid(struct parse_result *, char *, int);
+int parse_instance(struct parse_result *, char *);
void parse_free(struct parse_result *);
int parse(int, char *[]);
__dead void
/* vmctl.c */
int create_imagefile(const char *, long);
int vm_start(uint32_t, const char *, int, int, char **, int,
- char **, char *, char *);
+ char **, char *, char *, char *);
int vm_start_complete(struct imsg *, int *, int);
void terminate_vm(uint32_t, const char *, unsigned int);
int terminate_vm_complete(struct imsg *, int *, unsigned int);
-/* $OpenBSD: parse.y,v 1.40 2018/07/11 16:43:24 reyk Exp $ */
+/* $OpenBSD: parse.y,v 1.41 2018/07/12 12:04:49 reyk Exp $ */
/*
* Copyright (c) 2007-2016 Reyk Floeter <reyk@openbsd.org>
static struct vmop_create_params vmc;
static struct vm_create_params *vcp;
static struct vmd_switch *vsw;
-static struct vmd_vm *vm;
static char vsw_type[IF_NAMESIZE];
static int vcp_disable;
static size_t vcp_nnics;
%token INCLUDE ERROR
-%token ADD BOOT CDROM DISABLE DISK DOWN ENABLE GROUP INTERFACE LLADDR
+%token ADD BOOT CDROM DISABLE DISK DOWN ENABLE GROUP INSTANCE INTERFACE LLADDR
%token LOCAL LOCKED MEMORY NIFS OWNER PATH PREFIX RDOMAIN SIZE SOCKET SWITCH
%token UP VM VMID
%token <v.number> NUMBER
%type <v.owner> owner_id
%type <v.string> optstring
%type <v.string> string
+%type <v.string> vm_instance
%%
}
;
-vm : VM string {
+vm : VM string vm_instance {
unsigned int i;
+ char *name;
memset(&vmc, 0, sizeof(vmc));
vcp = &vmc.vmc_params;
vcp_disable = 0;
vcp_nnics = 0;
+ if ($3 != NULL) {
+ /* This is an instance of a pre-configured VM */
+ if (strlcpy(vmc.vmc_instance, $2,
+ sizeof(vmc.vmc_instance)) >=
+ sizeof(vmc.vmc_instance)) {
+ yyerror("vm %s name too long", $2);
+ free($2);
+ free($3);
+ YYERROR;
+ }
+
+ free($2);
+ name = $3;
+ vmc.vmc_flags |= VMOP_CREATE_INSTANCE;
+ } else
+ name = $2;
+
for (i = 0; i < VMM_MAX_NICS_PER_VM; i++) {
/* Set the interface to UP by default */
vmc.vmc_ifflags[i] |= IFF_UP;
}
- if (strlcpy(vcp->vcp_name, $2, sizeof(vcp->vcp_name)) >=
- sizeof(vcp->vcp_name)) {
+ if (strlcpy(vcp->vcp_name, name,
+ sizeof(vcp->vcp_name)) >= sizeof(vcp->vcp_name)) {
yyerror("vm name too long");
+ free($2);
+ free($3);
YYERROR;
}
vmc.vmc_uid = 0;
vmc.vmc_gid = -1;
} '{' optnl vm_opts_l '}' {
- int ret;
+ struct vmd_vm *vm;
+ int ret;
/* configured interfaces vs. number of interfaces */
if (vcp_nnics > vcp->vcp_nnics)
vcp->vcp_name, vm->vm_running ?
"running" : "already exists");
} else if (ret == -1) {
- log_warn("%s:%d: vm \"%s\" failed",
- file->name, yylval.lineno,
- vcp->vcp_name);
+ yyerror("vm \"%s\" failed: %s",
+ vcp->vcp_name, strerror(errno));
YYERROR;
} else {
if (vcp_disable)
}
;
+vm_instance : /* empty */ { $$ = NULL; }
+ | INSTANCE string { $$ = $2; }
+ ;
+
vm_opts_l : vm_opts_l vm_opts nl
| vm_opts optnl
;
{ "group", GROUP },
{ "id", VMID },
{ "include", INCLUDE },
+ { "instance", INSTANCE },
{ "interface", INTERFACE },
{ "interfaces", NIFS },
{ "lladdr", LLADDR },
int
parse_disk(char *word)
{
+ char path[PATH_MAX];
+
if (vcp->vcp_ndisks >= VMM_MAX_DISKS_PER_VM) {
log_warnx("too many disks");
return (-1);
}
- if (strlcpy(vcp->vcp_disks[vcp->vcp_ndisks], word,
+ if (realpath(word, path) == NULL) {
+ log_warn("disk %s", word);
+ return (-1);
+ }
+
+ if (strlcpy(vcp->vcp_disks[vcp->vcp_ndisks], path,
VMM_MAX_PATH_DISK) >= VMM_MAX_PATH_DISK) {
log_warnx("disk path too long");
return (-1);
-.\" $OpenBSD: vm.conf.5,v 1.31 2018/06/26 11:34:25 jmc Exp $
+.\" $OpenBSD: vm.conf.5,v 1.32 2018/07/12 12:04:49 reyk Exp $
.\"
.\" Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
.\" Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: June 26 2018 $
+.Dd $Mdocdate: July 12 2018 $
.Dt VM.CONF 5
.Os
.Sh NAME
The name can be any alphanumeric string along with '.', '-', and '_' characters.
However, it cannot start with '.', '-', or '_'.
Typically the name is a hostname.
+.It Ic vm Ar parent Ic instance Ar name Brq ...
+A virtual machine can be created as an instance of any other
+configured VM.
+The new instance will inherit settings from the VM
+.Ar parent ,
+except for exclusive options such as
+.Ic disk ,
+.Ic interface lladdr ,
+or
+.Ic interface name .
.El
.Pp
Followed by a block of parameters that is enclosed in curly brackets:
}
.Ed
.Pp
+Create a new VM as an instance from
+.Sq vm2.example.com :
+.Bd -literal -offset indent
+vm "vm2.example.com" instance "vm3.example.com" {
+ disk "/home/joe/vm3-disk.img"
+}
+.Ed
+.Pp
Create the switch "uplink" with an additional physical network interface:
.Bd -literal -offset indent
switch "uplink" {
-/* $OpenBSD: vmd.c,v 1.94 2018/07/11 16:37:31 reyk Exp $ */
+/* $OpenBSD: vmd.c,v 1.95 2018/07/12 12:04:49 reyk Exp $ */
/*
* Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
int vmd_dispatch_vmm(int, struct privsep_proc *, struct imsg *);
int vmd_check_vmh(struct vm_dump_header *);
+int vm_instance(struct privsep *, struct vmd_vm **,
+ struct vmop_create_params *, uid_t);
+
struct vmd *env;
static struct privsep_proc procs[] = {
/* For the privileged process */
static struct privsep_proc *proc_priv = &procs[0];
static struct passwd proc_privpw;
+static const uint8_t zero_mac[ETHER_ADDR_LEN];
int
vmd_dispatch_control(int fd, struct privsep_proc *p, struct imsg *imsg)
vm_register(struct privsep *ps, struct vmop_create_params *vmc,
struct vmd_vm **ret_vm, uint32_t id, uid_t uid)
{
- struct vmd_vm *vm = NULL;
+ struct vmd_vm *vm = NULL, *vm_parent = NULL;
struct vm_create_params *vcp = &vmc->vmc_params;
- static const uint8_t zero_mac[ETHER_ADDR_LEN];
uint32_t rng;
unsigned int i;
struct vmd_switch *sw;
char *s;
+ /* Check if this is an instance of another VM */
+ if (vm_instance(ps, &vm_parent, vmc, uid) == -1)
+ return (-1);
+
errno = 0;
*ret_vm = NULL;
goto fail;
}
- /*
- * non-root users can only start existing VMs
- * XXX there could be a mechanism to allow overriding some options
- */
- if (vm_checkperm(NULL, uid) != 0) {
+ /* non-root users can only start existing VMs or instances */
+ if (vm_checkperm(vm_parent, uid) != 0) {
errno = EPERM;
goto fail;
}
if (vmc->vmc_flags == 0) {
- errno = ENOENT;
+ log_warnx("invalid configuration, no devices");
+ errno = VMD_DISK_MISSING;
goto fail;
}
if (vcp->vcp_ncpus == 0)
return (-1);
}
+int
+vm_instance(struct privsep *ps, struct vmd_vm **vm_parent,
+ struct vmop_create_params *vmc, uid_t uid)
+{
+ char *name;
+ struct vm_create_params *vcp = &vmc->vmc_params;
+ struct vmop_create_params *vmcp;
+ struct vm_create_params *vcpp;
+ struct vmd_vm *vm = NULL;
+ unsigned int i, j;
+ uint32_t id;
+
+ /* return without error if the parent is NULL (nothing to inherit) */
+ if ((vmc->vmc_flags & VMOP_CREATE_INSTANCE) == 0 ||
+ (*vm_parent = vm_getbyname(vmc->vmc_instance)) == NULL)
+ return (0);
+
+ errno = 0;
+ vmcp = &(*vm_parent)->vm_params;
+ vcpp = &vmcp->vmc_params;
+
+ /*
+ * Are we allowed to create an instance from this VM?
+ *
+ * XXX This is currently allowed for root but will check *vm_parent
+ * XXX once we defined instance permissions and quota.
+ */
+ if (vm_checkperm(NULL, uid) != 0) {
+ log_warnx("vm \"%s\" no permission to create vm instance",
+ vcpp->vcp_name);
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+
+ id = vcp->vcp_id;
+ name = vcp->vcp_name;
+
+ if ((vm = vm_getbyname(vcp->vcp_name)) != NULL ||
+ (vm = vm_getbyvmid(vcp->vcp_id)) != NULL) {
+ errno = EPROCLIM;
+ return (-1);
+ }
+
+ /* machine options */
+ if (vcp->vcp_ncpus == 0)
+ vcp->vcp_ncpus = vcpp->vcp_ncpus;
+ if (vcp->vcp_memranges[0].vmr_size == 0)
+ vcp->vcp_memranges[0].vmr_size =
+ vcpp->vcp_memranges[0].vmr_size;
+
+ /* disks cannot be inherited */
+ for (i = 0; i < vcp->vcp_ndisks; i++) {
+ /* Check if this disk is already used in the parent */
+ for (j = 0; j < vcpp->vcp_ndisks; j++) {
+ if (strcmp(vcp->vcp_disks[i],
+ vcpp->vcp_disks[j]) == 0) {
+ log_warnx("vm \"%s\" disk %s cannot be reused",
+ name, vcp->vcp_disks[i]);
+ errno = EBUSY;
+ return (-1);
+ }
+ }
+ }
+
+ /* interfaces */
+ for (i = 0; i < vcpp->vcp_nnics; i++) {
+ /* Interface got overwritten */
+ if (i < vcp->vcp_nnics)
+ continue;
+
+ /* Copy interface from parent */
+ vmc->vmc_ifflags[i] = vmcp->vmc_ifflags[i];
+ (void)strlcpy(vmc->vmc_ifnames[i], vmcp->vmc_ifnames[i],
+ sizeof(vmc->vmc_ifnames[i]));
+ (void)strlcpy(vmc->vmc_ifswitch[i], vmcp->vmc_ifswitch[i],
+ sizeof(vmc->vmc_ifswitch[i]));
+ (void)strlcpy(vmc->vmc_ifgroup[i], vmcp->vmc_ifgroup[i],
+ sizeof(vmc->vmc_ifgroup[i]));
+ memcpy(vcp->vcp_macs[i], vcpp->vcp_macs[i],
+ sizeof(vcp->vcp_macs[i]));
+ vmc->vmc_ifrdomain[i] = vmcp->vmc_ifrdomain[i];
+ vcp->vcp_nnics++;
+ }
+ for (i = 0; i < vcp->vcp_nnics; i++) {
+ for (j = 0; j < vcpp->vcp_nnics; j++) {
+ if (memcmp(zero_mac, vcp->vcp_macs[i],
+ sizeof(vcp->vcp_macs[i])) != 0 &&
+ memcmp(vcpp->vcp_macs[i], vcp->vcp_macs[i],
+ sizeof(vcp->vcp_macs[i])) != 0) {
+ log_warnx("vm \"%s\" lladdr cannot be reused",
+ name);
+ errno = EBUSY;
+ return (-1);
+ }
+ if (strlen(vmc->vmc_ifnames[i]) &&
+ strcmp(vmc->vmc_ifnames[i],
+ vmcp->vmc_ifnames[j]) == 0) {
+ log_warnx("vm \"%s\" %s cannot be reused",
+ vmc->vmc_ifnames[i], name);
+ errno = EBUSY;
+ return (-1);
+ }
+ }
+ }
+
+ /* kernel */
+ if (strlen(vcp->vcp_kernel) == 0 &&
+ strlcpy(vcp->vcp_kernel, vcpp->vcp_kernel,
+ sizeof(vcp->vcp_kernel)) >= sizeof(vcp->vcp_kernel)) {
+ log_warnx("vm \"%s\" kernel name too long", name);
+ errno = EINVAL;
+ return (-1);
+ }
+
+ /* cdrom */
+ if (strlen(vcp->vcp_cdrom) == 0 &&
+ strlcpy(vcp->vcp_cdrom, vcpp->vcp_cdrom,
+ sizeof(vcp->vcp_cdrom)) >= sizeof(vcp->vcp_cdrom)) {
+ log_warnx("vm \"%s\" cdrom name too long", name);
+ errno = EINVAL;
+ return (-1);
+ }
+
+ /* user */
+ if (vmc->vmc_uid == 0)
+ vmc->vmc_uid = vmcp->vmc_uid;
+ else if (vmc->vmc_uid != vmcp->vmc_uid) {
+ log_warnx("vm \"%s\" user mismatch", name);
+ errno = EPERM;
+ return (-1);
+ }
+
+ /* group */
+ if (vmc->vmc_gid == 0)
+ vmc->vmc_gid = vmcp->vmc_gid;
+ else if (vmc->vmc_gid != vmcp->vmc_gid) {
+ log_warnx("vm \"%s\" group mismatch", name);
+ errno = EPERM;
+ return (-1);
+ }
+
+ /* finished, remove instance flags */
+ vmc->vmc_flags &= ~VMOP_CREATE_INSTANCE;
+
+ return (0);
+}
+
/*
* vm_checkperm
*
-/* $OpenBSD: vmd.h,v 1.74 2018/07/11 13:19:47 reyk Exp $ */
+/* $OpenBSD: vmd.h,v 1.75 2018/07/12 12:04:49 reyk Exp $ */
/*
* Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
#define VMOP_CREATE_NETWORK 0x04
#define VMOP_CREATE_DISK 0x08
#define VMOP_CREATE_CDROM 0x10
+#define VMOP_CREATE_INSTANCE 0x20
/* userland-only part of the create params */
unsigned int vmc_ifflags[VMM_MAX_NICS_PER_VM];
char vmc_ifswitch[VMM_MAX_NICS_PER_VM][VM_NAME_MAX];
char vmc_ifgroup[VMM_MAX_NICS_PER_VM][IF_NAMESIZE];
unsigned int vmc_ifrdomain[VMM_MAX_NICS_PER_VM];
+ char vmc_instance[VMM_MAX_NAME_LEN];
uid_t vmc_uid;
int64_t vmc_gid;
};
-/* $OpenBSD: vmm.c,v 1.86 2018/07/11 13:19:47 reyk Exp $ */
+/* $OpenBSD: vmm.c,v 1.87 2018/07/12 12:04:49 reyk Exp $ */
/*
* Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
case IMSG_VMDOP_RECEIVE_VM_REQUEST:
IMSG_SIZE_CHECK(imsg, &vmc);
memcpy(&vmc, imsg->data, sizeof(vmc));
- ret = vm_register(ps, &vmc, &vm, imsg->hdr.peerid, vmc.vmc_uid);
+ ret = vm_register(ps, &vmc, &vm,
+ imsg->hdr.peerid, vmc.vmc_uid);
vm->vm_tty = imsg->fd;
vm->vm_received = 1;
break;