chiark / gitweb /
logind: add infrastructure to keep track of machines, and move to slices
[elogind.git] / src / login / logind-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 "logind-machine.h"
27 #include "util.h"
28 #include "mkdir.h"
29 #include "cgroup-util.h"
30 #include "hashmap.h"
31 #include "strv.h"
32 #include "fileio.h"
33 #include "special.h"
34 #include <systemd/sd-messages.h>
35
36 Machine* machine_new(Manager *manager, const char *name) {
37         Machine *m;
38
39         assert(manager);
40         assert(name);
41
42         m = new0(Machine, 1);
43         if (!m)
44                 return NULL;
45
46         m->name = strdup(name);
47         if (!m->name)
48                 goto fail;
49
50         m->state_file = strappend("/run/systemd/machines/", m->name);
51         if (!m->state_file)
52                 goto fail;
53
54         if (hashmap_put(manager->machines, m->name, m) < 0)
55                 goto fail;
56
57         m->class = _MACHINE_CLASS_INVALID;
58         m->manager = manager;
59
60         return m;
61
62 fail:
63         free(m->state_file);
64         free(m->name);
65         free(m);
66
67         return NULL;
68 }
69
70 void machine_free(Machine *m) {
71         assert(m);
72
73         if (m->in_gc_queue)
74                 LIST_REMOVE(Machine, gc_queue, m->manager->machine_gc_queue, m);
75
76         if (m->cgroup_path) {
77                 hashmap_remove(m->manager->machine_cgroups, m->cgroup_path);
78                 free(m->cgroup_path);
79         }
80
81         hashmap_remove(m->manager->machines, m->name);
82
83         free(m->name);
84         free(m->state_file);
85         free(m->service);
86         free(m->slice);
87         free(m->root_directory);
88         free(m);
89 }
90
91 int machine_save(Machine *m) {
92         _cleanup_free_ char *temp_path = NULL;
93         _cleanup_fclose_ FILE *f = NULL;
94         int r;
95
96         assert(m);
97         assert(m->state_file);
98
99         if (!m->started)
100                 return 0;
101
102         r = mkdir_safe_label("/run/systemd/machines", 0755, 0, 0);
103         if (r < 0)
104                 goto finish;
105
106         r = fopen_temporary(m->state_file, &f, &temp_path);
107         if (r < 0)
108                 goto finish;
109
110         fchmod(fileno(f), 0644);
111
112         fprintf(f,
113                 "# This is private data. Do not parse.\n"
114                 "NAME=%s\n",
115                 m->name);
116
117         if (m->cgroup_path)
118                 fprintf(f, "CGROUP=%s\n", m->cgroup_path);
119
120         if (m->service)
121                 fprintf(f, "SERVICE=%s\n", m->service);
122
123         if (m->slice)
124                 fprintf(f, "SLICE=%s\n", m->slice);
125
126         if (m->root_directory)
127                 fprintf(f, "ROOT=%s\n", m->root_directory);
128
129         if (!sd_id128_equal(m->id, SD_ID128_NULL))
130                 fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id));
131
132         if (m->leader != 0)
133                 fprintf(f, "LEADER=%lu\n", (unsigned long) m->leader);
134
135         if (m->class != _MACHINE_CLASS_INVALID)
136                 fprintf(f, "CLASS=%s\n", machine_class_to_string(m->class));
137
138         if (dual_timestamp_is_set(&m->timestamp))
139                 fprintf(f,
140                         "REALTIME=%llu\n"
141                         "MONOTONIC=%llu\n",
142                         (unsigned long long) m->timestamp.realtime,
143                         (unsigned long long) m->timestamp.monotonic);
144
145         fflush(f);
146
147         if (ferror(f) || rename(temp_path, m->state_file) < 0) {
148                 r = -errno;
149                 unlink(m->state_file);
150                 unlink(temp_path);
151         }
152
153 finish:
154         if (r < 0)
155                 log_error("Failed to save machine data for %s: %s", m->name, strerror(-r));
156
157         return r;
158 }
159
160 int machine_load(Machine *m) {
161         _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *class = NULL;
162         int r;
163
164         assert(m);
165
166         r = parse_env_file(m->state_file, NEWLINE,
167                            "CGROUP",    &m->cgroup_path,
168                            "SERVICE",   &m->service,
169                            "SLICE",     &m->slice,
170                            "ROOT",      &m->root_directory,
171                            "ID",        &id,
172                            "LEADER",    &leader,
173                            "CLASS",     &class,
174                            "REALTIME",  &realtime,
175                            "MONOTONIC", &monotonic,
176                            NULL);
177         if (r < 0) {
178                 if (r == -ENOENT)
179                         return 0;
180
181                 log_error("Failed to read %s: %s", m->state_file, strerror(-r));
182                 return r;
183         }
184
185         if (id)
186                 sd_id128_from_string(id, &m->id);
187
188         if (leader)
189                 parse_pid(leader, &m->leader);
190
191         if (class) {
192                 MachineClass c;
193
194                 c = machine_class_from_string(class);
195                 if (c >= 0)
196                         m->class = c;
197         }
198
199         if (realtime) {
200                 unsigned long long l;
201                 if (sscanf(realtime, "%llu", &l) > 0)
202                         m->timestamp.realtime = l;
203         }
204
205         if (monotonic) {
206                 unsigned long long l;
207                 if (sscanf(monotonic, "%llu", &l) > 0)
208                         m->timestamp.monotonic = l;
209         }
210
211         return r;
212 }
213
214 static int machine_create_one_group(Machine *m, const char *controller, const char *path) {
215         int r;
216
217         assert(m);
218         assert(path);
219
220         if (m->leader > 0)
221                 r = cg_create_and_attach(controller, path, m->leader);
222         else
223                 r = -EINVAL;
224
225         if (r < 0) {
226                 r = cg_create(controller, path, NULL);
227                 if (r < 0)
228                         return r;
229         }
230
231         return 0;
232 }
233
234 static int machine_create_cgroup(Machine *m) {
235         char **k;
236         int r;
237
238         assert(m);
239
240         if (!m->slice) {
241                 m->slice = strdup(SPECIAL_MACHINE_SLICE);
242                 if (!m->slice)
243                         return log_oom();
244         }
245
246         if (!m->cgroup_path) {
247                 _cleanup_free_ char *escaped = NULL, *slice = NULL;
248                 char *name;
249
250                 name = strappenda(m->name, ".machine");
251
252                 escaped = cg_escape(name);
253                 if (!escaped)
254                         return log_oom();
255
256                 r = cg_slice_to_path(m->slice, &slice);
257                 if (r < 0)
258                         return r;
259
260                 m->cgroup_path = strjoin(m->manager->cgroup_root, "/", slice, "/", escaped, NULL);
261                 if (!m->cgroup_path)
262                         return log_oom();
263         }
264
265         r = machine_create_one_group(m, SYSTEMD_CGROUP_CONTROLLER, m->cgroup_path);
266         if (r < 0) {
267                 log_error("Failed to create cgroup "SYSTEMD_CGROUP_CONTROLLER":%s: %s", m->cgroup_path, strerror(-r));
268                 return r;
269         }
270
271         STRV_FOREACH(k, m->manager->controllers) {
272
273                 if (strv_contains(m->manager->reset_controllers, *k))
274                         continue;
275
276                 r = machine_create_one_group(m, *k, m->cgroup_path);
277                 if (r < 0)
278                         log_warning("Failed to create cgroup %s:%s: %s", *k, m->cgroup_path, strerror(-r));
279         }
280
281         if (m->leader > 0) {
282                 STRV_FOREACH(k, m->manager->reset_controllers) {
283                         r = cg_attach(*k, "/", m->leader);
284                         if (r < 0)
285                                 log_warning("Failed to reset controller %s: %s", *k, strerror(-r));
286                 }
287         }
288
289         r = hashmap_put(m->manager->machine_cgroups, m->cgroup_path, m);
290         if (r < 0)
291                 log_warning("Failed to create mapping between cgroup and machine");
292
293         return 0;
294 }
295
296 int machine_start(Machine *m) {
297         int r;
298
299         assert(m);
300
301         if (m->started)
302                 return 0;
303
304         log_struct(LOG_INFO,
305                    MESSAGE_ID(SD_MESSAGE_MACHINE_START),
306                    "NAME=%s", m->name,
307                    "LEADER=%lu", (unsigned long) m->leader,
308                    "MESSAGE=New machine %s.", m->name,
309                    NULL);
310
311         /* Create cgroup */
312         r = machine_create_cgroup(m);
313         if (r < 0)
314                 return r;
315
316         if (!dual_timestamp_is_set(&m->timestamp))
317                 dual_timestamp_get(&m->timestamp);
318
319         m->started = true;
320
321         /* Save new machine data */
322         machine_save(m);
323
324         machine_send_signal(m, true);
325
326         return 0;
327 }
328
329 static int machine_terminate_cgroup(Machine *m) {
330         int r;
331         char **k;
332
333         assert(m);
334
335         if (!m->cgroup_path)
336                 return 0;
337
338         cg_trim(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_path, false);
339
340         r = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_path, true);
341         if (r < 0)
342                 log_error("Failed to kill machine cgroup: %s", strerror(-r));
343
344         STRV_FOREACH(k, m->manager->controllers)
345                 cg_trim(*k, m->cgroup_path, true);
346
347         hashmap_remove(m->manager->machine_cgroups, m->cgroup_path);
348
349         free(m->cgroup_path);
350         m->cgroup_path = NULL;
351
352         return r;
353 }
354
355 int machine_stop(Machine *m) {
356         int r = 0, k;
357         assert(m);
358
359         if (m->started)
360                 log_struct(LOG_INFO,
361                            MESSAGE_ID(SD_MESSAGE_MACHINE_STOP),
362                            "NAME=%s", m->name,
363                            "LEADER=%lu", (unsigned long) m->leader,
364                            "MESSAGE=Machine %s terminated.", m->name,
365                            NULL);
366
367         /* Kill cgroup */
368         k = machine_terminate_cgroup(m);
369         if (k < 0)
370                 r = k;
371
372         unlink(m->state_file);
373         machine_add_to_gc_queue(m);
374
375         if (m->started)
376                 machine_send_signal(m, false);
377
378         m->started = false;
379
380         return r;
381 }
382
383 int machine_check_gc(Machine *m, bool drop_not_started) {
384         int r;
385
386         assert(m);
387
388         if (drop_not_started && !m->started)
389                 return 0;
390
391         if (m->cgroup_path) {
392                 r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_path, false);
393                 if (r < 0)
394                         return r;
395
396                 if (r <= 0)
397                         return 1;
398         }
399
400         return 0;
401 }
402
403 void machine_add_to_gc_queue(Machine *m) {
404         assert(m);
405
406         if (m->in_gc_queue)
407                 return;
408
409         LIST_PREPEND(Machine, gc_queue, m->manager->machine_gc_queue, m);
410         m->in_gc_queue = true;
411 }
412
413 int machine_kill(Machine *m, KillWho who, int signo) {
414         _cleanup_set_free_ Set *pid_set = NULL;
415         int r = 0;
416
417         assert(m);
418
419         if (!m->cgroup_path)
420                 return -ESRCH;
421
422         if (m->leader <= 0 && who == KILL_LEADER)
423                 return -ESRCH;
424
425         if (m->leader > 0)
426                 if (kill(m->leader, signo) < 0)
427                         r = -errno;
428
429         if (who == KILL_ALL) {
430                 int q;
431
432                 pid_set = set_new(trivial_hash_func, trivial_compare_func);
433                 if (!pid_set)
434                         return log_oom();
435
436                 if (m->leader > 0) {
437                         q = set_put(pid_set, LONG_TO_PTR(m->leader));
438                         if (q < 0)
439                                 r = q;
440                 }
441
442                 q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_path, signo, false, true, false, pid_set);
443                 if (q < 0 && (q != -EAGAIN && q != -ESRCH && q != -ENOENT))
444                         r = q;
445         }
446
447         return r;
448 }
449
450 static const char* const machine_class_table[_MACHINE_CLASS_MAX] = {
451         [MACHINE_CONTAINER] = "container",
452         [MACHINE_VM] = "vm"
453 };
454
455 DEFINE_STRING_TABLE_LOOKUP(machine_class, MachineClass);