vmd.c revision 1.27
1/*	$OpenBSD: vmd.c,v 1.27 2016/02/05 11:40:15 reyk Exp $	*/
2
3/*
4 * Copyright (c) 2015 Reyk Floeter <reyk@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/param.h>
20#include <sys/queue.h>
21#include <sys/wait.h>
22#include <sys/cdefs.h>
23
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <errno.h>
28#include <event.h>
29#include <fcntl.h>
30#include <pwd.h>
31#include <signal.h>
32#include <syslog.h>
33#include <unistd.h>
34#include <ctype.h>
35
36#include "proc.h"
37#include "vmd.h"
38
39__dead void usage(void);
40
41int	 main(int, char **);
42int	 vmd_configure(void);
43void	 vmd_sighdlr(int sig, short event, void *arg);
44void	 vmd_shutdown(void);
45int	 vmd_control_run(void);
46int	 vmd_dispatch_control(int, struct privsep_proc *, struct imsg *);
47int	 vmd_dispatch_vmm(int, struct privsep_proc *, struct imsg *);
48
49struct vmd	*env;
50
51static struct privsep_proc procs[] = {
52	{ "control",	PROC_CONTROL,	vmd_dispatch_control, control },
53	{ "vmm",	PROC_VMM,	vmd_dispatch_vmm, vmm },
54};
55
56int
57vmd_dispatch_control(int fd, struct privsep_proc *p, struct imsg *imsg)
58{
59	struct privsep			*ps = p->p_ps;
60	int				 res = 0, cmd = 0, v = 0;
61	struct vm_create_params		 vcp;
62	struct vmop_id			 vid;
63	struct vm_terminate_params	 vtp;
64	struct vmop_result		 vmr;
65	struct vmd_vm			*vm = NULL;
66	char				*str = NULL;
67	uint32_t			 id = 0;
68
69	switch (imsg->hdr.type) {
70	case IMSG_VMDOP_START_VM_REQUEST:
71		IMSG_SIZE_CHECK(imsg, &vcp);
72		memcpy(&vcp, imsg->data, sizeof(vcp));
73		res = config_getvm(ps, &vcp, -1, imsg->hdr.peerid);
74		if (res == -1) {
75			res = errno;
76			cmd = IMSG_VMDOP_START_VM_RESPONSE;
77		}
78		break;
79	case IMSG_VMDOP_TERMINATE_VM_REQUEST:
80		IMSG_SIZE_CHECK(imsg, &vid);
81		memcpy(&vid, imsg->data, sizeof(vid));
82		if ((id = vid.vid_id) == 0) {
83			/* Lookup vm (id) by name */
84			if ((vm = vm_getbyname(vid.vid_name)) == NULL) {
85				res = ENOENT;
86				cmd = IMSG_VMDOP_TERMINATE_VM_RESPONSE;
87				break;
88			}
89			id = vm->vm_params.vcp_id;
90		}
91		memset(&vtp, 0, sizeof(vtp));
92		vtp.vtp_vm_id = id;
93		if (proc_compose_imsg(ps, PROC_VMM, -1, imsg->hdr.type,
94		    imsg->hdr.peerid, -1, &vtp, sizeof(vtp)) == -1)
95			return (-1);
96		break;
97	case IMSG_VMDOP_GET_INFO_VM_REQUEST:
98		proc_forward_imsg(ps, imsg, PROC_VMM, -1);
99		break;
100	case IMSG_VMDOP_RELOAD:
101		v = 1;
102	case IMSG_VMDOP_LOAD:
103		if (IMSG_DATA_SIZE(imsg) > 0)
104			str = get_string((uint8_t *)imsg->data,
105			    IMSG_DATA_SIZE(imsg));
106		vmd_reload(v, str);
107		free(str);
108		break;
109	default:
110		return (-1);
111	}
112
113	switch (cmd) {
114	case 0:
115		break;
116	case IMSG_VMDOP_START_VM_RESPONSE:
117	case IMSG_VMDOP_TERMINATE_VM_RESPONSE:
118		memset(&vmr, 0, sizeof(vmr));
119		vmr.vmr_result = res;
120		vmr.vmr_id = id;
121		if (proc_compose_imsg(ps, PROC_CONTROL, -1, cmd,
122		    imsg->hdr.peerid, -1, &vmr, sizeof(vmr)) == -1)
123			return (-1);
124		break;
125	default:
126		if (proc_compose_imsg(ps, PROC_CONTROL, -1, cmd,
127		    imsg->hdr.peerid, -1, &res, sizeof(res)) == -1)
128			return (-1);
129		break;
130	}
131
132	return (0);
133}
134
135int
136vmd_dispatch_vmm(int fd, struct privsep_proc *p, struct imsg *imsg)
137{
138	struct vmop_result	 vmr;
139	struct privsep		*ps = p->p_ps;
140	int			 res = 0;
141	struct vmd_vm		*vm;
142	struct vm_create_params	*vcp;
143	struct vmop_info_result	 vir;
144
145	switch (imsg->hdr.type) {
146	case IMSG_VMDOP_START_VM_RESPONSE:
147		IMSG_SIZE_CHECK(imsg, &vmr);
148		memcpy(&vmr, imsg->data, sizeof(vmr));
149		if ((vm = vm_getbyvmid(imsg->hdr.peerid)) == NULL)
150			fatalx("%s: invalid vm response", __func__);
151		vcp = &vm->vm_params;
152		vcp->vcp_id = vmr.vmr_id;
153
154		/*
155		 * If the peerid is not -1, forward the response back to the
156		 * the control socket.  If it is -1, the request originated
157		 * from the parent, not the control socket.
158		 */
159		if (vm->vm_peerid != (uint32_t)-1) {
160			vmr.vmr_result = res;
161			(void)strlcpy(vmr.vmr_ttyname, vm->vm_ttyname,
162			    sizeof(vmr.vmr_ttyname));
163			if (proc_compose_imsg(ps, PROC_CONTROL, -1,
164			    imsg->hdr.type, vm->vm_peerid, -1,
165			    &vmr, sizeof(vmr)) == -1) {
166				errno = vmr.vmr_result;
167				log_warn("%s: failed to foward vm result",
168				    vcp->vcp_name);
169				vm_remove(vm);
170				return (-1);
171			}
172		}
173
174		if (vmr.vmr_result) {
175			errno = vmr.vmr_result;
176			log_warn("%s: failed to start vm", vcp->vcp_name);
177			vm_remove(vm);
178		} else {
179			log_info("%s: started vm %d successfully, tty %s",
180			    vcp->vcp_name, vcp->vcp_id, vm->vm_ttyname);
181		}
182		break;
183	case IMSG_VMDOP_TERMINATE_VM_RESPONSE:
184		IMSG_SIZE_CHECK(imsg, &vmr);
185		memcpy(&vmr, imsg->data, sizeof(vmr));
186		proc_forward_imsg(ps, imsg, PROC_CONTROL, -1);
187		if (vmr.vmr_result == 0) {
188			/* Remove local reference */
189			vm = vm_getbyid(vmr.vmr_id);
190			vm_remove(vm);
191		}
192		break;
193	case IMSG_VMDOP_GET_INFO_VM_DATA:
194		IMSG_SIZE_CHECK(imsg, &vir);
195		memcpy(&vir, imsg->data, sizeof(vir));
196		if ((vm = vm_getbyid(vir.vir_info.vir_id)) != NULL)
197			(void)strlcpy(vir.vir_ttyname, vm->vm_ttyname,
198			    sizeof(vir.vir_ttyname));
199		if (proc_compose_imsg(ps, PROC_CONTROL, -1, imsg->hdr.type,
200		    imsg->hdr.peerid, -1, &vir, sizeof(vir)) == -1) {
201			vm_remove(vm);
202			return (-1);
203		}
204		break;
205	case IMSG_VMDOP_GET_INFO_VM_END_DATA:
206		IMSG_SIZE_CHECK(imsg, &res);
207		proc_forward_imsg(ps, imsg, PROC_CONTROL, -1);
208		break;
209	default:
210		return (-1);
211	}
212
213	return (0);
214}
215
216void
217vmd_sighdlr(int sig, short event, void *arg)
218{
219	struct privsep	*ps = arg;
220	int		 die = 0, status, fail, id;
221	pid_t		 pid;
222	char		*cause;
223	const char	*title = "vm";
224
225	if (privsep_process != PROC_PARENT)
226		return;
227
228	switch (sig) {
229	case SIGHUP:
230		log_info("%s: reload requested with SIGHUP", __func__);
231
232		/*
233		 * This is safe because libevent uses async signal handlers
234		 * that run in the event loop and not in signal context.
235		 */
236		vmd_reload(1, NULL);
237		break;
238	case SIGPIPE:
239		log_info("%s: ignoring SIGPIPE", __func__);
240		break;
241	case SIGUSR1:
242		log_info("%s: ignoring SIGUSR1", __func__);
243		break;
244	case SIGTERM:
245	case SIGINT:
246		die = 1;
247		/* FALLTHROUGH */
248	case SIGCHLD:
249		do {
250			int len;
251
252			pid = waitpid(-1, &status, WNOHANG);
253			if (pid <= 0)
254				continue;
255
256			fail = 0;
257			if (WIFSIGNALED(status)) {
258				fail = 1;
259				len = asprintf(&cause, "terminated; signal %d",
260				    WTERMSIG(status));
261			} else if (WIFEXITED(status)) {
262				if (WEXITSTATUS(status) != 0) {
263					fail = 1;
264					len = asprintf(&cause,
265					    "exited abnormally");
266				} else
267					len = asprintf(&cause, "exited okay");
268			} else
269				fatalx("unexpected cause of SIGCHLD");
270
271			if (len == -1)
272				fatal("asprintf");
273
274			for (id = 0; id < PROC_MAX; id++) {
275				if (pid == ps->ps_pid[id]) {
276					die = 1;
277					title = ps->ps_title[id];
278					break;
279				}
280			}
281			if (fail)
282				log_warnx("lost child: %s %s", title, cause);
283
284			free(cause);
285		} while (pid > 0 || (pid == -1 && errno == EINTR));
286
287		if (die)
288			vmd_shutdown();
289		break;
290	default:
291		fatalx("unexpected signal");
292	}
293}
294
295__dead void
296usage(void)
297{
298	extern char *__progname;
299	fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n",
300	    __progname);
301	exit(1);
302}
303
304int
305main(int argc, char **argv)
306{
307	struct privsep	*ps;
308	int		 ch;
309	const char	*conffile = VMD_CONF;
310
311	/* log to stderr until daemonized */
312	log_init(1, LOG_DAEMON);
313
314	if ((env = calloc(1, sizeof(*env))) == NULL)
315		fatal("calloc: env");
316
317	while ((ch = getopt(argc, argv, "D:df:vn")) != -1) {
318		switch (ch) {
319		case 'D':
320			if (cmdline_symset(optarg) < 0)
321				log_warnx("could not parse macro definition %s",
322				    optarg);
323			break;
324		case 'd':
325			env->vmd_debug = 2;
326			break;
327		case 'f':
328			conffile = optarg;
329			break;
330		case 'v':
331			env->vmd_verbose++;
332			break;
333		case 'n':
334			env->vmd_noaction = 1;
335			break;
336		default:
337			usage();
338		}
339	}
340
341	if (env->vmd_noaction && !env->vmd_debug)
342		env->vmd_debug = 1;
343
344	/* check for root privileges */
345	if (env->vmd_noaction == 0) {
346		if (geteuid())
347			fatalx("need root privileges");
348	}
349
350	ps = &env->vmd_ps;
351	ps->ps_env = env;
352
353	if (config_init(env) == -1)
354		fatal("failed to initialize configuration");
355
356	if ((ps->ps_pw = getpwnam(VMD_USER)) == NULL)
357		fatal("unknown user %s", VMD_USER);
358
359	/* Configure the control socket */
360	ps->ps_csock.cs_name = SOCKET_NAME;
361	TAILQ_INIT(&ps->ps_rcsocks);
362
363	/* Open /dev/vmm */
364	if (env->vmd_noaction == 0) {
365		env->vmd_fd = open(VMM_NODE, O_RDWR);
366		if (env->vmd_fd == -1)
367			fatal("%s", VMM_NODE);
368	}
369
370	/* Configuration will be parsed after forking the children */
371	env->vmd_conffile = conffile;
372
373	log_init(env->vmd_debug, LOG_DAEMON);
374	log_verbose(env->vmd_verbose);
375
376	if (!env->vmd_debug && daemon(0, 0) == -1)
377		fatal("can't daemonize");
378
379	log_procinit("parent");
380
381	ps->ps_ninstances = 1;
382
383	if (!env->vmd_noaction)
384		proc_init(ps, procs, nitems(procs));
385
386	event_init();
387
388	signal_set(&ps->ps_evsigint, SIGINT, vmd_sighdlr, ps);
389	signal_set(&ps->ps_evsigterm, SIGTERM, vmd_sighdlr, ps);
390	signal_set(&ps->ps_evsigchld, SIGCHLD, vmd_sighdlr, ps);
391	signal_set(&ps->ps_evsighup, SIGHUP, vmd_sighdlr, ps);
392	signal_set(&ps->ps_evsigpipe, SIGPIPE, vmd_sighdlr, ps);
393	signal_set(&ps->ps_evsigusr1, SIGUSR1, vmd_sighdlr, ps);
394
395	signal_add(&ps->ps_evsigint, NULL);
396	signal_add(&ps->ps_evsigterm, NULL);
397	signal_add(&ps->ps_evsigchld, NULL);
398	signal_add(&ps->ps_evsighup, NULL);
399	signal_add(&ps->ps_evsigpipe, NULL);
400	signal_add(&ps->ps_evsigusr1, NULL);
401
402	if (!env->vmd_noaction)
403		proc_listen(ps, procs, nitems(procs));
404
405	if (vmd_configure() == -1)
406		fatalx("configuration failed");
407
408	event_dispatch();
409
410	log_debug("parent exiting");
411
412	return (0);
413}
414
415int
416vmd_configure(void)
417{
418	/*
419	 * pledge in the parent process:
420	 * stdio - for malloc and basic I/O including events.
421	 * rpath - for reload to open and read the configuration files.
422	 * wpath - for opening disk images and tap devices.
423	 * tty - for openpty.
424	 * proc - run kill to terminate its children safely.
425	 * sendfd - for disks, interfaces and other fds.
426	 */
427	if (pledge("stdio rpath wpath proc tty sendfd", NULL) == -1)
428		fatal("pledge");
429
430	if (parse_config(env->vmd_conffile) == -1) {
431		proc_kill(&env->vmd_ps);
432		exit(1);
433	}
434
435	if (env->vmd_noaction) {
436		fprintf(stderr, "configuration OK\n");
437		proc_kill(&env->vmd_ps);
438		exit(0);
439	}
440
441	return (0);
442}
443
444void
445vmd_reload(int reset, const char *filename)
446{
447	/* Switch back to the default config file */
448	if (filename == NULL || *filename == '\0')
449		filename = env->vmd_conffile;
450
451	log_debug("%s: level %d config file %s", __func__, reset, filename);
452
453	if (reset)
454		config_setreset(env, CONFIG_ALL);
455
456	if (parse_config(filename) == -1) {
457		log_debug("%s: failed to load config file %s",
458		    __func__, filename);
459	}
460}
461
462void
463vmd_shutdown(void)
464{
465	proc_kill(&env->vmd_ps);
466	free(env);
467
468	log_warnx("parent terminating");
469	exit(0);
470}
471
472struct vmd_vm *
473vm_getbyvmid(uint32_t vmid)
474{
475	struct vmd_vm	*vm;
476
477	TAILQ_FOREACH(vm, env->vmd_vms, vm_entry) {
478		if (vm->vm_vmid == vmid)
479			return (vm);
480	}
481
482	return (NULL);
483}
484
485struct vmd_vm *
486vm_getbyid(uint32_t id)
487{
488	struct vmd_vm	*vm;
489
490	TAILQ_FOREACH(vm, env->vmd_vms, vm_entry) {
491		if (vm->vm_params.vcp_id == id)
492			return (vm);
493	}
494
495	return (NULL);
496}
497
498struct vmd_vm *
499vm_getbyname(const char *name)
500{
501	struct vmd_vm	*vm;
502
503	TAILQ_FOREACH(vm, env->vmd_vms, vm_entry) {
504		if (strcmp(vm->vm_params.vcp_name, name) == 0)
505			return (vm);
506	}
507
508	return (NULL);
509}
510
511void
512vm_remove(struct vmd_vm *vm)
513{
514	unsigned int	 i;
515
516	if (vm == NULL)
517		return;
518
519	TAILQ_REMOVE(env->vmd_vms, vm, vm_entry);
520
521	for (i = 0; i < VMM_MAX_DISKS_PER_VM; i++) {
522		if (vm->vm_disks[i] != -1)
523			close(vm->vm_disks[i]);
524	}
525	for (i = 0; i < VMM_MAX_NICS_PER_VM; i++) {
526		if (vm->vm_ifs[i] != -1)
527			close(vm->vm_ifs[i]);
528	}
529	if (vm->vm_kernel != -1)
530		close(vm->vm_kernel);
531	if (vm->vm_tty != -1)
532		close(vm->vm_tty);
533
534	free(vm);
535}
536
537char *
538get_string(uint8_t *ptr, size_t len)
539{
540	size_t	 i;
541
542	for (i = 0; i < len; i++)
543		if (!isprint(ptr[i]))
544			break;
545
546	return strndup(ptr, i);
547}
548