]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
9444b1f2 | 2 | |
66cb2fde | 3 | #include <errno.h> |
9444b1f2 | 4 | #include <unistd.h> |
ca78ad1d | 5 | #include <sys/stat.h> |
9444b1f2 | 6 | |
c3350683 | 7 | #include "sd-messages.h" |
fb6becb4 | 8 | |
b5efdb8a | 9 | #include "alloc-util.h" |
66cb2fde | 10 | #include "bus-error.h" |
57f28dee | 11 | #include "bus-locator.h" |
7eda208f | 12 | #include "bus-unit-util.h" |
66cb2fde | 13 | #include "bus-util.h" |
686d13b9 | 14 | #include "env-file.h" |
66855de7 | 15 | #include "errno-util.h" |
4f5dd394 | 16 | #include "escape.h" |
cf0fbc49 | 17 | #include "extract-word.h" |
3ffd4af2 | 18 | #include "fd-util.h" |
9444b1f2 | 19 | #include "fileio.h" |
f97b34a6 | 20 | #include "format-util.h" |
70f1280c | 21 | #include "fs-util.h" |
66cb2fde | 22 | #include "hashmap.h" |
4f5dd394 | 23 | #include "machine-dbus.h" |
3ffd4af2 | 24 | #include "machine.h" |
35cd0ba5 | 25 | #include "mkdir-label.h" |
6bedfcbb | 26 | #include "parse-util.h" |
b910cc72 | 27 | #include "path-util.h" |
4a0b58c4 | 28 | #include "process-util.h" |
d68c645b | 29 | #include "serialize.h" |
1f815bf1 | 30 | #include "socket-util.h" |
9444b1f2 | 31 | #include "special.h" |
3401419b | 32 | #include "stdio-util.h" |
8b43440b | 33 | #include "string-table.h" |
885317f1 | 34 | #include "string-util.h" |
66cb2fde | 35 | #include "terminal-util.h" |
e4de7287 | 36 | #include "tmpfile-util.h" |
7312c422 | 37 | #include "uid-range.h" |
fb6becb4 | 38 | #include "unit-name.h" |
3a664727 | 39 | #include "user-util.h" |
9444b1f2 | 40 | |
885317f1 | 41 | int machine_new(MachineClass class, const char *name, Machine **ret) { |
38f51440 | 42 | _cleanup_(machine_freep) Machine *m = NULL; |
9444b1f2 | 43 | |
fbe55073 | 44 | assert(class < _MACHINE_CLASS_MAX); |
359e8d76 | 45 | assert(ret); |
9444b1f2 | 46 | |
fbe55073 LP |
47 | /* Passing class == _MACHINE_CLASS_INVALID here is fine. It |
48 | * means as much as "we don't know yet", and that we'll figure | |
49 | * it out later when loading the state file. */ | |
50 | ||
d8854ff1 | 51 | m = new(Machine, 1); |
9444b1f2 | 52 | if (!m) |
359e8d76 | 53 | return -ENOMEM; |
9444b1f2 | 54 | |
d8854ff1 LP |
55 | *m = (Machine) { |
56 | .leader = PIDREF_NULL, | |
1f815bf1 | 57 | .vsock_cid = VMADDR_CID_ANY, |
d8854ff1 LP |
58 | }; |
59 | ||
885317f1 SL |
60 | if (name) { |
61 | m->name = strdup(name); | |
62 | if (!m->name) | |
359e8d76 | 63 | return -ENOMEM; |
fbe55073 LP |
64 | } |
65 | ||
66 | m->class = class; | |
9444b1f2 | 67 | |
885317f1 SL |
68 | *ret = TAKE_PTR(m); |
69 | return 0; | |
70 | } | |
71 | ||
72 | int machine_link(Manager *manager, Machine *machine) { | |
73 | int r; | |
74 | ||
75 | assert(manager); | |
76 | assert(machine); | |
77 | ||
78 | if (machine->manager) | |
79 | return -EEXIST; | |
80 | if (!machine->name) | |
81 | return -EINVAL; | |
82 | ||
83 | if (machine->class != MACHINE_HOST) { | |
84 | char *temp = path_join("/run/systemd/machines", machine->name); | |
85 | if (!temp) | |
86 | return -ENOMEM; | |
87 | ||
88 | free_and_replace(machine->state_file, temp); | |
89 | } | |
90 | ||
91 | r = hashmap_put(manager->machines, machine->name, machine); | |
359e8d76 DT |
92 | if (r < 0) |
93 | return r; | |
9444b1f2 | 94 | |
885317f1 | 95 | machine->manager = manager; |
9444b1f2 | 96 | |
359e8d76 | 97 | return 0; |
9444b1f2 LP |
98 | } |
99 | ||
bb1a05d6 YW |
100 | Machine* machine_free(Machine *m) { |
101 | if (!m) | |
102 | return NULL; | |
9444b1f2 | 103 | |
0370612e | 104 | while (m->operations) |
795c5d31 | 105 | operation_free(m->operations); |
0370612e | 106 | |
c9e89db8 SL |
107 | if (m->in_gc_queue) { |
108 | assert(m->manager); | |
71fda00f | 109 | LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m); |
c9e89db8 | 110 | } |
9444b1f2 | 111 | |
c9e89db8 | 112 | if (m->manager) { |
885317f1 SL |
113 | machine_release_unit(m); |
114 | ||
c9e89db8 | 115 | (void) hashmap_remove(m->manager->machines, m->name); |
9444b1f2 | 116 | |
c9e89db8 SL |
117 | if (m->manager->host_machine == m) |
118 | m->manager->host_machine = NULL; | |
119 | } | |
fbe55073 | 120 | |
d8854ff1 | 121 | if (pidref_is_set(&m->leader)) { |
c9e89db8 SL |
122 | if (m->manager) |
123 | (void) hashmap_remove_value(m->manager->machine_leaders, PID_TO_PTR(m->leader.pid), m); | |
d8854ff1 LP |
124 | pidref_done(&m->leader); |
125 | } | |
d3e84ddb | 126 | |
c3350683 | 127 | sd_bus_message_unref(m->create_message); |
fb6becb4 | 128 | |
9444b1f2 | 129 | free(m->name); |
885317f1 | 130 | free(m->scope_job); |
9444b1f2 LP |
131 | free(m->state_file); |
132 | free(m->service); | |
9444b1f2 | 133 | free(m->root_directory); |
9b5ed6fe | 134 | free(m->netif); |
1f815bf1 SL |
135 | free(m->ssh_address); |
136 | free(m->ssh_private_key_path); | |
bb1a05d6 | 137 | return mfree(m); |
9444b1f2 LP |
138 | } |
139 | ||
140 | int machine_save(Machine *m) { | |
70f1280c | 141 | _cleanup_(unlink_and_freep) char *temp_path = NULL; |
9444b1f2 LP |
142 | _cleanup_fclose_ FILE *f = NULL; |
143 | int r; | |
144 | ||
145 | assert(m); | |
fbe55073 LP |
146 | |
147 | if (!m->state_file) | |
148 | return 0; | |
9444b1f2 LP |
149 | |
150 | if (!m->started) | |
151 | return 0; | |
152 | ||
37c1d5e9 | 153 | r = mkdir_safe_label("/run/systemd/machines", 0755, 0, 0, MKDIR_WARN_MODE); |
9444b1f2 | 154 | if (r < 0) |
dacd6cee | 155 | goto fail; |
9444b1f2 LP |
156 | |
157 | r = fopen_temporary(m->state_file, &f, &temp_path); | |
158 | if (r < 0) | |
dacd6cee | 159 | goto fail; |
9444b1f2 | 160 | |
dacd6cee | 161 | (void) fchmod(fileno(f), 0644); |
9444b1f2 LP |
162 | |
163 | fprintf(f, | |
164 | "# This is private data. Do not parse.\n" | |
165 | "NAME=%s\n", | |
166 | m->name); | |
167 | ||
ca5405bb | 168 | if (m->unit) { |
c2b2df60 | 169 | _cleanup_free_ char *escaped = NULL; |
ca5405bb LP |
170 | |
171 | escaped = cescape(m->unit); | |
172 | if (!escaped) { | |
173 | r = -ENOMEM; | |
dacd6cee | 174 | goto fail; |
ca5405bb LP |
175 | } |
176 | ||
177 | 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 */ | |
178 | } | |
fb6becb4 LP |
179 | |
180 | if (m->scope_job) | |
181 | fprintf(f, "SCOPE_JOB=%s\n", m->scope_job); | |
9444b1f2 | 182 | |
ca5405bb | 183 | if (m->service) { |
c2b2df60 | 184 | _cleanup_free_ char *escaped = NULL; |
9444b1f2 | 185 | |
ca5405bb LP |
186 | escaped = cescape(m->service); |
187 | if (!escaped) { | |
188 | r = -ENOMEM; | |
dacd6cee | 189 | goto fail; |
ca5405bb LP |
190 | } |
191 | fprintf(f, "SERVICE=%s\n", escaped); | |
192 | } | |
193 | ||
194 | if (m->root_directory) { | |
c2b2df60 | 195 | _cleanup_free_ char *escaped = NULL; |
ca5405bb LP |
196 | |
197 | escaped = cescape(m->root_directory); | |
198 | if (!escaped) { | |
199 | r = -ENOMEM; | |
dacd6cee | 200 | goto fail; |
ca5405bb LP |
201 | } |
202 | fprintf(f, "ROOT=%s\n", escaped); | |
203 | } | |
9444b1f2 | 204 | |
3bbaff3e | 205 | if (!sd_id128_is_null(m->id)) |
9444b1f2 LP |
206 | fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id)); |
207 | ||
d8854ff1 LP |
208 | if (pidref_is_set(&m->leader)) |
209 | fprintf(f, "LEADER="PID_FMT"\n", m->leader.pid); | |
9444b1f2 LP |
210 | |
211 | if (m->class != _MACHINE_CLASS_INVALID) | |
212 | fprintf(f, "CLASS=%s\n", machine_class_to_string(m->class)); | |
213 | ||
214 | if (dual_timestamp_is_set(&m->timestamp)) | |
215 | fprintf(f, | |
90b2de37 ZJS |
216 | "REALTIME="USEC_FMT"\n" |
217 | "MONOTONIC="USEC_FMT"\n", | |
218 | m->timestamp.realtime, | |
219 | m->timestamp.monotonic); | |
9444b1f2 | 220 | |
9b5ed6fe | 221 | if (m->n_netif > 0) { |
68e16e9c | 222 | size_t i; |
9b5ed6fe | 223 | |
0d536673 | 224 | fputs("NETIF=", f); |
9b5ed6fe LP |
225 | |
226 | for (i = 0; i < m->n_netif; i++) { | |
227 | if (i != 0) | |
0d536673 | 228 | fputc(' ', f); |
9b5ed6fe LP |
229 | |
230 | fprintf(f, "%i", m->netif[i]); | |
231 | } | |
232 | ||
0d536673 | 233 | fputc('\n', f); |
9b5ed6fe LP |
234 | } |
235 | ||
034753ac LP |
236 | r = fflush_and_check(f); |
237 | if (r < 0) | |
dacd6cee | 238 | goto fail; |
9444b1f2 | 239 | |
034753ac | 240 | if (rename(temp_path, m->state_file) < 0) { |
9444b1f2 | 241 | r = -errno; |
dacd6cee | 242 | goto fail; |
9444b1f2 LP |
243 | } |
244 | ||
70f1280c LP |
245 | temp_path = mfree(temp_path); |
246 | ||
89f7c846 LP |
247 | if (m->unit) { |
248 | char *sl; | |
249 | ||
250 | /* Create a symlink from the unit name to the machine | |
251 | * name, so that we can quickly find the machine for | |
e62d9b81 | 252 | * each given unit. Ignore error. */ |
63c372cb | 253 | sl = strjoina("/run/systemd/machines/unit:", m->unit); |
e62d9b81 | 254 | (void) symlink(m->name, sl); |
89f7c846 LP |
255 | } |
256 | ||
dacd6cee | 257 | return 0; |
034753ac | 258 | |
dacd6cee LP |
259 | fail: |
260 | (void) unlink(m->state_file); | |
261 | ||
dacd6cee | 262 | return log_error_errno(r, "Failed to save machine data %s: %m", m->state_file); |
9444b1f2 LP |
263 | } |
264 | ||
89f7c846 LP |
265 | static void machine_unlink(Machine *m) { |
266 | assert(m); | |
267 | ||
268 | if (m->unit) { | |
89f7c846 LP |
269 | char *sl; |
270 | ||
63c372cb | 271 | sl = strjoina("/run/systemd/machines/unit:", m->unit); |
491ac9f2 | 272 | (void) unlink(sl); |
89f7c846 LP |
273 | } |
274 | ||
275 | if (m->state_file) | |
491ac9f2 | 276 | (void) unlink(m->state_file); |
89f7c846 LP |
277 | } |
278 | ||
9444b1f2 | 279 | int machine_load(Machine *m) { |
9b5ed6fe | 280 | _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *class = NULL, *netif = NULL; |
9444b1f2 LP |
281 | int r; |
282 | ||
283 | assert(m); | |
284 | ||
fbe55073 LP |
285 | if (!m->state_file) |
286 | return 0; | |
287 | ||
aa8fbc74 | 288 | r = parse_env_file(NULL, m->state_file, |
89f7c846 | 289 | "SCOPE", &m->unit, |
fb6becb4 | 290 | "SCOPE_JOB", &m->scope_job, |
9444b1f2 | 291 | "SERVICE", &m->service, |
9444b1f2 LP |
292 | "ROOT", &m->root_directory, |
293 | "ID", &id, | |
294 | "LEADER", &leader, | |
295 | "CLASS", &class, | |
296 | "REALTIME", &realtime, | |
297 | "MONOTONIC", &monotonic, | |
13df9c39 | 298 | "NETIF", &netif); |
40f35786 ZJS |
299 | if (r == -ENOENT) |
300 | return 0; | |
301 | if (r < 0) | |
8d3d7072 | 302 | return log_error_errno(r, "Failed to read %s: %m", m->state_file); |
9444b1f2 LP |
303 | |
304 | if (id) | |
d8854ff1 | 305 | (void) sd_id128_from_string(id, &m->id); |
9444b1f2 | 306 | |
d8854ff1 LP |
307 | if (leader) { |
308 | pidref_done(&m->leader); | |
309 | r = pidref_set_pidstr(&m->leader, leader); | |
310 | if (r < 0) | |
311 | log_debug_errno(r, "Failed to set leader PID to '%s', ignoring: %m", leader); | |
312 | } | |
9444b1f2 LP |
313 | |
314 | if (class) { | |
315 | MachineClass c; | |
316 | ||
317 | c = machine_class_from_string(class); | |
318 | if (c >= 0) | |
319 | m->class = c; | |
320 | } | |
321 | ||
b895a735 | 322 | if (realtime) |
d68c645b | 323 | (void) deserialize_usec(realtime, &m->timestamp.realtime); |
b895a735 | 324 | if (monotonic) |
d68c645b | 325 | (void) deserialize_usec(monotonic, &m->timestamp.monotonic); |
9444b1f2 | 326 | |
9b5ed6fe | 327 | if (netif) { |
597da51b | 328 | _cleanup_free_ int *ni = NULL; |
319a4f4b LP |
329 | size_t nr = 0; |
330 | const char *p; | |
9b5ed6fe | 331 | |
75a8fd6a | 332 | p = netif; |
9ed794a3 | 333 | for (;;) { |
75a8fd6a | 334 | _cleanup_free_ char *word = NULL; |
9b5ed6fe | 335 | |
75a8fd6a | 336 | r = extract_first_word(&p, &word, NULL, 0); |
75a8fd6a SS |
337 | if (r == 0) |
338 | break; | |
6a37c684 | 339 | if (r == -ENOMEM) |
52278ad3 | 340 | return log_oom(); |
6a37c684 | 341 | if (r < 0) { |
52278ad3 | 342 | log_warning_errno(r, "Failed to parse NETIF: %s", netif); |
6a37c684 | 343 | break; |
52278ad3 | 344 | } |
75a8fd6a | 345 | |
597da51b ZJS |
346 | r = parse_ifindex(word); |
347 | if (r < 0) | |
9b5ed6fe LP |
348 | continue; |
349 | ||
319a4f4b | 350 | if (!GREEDY_REALLOC(ni, nr + 1)) |
9b5ed6fe | 351 | return log_oom(); |
9b5ed6fe | 352 | |
597da51b | 353 | ni[nr++] = r; |
9b5ed6fe LP |
354 | } |
355 | ||
319a4f4b | 356 | free_and_replace(m->netif, ni); |
9b5ed6fe LP |
357 | m->n_netif = nr; |
358 | } | |
359 | ||
9444b1f2 LP |
360 | return r; |
361 | } | |
362 | ||
af227947 | 363 | static int machine_start_scope( |
a01ecfa9 | 364 | Machine *machine, |
132f6cfc | 365 | bool allow_pidfd, |
af227947 | 366 | sd_bus_message *more_properties, |
a01ecfa9 | 367 | sd_bus_error *error) { |
af227947 ZJS |
368 | |
369 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; | |
132f6cfc | 370 | _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; |
a01ecfa9 ZJS |
371 | _cleanup_free_ char *escaped = NULL, *unit = NULL; |
372 | const char *description; | |
af227947 ZJS |
373 | int r; |
374 | ||
a01ecfa9 | 375 | assert(machine); |
d8854ff1 | 376 | assert(pidref_is_set(&machine->leader)); |
a01ecfa9 ZJS |
377 | assert(!machine->unit); |
378 | ||
379 | escaped = unit_name_escape(machine->name); | |
380 | if (!escaped) | |
381 | return log_oom(); | |
382 | ||
383 | unit = strjoin("machine-", escaped, ".scope"); | |
384 | if (!unit) | |
385 | return log_oom(); | |
af227947 | 386 | |
57f28dee | 387 | r = bus_message_new_method_call( |
a01ecfa9 | 388 | machine->manager->bus, |
af227947 | 389 | &m, |
57f28dee | 390 | bus_systemd_mgr, |
af227947 ZJS |
391 | "StartTransientUnit"); |
392 | if (r < 0) | |
393 | return r; | |
394 | ||
a01ecfa9 | 395 | r = sd_bus_message_append(m, "ss", unit, "fail"); |
af227947 ZJS |
396 | if (r < 0) |
397 | return r; | |
398 | ||
399 | r = sd_bus_message_open_container(m, 'a', "(sv)"); | |
400 | if (r < 0) | |
401 | return r; | |
402 | ||
a01ecfa9 ZJS |
403 | r = sd_bus_message_append(m, "(sv)", "Slice", "s", SPECIAL_MACHINE_SLICE); |
404 | if (r < 0) | |
405 | return r; | |
af227947 | 406 | |
a01ecfa9 ZJS |
407 | description = strjoina(machine->class == MACHINE_VM ? "Virtual Machine " : "Container ", machine->name); |
408 | r = sd_bus_message_append(m, "(sv)", "Description", "s", description); | |
409 | if (r < 0) | |
410 | return r; | |
af227947 | 411 | |
132f6cfc | 412 | r = bus_append_scope_pidref(m, &machine->leader, allow_pidfd); |
7eda208f LP |
413 | if (r < 0) |
414 | return r; | |
415 | ||
416 | r = sd_bus_message_append(m, "(sv)(sv)(sv)(sv)", | |
af227947 ZJS |
417 | "Delegate", "b", 1, |
418 | "CollectMode", "s", "inactive-or-failed", | |
419 | "AddRef", "b", 1, | |
420 | "TasksMax", "t", UINT64_C(16384)); | |
421 | if (r < 0) | |
422 | return r; | |
423 | ||
424 | if (more_properties) { | |
425 | r = sd_bus_message_copy(m, more_properties, true); | |
426 | if (r < 0) | |
427 | return r; | |
428 | } | |
429 | ||
430 | r = sd_bus_message_close_container(m); | |
431 | if (r < 0) | |
432 | return r; | |
433 | ||
434 | r = sd_bus_message_append(m, "a(sa(sv))", 0); | |
435 | if (r < 0) | |
436 | return r; | |
437 | ||
132f6cfc DDM |
438 | r = sd_bus_call(NULL, m, 0, &e, &reply); |
439 | if (r < 0) { | |
440 | /* If this failed with a property we couldn't write, this is quite likely because the server | |
441 | * doesn't support PIDFDs yet, let's try without. */ | |
442 | if (allow_pidfd && | |
443 | sd_bus_error_has_names(&e, SD_BUS_ERROR_UNKNOWN_PROPERTY, SD_BUS_ERROR_PROPERTY_READ_ONLY)) | |
444 | return machine_start_scope(machine, /* allow_pidfd = */ false, more_properties, error); | |
445 | ||
446 | return sd_bus_error_move(error, &e); | |
447 | } | |
af227947 | 448 | |
a01ecfa9 ZJS |
449 | machine->unit = TAKE_PTR(unit); |
450 | machine->referenced = true; | |
af227947 | 451 | |
a01ecfa9 ZJS |
452 | const char *job; |
453 | r = sd_bus_message_read(reply, "o", &job); | |
454 | if (r < 0) | |
455 | return r; | |
af227947 | 456 | |
a01ecfa9 | 457 | return free_and_strdup(&machine->scope_job, job); |
af227947 ZJS |
458 | } |
459 | ||
460 | static int machine_ensure_scope(Machine *m, sd_bus_message *properties, sd_bus_error *error) { | |
a01ecfa9 ZJS |
461 | int r; |
462 | ||
9444b1f2 | 463 | assert(m); |
fbe55073 | 464 | assert(m->class != MACHINE_HOST); |
9444b1f2 | 465 | |
89f7c846 | 466 | if (!m->unit) { |
132f6cfc | 467 | r = machine_start_scope(m, /* allow_pidfd = */ true, properties, error); |
354f62cf YW |
468 | if (r < 0) |
469 | return log_error_errno(r, "Failed to start machine scope: %s", bus_error_message(error, r)); | |
9444b1f2 LP |
470 | } |
471 | ||
a01ecfa9 ZJS |
472 | assert(m->unit); |
473 | hashmap_put(m->manager->machine_units, m->unit, m); | |
d0af76e6 | 474 | |
354f62cf | 475 | return 0; |
9444b1f2 LP |
476 | } |
477 | ||
c3350683 | 478 | int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) { |
9444b1f2 LP |
479 | int r; |
480 | ||
481 | assert(m); | |
482 | ||
fbe55073 LP |
483 | if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) |
484 | return -EOPNOTSUPP; | |
485 | ||
9444b1f2 LP |
486 | if (m->started) |
487 | return 0; | |
488 | ||
d8854ff1 | 489 | r = hashmap_put(m->manager->machine_leaders, PID_TO_PTR(m->leader.pid), m); |
d3e84ddb LP |
490 | if (r < 0) |
491 | return r; | |
492 | ||
fb6becb4 | 493 | /* Create cgroup */ |
af227947 | 494 | r = machine_ensure_scope(m, properties, error); |
fb6becb4 LP |
495 | if (r < 0) |
496 | return r; | |
497 | ||
9444b1f2 | 498 | log_struct(LOG_INFO, |
2b044526 | 499 | "MESSAGE_ID=" SD_MESSAGE_MACHINE_START_STR, |
9444b1f2 | 500 | "NAME=%s", m->name, |
d8854ff1 | 501 | "LEADER="PID_FMT, m->leader.pid, |
a1230ff9 | 502 | LOG_MESSAGE("New machine %s.", m->name)); |
9444b1f2 | 503 | |
9444b1f2 | 504 | if (!dual_timestamp_is_set(&m->timestamp)) |
fa5a0251 | 505 | dual_timestamp_now(&m->timestamp); |
9444b1f2 LP |
506 | |
507 | m->started = true; | |
508 | ||
509 | /* Save new machine data */ | |
510 | machine_save(m); | |
511 | ||
512 | machine_send_signal(m, true); | |
9fdcbae5 | 513 | (void) manager_enqueue_nscd_cache_flush(m->manager); |
9444b1f2 LP |
514 | |
515 | return 0; | |
516 | } | |
517 | ||
9444b1f2 | 518 | int machine_stop(Machine *m) { |
49f3fffd | 519 | int r; |
69887664 | 520 | |
49f3fffd LP |
521 | assert(m); |
522 | ||
fbe55073 LP |
523 | if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) |
524 | return -EOPNOTSUPP; | |
525 | ||
69887664 ZJS |
526 | if (m->unit) { |
527 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
528 | char *job = NULL; | |
529 | ||
530 | r = manager_stop_unit(m->manager, m->unit, &error, &job); | |
531 | if (r < 0) | |
532 | return log_error_errno(r, "Failed to stop machine scope: %s", bus_error_message(&error, r)); | |
533 | ||
534 | free_and_replace(m->scope_job, job); | |
535 | } | |
49f3fffd LP |
536 | |
537 | m->stopping = true; | |
538 | ||
539 | machine_save(m); | |
9fdcbae5 | 540 | (void) manager_enqueue_nscd_cache_flush(m->manager); |
49f3fffd | 541 | |
69887664 | 542 | return 0; |
49f3fffd LP |
543 | } |
544 | ||
545 | int machine_finalize(Machine *m) { | |
9444b1f2 LP |
546 | assert(m); |
547 | ||
ef8ff92e | 548 | if (m->started) { |
9444b1f2 | 549 | log_struct(LOG_INFO, |
2b044526 | 550 | "MESSAGE_ID=" SD_MESSAGE_MACHINE_STOP_STR, |
9444b1f2 | 551 | "NAME=%s", m->name, |
d8854ff1 | 552 | "LEADER="PID_FMT, m->leader.pid, |
a1230ff9 | 553 | LOG_MESSAGE("Machine %s terminated.", m->name)); |
9444b1f2 | 554 | |
ef8ff92e ZJS |
555 | m->stopping = true; /* The machine is supposed to be going away. Don't try to kill it. */ |
556 | } | |
557 | ||
89f7c846 | 558 | machine_unlink(m); |
9444b1f2 LP |
559 | machine_add_to_gc_queue(m); |
560 | ||
49f3fffd | 561 | if (m->started) { |
9444b1f2 | 562 | machine_send_signal(m, false); |
49f3fffd LP |
563 | m->started = false; |
564 | } | |
9444b1f2 | 565 | |
49f3fffd | 566 | return 0; |
9444b1f2 LP |
567 | } |
568 | ||
554ce41f | 569 | bool machine_may_gc(Machine *m, bool drop_not_started) { |
9444b1f2 LP |
570 | assert(m); |
571 | ||
fbe55073 | 572 | if (m->class == MACHINE_HOST) |
554ce41f | 573 | return false; |
fbe55073 | 574 | |
9444b1f2 | 575 | if (drop_not_started && !m->started) |
554ce41f | 576 | return true; |
9444b1f2 | 577 | |
c3350683 | 578 | if (m->scope_job && manager_job_is_active(m->manager, m->scope_job)) |
554ce41f | 579 | return false; |
9444b1f2 | 580 | |
89f7c846 | 581 | if (m->unit && manager_unit_is_active(m->manager, m->unit)) |
554ce41f | 582 | return false; |
9444b1f2 | 583 | |
554ce41f | 584 | return true; |
9444b1f2 LP |
585 | } |
586 | ||
587 | void machine_add_to_gc_queue(Machine *m) { | |
588 | assert(m); | |
589 | ||
590 | if (m->in_gc_queue) | |
591 | return; | |
592 | ||
71fda00f | 593 | LIST_PREPEND(gc_queue, m->manager->machine_gc_queue, m); |
9444b1f2 LP |
594 | m->in_gc_queue = true; |
595 | } | |
596 | ||
fb6becb4 LP |
597 | MachineState machine_get_state(Machine *s) { |
598 | assert(s); | |
9444b1f2 | 599 | |
fbe55073 LP |
600 | if (s->class == MACHINE_HOST) |
601 | return MACHINE_RUNNING; | |
602 | ||
49f3fffd LP |
603 | if (s->stopping) |
604 | return MACHINE_CLOSING; | |
605 | ||
fb6becb4 | 606 | if (s->scope_job) |
49f3fffd | 607 | return MACHINE_OPENING; |
9444b1f2 | 608 | |
fb6becb4 LP |
609 | return MACHINE_RUNNING; |
610 | } | |
9444b1f2 | 611 | |
fb6becb4 LP |
612 | int machine_kill(Machine *m, KillWho who, int signo) { |
613 | assert(m); | |
9444b1f2 | 614 | |
fbe55073 LP |
615 | if (!IN_SET(m->class, MACHINE_VM, MACHINE_CONTAINER)) |
616 | return -EOPNOTSUPP; | |
617 | ||
89f7c846 | 618 | if (!m->unit) |
fb6becb4 | 619 | return -ESRCH; |
9444b1f2 | 620 | |
7c248223 | 621 | if (who == KILL_LEADER) /* If we shall simply kill the leader, do so directly */ |
d8854ff1 | 622 | return pidref_kill(&m->leader, signo); |
de58a50e | 623 | |
b938cb90 | 624 | /* Otherwise, make PID 1 do it for us, for the entire cgroup */ |
de58a50e | 625 | return manager_kill_unit(m->manager, m->unit, signo, NULL); |
9444b1f2 LP |
626 | } |
627 | ||
ae1d13db | 628 | int machine_openpt(Machine *m, int flags, char **ret_slave) { |
fbe55073 LP |
629 | assert(m); |
630 | ||
631 | switch (m->class) { | |
632 | ||
ae1d13db | 633 | case MACHINE_HOST: |
ae1d13db | 634 | return openpt_allocate(flags, ret_slave); |
fbe55073 LP |
635 | |
636 | case MACHINE_CONTAINER: | |
d8854ff1 | 637 | if (!pidref_is_set(&m->leader)) |
fbe55073 LP |
638 | return -EINVAL; |
639 | ||
d8854ff1 | 640 | return openpt_allocate_in_namespace(m->leader.pid, flags, ret_slave); |
fbe55073 LP |
641 | |
642 | default: | |
643 | return -EOPNOTSUPP; | |
644 | } | |
645 | } | |
646 | ||
40e1f4ea LP |
647 | int machine_open_terminal(Machine *m, const char *path, int mode) { |
648 | assert(m); | |
649 | ||
650 | switch (m->class) { | |
651 | ||
652 | case MACHINE_HOST: | |
653 | return open_terminal(path, mode); | |
654 | ||
655 | case MACHINE_CONTAINER: | |
d8854ff1 | 656 | if (!pidref_is_set(&m->leader)) |
40e1f4ea LP |
657 | return -EINVAL; |
658 | ||
d8854ff1 | 659 | return open_terminal_in_namespace(m->leader.pid, path, mode); |
40e1f4ea LP |
660 | |
661 | default: | |
662 | return -EOPNOTSUPP; | |
663 | } | |
664 | } | |
665 | ||
9b420b3c LP |
666 | void machine_release_unit(Machine *m) { |
667 | assert(m); | |
668 | ||
669 | if (!m->unit) | |
670 | return; | |
671 | ||
eec12b77 ZJS |
672 | if (m->referenced) { |
673 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
674 | int r; | |
675 | ||
676 | r = manager_unref_unit(m->manager, m->unit, &error); | |
677 | if (r < 0) | |
678 | log_warning_errno(r, "Failed to drop reference to machine scope, ignoring: %s", | |
679 | bus_error_message(&error, r)); | |
680 | ||
681 | m->referenced = false; | |
682 | } | |
683 | ||
9b420b3c | 684 | (void) hashmap_remove(m->manager->machine_units, m->unit); |
a1e58e8e | 685 | m->unit = mfree(m->unit); |
9b420b3c LP |
686 | } |
687 | ||
3401419b | 688 | int machine_get_uid_shift(Machine *m, uid_t *ret) { |
fbd0b64f | 689 | char p[STRLEN("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1]; |
3401419b LP |
690 | uid_t uid_base, uid_shift, uid_range; |
691 | gid_t gid_base, gid_shift, gid_range; | |
692 | _cleanup_fclose_ FILE *f = NULL; | |
e1842764 | 693 | int r; |
3401419b LP |
694 | |
695 | assert(m); | |
696 | assert(ret); | |
697 | ||
698 | /* Return the base UID/GID of the specified machine. Note that this only works for containers with simple | |
699 | * mappings. In most cases setups should be simple like this, and administrators should only care about the | |
700 | * basic offset a container has relative to the host. This is what this function exposes. | |
701 | * | |
702 | * If we encounter any more complex mappings we politely refuse this with ENXIO. */ | |
703 | ||
704 | if (m->class == MACHINE_HOST) { | |
705 | *ret = 0; | |
706 | return 0; | |
707 | } | |
708 | ||
709 | if (m->class != MACHINE_CONTAINER) | |
710 | return -EOPNOTSUPP; | |
711 | ||
d8854ff1 | 712 | xsprintf(p, "/proc/" PID_FMT "/uid_map", m->leader.pid); |
3401419b LP |
713 | f = fopen(p, "re"); |
714 | if (!f) { | |
715 | if (errno == ENOENT) { | |
716 | /* If the file doesn't exist, user namespacing is off in the kernel, return a zero mapping hence. */ | |
717 | *ret = 0; | |
718 | return 0; | |
719 | } | |
720 | ||
721 | return -errno; | |
722 | } | |
723 | ||
724 | /* Read the first line. There's at least one. */ | |
7312c422 MY |
725 | r = uid_map_read_one(f, &uid_base, &uid_shift, &uid_range); |
726 | if (r < 0) | |
727 | return r; | |
3401419b LP |
728 | |
729 | /* Not a mapping starting at 0? Then it's a complex mapping we can't expose here. */ | |
730 | if (uid_base != 0) | |
731 | return -ENXIO; | |
732 | /* Insist that at least the nobody user is mapped, everything else is weird, and hence complex, and we don't support it */ | |
3a664727 | 733 | if (uid_range < UID_NOBODY) |
3401419b LP |
734 | return -ENXIO; |
735 | ||
736 | /* If there's more than one line, then we don't support this mapping. */ | |
03a7dbea LP |
737 | r = safe_fgetc(f, NULL); |
738 | if (r < 0) | |
739 | return r; | |
740 | if (r != 0) /* Insist on EOF */ | |
3401419b LP |
741 | return -ENXIO; |
742 | ||
743 | fclose(f); | |
744 | ||
d8854ff1 | 745 | xsprintf(p, "/proc/" PID_FMT "/gid_map", m->leader.pid); |
3401419b LP |
746 | f = fopen(p, "re"); |
747 | if (!f) | |
748 | return -errno; | |
749 | ||
750 | /* Read the first line. There's at least one. */ | |
751 | errno = 0; | |
e1842764 MY |
752 | r = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT "\n", &gid_base, &gid_shift, &gid_range); |
753 | if (r == EOF) | |
754 | return errno_or_else(ENOMSG); | |
755 | assert(r >= 0); | |
756 | if (r != 3) | |
3401419b | 757 | return -EBADMSG; |
3401419b LP |
758 | |
759 | /* If there's more than one line, then we don't support this file. */ | |
03a7dbea LP |
760 | r = safe_fgetc(f, NULL); |
761 | if (r < 0) | |
762 | return r; | |
763 | if (r != 0) /* Insist on EOF */ | |
3401419b LP |
764 | return -ENXIO; |
765 | ||
766 | /* If the UID and GID mapping doesn't match, we don't support this mapping. */ | |
767 | if (uid_base != (uid_t) gid_base) | |
768 | return -ENXIO; | |
769 | if (uid_shift != (uid_t) gid_shift) | |
770 | return -ENXIO; | |
771 | if (uid_range != (uid_t) gid_range) | |
772 | return -ENXIO; | |
773 | ||
774 | *ret = uid_shift; | |
775 | return 0; | |
776 | } | |
777 | ||
74d1b7d2 LP |
778 | static int machine_owns_uid_internal( |
779 | Machine *machine, | |
780 | const char *map_file, /* "uid_map" or "gid_map" */ | |
781 | uid_t uid, | |
782 | uid_t *ret_internal_uid) { | |
783 | ||
784 | _cleanup_fclose_ FILE *f = NULL; | |
785 | const char *p; | |
7312c422 | 786 | int r; |
74d1b7d2 LP |
787 | |
788 | /* This is a generic implementation for both uids and gids, under the assumptions they have the same types and semantics. */ | |
789 | assert_cc(sizeof(uid_t) == sizeof(gid_t)); | |
790 | ||
791 | assert(machine); | |
792 | ||
793 | /* Checks if the specified host UID is owned by the machine, and returns the UID it maps to | |
794 | * internally in the machine */ | |
795 | ||
796 | if (machine->class != MACHINE_CONTAINER) | |
797 | goto negative; | |
798 | ||
d8854ff1 | 799 | p = procfs_file_alloca(machine->leader.pid, map_file); |
74d1b7d2 LP |
800 | f = fopen(p, "re"); |
801 | if (!f) { | |
802 | log_debug_errno(errno, "Failed to open %s, ignoring.", p); | |
803 | goto negative; | |
804 | } | |
805 | ||
806 | for (;;) { | |
807 | uid_t uid_base, uid_shift, uid_range, converted; | |
74d1b7d2 | 808 | |
7312c422 MY |
809 | r = uid_map_read_one(f, &uid_base, &uid_shift, &uid_range); |
810 | if (r == -ENOMSG) | |
74d1b7d2 | 811 | break; |
7312c422 MY |
812 | if (r < 0) |
813 | return r; | |
74d1b7d2 LP |
814 | |
815 | /* The private user namespace is disabled, ignoring. */ | |
816 | if (uid_shift == 0) | |
817 | continue; | |
818 | ||
819 | if (uid < uid_shift || uid >= uid_shift + uid_range) | |
820 | continue; | |
821 | ||
822 | converted = (uid - uid_shift + uid_base); | |
823 | if (!uid_is_valid(converted)) | |
824 | return -EINVAL; | |
825 | ||
826 | if (ret_internal_uid) | |
827 | *ret_internal_uid = converted; | |
828 | ||
829 | return true; | |
830 | } | |
831 | ||
832 | negative: | |
833 | if (ret_internal_uid) | |
834 | *ret_internal_uid = UID_INVALID; | |
835 | ||
836 | return false; | |
837 | } | |
838 | ||
839 | int machine_owns_uid(Machine *machine, uid_t uid, uid_t *ret_internal_uid) { | |
840 | return machine_owns_uid_internal(machine, "uid_map", uid, ret_internal_uid); | |
841 | } | |
842 | ||
843 | int machine_owns_gid(Machine *machine, gid_t gid, gid_t *ret_internal_gid) { | |
844 | return machine_owns_uid_internal(machine, "gid_map", (uid_t) gid, (uid_t*) ret_internal_gid); | |
845 | } | |
846 | ||
847 | static int machine_translate_uid_internal( | |
848 | Machine *machine, | |
849 | const char *map_file, /* "uid_map" or "gid_map" */ | |
850 | uid_t uid, | |
851 | uid_t *ret_host_uid) { | |
852 | ||
853 | _cleanup_fclose_ FILE *f = NULL; | |
854 | const char *p; | |
7312c422 | 855 | int r; |
74d1b7d2 LP |
856 | |
857 | /* This is a generic implementation for both uids and gids, under the assumptions they have the same types and semantics. */ | |
858 | assert_cc(sizeof(uid_t) == sizeof(gid_t)); | |
859 | ||
860 | assert(machine); | |
861 | assert(uid_is_valid(uid)); | |
862 | ||
863 | if (machine->class != MACHINE_CONTAINER) | |
864 | return -ESRCH; | |
865 | ||
866 | /* Translates a machine UID into a host UID */ | |
867 | ||
d8854ff1 | 868 | p = procfs_file_alloca(machine->leader.pid, map_file); |
74d1b7d2 LP |
869 | f = fopen(p, "re"); |
870 | if (!f) | |
871 | return -errno; | |
872 | ||
873 | for (;;) { | |
874 | uid_t uid_base, uid_shift, uid_range, converted; | |
74d1b7d2 | 875 | |
7312c422 MY |
876 | r = uid_map_read_one(f, &uid_base, &uid_shift, &uid_range); |
877 | if (r == -ENOMSG) | |
74d1b7d2 | 878 | break; |
7312c422 MY |
879 | if (r < 0) |
880 | return r; | |
74d1b7d2 LP |
881 | |
882 | if (uid < uid_base || uid >= uid_base + uid_range) | |
883 | continue; | |
884 | ||
885 | converted = uid - uid_base + uid_shift; | |
886 | if (!uid_is_valid(converted)) | |
887 | return -EINVAL; | |
888 | ||
889 | if (ret_host_uid) | |
890 | *ret_host_uid = converted; | |
7312c422 | 891 | |
74d1b7d2 LP |
892 | return 0; |
893 | } | |
894 | ||
895 | return -ESRCH; | |
896 | } | |
897 | ||
898 | int machine_translate_uid(Machine *machine, gid_t uid, gid_t *ret_host_uid) { | |
899 | return machine_translate_uid_internal(machine, "uid_map", uid, ret_host_uid); | |
900 | } | |
901 | ||
902 | int machine_translate_gid(Machine *machine, gid_t gid, gid_t *ret_host_gid) { | |
903 | return machine_translate_uid_internal(machine, "gid_map", (uid_t) gid, (uid_t*) ret_host_gid); | |
904 | } | |
905 | ||
9444b1f2 LP |
906 | static const char* const machine_class_table[_MACHINE_CLASS_MAX] = { |
907 | [MACHINE_CONTAINER] = "container", | |
fbe55073 LP |
908 | [MACHINE_VM] = "vm", |
909 | [MACHINE_HOST] = "host", | |
9444b1f2 LP |
910 | }; |
911 | ||
912 | DEFINE_STRING_TABLE_LOOKUP(machine_class, MachineClass); | |
fb6becb4 LP |
913 | |
914 | static const char* const machine_state_table[_MACHINE_STATE_MAX] = { | |
915 | [MACHINE_OPENING] = "opening", | |
916 | [MACHINE_RUNNING] = "running", | |
917 | [MACHINE_CLOSING] = "closing" | |
918 | }; | |
919 | ||
920 | DEFINE_STRING_TABLE_LOOKUP(machine_state, MachineState); | |
1ee306e1 LP |
921 | |
922 | static const char* const kill_who_table[_KILL_WHO_MAX] = { | |
923 | [KILL_LEADER] = "leader", | |
924 | [KILL_ALL] = "all" | |
925 | }; | |
926 | ||
927 | DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); |