vmd.c revision 1.24
1/*	$OpenBSD: vmd.c,v 1.24 2015/12/08 23:59:39 jsg 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	/* log to stderr until daemonized */
269	log_init(1, LOG_DAEMON);
270
271	if ((env = calloc(1, sizeof(*env))) == NULL)
272		fatal("calloc: env");
273
274	while ((ch = getopt(argc, argv, "D:df:vn")) != -1) {
275		switch (ch) {
276		case 'D':
277			if (cmdline_symset(optarg) < 0)
278				log_warnx("could not parse macro definition %s",
279				    optarg);
280			break;
281		case 'd':
282			env->vmd_debug = 2;
283			break;
284		case 'f':
285			conffile = optarg;
286			break;
287		case 'v':
288			env->vmd_verbose++;
289			break;
290		case 'n':
291			env->vmd_noaction = 1;
292			break;
293		default:
294			usage();
295		}
296	}
297
298	if (env->vmd_noaction && !env->vmd_debug)
299		env->vmd_debug = 1;
300
301	/* check for root privileges */
302	if (env->vmd_noaction == 0) {
303		if (geteuid())
304			fatalx("need root privileges");
305	}
306
307	ps = &env->vmd_ps;
308	ps->ps_env = env;
309
310	if (config_init(env) == -1)
311		fatal("failed to initialize configuration");
312
313	if ((ps->ps_pw = getpwnam(VMD_USER)) == NULL)
314		fatal("unknown user %s", VMD_USER);
315
316	/* Configure the control socket */
317	ps->ps_csock.cs_name = SOCKET_NAME;
318	TAILQ_INIT(&ps->ps_rcsocks);
319
320	/* Open /dev/vmm */
321	if (env->vmd_noaction == 0) {
322		env->vmd_fd = open(VMM_NODE, O_RDWR);
323		if (env->vmd_fd == -1)
324			fatal("%s", VMM_NODE);
325	}
326
327	/* Configuration will be parsed after forking the children */
328	env->vmd_conffile = conffile;
329
330	log_init(env->vmd_debug, LOG_DAEMON);
331	log_verbose(env->vmd_verbose);
332
333	if (!env->vmd_debug && daemon(0, 0) == -1)
334		fatal("can't daemonize");
335
336	setproctitle("parent");
337	log_procinit("parent");
338
339	ps->ps_ninstances = 1;
340
341	if (!env->vmd_noaction)
342		proc_init(ps, procs, nitems(procs));
343
344	event_init();
345
346	signal_set(&ps->ps_evsigint, SIGINT, vmd_sighdlr, ps);
347	signal_set(&ps->ps_evsigterm, SIGTERM, vmd_sighdlr, ps);
348	signal_set(&ps->ps_evsigchld, SIGCHLD, vmd_sighdlr, ps);
349	signal_set(&ps->ps_evsighup, SIGHUP, vmd_sighdlr, ps);
350	signal_set(&ps->ps_evsigpipe, SIGPIPE, vmd_sighdlr, ps);
351	signal_set(&ps->ps_evsigusr1, SIGUSR1, vmd_sighdlr, ps);
352
353	signal_add(&ps->ps_evsigint, NULL);
354	signal_add(&ps->ps_evsigterm, NULL);
355	signal_add(&ps->ps_evsigchld, NULL);
356	signal_add(&ps->ps_evsighup, NULL);
357	signal_add(&ps->ps_evsigpipe, NULL);
358	signal_add(&ps->ps_evsigusr1, NULL);
359
360	if (!env->vmd_noaction)
361		proc_listen(ps, procs, nitems(procs));
362
363	if (vmd_configure() == -1)
364		fatalx("configuration failed");
365
366	event_dispatch();
367
368	log_debug("parent exiting");
369
370	return (0);
371}
372
373int
374vmd_configure(void)
375{
376	/*
377	 * pledge in the parent process:
378	 * stdio - for malloc and basic I/O including events.
379	 * rpath - for reload to open and read the configuration files.
380	 * wpath - for opening disk images and tap devices.
381	 * tty - for openpty.
382	 * proc - run kill to terminate its children safely.
383	 * sendfd - for disks, interfaces and other fds.
384	 */
385	if (pledge("stdio rpath wpath proc tty sendfd", NULL) == -1)
386		fatal("pledge");
387
388	if (parse_config(env->vmd_conffile) == -1) {
389		proc_kill(&env->vmd_ps);
390		exit(1);
391	}
392
393	if (env->vmd_noaction) {
394		fprintf(stderr, "configuration OK\n");
395		proc_kill(&env->vmd_ps);
396		exit(0);
397	}
398
399	return (0);
400}
401
402void
403vmd_reload(int reset, const char *filename)
404{
405	/* Switch back to the default config file */
406	if (filename == NULL || *filename == '\0')
407		filename = env->vmd_conffile;
408
409	log_debug("%s: level %d config file %s", __func__, reset, filename);
410
411	if (reset)
412		config_setreset(env, CONFIG_ALL);
413
414	if (parse_config(filename) == -1) {
415		log_debug("%s: failed to load config file %s",
416		    __func__, filename);
417	}
418}
419
420void
421vmd_shutdown(void)
422{
423	proc_kill(&env->vmd_ps);
424	free(env);
425
426	log_warnx("parent terminating");
427	exit(0);
428}
429
430struct vmd_vm *
431vm_getbyvmid(uint32_t vmid)
432{
433	struct vmd_vm	*vm;
434
435	TAILQ_FOREACH(vm, env->vmd_vms, vm_entry) {
436		if (vm->vm_vmid == vmid)
437			return (vm);
438	}
439
440	return (NULL);
441}
442
443struct vmd_vm *
444vm_getbyid(uint32_t id)
445{
446	struct vmd_vm	*vm;
447
448	TAILQ_FOREACH(vm, env->vmd_vms, vm_entry) {
449		if (vm->vm_params.vcp_id == id)
450			return (vm);
451	}
452
453	return (NULL);
454}
455
456struct vmd_vm *
457vm_getbyname(const char *name)
458{
459	struct vmd_vm	*vm;
460
461	TAILQ_FOREACH(vm, env->vmd_vms, vm_entry) {
462		if (strcmp(vm->vm_params.vcp_name, name) == 0)
463			return (vm);
464	}
465
466	return (NULL);
467}
468
469void
470vm_remove(struct vmd_vm *vm)
471{
472	unsigned int	 i;
473
474	if (vm == NULL)
475		return;
476
477	TAILQ_REMOVE(env->vmd_vms, vm, vm_entry);
478
479	for (i = 0; i < VMM_MAX_DISKS_PER_VM; i++) {
480		if (vm->vm_disks[i] != -1)
481			close(vm->vm_disks[i]);
482	}
483	for (i = 0; i < VMM_MAX_NICS_PER_VM; i++) {
484		if (vm->vm_ifs[i] != -1)
485			close(vm->vm_ifs[i]);
486	}
487	if (vm->vm_kernel != -1)
488		close(vm->vm_kernel);
489	if (vm->vm_tty != -1)
490		close(vm->vm_tty);
491
492	free(vm);
493}
494
495char *
496get_string(uint8_t *ptr, size_t len)
497{
498	size_t	 i;
499
500	for (i = 0; i < len; i++)
501		if (!isprint(ptr[i]))
502			break;
503
504	return strndup(ptr, i);
505}
506