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