]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/machine/machine.c
shared: add formats-util.h
[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 <string.h>
23 #include <unistd.h>
24 #include <errno.h>
25
26 #include "sd-messages.h"
27
28 #include "util.h"
29 #include "mkdir.h"
30 #include "hashmap.h"
31 #include "fileio.h"
32 #include "special.h"
33 #include "unit-name.h"
34 #include "bus-util.h"
35 #include "bus-error.h"
36 #include "machine.h"
37 #include "machine-dbus.h"
38 #include "formats-util.h"
39
40 Machine* machine_new(Manager *manager, const char *name) {
41 Machine *m;
42
43 assert(manager);
44 assert(name);
45
46 m = new0(Machine, 1);
47 if (!m)
48 return NULL;
49
50 m->name = strdup(name);
51 if (!m->name)
52 goto fail;
53
54 m->state_file = strappend("/run/systemd/machines/", m->name);
55 if (!m->state_file)
56 goto fail;
57
58 if (hashmap_put(manager->machines, m->name, m) < 0)
59 goto fail;
60
61 m->class = _MACHINE_CLASS_INVALID;
62 m->manager = manager;
63
64 return m;
65
66 fail:
67 free(m->state_file);
68 free(m->name);
69 free(m);
70
71 return NULL;
72 }
73
74 void machine_free(Machine *m) {
75 assert(m);
76
77 while (m->operations)
78 machine_operation_unref(m->operations);
79
80 if (m->in_gc_queue)
81 LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m);
82
83 if (m->unit) {
84 hashmap_remove(m->manager->machine_units, m->unit);
85 free(m->unit);
86 }
87
88 free(m->scope_job);
89
90 hashmap_remove(m->manager->machines, m->name);
91
92 if (m->leader > 0)
93 hashmap_remove_value(m->manager->machine_leaders, UINT_TO_PTR(m->leader), m);
94
95 sd_bus_message_unref(m->create_message);
96
97 free(m->name);
98 free(m->state_file);
99 free(m->service);
100 free(m->root_directory);
101 free(m->netif);
102 free(m);
103 }
104
105 int machine_save(Machine *m) {
106 _cleanup_free_ char *temp_path = NULL;
107 _cleanup_fclose_ FILE *f = NULL;
108 int r;
109
110 assert(m);
111 assert(m->state_file);
112
113 if (!m->started)
114 return 0;
115
116 r = mkdir_safe_label("/run/systemd/machines", 0755, 0, 0);
117 if (r < 0)
118 goto finish;
119
120 r = fopen_temporary(m->state_file, &f, &temp_path);
121 if (r < 0)
122 goto finish;
123
124 fchmod(fileno(f), 0644);
125
126 fprintf(f,
127 "# This is private data. Do not parse.\n"
128 "NAME=%s\n",
129 m->name);
130
131 if (m->unit) {
132 _cleanup_free_ char *escaped;
133
134 escaped = cescape(m->unit);
135 if (!escaped) {
136 r = -ENOMEM;
137 goto finish;
138 }
139
140 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 */
141 }
142
143 if (m->scope_job)
144 fprintf(f, "SCOPE_JOB=%s\n", m->scope_job);
145
146 if (m->service) {
147 _cleanup_free_ char *escaped;
148
149 escaped = cescape(m->service);
150 if (!escaped) {
151 r = -ENOMEM;
152 goto finish;
153 }
154 fprintf(f, "SERVICE=%s\n", escaped);
155 }
156
157 if (m->root_directory) {
158 _cleanup_free_ char *escaped;
159
160 escaped = cescape(m->root_directory);
161 if (!escaped) {
162 r = -ENOMEM;
163 goto finish;
164 }
165 fprintf(f, "ROOT=%s\n", escaped);
166 }
167
168 if (!sd_id128_equal(m->id, SD_ID128_NULL))
169 fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id));
170
171 if (m->leader != 0)
172 fprintf(f, "LEADER="PID_FMT"\n", m->leader);
173
174 if (m->class != _MACHINE_CLASS_INVALID)
175 fprintf(f, "CLASS=%s\n", machine_class_to_string(m->class));
176
177 if (dual_timestamp_is_set(&m->timestamp))
178 fprintf(f,
179 "REALTIME="USEC_FMT"\n"
180 "MONOTONIC="USEC_FMT"\n",
181 m->timestamp.realtime,
182 m->timestamp.monotonic);
183
184 if (m->n_netif > 0) {
185 unsigned i;
186
187 fputs("NETIF=", f);
188
189 for (i = 0; i < m->n_netif; i++) {
190 if (i != 0)
191 fputc(' ', f);
192
193 fprintf(f, "%i", m->netif[i]);
194 }
195
196 fputc('\n', f);
197 }
198
199 r = fflush_and_check(f);
200 if (r < 0)
201 goto finish;
202
203 if (rename(temp_path, m->state_file) < 0) {
204 r = -errno;
205 goto finish;
206 }
207
208 free(temp_path);
209 temp_path = NULL;
210
211 if (m->unit) {
212 char *sl;
213
214 /* Create a symlink from the unit name to the machine
215 * name, so that we can quickly find the machine for
216 * each given unit. Ignore error. */
217 sl = strjoina("/run/systemd/machines/unit:", m->unit);
218 (void) symlink(m->name, sl);
219 }
220
221 finish:
222 if (temp_path)
223 unlink(temp_path);
224
225 if (r < 0)
226 log_error_errno(r, "Failed to save machine data %s: %m", m->state_file);
227
228 return r;
229 }
230
231 static void machine_unlink(Machine *m) {
232 assert(m);
233
234 if (m->unit) {
235
236 char *sl;
237
238 sl = strjoina("/run/systemd/machines/unit:", m->unit);
239 unlink(sl);
240 }
241
242 if (m->state_file)
243 unlink(m->state_file);
244 }
245
246 int machine_load(Machine *m) {
247 _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *class = NULL, *netif = NULL;
248 int r;
249
250 assert(m);
251
252 r = parse_env_file(m->state_file, NEWLINE,
253 "SCOPE", &m->unit,
254 "SCOPE_JOB", &m->scope_job,
255 "SERVICE", &m->service,
256 "ROOT", &m->root_directory,
257 "ID", &id,
258 "LEADER", &leader,
259 "CLASS", &class,
260 "REALTIME", &realtime,
261 "MONOTONIC", &monotonic,
262 "NETIF", &netif,
263 NULL);
264 if (r < 0) {
265 if (r == -ENOENT)
266 return 0;
267
268 return log_error_errno(r, "Failed to read %s: %m", m->state_file);
269 }
270
271 if (id)
272 sd_id128_from_string(id, &m->id);
273
274 if (leader)
275 parse_pid(leader, &m->leader);
276
277 if (class) {
278 MachineClass c;
279
280 c = machine_class_from_string(class);
281 if (c >= 0)
282 m->class = c;
283 }
284
285 if (realtime) {
286 unsigned long long l;
287 if (sscanf(realtime, "%llu", &l) > 0)
288 m->timestamp.realtime = l;
289 }
290
291 if (monotonic) {
292 unsigned long long l;
293 if (sscanf(monotonic, "%llu", &l) > 0)
294 m->timestamp.monotonic = l;
295 }
296
297 if (netif) {
298 size_t l, allocated = 0, nr = 0;
299 const char *word, *state;
300 int *ni = NULL;
301
302 FOREACH_WORD(word, l, netif, state) {
303 char buf[l+1];
304 int ifi;
305
306 *(char*) (mempcpy(buf, word, l)) = 0;
307
308 if (safe_atoi(buf, &ifi) < 0)
309 continue;
310 if (ifi <= 0)
311 continue;
312
313 if (!GREEDY_REALLOC(ni, allocated, nr+1)) {
314 free(ni);
315 return log_oom();
316 }
317
318 ni[nr++] = ifi;
319 }
320
321 free(m->netif);
322 m->netif = ni;
323 m->n_netif = nr;
324 }
325
326 return r;
327 }
328
329 static int machine_start_scope(Machine *m, sd_bus_message *properties, sd_bus_error *error) {
330 int r = 0;
331
332 assert(m);
333
334 if (!m->unit) {
335 _cleanup_free_ char *escaped = NULL;
336 char *scope, *description, *job = NULL;
337
338 escaped = unit_name_escape(m->name);
339 if (!escaped)
340 return log_oom();
341
342 scope = strjoin("machine-", escaped, ".scope", NULL);
343 if (!scope)
344 return log_oom();
345
346 description = strjoina(m->class == MACHINE_VM ? "Virtual Machine " : "Container ", m->name);
347
348 r = manager_start_scope(m->manager, scope, m->leader, SPECIAL_MACHINE_SLICE, description, properties, error, &job);
349 if (r < 0) {
350 log_error("Failed to start machine scope: %s", bus_error_message(error, r));
351 free(scope);
352 return r;
353 } else {
354 m->unit = scope;
355
356 free(m->scope_job);
357 m->scope_job = job;
358 }
359 }
360
361 if (m->unit)
362 hashmap_put(m->manager->machine_units, m->unit, m);
363
364 return r;
365 }
366
367 int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) {
368 int r;
369
370 assert(m);
371
372 if (m->started)
373 return 0;
374
375 r = hashmap_put(m->manager->machine_leaders, UINT_TO_PTR(m->leader), m);
376 if (r < 0)
377 return r;
378
379 /* Create cgroup */
380 r = machine_start_scope(m, properties, error);
381 if (r < 0)
382 return r;
383
384 log_struct(LOG_INFO,
385 LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_START),
386 "NAME=%s", m->name,
387 "LEADER="PID_FMT, m->leader,
388 LOG_MESSAGE("New machine %s.", m->name),
389 NULL);
390
391 if (!dual_timestamp_is_set(&m->timestamp))
392 dual_timestamp_get(&m->timestamp);
393
394 m->started = true;
395
396 /* Save new machine data */
397 machine_save(m);
398
399 machine_send_signal(m, true);
400
401 return 0;
402 }
403
404 static int machine_stop_scope(Machine *m) {
405 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
406 char *job = NULL;
407 int r;
408
409 assert(m);
410
411 if (!m->unit)
412 return 0;
413
414 r = manager_stop_unit(m->manager, m->unit, &error, &job);
415 if (r < 0) {
416 log_error("Failed to stop machine scope: %s", bus_error_message(&error, r));
417 return r;
418 }
419
420 free(m->scope_job);
421 m->scope_job = job;
422
423 return 0;
424 }
425
426 int machine_stop(Machine *m) {
427 int r = 0, k;
428 assert(m);
429
430 if (m->started)
431 log_struct(LOG_INFO,
432 LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_STOP),
433 "NAME=%s", m->name,
434 "LEADER="PID_FMT, m->leader,
435 LOG_MESSAGE("Machine %s terminated.", m->name),
436 NULL);
437
438 /* Kill cgroup */
439 k = machine_stop_scope(m);
440 if (k < 0)
441 r = k;
442
443 machine_unlink(m);
444 machine_add_to_gc_queue(m);
445
446 if (m->started)
447 machine_send_signal(m, false);
448
449 m->started = false;
450
451 return r;
452 }
453
454 bool machine_check_gc(Machine *m, bool drop_not_started) {
455 assert(m);
456
457 if (drop_not_started && !m->started)
458 return false;
459
460 if (m->scope_job && manager_job_is_active(m->manager, m->scope_job))
461 return true;
462
463 if (m->unit && manager_unit_is_active(m->manager, m->unit))
464 return true;
465
466 return false;
467 }
468
469 void machine_add_to_gc_queue(Machine *m) {
470 assert(m);
471
472 if (m->in_gc_queue)
473 return;
474
475 LIST_PREPEND(gc_queue, m->manager->machine_gc_queue, m);
476 m->in_gc_queue = true;
477 }
478
479 MachineState machine_get_state(Machine *s) {
480 assert(s);
481
482 if (s->scope_job)
483 return s->started ? MACHINE_OPENING : MACHINE_CLOSING;
484
485 return MACHINE_RUNNING;
486 }
487
488 int machine_kill(Machine *m, KillWho who, int signo) {
489 assert(m);
490
491 if (!m->unit)
492 return -ESRCH;
493
494 if (who == KILL_LEADER) {
495 /* If we shall simply kill the leader, do so directly */
496
497 if (kill(m->leader, signo) < 0)
498 return -errno;
499
500 return 0;
501 }
502
503 /* Otherwise make PID 1 do it for us, for the entire cgroup */
504 return manager_kill_unit(m->manager, m->unit, signo, NULL);
505 }
506
507 MachineOperation *machine_operation_unref(MachineOperation *o) {
508 if (!o)
509 return NULL;
510
511 sd_event_source_unref(o->event_source);
512
513 safe_close(o->errno_fd);
514
515 if (o->pid > 1)
516 (void) kill(o->pid, SIGKILL);
517
518 sd_bus_message_unref(o->message);
519
520 if (o->machine) {
521 LIST_REMOVE(operations, o->machine->operations, o);
522 o->machine->n_operations--;
523 }
524
525 free(o);
526 return NULL;
527 }
528
529 static const char* const machine_class_table[_MACHINE_CLASS_MAX] = {
530 [MACHINE_CONTAINER] = "container",
531 [MACHINE_VM] = "vm"
532 };
533
534 DEFINE_STRING_TABLE_LOOKUP(machine_class, MachineClass);
535
536 static const char* const machine_state_table[_MACHINE_STATE_MAX] = {
537 [MACHINE_OPENING] = "opening",
538 [MACHINE_RUNNING] = "running",
539 [MACHINE_CLOSING] = "closing"
540 };
541
542 DEFINE_STRING_TABLE_LOOKUP(machine_state, MachineState);
543
544 static const char* const kill_who_table[_KILL_WHO_MAX] = {
545 [KILL_LEADER] = "leader",
546 [KILL_ALL] = "all"
547 };
548
549 DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho);