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