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