prototype of irdumm - interactive ruby shell for dumm
[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 PID_FILE "pid"
45 #define KERNEL_FILE "linux"
46 #define LOG_FILE "boot.log"
47 #define NOTIFY_FILE "notify"
48 #define PTYS 0
49
50 typedef struct private_guest_t private_guest_t;
51
52 struct private_guest_t {
53 /** implemented public interface */
54 guest_t public;
55 /** name of the guest */
56 char *name;
57 /** directory of guest */
58 int dir;
59 /** directory name of guest */
60 char *dirname;
61 /** amount of memory for guest, in MB */
62 int mem;
63 /** pid of guest child process */
64 int pid;
65 /** state of guest */
66 guest_state_t state;
67 /** FUSE cowfs instance */
68 cowfs_t *cowfs;
69 /** mconsole to control running UML */
70 mconsole_t *mconsole;
71 /** list of interfaces attached to the guest */
72 linked_list_t *ifaces;
73 };
74
75 ENUM(guest_state_names, GUEST_STOPPED, GUEST_STOPPING,
76 "STOPPED",
77 "STARTING",
78 "RUNNING",
79 "PAUSED",
80 "STOPPING",
81 );
82
83 /**
84 * Implementation of guest_t.get_name.
85 */
86 static char* get_name(private_guest_t *this)
87 {
88 return this->name;
89 }
90
91 /**
92 * Implementation of guest_t.create_iface.
93 */
94 static iface_t* create_iface(private_guest_t *this, char *name)
95 {
96 enumerator_t *enumerator;
97 iface_t *iface;
98
99 if (this->state != GUEST_RUNNING)
100 {
101 DBG1("guest '%s' not running, unable to add interface", this->name);
102 return NULL;
103 }
104
105 enumerator = this->ifaces->create_enumerator(this->ifaces);
106 while (enumerator->enumerate(enumerator, (void**)&iface))
107 {
108 if (streq(name, iface->get_guestif(iface)))
109 {
110 DBG1("guest '%s' already has an interface '%s'", this->name, name);
111 enumerator->destroy(enumerator);
112 return NULL;
113 }
114 }
115 enumerator->destroy(enumerator);
116
117 iface = iface_create(name, &this->public, this->mconsole);
118 if (iface)
119 {
120 this->ifaces->insert_last(this->ifaces, iface);
121 }
122 return iface;
123 }
124
125 /**
126 * Implementation of guest_t.destroy_iface.
127 */
128 static void destroy_iface(private_guest_t *this, iface_t *iface)
129 {
130 enumerator_t *enumerator;
131 iface_t *current;
132
133 enumerator = this->ifaces->create_enumerator(this->ifaces);
134 while (enumerator->enumerate(enumerator, (void**)&current))
135 {
136 if (current == iface)
137 {
138 this->ifaces->remove_at(this->ifaces, enumerator);
139 current->destroy(current);
140 break;
141 }
142 }
143 enumerator->destroy(enumerator);
144 }
145
146 /**
147 * Implementation of guest_t.create_iface_enumerator.
148 */
149 static enumerator_t* create_iface_enumerator(private_guest_t *this)
150 {
151 return this->ifaces->create_enumerator(this->ifaces);
152 }
153
154 /**
155 * Implementation of guest_t.get_state.
156 */
157 static guest_state_t get_state(private_guest_t *this)
158 {
159 return this->state;
160 }
161
162 /**
163 * Implementation of guest_t.get_pid.
164 */
165 static pid_t get_pid(private_guest_t *this)
166 {
167 return this->pid;
168 }
169
170 /**
171 * write format string to a buffer, and advance buffer position
172 */
173 static char* write_arg(char **pos, size_t *left, char *format, ...)
174 {
175 size_t len;
176 char *res = NULL;
177 va_list args;
178
179 va_start(args, format);
180 len = vsnprintf(*pos, *left, format, args);
181 va_end(args);
182 if (len < *left)
183 {
184 res = *pos;
185 len++;
186 *pos += len + 1;
187 *left -= len + 1;
188 }
189 return res;
190 }
191
192 /**
193 * Implementation of guest_t.stop.
194 */
195 static void stop(private_guest_t *this, idle_function_t idle)
196 {
197 if (this->state != GUEST_STOPPED)
198 {
199 this->state = GUEST_STOPPING;
200 this->ifaces->destroy_offset(this->ifaces, offsetof(iface_t, destroy));
201 this->ifaces = linked_list_create();
202 kill(this->pid, SIGINT);
203 while (this->state != GUEST_STOPPED)
204 {
205 if (idle)
206 {
207 idle();
208 }
209 else
210 {
211 usleep(50000);
212 }
213 }
214 unlinkat(this->dir, PID_FILE, 0);
215 }
216 }
217
218 /**
219 * save pid in file
220 */
221 void savepid(private_guest_t *this)
222 {
223 FILE *file;
224
225 file = fdopen(openat(this->dir, PID_FILE, O_RDWR | O_CREAT | O_TRUNC,
226 PERM), "w");
227 if (file)
228 {
229 fprintf(file, "%d", this->pid);
230 fclose(file);
231 }
232 }
233
234 /**
235 * Implementation of guest_t.start.
236 */
237 static bool start(private_guest_t *this, invoke_function_t invoke, void* data,
238 idle_function_t idle)
239 {
240 char buf[2048];
241 char *notify;
242 char *pos = buf;
243 char *args[32];
244 int i = 0;
245 size_t left = sizeof(buf);
246
247 memset(args, 0, sizeof(args));
248
249 if (this->state != GUEST_STOPPED)
250 {
251 DBG1("unable to start guest in state %N", guest_state_names, this->state);
252 return FALSE;
253 }
254 this->state = GUEST_STARTING;
255
256 notify = write_arg(&pos, &left, "%s/%s", this->dirname, NOTIFY_FILE);
257
258 args[i++] = write_arg(&pos, &left, "%s/%s", this->dirname, KERNEL_FILE);
259 args[i++] = write_arg(&pos, &left, "root=/dev/root");
260 args[i++] = write_arg(&pos, &left, "rootfstype=hostfs");
261 args[i++] = write_arg(&pos, &left, "rootflags=%s/%s", this->dirname, UNION_DIR);
262 args[i++] = write_arg(&pos, &left, "uml_dir=%s", this->dirname);
263 args[i++] = write_arg(&pos, &left, "umid=%s", this->name);
264 args[i++] = write_arg(&pos, &left, "mem=%dM", this->mem);
265 args[i++] = write_arg(&pos, &left, "mconsole=notify:%s", notify);
266 args[i++] = write_arg(&pos, &left, "con=null");
267
268 this->pid = invoke(data, &this->public, args, i);
269 if (!this->pid)
270 {
271 this->state = GUEST_STOPPED;
272 return FALSE;
273 }
274 savepid(this);
275
276 /* open mconsole */
277 this->mconsole = mconsole_create(notify, idle);
278 if (this->mconsole == NULL)
279 {
280 DBG1("opening mconsole at '%s' failed, stopping guest", buf);
281 stop(this, NULL);
282 return FALSE;
283 }
284
285 this->state = GUEST_RUNNING;
286 return TRUE;
287 }
288
289 /**
290 * Implementation of guest_t.load_template.
291 */
292 static bool load_template(private_guest_t *this, char *path)
293 {
294 char dir[PATH_MAX];
295 size_t len;
296 iface_t *iface;
297
298 if (path == NULL)
299 {
300 return this->cowfs->set_overlay(this->cowfs, NULL);
301 }
302
303 len = snprintf(dir, sizeof(dir), "%s/%s", path, this->name);
304 if (len < 0 || len >= sizeof(dir))
305 {
306 return FALSE;
307 }
308 if (access(dir, F_OK) != 0)
309 {
310 if (mkdir(dir, PERME) != 0)
311 {
312 DBG1("creating overlay for guest '%s' failed: %m", this->name);
313 return FALSE;
314 }
315 }
316 if (!this->cowfs->set_overlay(this->cowfs, dir))
317 {
318 return FALSE;
319 }
320 while (this->ifaces->remove_last(this->ifaces, (void**)&iface) == SUCCESS)
321 {
322 iface->destroy(iface);
323 }
324 return TRUE;
325 }
326
327 /**
328 * Implementation of guest_t.sigchild.
329 */
330 static void sigchild(private_guest_t *this)
331 {
332 DESTROY_IF(this->mconsole);
333 this->mconsole = NULL;
334 this->state = GUEST_STOPPED;
335 }
336
337 /**
338 * umount the union filesystem
339 */
340 static bool umount_unionfs(private_guest_t *this)
341 {
342 if (this->cowfs)
343 {
344 this->cowfs->destroy(this->cowfs);
345 this->cowfs = NULL;
346 return TRUE;
347 }
348 return FALSE;
349 }
350
351 /**
352 * mount the union filesystem
353 */
354 static bool mount_unionfs(private_guest_t *this)
355 {
356 char master[PATH_MAX];
357 char diff[PATH_MAX];
358 char mount[PATH_MAX];
359
360 if (this->cowfs == NULL)
361 {
362 snprintf(master, sizeof(master), "%s/%s", this->dirname, MASTER_DIR);
363 snprintf(diff, sizeof(diff), "%s/%s", this->dirname, DIFF_DIR);
364 snprintf(mount, sizeof(mount), "%s/%s", this->dirname, UNION_DIR);
365
366 this->cowfs = cowfs_create(master, diff, mount);
367 if (this->cowfs)
368 {
369 return TRUE;
370 }
371 }
372 return FALSE;
373 }
374
375 /**
376 * load memory configuration from file
377 */
378 int loadmem(private_guest_t *this)
379 {
380 FILE *file;
381 int mem = 0;
382
383 file = fdopen(openat(this->dir, MEMORY_FILE, O_RDONLY, PERM), "r");
384 if (file)
385 {
386 if (fscanf(file, "%d", &mem) <= 0)
387 {
388 mem = 0;
389 }
390 fclose(file);
391 }
392 return mem;
393 }
394
395 /**
396 * save memory configuration to file
397 */
398 bool savemem(private_guest_t *this, int mem)
399 {
400 FILE *file;
401 bool retval = FALSE;
402
403 file = fdopen(openat(this->dir, MEMORY_FILE, O_RDWR | O_CREAT | O_TRUNC,
404 PERM), "w");
405 if (file)
406 {
407 if (fprintf(file, "%d", mem) > 0)
408 {
409 retval = TRUE;
410 }
411 fclose(file);
412 }
413 return retval;
414 }
415
416 /**
417 * Implementation of guest_t.destroy.
418 */
419 static void destroy(private_guest_t *this)
420 {
421 stop(this, NULL);
422 umount_unionfs(this);
423 if (this->dir > 0)
424 {
425 close(this->dir);
426 }
427 this->ifaces->destroy(this->ifaces);
428 free(this->dirname);
429 free(this->name);
430 free(this);
431 }
432
433 /**
434 * generic guest constructor
435 */
436 static private_guest_t *guest_create_generic(char *parent, char *name,
437 bool create)
438 {
439 char cwd[PATH_MAX];
440 private_guest_t *this = malloc_thing(private_guest_t);
441
442 this->public.get_name = (void*)get_name;
443 this->public.get_pid = (pid_t(*)(guest_t*))get_pid;
444 this->public.get_state = (guest_state_t(*)(guest_t*))get_state;
445 this->public.create_iface = (iface_t*(*)(guest_t*,char*))create_iface;
446 this->public.destroy_iface = (void(*)(guest_t*,iface_t*))destroy_iface;
447 this->public.create_iface_enumerator = (enumerator_t*(*)(guest_t*))create_iface_enumerator;
448 this->public.start = (void*)start;
449 this->public.stop = (void*)stop;
450 this->public.load_template = (bool(*)(guest_t*, char *path))load_template;
451 this->public.sigchild = (void(*)(guest_t*))sigchild;
452 this->public.destroy = (void*)destroy;
453
454 if (*parent == '/' || getcwd(cwd, sizeof(cwd)) == NULL)
455 {
456 asprintf(&this->dirname, "%s/%s", parent, name);
457 }
458 else
459 {
460 asprintf(&this->dirname, "%s/%s/%s", cwd, parent, name);
461 }
462 if (create)
463 {
464 mkdir(this->dirname, PERME);
465 }
466 this->dir = open(this->dirname, O_DIRECTORY, PERME);
467 if (this->dir < 0)
468 {
469 DBG1("opening guest directory '%s' failed: %m", this->dirname);
470 free(this->dirname);
471 free(this);
472 return NULL;
473 }
474 this->pid = 0;
475 this->state = GUEST_STOPPED;
476 this->mconsole = NULL;
477 this->ifaces = linked_list_create();
478 this->mem = 0;
479 this->name = strdup(name);
480 this->cowfs = NULL;
481
482 return this;
483 }
484
485 /**
486 * create a symlink to old called new in our working dir
487 */
488 static bool make_symlink(private_guest_t *this, char *old, char *new)
489 {
490 char cwd[PATH_MAX];
491 char buf[PATH_MAX];
492
493 if (*old == '/' || getcwd(cwd, sizeof(cwd)) == NULL)
494 {
495 snprintf(buf, sizeof(buf), "%s", old);
496 }
497 else
498 {
499 snprintf(buf, sizeof(buf), "%s/%s", cwd, old);
500 }
501 return symlinkat(buf, this->dir, new) == 0;
502 }
503
504
505 /**
506 * create the guest instance, including required dirs and mounts
507 */
508 guest_t *guest_create(char *parent, char *name, char *kernel,
509 char *master, int mem)
510 {
511 private_guest_t *this = guest_create_generic(parent, name, TRUE);
512
513 if (this == NULL)
514 {
515 return NULL;
516 }
517
518 if (!make_symlink(this, master, MASTER_DIR) ||
519 !make_symlink(this, kernel, KERNEL_FILE))
520 {
521 DBG1("creating master/kernel symlink failed: %m");
522 destroy(this);
523 return NULL;
524 }
525
526 if (mkdirat(this->dir, UNION_DIR, PERME) != 0 ||
527 mkdirat(this->dir, DIFF_DIR, PERME) != 0)
528 {
529 DBG1("unable to create directories for '%s': %m", name);
530 destroy(this);
531 return NULL;
532 }
533
534 this->mem = mem;
535 if (!savemem(this, mem))
536 {
537 destroy(this);
538 return NULL;
539 }
540
541 if (!mount_unionfs(this))
542 {
543 destroy(this);
544 return NULL;
545 }
546
547 return &this->public;
548 }
549
550 /**
551 * load an already created guest
552 */
553 guest_t *guest_load(char *parent, char *name)
554 {
555 private_guest_t *this = guest_create_generic(parent, name, FALSE);
556
557 if (this == NULL)
558 {
559 return NULL;
560 }
561
562 this->mem = loadmem(this);
563 if (this->mem == 0)
564 {
565 DBG1("unable to open memory configuration file: %m", name);
566 destroy(this);
567 return NULL;
568 }
569
570 if (!mount_unionfs(this))
571 {
572 destroy(this);
573 return NULL;
574 }
575
576 return &this->public;
577 }
578