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