]>
Commit | Line | Data |
---|---|---|
9444b1f2 LP |
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) { | |
4ad49000 | 226 | r = cg_create(controller, path); |
9444b1f2 LP |
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); |