* allow to load templates from arbitrary places
[strongswan.git] / src / dumm / guest.c
1 /*
2 * Copyright (C) 2008 Tobias Brunner
3 * Copyright (C) 2007 Martin Willi
4 * Hochschule fuer Technik Rapperswil
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17 #define _GNU_SOURCE
18
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <sys/wait.h>
22 #include <sys/uio.h>
23 #include <unistd.h>
24 #include <stdio.h>
25 #include <fcntl.h>
26 #include <signal.h>
27 #include <dirent.h>
28 #include <termios.h>
29 #include <stdarg.h>
30
31 #include <debug.h>
32 #include <utils/linked_list.h>
33
34 #include "dumm.h"
35 #include "guest.h"
36 #include "mconsole.h"
37 #include "cowfs.h"
38
39 #define PERME (S_IRWXU | S_IRWXG)
40 #define PERM (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
41
42 #define MASTER_DIR "master"
43 #define DIFF_DIR "diff"
44 #define UNION_DIR "union"
45 #define MEMORY_FILE "mem"
46 #define PID_FILE "pid"
47 #define KERNEL_FILE "linux"
48 #define LOG_FILE "boot.log"
49 #define NOTIFY_FILE "notify"
50 #define PTYS 0
51
52 typedef struct private_guest_t private_guest_t;
53
54 struct private_guest_t {
55 /** implemented public interface */
56 guest_t public;
57 /** name of the guest */
58 char *name;
59 /** directory of guest */
60 int dir;
61 /** directory name of guest */
62 char *dirname;
63 /** amount of memory for guest, in MB */
64 int mem;
65 /** pid of guest child process */
66 int pid;
67 /** state of guest */
68 guest_state_t state;
69 /** FUSE cowfs instance */
70 cowfs_t *cowfs;
71 /** mconsole to control running UML */
72 mconsole_t *mconsole;
73 /** list of interfaces attached to the guest */
74 linked_list_t *ifaces;
75 };
76
77 ENUM(guest_state_names, GUEST_STOPPED, GUEST_STOPPING,
78 "STOPPED",
79 "STARTING",
80 "RUNNING",
81 "PAUSED",
82 "STOPPING",
83 );
84
85 /**
86 * Implementation of guest_t.get_name.
87 */
88 static char* get_name(private_guest_t *this)
89 {
90 return this->name;
91 }
92
93 /**
94 * Implementation of guest_t.create_iface.
95 */
96 static iface_t* create_iface(private_guest_t *this, char *name)
97 {
98 enumerator_t *enumerator;
99 iface_t *iface;
100
101 if (this->state != GUEST_RUNNING)
102 {
103 DBG1("guest '%s' not running, unable to add interface", this->name);
104 return NULL;
105 }
106
107 enumerator = this->ifaces->create_enumerator(this->ifaces);
108 while (enumerator->enumerate(enumerator, (void**)&iface))
109 {
110 if (streq(name, iface->get_guestif(iface)))
111 {
112 DBG1("guest '%s' already has an interface '%s'", this->name, name);
113 enumerator->destroy(enumerator);
114 return NULL;
115 }
116 }
117 enumerator->destroy(enumerator);
118
119 iface = iface_create(name, &this->public, this->mconsole);
120 if (iface)
121 {
122 this->ifaces->insert_last(this->ifaces, iface);
123 }
124 return iface;
125 }
126
127 /**
128 * Implementation of guest_t.destroy_iface.
129 */
130 static void destroy_iface(private_guest_t *this, iface_t *iface)
131 {
132 enumerator_t *enumerator;
133 iface_t *current;
134
135 enumerator = this->ifaces->create_enumerator(this->ifaces);
136 while (enumerator->enumerate(enumerator, (void**)&current))
137 {
138 if (current == iface)
139 {
140 this->ifaces->remove_at(this->ifaces, enumerator);
141 current->destroy(current);
142 break;
143 }
144 }
145 enumerator->destroy(enumerator);
146 }
147
148 /**
149 * Implementation of guest_t.create_iface_enumerator.
150 */
151 static enumerator_t* create_iface_enumerator(private_guest_t *this)
152 {
153 return this->ifaces->create_enumerator(this->ifaces);
154 }
155
156 /**
157 * Implementation of guest_t.get_state.
158 */
159 static guest_state_t get_state(private_guest_t *this)
160 {
161 return this->state;
162 }
163
164 /**
165 * Implementation of guest_t.get_pid.
166 */
167 static pid_t get_pid(private_guest_t *this)
168 {
169 return this->pid;
170 }
171
172 /**
173 * write format string to a buffer, and advance buffer position
174 */
175 static char* write_arg(char **pos, size_t *left, char *format, ...)
176 {
177 size_t len;
178 char *res = NULL;
179 va_list args;
180
181 va_start(args, format);
182 len = vsnprintf(*pos, *left, format, args);
183 va_end(args);
184 if (len < *left)
185 {
186 res = *pos;
187 len++;
188 *pos += len + 1;
189 *left -= len + 1;
190 }
191 return res;
192 }
193
194 /**
195 * Implementation of guest_t.stop.
196 */
197 static void stop(private_guest_t *this, idle_function_t idle)
198 {
199 if (this->state != GUEST_STOPPED)
200 {
201 this->state = GUEST_STOPPING;
202 this->ifaces->destroy_offset(this->ifaces, offsetof(iface_t, destroy));
203 this->ifaces = linked_list_create();
204 kill(this->pid, SIGINT);
205 while (this->state != GUEST_STOPPED)
206 {
207 if (idle)
208 {
209 idle();
210 }
211 else
212 {
213 usleep(50000);
214 }
215 }
216 unlinkat(this->dir, PID_FILE, 0);
217 this->pid = 0;
218 }
219 }
220
221 /**
222 * save pid in file
223 */
224 void savepid(private_guest_t *this)
225 {
226 FILE *file;
227
228 file = fdopen(openat(this->dir, PID_FILE, O_RDWR | O_CREAT | O_TRUNC,
229 PERM), "w");
230 if (file)
231 {
232 fprintf(file, "%d", this->pid);
233 fclose(file);
234 }
235 }
236
237 /**
238 * Implementation of guest_t.start.
239 */
240 static bool start(private_guest_t *this, invoke_function_t invoke, void* data,
241 idle_function_t idle)
242 {
243 char buf[2048];
244 char *notify;
245 char *pos = buf;
246 char *args[32];
247 int i = 0;
248 size_t left = sizeof(buf);
249
250 memset(args, 0, sizeof(args));
251
252 if (this->state != GUEST_STOPPED)
253 {
254 DBG1("unable to start guest in state %N", guest_state_names, this->state);
255 return FALSE;
256 }
257 this->state = GUEST_STARTING;
258
259 notify = write_arg(&pos, &left, "%s/%s", this->dirname, NOTIFY_FILE);
260
261 args[i++] = write_arg(&pos, &left, "nice");
262 args[i++] = write_arg(&pos, &left, "%s/%s", this->dirname, KERNEL_FILE);
263 args[i++] = write_arg(&pos, &left, "root=/dev/root");
264 args[i++] = write_arg(&pos, &left, "rootfstype=hostfs");
265 args[i++] = write_arg(&pos, &left, "rootflags=%s/%s", this->dirname, UNION_DIR);
266 args[i++] = write_arg(&pos, &left, "uml_dir=%s", this->dirname);
267 args[i++] = write_arg(&pos, &left, "umid=%s", this->name);
268 args[i++] = write_arg(&pos, &left, "mem=%dM", this->mem);
269 args[i++] = write_arg(&pos, &left, "mconsole=notify:%s", notify);
270 args[i++] = write_arg(&pos, &left, "con=null");
271
272 this->pid = invoke(data, &this->public, args, i);
273 if (!this->pid)
274 {
275 this->state = GUEST_STOPPED;
276 return FALSE;
277 }
278 savepid(this);
279
280 /* open mconsole */
281 this->mconsole = mconsole_create(notify, idle);
282 if (this->mconsole == NULL)
283 {
284 DBG1("opening mconsole at '%s' failed, stopping guest", buf);
285 stop(this, NULL);
286 return FALSE;
287 }
288
289 this->state = GUEST_RUNNING;
290 return TRUE;
291 }
292
293 /**
294 * Implementation of guest_t.load_template.
295 */
296 static bool load_template(private_guest_t *this, char *path)
297 {
298 char dir[PATH_MAX];
299 size_t len;
300 iface_t *iface;
301
302 if (path == NULL)
303 {
304 return this->cowfs->set_overlay(this->cowfs, NULL);
305 }
306
307 len = snprintf(dir, sizeof(dir), "%s/%s", path, this->name);
308 if (len < 0 || len >= sizeof(dir))
309 {
310 return FALSE;
311 }
312 if (access(dir, F_OK) != 0)
313 {
314 if (!mkdir_p(dir, PERME))
315 {
316 DBG1("creating overlay for guest '%s' failed: %m", this->name);
317 return FALSE;
318 }
319 }
320 if (!this->cowfs->set_overlay(this->cowfs, dir))
321 {
322 return FALSE;
323 }
324 while (this->ifaces->remove_last(this->ifaces, (void**)&iface) == SUCCESS)
325 {
326 iface->destroy(iface);
327 }
328 return TRUE;
329 }
330
331 /**
332 * Variadic version of the exec function
333 */
334 static int vexec(private_guest_t *this, void(*cb)(void*,char*,size_t), void *data,
335 char *cmd, va_list args)
336 {
337 char buf[1024];
338 size_t len;
339
340 if (this->mconsole)
341 {
342 len = vsnprintf(buf, sizeof(buf), cmd, args);
343
344 if (len > 0 && len < sizeof(buf))
345 {
346 return this->mconsole->exec(this->mconsole, cb, data, buf);
347 }
348 }
349 return -1;
350 }
351
352 /**
353 * Implementation of guest_t.exec
354 */
355 static int exec(private_guest_t *this, void(*cb)(void*,char*,size_t), void *data,
356 char *cmd, ...)
357 {
358 int res;
359 va_list args;
360 va_start(args, cmd);
361 res = vexec(this, cb, data, cmd, args);
362 va_end(args);
363 return res;
364 }
365
366 typedef struct {
367 chunk_t buf;
368 void (*cb)(void*,char*);
369 void *data;
370 } exec_str_t;
371
372 /**
373 * callback that combines chunks to a string. if a callback is given, the string
374 * is split at newlines and the callback is called for each line.
375 */
376 static void exec_str_cb(exec_str_t *data, char *buf, size_t len)
377 {
378 if (!data->buf.ptr)
379 {
380 data->buf = chunk_alloc(len + 1);
381 memcpy(data->buf.ptr, buf, len);
382 data->buf.ptr[len] = '\0';
383 }
384 else
385 {
386 size_t newlen = strlen(data->buf.ptr) + len + 1;
387 if (newlen > data->buf.len)
388 {
389 data->buf.ptr = realloc(data->buf.ptr, newlen);
390 data->buf.len = newlen;
391 }
392 strncat(data->buf.ptr, buf, len);
393 }
394
395 if (data->cb)
396 {
397 char *nl;
398 while ((nl = strchr(data->buf.ptr, '\n')) != NULL)
399 {
400 *nl++ = '\0';
401 data->cb(data->data, data->buf.ptr);
402 memmove(data->buf.ptr, nl, strlen(nl) + 1);
403 }
404 }
405 }
406
407 /**
408 * Implementation of guest_t.exec_str
409 */
410 static int exec_str(private_guest_t *this, void(*cb)(void*,char*), bool lines,
411 void *data, char *cmd, ...)
412 {
413 int res;
414 va_list args;
415 va_start(args, cmd);
416 if (cb)
417 {
418 exec_str_t exec = { chunk_empty, NULL, NULL };
419 if (lines)
420 {
421 exec.cb = cb;
422 exec.data = data;
423 }
424 res = vexec(this, (void(*)(void*,char*,size_t))exec_str_cb, &exec, cmd, args);
425 if (exec.buf.ptr)
426 {
427 if (!lines || strlen(exec.buf.ptr) > 0)
428 {
429 /* return the complete string or the remaining stuff in the
430 * buffer (i.e. when there was no newline at the end) */
431 cb(data, exec.buf.ptr);
432 }
433 chunk_free(&exec.buf);
434 }
435 }
436 else
437 {
438 res = vexec(this, NULL, NULL, cmd, args);
439 }
440 va_end(args);
441 return res;
442 }
443
444 /**
445 * Implementation of guest_t.sigchild.
446 */
447 static void sigchild(private_guest_t *this)
448 {
449 DESTROY_IF(this->mconsole);
450 this->mconsole = NULL;
451 this->state = GUEST_STOPPED;
452 }
453
454 /**
455 * umount the union filesystem
456 */
457 static bool umount_unionfs(private_guest_t *this)
458 {
459 if (this->cowfs)
460 {
461 this->cowfs->destroy(this->cowfs);
462 this->cowfs = NULL;
463 return TRUE;
464 }
465 return FALSE;
466 }
467
468 /**
469 * mount the union filesystem
470 */
471 static bool mount_unionfs(private_guest_t *this)
472 {
473 char master[PATH_MAX];
474 char diff[PATH_MAX];
475 char mount[PATH_MAX];
476
477 if (this->cowfs == NULL)
478 {
479 snprintf(master, sizeof(master), "%s/%s", this->dirname, MASTER_DIR);
480 snprintf(diff, sizeof(diff), "%s/%s", this->dirname, DIFF_DIR);
481 snprintf(mount, sizeof(mount), "%s/%s", this->dirname, UNION_DIR);
482
483 this->cowfs = cowfs_create(master, diff, mount);
484 if (this->cowfs)
485 {
486 return TRUE;
487 }
488 }
489 return FALSE;
490 }
491
492 /**
493 * load memory configuration from file
494 */
495 int loadmem(private_guest_t *this)
496 {
497 FILE *file;
498 int mem = 0;
499
500 file = fdopen(openat(this->dir, MEMORY_FILE, O_RDONLY, PERM), "r");
501 if (file)
502 {
503 if (fscanf(file, "%d", &mem) <= 0)
504 {
505 mem = 0;
506 }
507 fclose(file);
508 }
509 return mem;
510 }
511
512 /**
513 * save memory configuration to file
514 */
515 bool savemem(private_guest_t *this, int mem)
516 {
517 FILE *file;
518 bool retval = FALSE;
519
520 file = fdopen(openat(this->dir, MEMORY_FILE, O_RDWR | O_CREAT | O_TRUNC,
521 PERM), "w");
522 if (file)
523 {
524 if (fprintf(file, "%d", mem) > 0)
525 {
526 retval = TRUE;
527 }
528 fclose(file);
529 }
530 return retval;
531 }
532
533 /**
534 * Implementation of guest_t.destroy.
535 */
536 static void destroy(private_guest_t *this)
537 {
538 stop(this, NULL);
539 umount_unionfs(this);
540 if (this->dir > 0)
541 {
542 close(this->dir);
543 }
544 this->ifaces->destroy(this->ifaces);
545 free(this->dirname);
546 free(this->name);
547 free(this);
548 }
549
550 /**
551 * generic guest constructor
552 */
553 static private_guest_t *guest_create_generic(char *parent, char *name,
554 bool create)
555 {
556 char cwd[PATH_MAX];
557 private_guest_t *this = malloc_thing(private_guest_t);
558
559 this->public.get_name = (void*)get_name;
560 this->public.get_pid = (pid_t(*)(guest_t*))get_pid;
561 this->public.get_state = (guest_state_t(*)(guest_t*))get_state;
562 this->public.create_iface = (iface_t*(*)(guest_t*,char*))create_iface;
563 this->public.destroy_iface = (void(*)(guest_t*,iface_t*))destroy_iface;
564 this->public.create_iface_enumerator = (enumerator_t*(*)(guest_t*))create_iface_enumerator;
565 this->public.start = (void*)start;
566 this->public.stop = (void*)stop;
567 this->public.load_template = (bool(*)(guest_t*, char *path))load_template;
568 this->public.exec = (int(*)(guest_t*, void(*cb)(void*,char*,size_t),void*,char*,...))exec;
569 this->public.exec_str = (int(*)(guest_t*, void(*cb)(void*,char*),bool,void*,char*,...))exec_str;
570 this->public.sigchild = (void(*)(guest_t*))sigchild;
571 this->public.destroy = (void*)destroy;
572
573 if (*parent == '/' || getcwd(cwd, sizeof(cwd)) == NULL)
574 {
575 asprintf(&this->dirname, "%s/%s", parent, name);
576 }
577 else
578 {
579 asprintf(&this->dirname, "%s/%s/%s", cwd, parent, name);
580 }
581 if (create)
582 {
583 mkdir(this->dirname, PERME);
584 }
585 this->dir = open(this->dirname, O_DIRECTORY, PERME);
586 if (this->dir < 0)
587 {
588 DBG1("opening guest directory '%s' failed: %m", this->dirname);
589 free(this->dirname);
590 free(this);
591 return NULL;
592 }
593 this->pid = 0;
594 this->state = GUEST_STOPPED;
595 this->mconsole = NULL;
596 this->ifaces = linked_list_create();
597 this->mem = 0;
598 this->name = strdup(name);
599 this->cowfs = NULL;
600
601 return this;
602 }
603
604 /**
605 * create a symlink to old called new in our working dir
606 */
607 static bool make_symlink(private_guest_t *this, char *old, char *new)
608 {
609 char cwd[PATH_MAX];
610 char buf[PATH_MAX];
611
612 if (*old == '/' || getcwd(cwd, sizeof(cwd)) == NULL)
613 {
614 snprintf(buf, sizeof(buf), "%s", old);
615 }
616 else
617 {
618 snprintf(buf, sizeof(buf), "%s/%s", cwd, old);
619 }
620 return symlinkat(buf, this->dir, new) == 0;
621 }
622
623
624 /**
625 * create the guest instance, including required dirs and mounts
626 */
627 guest_t *guest_create(char *parent, char *name, char *kernel,
628 char *master, int mem)
629 {
630 private_guest_t *this = guest_create_generic(parent, name, TRUE);
631
632 if (this == NULL)
633 {
634 return NULL;
635 }
636
637 if (!make_symlink(this, master, MASTER_DIR) ||
638 !make_symlink(this, kernel, KERNEL_FILE))
639 {
640 DBG1("creating master/kernel symlink failed: %m");
641 destroy(this);
642 return NULL;
643 }
644
645 if (mkdirat(this->dir, UNION_DIR, PERME) != 0 ||
646 mkdirat(this->dir, DIFF_DIR, PERME) != 0)
647 {
648 DBG1("unable to create directories for '%s': %m", name);
649 destroy(this);
650 return NULL;
651 }
652
653 this->mem = mem;
654 if (!savemem(this, mem))
655 {
656 destroy(this);
657 return NULL;
658 }
659
660 if (!mount_unionfs(this))
661 {
662 destroy(this);
663 return NULL;
664 }
665
666 return &this->public;
667 }
668
669 /**
670 * load an already created guest
671 */
672 guest_t *guest_load(char *parent, char *name)
673 {
674 private_guest_t *this = guest_create_generic(parent, name, FALSE);
675
676 if (this == NULL)
677 {
678 return NULL;
679 }
680
681 this->mem = loadmem(this);
682 if (this->mem == 0)
683 {
684 DBG1("unable to open memory configuration file: %m", name);
685 destroy(this);
686 return NULL;
687 }
688
689 if (!mount_unionfs(this))
690 {
691 destroy(this);
692 return NULL;
693 }
694
695 return &this->public;
696 }
697