xref: /openbsd-src/usr.sbin/vmctl/vmctl.c (revision 0b7734b3d77bb9b21afec6f4621cae6c805dbd45)
1 /*	$OpenBSD: vmctl.c,v 1.15 2016/05/10 11:00:54 mlarkin Exp $	*/
2 
3 /*
4  * Copyright (c) 2014 Mike Larkin <mlarkin@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/queue.h>
21 #include <sys/uio.h>
22 #include <sys/stat.h>
23 #include <sys/socket.h>
24 #include <sys/un.h>
25 
26 #include <machine/vmmvar.h>
27 
28 #include <err.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <imsg.h>
32 #include <limits.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 
38 #include "vmd.h"
39 #include "vmctl.h"
40 
41 extern char *__progname;
42 uint32_t info_id;
43 char info_name[VMM_MAX_NAME_LEN];
44 int info_console;
45 
46 /*
47  * start_vm
48  *
49  * Request vmd to start the VM defined by the supplied parameters
50  *
51  * Parameters:
52  *  name: optional name of the VM
53  *  memsize: memory size (MB) of the VM to create
54  *  nnics: number of vionet network interfaces to create
55  *  ndisks: number of disk images
56  *  disks: disk image file names
57  *  kernel: kernel image to load
58  *
59  * Return:
60  *  0 if the request to start the VM was sent successfully.
61  *  ENOMEM if a memory allocation failure occurred.
62  */
63 int
64 start_vm(const char *name, int memsize, int nnics, int ndisks, char **disks,
65     char *kernel)
66 {
67 	struct vm_create_params *vcp;
68 	int i;
69 
70 	if (memsize < 1)
71 		errx(1, "specified memory size too small");
72 	if (kernel == NULL)
73 		errx(1, "no kernel specified");
74 	if (ndisks > VMM_MAX_DISKS_PER_VM)
75 		errx(1, "too many disks");
76 	else if (ndisks == 0)
77 		warnx("starting without disks");
78 	if (nnics == -1)
79 		nnics = 0;
80 	if (nnics == 0)
81 		warnx("starting without network interfaces");
82 
83 	vcp = malloc(sizeof(struct vm_create_params));
84 	if (vcp == NULL)
85 		return (ENOMEM);
86 
87 	bzero(vcp, sizeof(struct vm_create_params));
88 
89 	/*
90 	 * XXX: vmd(8) fills in the actual memory ranges. vmctl(8)
91 	 * just passes in the actual memory size in MB here.
92 	 */
93 	vcp->vcp_nmemranges = 1;
94 	vcp->vcp_memranges[0].vmr_size = memsize;
95 
96 	vcp->vcp_ncpus = 1;
97 	vcp->vcp_ndisks = ndisks;
98 
99 	for (i = 0 ; i < ndisks; i++)
100 		strlcpy(vcp->vcp_disks[i], disks[i], VMM_MAX_PATH_DISK);
101 
102 	if (name != NULL)
103 		strlcpy(vcp->vcp_name, name, VMM_MAX_NAME_LEN);
104 	strlcpy(vcp->vcp_kernel, kernel, VMM_MAX_KERNEL_PATH);
105 	vcp->vcp_nnics = nnics;
106 
107 	imsg_compose(ibuf, IMSG_VMDOP_START_VM_REQUEST, 0, 0, -1,
108 	    vcp, sizeof(struct vm_create_params));
109 
110 	free(vcp);
111 	return (0);
112 }
113 
114 /*
115  * start_vm_complete
116  *
117  * Callback function invoked when we are expecting an
118  * IMSG_VMDOP_START_VMM_RESPONSE message indicating the completion of
119  * a start vm operation.
120  *
121  * Parameters:
122  *  imsg : response imsg received from vmd
123  *  ret  : return value
124  *  autoconnect : open the console after startup
125  *
126  * Return:
127  *  Always 1 to indicate we have processed the return message (even if it
128  *  was an incorrect/failure message)
129  *
130  *  The function also sets 'ret' to the error code as follows:
131  *   0     : Message successfully processed
132  *   EINVAL: Invalid or unexpected response from vmd
133  *   EIO   : start_vm command failed
134  */
135 int
136 start_vm_complete(struct imsg *imsg, int *ret, int autoconnect)
137 {
138 	struct vmop_result *vmr;
139 	int res;
140 
141 	if (imsg->hdr.type == IMSG_VMDOP_START_VM_RESPONSE) {
142 		vmr = (struct vmop_result *)imsg->data;
143 		res = vmr->vmr_result;
144 		if (res) {
145 			errno = res;
146 			warn("start vm command failed");
147 			*ret = EIO;
148 		} else if (autoconnect) {
149 			/* does not return */
150 			ctl_openconsole(vmr->vmr_ttyname);
151 		} else {
152 			warnx("started vm %d successfully, tty %s",
153 			    vmr->vmr_id, vmr->vmr_ttyname);
154 			*ret = 0;
155 		}
156 	} else {
157 		warnx("unexpected response received from vmd");
158 		*ret = EINVAL;
159 	}
160 
161 	return (1);
162 }
163 
164 /*
165  * terminate_vm
166  *
167  * Request vmd to stop the VM indicated
168  *
169  * Parameters:
170  *  terminate_id: ID of the vm to be terminated
171  *  name: optional name of the VM to be terminated
172  */
173 void
174 terminate_vm(uint32_t terminate_id, const char *name)
175 {
176 	struct vmop_id vid;
177 
178 	memset(&vid, 0, sizeof(vid));
179 	vid.vid_id = terminate_id;
180 	if (name != NULL)
181 		(void)strlcpy(vid.vid_name, name, sizeof(vid.vid_name));
182 
183 	imsg_compose(ibuf, IMSG_VMDOP_TERMINATE_VM_REQUEST, 0, 0, -1,
184 	    &vid, sizeof(vid));
185 }
186 
187 /*
188  * terminate_vm_complete
189  *
190  * Callback function invoked when we are expecting an
191  * IMSG_VMDOP_TERMINATE_VMM_RESPONSE message indicating the completion of
192  * a terminate vm operation.
193  *
194  * Parameters:
195  *  imsg : response imsg received from vmd
196  *  ret  : return value
197  *
198  * Return:
199  *  Always 1 to indicate we have processed the return message (even if it
200  *  was an incorrect/failure message)
201  *
202  *  The function also sets 'ret' to the error code as follows:
203  *   0     : Message successfully processed
204  *   EINVAL: Invalid or unexpected response from vmd
205  *   EIO   : terminate_vm command failed
206  */
207 int
208 terminate_vm_complete(struct imsg *imsg, int *ret)
209 {
210 	struct vmop_result *vmr;
211 	int res;
212 
213 	if (imsg->hdr.type == IMSG_VMDOP_TERMINATE_VM_RESPONSE) {
214 		vmr = (struct vmop_result *)imsg->data;
215 		res = vmr->vmr_result;
216 		if (res) {
217 			errno = res;
218 			warn("terminate vm command failed");
219 			*ret = EIO;
220 		} else {
221 			warnx("terminated vm %d successfully", vmr->vmr_id);
222 			*ret = 0;
223 		}
224 	} else {
225 		warnx("unexpected response received from vmd");
226 		*ret = EINVAL;
227 	}
228 
229 	return (1);
230 }
231 
232 /*
233  * get_info_vm
234  *
235  * Return the list of all running VMs or find a specific VM by ID or name.
236  *
237  * Parameters:
238  *  id: optional ID of a VM to list
239  *  name: optional name of a VM to list
240  *  console: if true, open the console of the selected VM (by name or ID)
241  *
242  * Request a list of running VMs from vmd
243  */
244 void
245 get_info_vm(uint32_t id, const char *name, int console)
246 {
247 	info_id = id;
248 	if (name != NULL)
249 		(void)strlcpy(info_name, name, sizeof(info_name));
250 	info_console = console;
251 	imsg_compose(ibuf, IMSG_VMDOP_GET_INFO_VM_REQUEST, 0, 0, -1, NULL, 0);
252 }
253 
254 /*
255  * check_info_id
256  *
257  * Check if requested name or ID of a VM matches specified arguments
258  *
259  * Parameters:
260  *  name: name of the VM
261  *  id: ID of the VM
262  */
263 int
264 check_info_id(const char *name, uint32_t id)
265 {
266 	if (info_id == 0 && *info_name == '\0')
267 		return (-1);
268 	if (info_id != 0 && info_id == id)
269 		return (1);
270 	if (*info_name != '\0' && name && strcmp(info_name, name) == 0)
271 		return (1);
272 	return (0);
273 }
274 
275 /*
276  * add_info
277  *
278  * Callback function invoked when we are expecting an
279  * IMSG_VMDOP_GET_INFO_VM_DATA message indicating the receipt of additional
280  * "list vm" data, or an IMSG_VMDOP_GET_INFO_VM_END_DATA message indicating
281  * that no additional "list vm" data will be forthcoming.
282  *
283  * Parameters:
284  *  imsg : response imsg received from vmd
285  *  ret  : return value
286  *
287  * Return:
288  *  0     : the returned data was successfully added to the "list vm" data.
289  *          The caller can expect more data.
290  *  1     : IMSG_VMDOP_GET_INFO_VM_END_DATA received (caller should not call
291  *          add_info again), or an error occurred adding the returned data
292  *          to the "list vm" data. The caller should check the value of
293  *          'ret' to determine which case occurred.
294  *
295  * This function does not return if a VM is found and info_console is set.
296  *
297  *  The function also sets 'ret' to the error code as follows:
298  *   0     : Message successfully processed
299  *   EINVAL: Invalid or unexpected response from vmd
300  *   ENOMEM: memory allocation failure
301  */
302 int
303 add_info(struct imsg *imsg, int *ret)
304 {
305 	static size_t ct = 0;
306 	static struct vmop_info_result *vir = NULL;
307 
308 	if (imsg->hdr.type == IMSG_VMDOP_GET_INFO_VM_DATA) {
309 		vir = reallocarray(vir, ct + 1,
310 		    sizeof(struct vmop_info_result));
311 		if (vir == NULL) {
312 			*ret = ENOMEM;
313 			return (1);
314 		}
315 		memcpy(&vir[ct], imsg->data, sizeof(struct vmop_info_result));
316 		ct++;
317 		*ret = 0;
318 		return (0);
319 	} else if (imsg->hdr.type == IMSG_VMDOP_GET_INFO_VM_END_DATA) {
320 		if (info_console)
321 			vm_console(vir, ct);
322 		else
323 			print_vm_info(vir, ct);
324 		free(vir);
325 		*ret = 0;
326 		return (1);
327 	} else {
328 		*ret = EINVAL;
329 		return (1);
330 	}
331 }
332 
333 /*
334  * print_vm_info
335  *
336  * Prints the vm information returned from vmd in 'list' to stdout.
337  *
338  * Parameters
339  *  list: the vm information (consolidated) returned from vmd via imsg
340  *  ct  : the size (number of elements in 'list') of the result
341  */
342 void
343 print_vm_info(struct vmop_info_result *list, size_t ct)
344 {
345 	struct vm_info_result *vir;
346 	size_t i, j;
347 	char *vcpu_state;
348 
349 	printf("%5s %5s %5s %9s %9s %*s %s\n", "ID", "PID", "VCPUS", "MAXMEM",
350 	    "CURMEM", VM_TTYNAME_MAX, "TTY", "NAME");
351 	for (i = 0; i < ct; i++) {
352 		vir = &list[i].vir_info;
353 		if (check_info_id(vir->vir_name, vir->vir_id)) {
354 			printf("%5u %5u %5zd %7zdMB %7zdMB %*s %s\n",
355 			    vir->vir_id, vir->vir_creator_pid,
356 			    vir->vir_ncpus, vir->vir_memory_size,
357 			    vir->vir_used_size / 1024 / 1024 , VM_TTYNAME_MAX,
358 			    list[i].vir_ttyname, vir->vir_name);
359 		}
360 		if (check_info_id(vir->vir_name, vir->vir_id) > 0) {
361 			for (j = 0; j < vir->vir_ncpus; j++) {
362 				if (vir->vir_vcpu_state[j] ==
363 				    VCPU_STATE_STOPPED)
364 					vcpu_state = "STOPPED";
365 				else if (vir->vir_vcpu_state[j] ==
366 				    VCPU_STATE_RUNNING)
367 					vcpu_state = "RUNNING";
368 				else
369 					vcpu_state = "UNKNOWN";
370 
371 				printf(" VCPU: %2zd STATE: %s\n",
372 				    j, vcpu_state);
373 			}
374 		}
375 	}
376 }
377 
378 /*
379  * vm_console
380  *
381  * Connects to the vm console returned from vmd in 'list'.
382  *
383  * Parameters
384  *  list: the vm information (consolidated) returned from vmd via imsg
385  *  ct  : the size (number of elements in 'list') of the result
386  */
387 __dead void
388 vm_console(struct vmop_info_result *list, size_t ct)
389 {
390 	struct vmop_info_result *vir;
391 	size_t i;
392 
393 	for (i = 0; i < ct; i++) {
394 		vir = &list[i];
395 		if (check_info_id(vir->vir_info.vir_name,
396 		    vir->vir_info.vir_id) > 0) {
397 			/* does not return */
398 			ctl_openconsole(vir->vir_ttyname);
399 		}
400 	}
401 
402 	errx(1, "console %d not found", info_id);
403 }
404 
405 /*
406  * create_imagefile
407  *
408  * Create an empty imagefile with the specified path and size.
409  *
410  * Parameters:
411  *  imgfile_path: path to the image file to create
412  *  imgsize     : size of the image file to create (in MB)
413  *
414  * Return:
415  *  EEXIST: The requested image file already exists
416  *  0     : Image file successfully created
417  *  Exxxx : Various other Exxxx errno codes due to other I/O errors
418  */
419 int
420 create_imagefile(const char *imgfile_path, long imgsize)
421 {
422 	int fd, ret;
423 
424 	/* Refuse to overwrite an existing image */
425 	fd = open(imgfile_path, O_RDWR | O_CREAT | O_TRUNC | O_EXCL,
426 	    S_IRUSR | S_IWUSR);
427 	if (fd == -1)
428 		return (errno);
429 
430 	/* Extend to desired size */
431 	if (ftruncate(fd, (off_t)imgsize * 1024 * 1024) == -1) {
432 		ret = errno;
433 		close(fd);
434 		unlink(imgfile_path);
435 		return (ret);
436 	}
437 
438 	ret = close(fd);
439 	return (ret);
440 }
441