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