]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/machine/machine.c
Merge pull request #1659 from vcaputo/journal_verify_envalid
[thirdparty/systemd.git] / src / machine / machine.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2011 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24 #include <unistd.h>
25
26 #include "sd-messages.h"
27
28 #include "bus-error.h"
29 #include "bus-util.h"
30 #include "fileio.h"
31 #include "formats-util.h"
32 #include "hashmap.h"
33 #include "mkdir.h"
34 #include "special.h"
35 #include "terminal-util.h"
36 #include "unit-name.h"
37 #include "util.h"
38 #include "machine-dbus.h"
39 #include "machine.h"
40
41 Machine* machine_new(Manager *manager, MachineClass class, const char *name) {
42 Machine *m;
43
44 assert(manager);
45 assert(class < _MACHINE_CLASS_MAX);
46 assert(name);
47
48 /* Passing class == _MACHINE_CLASS_INVALID here is fine. It
49 * means as much as "we don't know yet", and that we'll figure
50 * it out later when loading the state file. */
51
52 m = new0(Machine, 1);
53 if (!m)
54 return NULL;
55
56 m->name = strdup(name);
57 if (!m->name)
58 goto fail;
59
60 if (class != MACHINE_HOST) {
61 m->state_file = strappend("/run/systemd/machines/", m->name);
62 if (!m->state_file)
63 goto fail;
64 }
65
66 m->class = class;
67
68 if (hashmap_put(manager->machines, m->name, m) < 0)
69 goto fail;
70
71 m->manager = manager;
72
73 return m;
74
75 fail:
76 free(m->state_file);
77 free(m->name);
78 free(m);
79
80 return NULL;
81 }
82
83 void machine_free(Machine *m) {
84 assert(m);
85
86 while (m->operations)
87 machine_operation_unref(m->operations);
88
89 if (m->in_gc_queue)
90 LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m);
91
92 machine_release_unit(m);
93
94 free(m->scope_job);
95
96 (void) hashmap_remove(m->manager->machines, m->name);
97
98 if (m->manager->host_machine == m)
99 m->manager->host_machine = NULL;
100
101 if (m->leader > 0)
102 (void) hashmap_remove_value(m->manager->machine_leaders, UINT_TO_PTR(m->leader), m);
103
104 sd_bus_message_unref(m->create_message);
105
106 free(m->name);
107 free(m->state_file);
108 free(m->service);
109 free(m->root_directory);
110 free(m->netif);
111 free(m);
112 }
113
114 int machine_save(Machine *m) {
115 _cleanup_free_ char *temp_path = NULL;
116 _cleanup_fclose_ FILE *f = NULL;
117 int r;
118
119 assert(m);
120
121 if (!m->state_file)
122 return 0;
123
124 if (!m->started)
125 return 0;
126
127 r = mkdir_safe_label("/run/systemd/machines", 0755, 0, 0);
128 if (r < 0)
129 goto fail;
130
131 r = fopen_temporary(m->state_file, &f, &temp_path);
132 if (r < 0)
133 goto fail;
134
135 (void) fchmod(fileno(f), 0644);
136
137 fprintf(f,
138 "# This is private data. Do not parse.\n"
139 "NAME=%s\n",
140 m->name);
141
142 if (m->unit) {
143 _cleanup_free_ char *escaped;
144
145 escaped = cescape(m->unit);
146 if (!escaped) {
147 r = -ENOMEM;
148 goto fail;
149 }
150
151 fprintf(f, "SCOPE=%s\n", escaped); /* We continue to call this "SCOPE=" because it is internal only, and we want to stay compatible with old files */
152 }
153
154 if (m->scope_job)
155 fprintf(f, "SCOPE_JOB=%s\n", m->scope_job);
156
157 if (m->service) {
158 _cleanup_free_ char *escaped;
159
160 escaped = cescape(m->service);
161 if (!escaped) {
162 r = -ENOMEM;
163 goto fail;
164 }
165 fprintf(f, "SERVICE=%s\n", escaped);
166 }
167
168 if (m->root_directory) {
169 _cleanup_free_ char *escaped;
170
171 escaped = cescape(m->root_directory);
172 if (!escaped) {
173 r = -ENOMEM;
174 goto fail;
175 }
176 fprintf(f, "ROOT=%s\n", escaped);
177 }
178
179 if (!sd_id128_equal(m->id, SD_ID128_NULL))
180 fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id));
181
182 if (m->leader != 0)
183 fprintf(f, "LEADER="PID_FMT"\n", m->leader);
184
185 if (m->class != _MACHINE_CLASS_INVALID)
186 fprintf(f, "CLASS=%s\n", machine_class_to_string(m->class));
187
188 if (dual_timestamp_is_set(&m->timestamp))
189 fprintf(f,
190 "REALTIME="USEC_FMT"\n"
191 "MONOTONIC="USEC_FMT"\n",
192 m->timestamp.realtime,
193 m->timestamp.monotonic);
194
195 if (m->n_netif > 0) {
196 unsigned i;
197
198 fputs("NETIF=", f);
199
200 for (i = 0; i < m->n_netif; i++) {
201 if (i != 0)
202 fputc(' ', f);
203
204 fprintf(f, "%i", m->netif[i]);
205 }
206
207 fputc('\n', f);
208 }
209
210 r = fflush_and_check(f);
211 if (r < 0)
212 goto fail;
213
214 if (rename(temp_path, m->state_file) < 0) {
215 r = -errno;
216 goto fail;
217 }
218
219 if (m->unit) {
220 char *sl;
221
222 /* Create a symlink from the unit name to the machine
223 * name, so that we can quickly find the machine for
224 * each given unit. Ignore error. */
225 sl = strjoina("/run/systemd/machines/unit:", m->unit);
226 (void) symlink(m->name, sl);
227 }
228
229 return 0;
230
231 fail:
232 (void) unlink(m->state_file);
233
234 if (temp_path)
235 (void) unlink(temp_path);
236
237 return log_error_errno(r, "Failed to save machine data %s: %m", m->state_file);
238 }
239
240 static void machine_unlink(Machine *m) {
241 assert(m);
242
243 if (m->unit) {
244
245 char *sl;
246
247 sl = strjoina("/run/systemd/machines/unit:", m->unit);
248 (void) unlink(sl);
249 }
250
251 if (m->state_file)
252 (void) unlink(m->state_file);
253 }
254
255 int machine_load(Machine *m) {
256 _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *class = NULL, *netif = NULL;
257 int r;
258
259 assert(m);
260
261 if (!m->state_file)
262 return 0;
263
264 r = parse_env_file(m->state_file, NEWLINE,
265 "SCOPE", &m->unit,
266 "SCOPE_JOB", &m->scope_job,
267 "SERVICE", &m->service,
268 "ROOT", &m->root_directory,
269 "ID", &id,
270 "LEADER", &leader,
271 "CLASS", &class,
272 "REALTIME", &realtime,
273 "MONOTONIC", &monotonic,
274 "NETIF", &netif,
275 NULL);
276 if (r < 0) {
277 if (r == -ENOENT)
278 return 0;
279
280 return log_error_errno(r, "Failed to read %s: %m", m->state_file);
281 }
282
283 if (id)
284 sd_id128_from_string(id, &m->id);
285
286 if (leader)
287 parse_pid(leader, &m->leader);
288
289 if (class) {
290 MachineClass c;
291
292 c = machine_class_from_string(class);
293 if (c >= 0)
294 m->class = c;
295 }
296
297 if (realtime) {
298 unsigned long long l;
299 if (sscanf(realtime, "%llu", &l) > 0)
300 m->timestamp.realtime = l;
301 }
302
303 if (monotonic) {
304 unsigned long long l;
305 if (sscanf(monotonic, "%llu", &l) > 0)
306 m->timestamp.monotonic = l;
307 }
308
309 if (netif) {
310 size_t l, allocated = 0, nr = 0;
311 const char *word, *state;
312 int *ni = NULL;
313
314 FOREACH_WORD(word, l, netif, state) {
315 char buf[l+1];
316 int ifi;
317
318 *(char*) (mempcpy(buf, word, l)) = 0;
319
320 if (safe_atoi(buf, &ifi) < 0)
321 continue;
322 if (ifi <= 0)
323 continue;
324
325 if (!GREEDY_REALLOC(ni, allocated, nr+1)) {
326 free(ni);
327 return log_oom();
328 }
329
330 ni[nr++] = ifi;
331 }
332
333 free(m->netif);
334 m->netif = ni;
335 m->n_netif = nr;
336 }
337
338 return r;
339 }
340
341 static int machine_start_scope(Machine *m, sd_bus_message *properties, sd_bus_error *error) {
342 int r = 0;
343
344 assert(m);
345 assert(m->class != MACHINE_HOST);
346
347 if (!m->unit) {
348 _cleanup_free_ char *escaped = NULL;
349 char *scope, *description, *job = NULL;
350
351 escaped = unit_name_escape(m->name);
352 if (!escaped)
353 return log_oom();
354
355 scope = strjoin("machine-", escaped, ".scope", NULL);
356 if (!scope)
357 return log_oom();
358
359 description = strjoina(m->class == MACHINE_VM ? "Virtual Machine " : "Container ", m->name);
360
361 r = manager_start_scope(m->manager, scope, m->leader, SPECIAL_MACHINE_SLICE, description, properties, error, &job);
362 if (r < 0) {
363 log_error("Failed to start machine scope: %s", bus_error_message(error, r));
364 free(scope);
365 return r;
366 } else {
367 m->unit = scope;
368
369 free(m->scope_job);
370 m->scope_job = job;
371 }
372 }
373
374 if (m->unit)
375 hashmap_put(m->manager->machine_units, m->unit, m);
376
377 return r;
378 }
379
380 int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) {
381 int r;
382
383 assert(m);
384
385 if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM))
386 return -EOPNOTSUPP;
387
388 if (m->started)
389 return 0;
390
391 r = hashmap_put(m->manager->machine_leaders, UINT_TO_PTR(m->leader), m);
392 if (r < 0)
393 return r;
394
395 /* Create cgroup */
396 r = machine_start_scope(m, properties, error);
397 if (r < 0)
398 return r;
399
400 log_struct(LOG_INFO,
401 LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_START),
402 "NAME=%s", m->name,
403 "LEADER="PID_FMT, m->leader,
404 LOG_MESSAGE("New machine %s.", m->name),
405 NULL);
406
407 if (!dual_timestamp_is_set(&m->timestamp))
408 dual_timestamp_get(&m->timestamp);
409
410 m->started = true;
411
412 /* Save new machine data */
413 machine_save(m);
414
415 machine_send_signal(m, true);
416
417 return 0;
418 }
419
420 static int machine_stop_scope(Machine *m) {
421 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
422 char *job = NULL;
423 int r;
424
425 assert(m);
426 assert(m->class != MACHINE_HOST);
427
428 if (!m->unit)
429 return 0;
430
431 r = manager_stop_unit(m->manager, m->unit, &error, &job);
432 if (r < 0) {
433 log_error("Failed to stop machine scope: %s", bus_error_message(&error, r));
434 return r;
435 }
436
437 free(m->scope_job);
438 m->scope_job = job;
439
440 return 0;
441 }
442
443 int machine_stop(Machine *m) {
444 int r;
445 assert(m);
446
447 if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM))
448 return -EOPNOTSUPP;
449
450 r = machine_stop_scope(m);
451
452 m->stopping = true;
453
454 machine_save(m);
455
456 return r;
457 }
458
459 int machine_finalize(Machine *m) {
460 assert(m);
461
462 if (m->started)
463 log_struct(LOG_INFO,
464 LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_STOP),
465 "NAME=%s", m->name,
466 "LEADER="PID_FMT, m->leader,
467 LOG_MESSAGE("Machine %s terminated.", m->name),
468 NULL);
469
470 machine_unlink(m);
471 machine_add_to_gc_queue(m);
472
473 if (m->started) {
474 machine_send_signal(m, false);
475 m->started = false;
476 }
477
478 return 0;
479 }
480
481 bool machine_check_gc(Machine *m, bool drop_not_started) {
482 assert(m);
483
484 if (m->class == MACHINE_HOST)
485 return true;
486
487 if (drop_not_started && !m->started)
488 return false;
489
490 if (m->scope_job && manager_job_is_active(m->manager, m->scope_job))
491 return true;
492
493 if (m->unit && manager_unit_is_active(m->manager, m->unit))
494 return true;
495
496 return false;
497 }
498
499 void machine_add_to_gc_queue(Machine *m) {
500 assert(m);
501
502 if (m->in_gc_queue)
503 return;
504
505 LIST_PREPEND(gc_queue, m->manager->machine_gc_queue, m);
506 m->in_gc_queue = true;
507 }
508
509 MachineState machine_get_state(Machine *s) {
510 assert(s);
511
512 if (s->class == MACHINE_HOST)
513 return MACHINE_RUNNING;
514
515 if (s->stopping)
516 return MACHINE_CLOSING;
517
518 if (s->scope_job)
519 return MACHINE_OPENING;
520
521 return MACHINE_RUNNING;
522 }
523
524 int machine_kill(Machine *m, KillWho who, int signo) {
525 assert(m);
526
527 if (!IN_SET(m->class, MACHINE_VM, MACHINE_CONTAINER))
528 return -EOPNOTSUPP;
529
530 if (!m->unit)
531 return -ESRCH;
532
533 if (who == KILL_LEADER) {
534 /* If we shall simply kill the leader, do so directly */
535
536 if (kill(m->leader, signo) < 0)
537 return -errno;
538
539 return 0;
540 }
541
542 /* Otherwise make PID 1 do it for us, for the entire cgroup */
543 return manager_kill_unit(m->manager, m->unit, signo, NULL);
544 }
545
546 int machine_openpt(Machine *m, int flags) {
547 assert(m);
548
549 switch (m->class) {
550
551 case MACHINE_HOST: {
552 int fd;
553
554 fd = posix_openpt(flags);
555 if (fd < 0)
556 return -errno;
557
558 if (unlockpt(fd) < 0)
559 return -errno;
560
561 return fd;
562 }
563
564 case MACHINE_CONTAINER:
565 if (m->leader <= 0)
566 return -EINVAL;
567
568 return openpt_in_namespace(m->leader, flags);
569
570 default:
571 return -EOPNOTSUPP;
572 }
573 }
574
575 int machine_open_terminal(Machine *m, const char *path, int mode) {
576 assert(m);
577
578 switch (m->class) {
579
580 case MACHINE_HOST:
581 return open_terminal(path, mode);
582
583 case MACHINE_CONTAINER:
584 if (m->leader <= 0)
585 return -EINVAL;
586
587 return open_terminal_in_namespace(m->leader, path, mode);
588
589 default:
590 return -EOPNOTSUPP;
591 }
592 }
593
594 MachineOperation *machine_operation_unref(MachineOperation *o) {
595 if (!o)
596 return NULL;
597
598 sd_event_source_unref(o->event_source);
599
600 safe_close(o->errno_fd);
601
602 if (o->pid > 1)
603 (void) kill(o->pid, SIGKILL);
604
605 sd_bus_message_unref(o->message);
606
607 if (o->machine) {
608 LIST_REMOVE(operations, o->machine->operations, o);
609 o->machine->n_operations--;
610 }
611
612 free(o);
613 return NULL;
614 }
615
616 void machine_release_unit(Machine *m) {
617 assert(m);
618
619 if (!m->unit)
620 return;
621
622 (void) hashmap_remove(m->manager->machine_units, m->unit);
623 m->unit = mfree(m->unit);
624 }
625
626 static const char* const machine_class_table[_MACHINE_CLASS_MAX] = {
627 [MACHINE_CONTAINER] = "container",
628 [MACHINE_VM] = "vm",
629 [MACHINE_HOST] = "host",
630 };
631
632 DEFINE_STRING_TABLE_LOOKUP(machine_class, MachineClass);
633
634 static const char* const machine_state_table[_MACHINE_STATE_MAX] = {
635 [MACHINE_OPENING] = "opening",
636 [MACHINE_RUNNING] = "running",
637 [MACHINE_CLOSING] = "closing"
638 };
639
640 DEFINE_STRING_TABLE_LOOKUP(machine_state, MachineState);
641
642 static const char* const kill_who_table[_KILL_WHO_MAX] = {
643 [KILL_LEADER] = "leader",
644 [KILL_ALL] = "all"
645 };
646
647 DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho);