]>
Commit | Line | Data |
---|---|---|
a032b68d MB |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #include <sys/reboot.h> | |
4 | #include <unistd.h> | |
5 | ||
6 | #include "sd-bus.h" | |
7 | #include "sd-daemon.h" | |
8 | ||
9 | #include "bus-common-errors.h" | |
10 | #include "bus-locator.h" | |
11 | #include "bus-map-properties.h" | |
12 | #include "bus-unit-util.h" | |
13 | #include "dropin.h" | |
14 | #include "env-util.h" | |
15 | #include "exit-status.h" | |
16 | #include "fs-util.h" | |
17 | #include "glob-util.h" | |
18 | #include "macro.h" | |
19 | #include "path-util.h" | |
20 | #include "reboot-util.h" | |
21 | #include "set.h" | |
22 | #include "spawn-ask-password-agent.h" | |
23 | #include "spawn-polkit-agent.h" | |
24 | #include "stat-util.h" | |
25 | #include "systemctl-util.h" | |
26 | #include "systemctl.h" | |
27 | #include "terminal-util.h" | |
28 | #include "verbs.h" | |
29 | ||
30 | static sd_bus *buses[_BUS_FOCUS_MAX] = {}; | |
31 | ||
32 | int acquire_bus(BusFocus focus, sd_bus **ret) { | |
33 | int r; | |
34 | ||
35 | assert(focus < _BUS_FOCUS_MAX); | |
36 | assert(ret); | |
37 | ||
38 | /* We only go directly to the manager, if we are using a local transport */ | |
39 | if (arg_transport != BUS_TRANSPORT_LOCAL) | |
40 | focus = BUS_FULL; | |
41 | ||
42 | if (getenv_bool("SYSTEMCTL_FORCE_BUS") > 0) | |
43 | focus = BUS_FULL; | |
44 | ||
45 | if (!buses[focus]) { | |
46 | bool user; | |
47 | ||
48 | user = arg_scope != UNIT_FILE_SYSTEM; | |
49 | ||
50 | if (focus == BUS_MANAGER) | |
51 | r = bus_connect_transport_systemd(arg_transport, arg_host, user, &buses[focus]); | |
52 | else | |
53 | r = bus_connect_transport(arg_transport, arg_host, user, &buses[focus]); | |
54 | if (r < 0) | |
55 | return bus_log_connect_error(r); | |
56 | ||
57 | (void) sd_bus_set_allow_interactive_authorization(buses[focus], arg_ask_password); | |
58 | } | |
59 | ||
60 | *ret = buses[focus]; | |
61 | return 0; | |
62 | } | |
63 | ||
64 | void release_busses(void) { | |
3a6ce677 | 65 | for (BusFocus w = 0; w < _BUS_FOCUS_MAX; w++) |
a032b68d MB |
66 | buses[w] = sd_bus_flush_close_unref(buses[w]); |
67 | } | |
68 | ||
69 | void ask_password_agent_open_maybe(void) { | |
70 | /* Open the password agent as a child process if necessary */ | |
71 | ||
72 | if (arg_dry_run) | |
73 | return; | |
74 | ||
75 | if (arg_scope != UNIT_FILE_SYSTEM) | |
76 | return; | |
77 | ||
78 | ask_password_agent_open_if_enabled(arg_transport, arg_ask_password); | |
79 | } | |
80 | ||
81 | void polkit_agent_open_maybe(void) { | |
82 | /* Open the polkit agent as a child process if necessary */ | |
83 | ||
84 | if (arg_scope != UNIT_FILE_SYSTEM) | |
85 | return; | |
86 | ||
87 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); | |
88 | } | |
89 | ||
90 | int translate_bus_error_to_exit_status(int r, const sd_bus_error *error) { | |
91 | assert(error); | |
92 | ||
93 | if (!sd_bus_error_is_set(error)) | |
94 | return r; | |
95 | ||
96 | if (sd_bus_error_has_names(error, SD_BUS_ERROR_ACCESS_DENIED, | |
97 | BUS_ERROR_ONLY_BY_DEPENDENCY, | |
98 | BUS_ERROR_NO_ISOLATION, | |
99 | BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE)) | |
100 | return EXIT_NOPERMISSION; | |
101 | ||
102 | if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT)) | |
103 | return EXIT_NOTINSTALLED; | |
104 | ||
105 | if (sd_bus_error_has_names(error, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, | |
106 | SD_BUS_ERROR_NOT_SUPPORTED)) | |
107 | return EXIT_NOTIMPLEMENTED; | |
108 | ||
109 | if (sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) | |
110 | return EXIT_NOTCONFIGURED; | |
111 | ||
112 | if (r != 0) | |
113 | return r; | |
114 | ||
115 | return EXIT_FAILURE; | |
116 | } | |
117 | ||
118 | int get_state_one_unit(sd_bus *bus, const char *unit, UnitActiveState *ret_active_state) { | |
119 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
120 | _cleanup_free_ char *buf = NULL, *dbus_path = NULL; | |
121 | UnitActiveState state; | |
122 | int r; | |
123 | ||
124 | assert(unit); | |
125 | assert(ret_active_state); | |
126 | ||
127 | dbus_path = unit_dbus_path_from_name(unit); | |
128 | if (!dbus_path) | |
129 | return log_oom(); | |
130 | ||
131 | r = sd_bus_get_property_string( | |
132 | bus, | |
133 | "org.freedesktop.systemd1", | |
134 | dbus_path, | |
135 | "org.freedesktop.systemd1.Unit", | |
136 | "ActiveState", | |
137 | &error, | |
138 | &buf); | |
139 | if (r < 0) | |
140 | return log_error_errno(r, "Failed to retrieve unit state: %s", bus_error_message(&error, r)); | |
141 | ||
142 | state = unit_active_state_from_string(buf); | |
143 | if (state < 0) | |
3a6ce677 | 144 | return log_error_errno(state, "Invalid unit state '%s' for: %s", buf, unit); |
a032b68d MB |
145 | |
146 | *ret_active_state = state; | |
147 | return 0; | |
148 | } | |
149 | ||
150 | int get_unit_list( | |
151 | sd_bus *bus, | |
152 | const char *machine, | |
153 | char **patterns, | |
154 | UnitInfo **unit_infos, | |
155 | int c, | |
156 | sd_bus_message **ret_reply) { | |
157 | ||
158 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; | |
159 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
160 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
a032b68d MB |
161 | int r; |
162 | bool fallback = false; | |
163 | ||
164 | assert(bus); | |
165 | assert(unit_infos); | |
166 | assert(ret_reply); | |
167 | ||
168 | r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "ListUnitsByPatterns"); | |
169 | if (r < 0) | |
170 | return bus_log_create_error(r); | |
171 | ||
172 | r = sd_bus_message_append_strv(m, arg_states); | |
173 | if (r < 0) | |
174 | return bus_log_create_error(r); | |
175 | ||
176 | r = sd_bus_message_append_strv(m, patterns); | |
177 | if (r < 0) | |
178 | return bus_log_create_error(r); | |
179 | ||
180 | r = sd_bus_call(bus, m, 0, &error, &reply); | |
181 | if (r < 0 && (sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_METHOD, | |
182 | SD_BUS_ERROR_ACCESS_DENIED))) { | |
183 | /* Fallback to legacy ListUnitsFiltered method */ | |
184 | fallback = true; | |
185 | log_debug_errno(r, "Failed to list units: %s Falling back to ListUnitsFiltered method.", bus_error_message(&error, r)); | |
186 | m = sd_bus_message_unref(m); | |
187 | sd_bus_error_free(&error); | |
188 | ||
189 | r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "ListUnitsFiltered"); | |
190 | if (r < 0) | |
191 | return bus_log_create_error(r); | |
192 | ||
193 | r = sd_bus_message_append_strv(m, arg_states); | |
194 | if (r < 0) | |
195 | return bus_log_create_error(r); | |
196 | ||
197 | r = sd_bus_call(bus, m, 0, &error, &reply); | |
198 | } | |
199 | if (r < 0) | |
200 | return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r)); | |
201 | ||
202 | r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); | |
203 | if (r < 0) | |
204 | return bus_log_parse_error(r); | |
205 | ||
206 | for (;;) { | |
207 | UnitInfo u; | |
208 | ||
209 | r = bus_parse_unit_info(reply, &u); | |
210 | if (r < 0) | |
211 | return bus_log_parse_error(r); | |
212 | if (r == 0) | |
213 | break; | |
214 | ||
215 | u.machine = machine; | |
216 | ||
217 | if (!output_show_unit(&u, fallback ? patterns : NULL)) | |
218 | continue; | |
219 | ||
8b3d4ff0 | 220 | if (!GREEDY_REALLOC(*unit_infos, c+1)) |
a032b68d MB |
221 | return log_oom(); |
222 | ||
223 | (*unit_infos)[c++] = u; | |
224 | } | |
225 | ||
226 | r = sd_bus_message_exit_container(reply); | |
227 | if (r < 0) | |
228 | return bus_log_parse_error(r); | |
229 | ||
230 | *ret_reply = TAKE_PTR(reply); | |
231 | return c; | |
232 | } | |
233 | ||
234 | int expand_unit_names(sd_bus *bus, char **names, const char* suffix, char ***ret, bool *ret_expanded) { | |
235 | _cleanup_strv_free_ char **mangled = NULL, **globs = NULL; | |
236 | char **name; | |
3a6ce677 | 237 | int r; |
a032b68d MB |
238 | |
239 | assert(bus); | |
240 | assert(ret); | |
241 | ||
242 | STRV_FOREACH(name, names) { | |
243 | UnitNameMangle options = UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN); | |
244 | char *t; | |
245 | ||
246 | r = unit_name_mangle_with_suffix(*name, NULL, options, suffix ?: ".service", &t); | |
247 | if (r < 0) | |
248 | return log_error_errno(r, "Failed to mangle name: %m"); | |
249 | ||
250 | if (string_is_glob(t)) | |
251 | r = strv_consume(&globs, t); | |
252 | else | |
253 | r = strv_consume(&mangled, t); | |
254 | if (r < 0) | |
255 | return log_oom(); | |
256 | } | |
257 | ||
258 | /* Query the manager only if any of the names are a glob, since this is fairly expensive */ | |
259 | bool expanded = !strv_isempty(globs); | |
260 | if (expanded) { | |
261 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
262 | _cleanup_free_ UnitInfo *unit_infos = NULL; | |
8b3d4ff0 | 263 | size_t n; |
a032b68d MB |
264 | |
265 | r = get_unit_list(bus, NULL, globs, &unit_infos, 0, &reply); | |
266 | if (r < 0) | |
267 | return r; | |
268 | ||
269 | n = strv_length(mangled); | |
a032b68d | 270 | |
3a6ce677 | 271 | for (int i = 0; i < r; i++) { |
8b3d4ff0 | 272 | if (!GREEDY_REALLOC(mangled, n+2)) |
a032b68d MB |
273 | return log_oom(); |
274 | ||
275 | mangled[n] = strdup(unit_infos[i].id); | |
276 | if (!mangled[n]) | |
277 | return log_oom(); | |
278 | ||
279 | mangled[++n] = NULL; | |
280 | } | |
281 | } | |
282 | ||
283 | if (ret_expanded) | |
284 | *ret_expanded = expanded; | |
285 | ||
286 | *ret = TAKE_PTR(mangled); | |
287 | return 0; | |
288 | } | |
289 | ||
290 | int check_triggering_units(sd_bus *bus, const char *unit) { | |
291 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
292 | _cleanup_free_ char *n = NULL, *dbus_path = NULL, *load_state = NULL; | |
293 | _cleanup_strv_free_ char **triggered_by = NULL; | |
294 | bool print_warning_label = true; | |
295 | UnitActiveState active_state; | |
296 | char **i; | |
297 | int r; | |
298 | ||
299 | r = unit_name_mangle(unit, 0, &n); | |
300 | if (r < 0) | |
301 | return log_error_errno(r, "Failed to mangle unit name: %m"); | |
302 | ||
303 | r = unit_load_state(bus, n, &load_state); | |
304 | if (r < 0) | |
305 | return r; | |
306 | ||
307 | if (streq(load_state, "masked")) | |
308 | return 0; | |
309 | ||
310 | dbus_path = unit_dbus_path_from_name(n); | |
311 | if (!dbus_path) | |
312 | return log_oom(); | |
313 | ||
314 | r = sd_bus_get_property_strv( | |
315 | bus, | |
316 | "org.freedesktop.systemd1", | |
317 | dbus_path, | |
318 | "org.freedesktop.systemd1.Unit", | |
319 | "TriggeredBy", | |
320 | &error, | |
321 | &triggered_by); | |
322 | if (r < 0) | |
323 | return log_error_errno(r, "Failed to get triggered by array of %s: %s", n, bus_error_message(&error, r)); | |
324 | ||
325 | STRV_FOREACH(i, triggered_by) { | |
326 | r = get_state_one_unit(bus, *i, &active_state); | |
327 | if (r < 0) | |
328 | return r; | |
329 | ||
330 | if (!IN_SET(active_state, UNIT_ACTIVE, UNIT_RELOADING)) | |
331 | continue; | |
332 | ||
333 | if (print_warning_label) { | |
334 | log_warning("Warning: Stopping %s, but it can still be activated by:", n); | |
335 | print_warning_label = false; | |
336 | } | |
337 | ||
338 | log_warning(" %s", *i); | |
339 | } | |
340 | ||
341 | return 0; | |
342 | } | |
343 | ||
344 | int need_daemon_reload(sd_bus *bus, const char *unit) { | |
345 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
346 | const char *path; | |
347 | int b, r; | |
348 | ||
349 | /* We ignore all errors here, since this is used to show a | |
350 | * warning only */ | |
351 | ||
352 | /* We don't use unit_dbus_path_from_name() directly since we | |
353 | * don't want to load the unit if it isn't loaded. */ | |
354 | ||
355 | r = bus_call_method(bus, bus_systemd_mgr, "GetUnit", NULL, &reply, "s", unit); | |
356 | if (r < 0) | |
357 | return r; | |
358 | ||
359 | r = sd_bus_message_read(reply, "o", &path); | |
360 | if (r < 0) | |
361 | return r; | |
362 | ||
363 | r = sd_bus_get_property_trivial( | |
364 | bus, | |
365 | "org.freedesktop.systemd1", | |
366 | path, | |
367 | "org.freedesktop.systemd1.Unit", | |
368 | "NeedDaemonReload", | |
369 | NULL, | |
370 | 'b', &b); | |
371 | if (r < 0) | |
372 | return r; | |
373 | ||
374 | return b; | |
375 | } | |
376 | ||
377 | void warn_unit_file_changed(const char *unit) { | |
378 | assert(unit); | |
379 | ||
380 | log_warning("%sWarning:%s The unit file, source configuration file or drop-ins of %s changed on disk. Run 'systemctl%s daemon-reload' to reload units.", | |
381 | ansi_highlight_red(), | |
382 | ansi_normal(), | |
383 | unit, | |
384 | arg_scope == UNIT_FILE_SYSTEM ? "" : " --user"); | |
385 | } | |
386 | ||
387 | int unit_file_find_path(LookupPaths *lp, const char *unit_name, char **ret_unit_path) { | |
388 | char **p; | |
389 | ||
390 | assert(lp); | |
391 | assert(unit_name); | |
392 | ||
393 | STRV_FOREACH(p, lp->search_path) { | |
394 | _cleanup_free_ char *path = NULL, *lpath = NULL; | |
395 | int r; | |
396 | ||
397 | path = path_join(*p, unit_name); | |
398 | if (!path) | |
399 | return log_oom(); | |
400 | ||
401 | r = chase_symlinks(path, arg_root, 0, &lpath, NULL); | |
402 | if (r == -ENOENT) | |
403 | continue; | |
404 | if (r == -ENOMEM) | |
405 | return log_oom(); | |
406 | if (r < 0) | |
407 | return log_error_errno(r, "Failed to access path \"%s\": %m", path); | |
408 | ||
409 | if (ret_unit_path) | |
410 | *ret_unit_path = TAKE_PTR(lpath); | |
411 | ||
412 | return 1; | |
413 | } | |
414 | ||
415 | if (ret_unit_path) | |
416 | *ret_unit_path = NULL; | |
417 | ||
418 | return 0; | |
419 | } | |
420 | ||
421 | int unit_find_paths( | |
422 | sd_bus *bus, | |
423 | const char *unit_name, | |
424 | LookupPaths *lp, | |
425 | bool force_client_side, | |
426 | Hashmap **cached_name_map, | |
427 | Hashmap **cached_id_map, | |
428 | char **ret_fragment_path, | |
429 | char ***ret_dropin_paths) { | |
430 | ||
431 | _cleanup_strv_free_ char **dropins = NULL; | |
432 | _cleanup_free_ char *path = NULL; | |
433 | int r; | |
434 | ||
435 | /** | |
436 | * Finds where the unit is defined on disk. Returns 0 if the unit is not found. Returns 1 if it is | |
437 | * found, and sets: | |
3a6ce677 | 438 | * |
a032b68d | 439 | * - the path to the unit in *ret_frament_path, if it exists on disk, |
3a6ce677 | 440 | * |
a032b68d MB |
441 | * - and a strv of existing drop-ins in *ret_dropin_paths, if the arg is not NULL and any dropins |
442 | * were found. | |
443 | * | |
444 | * Returns -ERFKILL if the unit is masked, and -EKEYREJECTED if the unit file could not be loaded for | |
3a6ce677 BR |
445 | * some reason (the latter only applies if we are going through the service manager). As special |
446 | * exception it won't log for these two error cases. | |
a032b68d MB |
447 | */ |
448 | ||
449 | assert(unit_name); | |
450 | assert(ret_fragment_path); | |
451 | assert(lp); | |
452 | ||
453 | /* Go via the bus to acquire the path, unless we are explicitly told not to, or when the unit name is a template */ | |
454 | if (!force_client_side && | |
455 | !install_client_side() && | |
456 | !unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) { | |
457 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
458 | _cleanup_free_ char *load_state = NULL, *dbus_path = NULL; | |
459 | ||
460 | dbus_path = unit_dbus_path_from_name(unit_name); | |
461 | if (!dbus_path) | |
462 | return log_oom(); | |
463 | ||
464 | r = sd_bus_get_property_string( | |
465 | bus, | |
466 | "org.freedesktop.systemd1", | |
467 | dbus_path, | |
468 | "org.freedesktop.systemd1.Unit", | |
469 | "LoadState", | |
470 | &error, | |
471 | &load_state); | |
472 | if (r < 0) | |
473 | return log_error_errno(r, "Failed to get LoadState: %s", bus_error_message(&error, r)); | |
474 | ||
475 | if (streq(load_state, "masked")) | |
3a6ce677 | 476 | return -ERFKILL; /* special case: no logging */ |
a032b68d MB |
477 | if (streq(load_state, "not-found")) { |
478 | r = 0; | |
3a6ce677 | 479 | goto finish; |
a032b68d MB |
480 | } |
481 | if (!STR_IN_SET(load_state, "loaded", "bad-setting")) | |
3a6ce677 | 482 | return -EKEYREJECTED; /* special case: no logging */ |
a032b68d MB |
483 | |
484 | r = sd_bus_get_property_string( | |
485 | bus, | |
486 | "org.freedesktop.systemd1", | |
487 | dbus_path, | |
488 | "org.freedesktop.systemd1.Unit", | |
489 | "FragmentPath", | |
490 | &error, | |
491 | &path); | |
492 | if (r < 0) | |
493 | return log_error_errno(r, "Failed to get FragmentPath: %s", bus_error_message(&error, r)); | |
494 | ||
495 | if (ret_dropin_paths) { | |
496 | r = sd_bus_get_property_strv( | |
497 | bus, | |
498 | "org.freedesktop.systemd1", | |
499 | dbus_path, | |
500 | "org.freedesktop.systemd1.Unit", | |
501 | "DropInPaths", | |
502 | &error, | |
503 | &dropins); | |
504 | if (r < 0) | |
505 | return log_error_errno(r, "Failed to get DropInPaths: %s", bus_error_message(&error, r)); | |
506 | } | |
507 | } else { | |
508 | const char *_path; | |
509 | _cleanup_set_free_free_ Set *names = NULL; | |
510 | ||
511 | if (!*cached_name_map) { | |
512 | r = unit_file_build_name_map(lp, NULL, cached_id_map, cached_name_map, NULL); | |
513 | if (r < 0) | |
514 | return r; | |
515 | } | |
516 | ||
517 | r = unit_file_find_fragment(*cached_id_map, *cached_name_map, unit_name, &_path, &names); | |
518 | if (r < 0) | |
3a6ce677 | 519 | return log_error_errno(r, "Failed to find fragment for '%s': %m", unit_name); |
a032b68d MB |
520 | |
521 | if (_path) { | |
522 | path = strdup(_path); | |
523 | if (!path) | |
524 | return log_oom(); | |
525 | } | |
526 | ||
527 | if (ret_dropin_paths) { | |
528 | r = unit_file_find_dropin_paths(arg_root, lp->search_path, NULL, | |
529 | ".d", ".conf", | |
530 | NULL, names, &dropins); | |
531 | if (r < 0) | |
532 | return r; | |
533 | } | |
534 | } | |
535 | ||
3a6ce677 | 536 | finish: |
a032b68d MB |
537 | if (isempty(path)) { |
538 | *ret_fragment_path = NULL; | |
539 | r = 0; | |
540 | } else { | |
541 | *ret_fragment_path = TAKE_PTR(path); | |
542 | r = 1; | |
543 | } | |
544 | ||
545 | if (ret_dropin_paths) { | |
546 | if (!strv_isempty(dropins)) { | |
547 | *ret_dropin_paths = TAKE_PTR(dropins); | |
548 | r = 1; | |
549 | } else | |
550 | *ret_dropin_paths = NULL; | |
551 | } | |
552 | ||
a032b68d MB |
553 | if (r == 0 && !arg_force) |
554 | log_error("No files found for %s.", unit_name); | |
555 | ||
556 | return r; | |
557 | } | |
558 | ||
559 | static int unit_find_template_path( | |
560 | const char *unit_name, | |
561 | LookupPaths *lp, | |
562 | char **ret_fragment_path, | |
563 | char **ret_template) { | |
564 | ||
565 | _cleanup_free_ char *t = NULL, *f = NULL; | |
566 | int r; | |
567 | ||
568 | /* Returns 1 if a fragment was found, 0 if not found, negative on error. */ | |
569 | ||
570 | r = unit_file_find_path(lp, unit_name, &f); | |
571 | if (r < 0) | |
572 | return r; | |
573 | if (r > 0) { | |
574 | if (ret_fragment_path) | |
575 | *ret_fragment_path = TAKE_PTR(f); | |
576 | if (ret_template) | |
577 | *ret_template = NULL; | |
578 | return r; /* found a real unit */ | |
579 | } | |
580 | ||
581 | r = unit_name_template(unit_name, &t); | |
582 | if (r == -EINVAL) { | |
583 | if (ret_fragment_path) | |
584 | *ret_fragment_path = NULL; | |
585 | if (ret_template) | |
586 | *ret_template = NULL; | |
587 | ||
588 | return 0; /* not a template, does not exist */ | |
589 | } | |
590 | if (r < 0) | |
591 | return log_error_errno(r, "Failed to determine template name: %m"); | |
592 | ||
593 | r = unit_file_find_path(lp, t, ret_fragment_path); | |
594 | if (r < 0) | |
595 | return r; | |
596 | ||
597 | if (ret_template) | |
598 | *ret_template = r > 0 ? TAKE_PTR(t) : NULL; | |
599 | ||
600 | return r; | |
601 | } | |
602 | ||
603 | int unit_is_masked(sd_bus *bus, LookupPaths *lp, const char *name) { | |
604 | _cleanup_free_ char *load_state = NULL; | |
605 | int r; | |
606 | ||
607 | if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { | |
608 | _cleanup_free_ char *path = NULL; | |
609 | ||
610 | /* A template cannot be loaded, but it can be still masked, so | |
611 | * we need to use a different method. */ | |
612 | ||
613 | r = unit_file_find_path(lp, name, &path); | |
614 | if (r < 0) | |
615 | return r; | |
616 | if (r == 0) | |
617 | return false; | |
618 | return null_or_empty_path(path); | |
619 | } | |
620 | ||
621 | r = unit_load_state(bus, name, &load_state); | |
622 | if (r < 0) | |
623 | return r; | |
624 | ||
625 | return streq(load_state, "masked"); | |
626 | } | |
627 | ||
628 | int unit_exists(LookupPaths *lp, const char *unit) { | |
629 | typedef struct UnitStateInfo { | |
630 | const char *load_state; | |
631 | const char *active_state; | |
632 | } UnitStateInfo; | |
633 | ||
634 | static const struct bus_properties_map property_map[] = { | |
635 | { "LoadState", "s", NULL, offsetof(UnitStateInfo, load_state) }, | |
636 | { "ActiveState", "s", NULL, offsetof(UnitStateInfo, active_state) }, | |
637 | {}, | |
638 | }; | |
639 | ||
640 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
641 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; | |
642 | _cleanup_free_ char *path = NULL; | |
643 | UnitStateInfo info = {}; | |
644 | sd_bus *bus; | |
645 | int r; | |
646 | ||
647 | if (unit_name_is_valid(unit, UNIT_NAME_TEMPLATE)) | |
648 | return unit_find_template_path(unit, lp, NULL, NULL); | |
649 | ||
650 | path = unit_dbus_path_from_name(unit); | |
651 | if (!path) | |
652 | return log_oom(); | |
653 | ||
654 | r = acquire_bus(BUS_MANAGER, &bus); | |
655 | if (r < 0) | |
656 | return r; | |
657 | ||
658 | r = bus_map_all_properties(bus, "org.freedesktop.systemd1", path, property_map, 0, &error, &m, &info); | |
659 | if (r < 0) | |
660 | return log_error_errno(r, "Failed to get properties: %s", bus_error_message(&error, r)); | |
661 | ||
662 | return !streq_ptr(info.load_state, "not-found") || !streq_ptr(info.active_state, "inactive"); | |
663 | } | |
664 | ||
665 | ||
666 | int append_unit_dependencies(sd_bus *bus, char **names, char ***ret) { | |
667 | _cleanup_strv_free_ char **with_deps = NULL; | |
668 | char **name; | |
669 | ||
670 | assert(bus); | |
671 | assert(ret); | |
672 | ||
673 | STRV_FOREACH(name, names) { | |
674 | _cleanup_strv_free_ char **deps = NULL; | |
675 | ||
676 | if (strv_extend(&with_deps, *name) < 0) | |
677 | return log_oom(); | |
678 | ||
679 | (void) unit_get_dependencies(bus, *name, &deps); | |
680 | ||
681 | if (strv_extend_strv(&with_deps, deps, true) < 0) | |
682 | return log_oom(); | |
683 | } | |
684 | ||
685 | *ret = TAKE_PTR(with_deps); | |
686 | ||
687 | return 0; | |
688 | } | |
689 | ||
690 | int maybe_extend_with_unit_dependencies(sd_bus *bus, char ***list) { | |
691 | _cleanup_strv_free_ char **list_with_deps = NULL; | |
692 | int r; | |
693 | ||
694 | assert(bus); | |
695 | assert(list); | |
696 | ||
697 | if (!arg_with_dependencies) | |
698 | return 0; | |
699 | ||
700 | r = append_unit_dependencies(bus, *list, &list_with_deps); | |
701 | if (r < 0) | |
702 | return log_error_errno(r, "Failed to append unit dependencies: %m"); | |
703 | ||
704 | strv_free(*list); | |
705 | *list = TAKE_PTR(list_with_deps); | |
706 | return 0; | |
707 | } | |
708 | ||
709 | int unit_get_dependencies(sd_bus *bus, const char *name, char ***ret) { | |
710 | _cleanup_strv_free_ char **deps = NULL; | |
711 | ||
712 | static const struct bus_properties_map map[_DEPENDENCY_MAX][6] = { | |
713 | [DEPENDENCY_FORWARD] = { | |
714 | { "Requires", "as", NULL, 0 }, | |
715 | { "Requisite", "as", NULL, 0 }, | |
716 | { "Wants", "as", NULL, 0 }, | |
717 | { "ConsistsOf", "as", NULL, 0 }, | |
718 | { "BindsTo", "as", NULL, 0 }, | |
719 | {} | |
720 | }, | |
721 | [DEPENDENCY_REVERSE] = { | |
722 | { "RequiredBy", "as", NULL, 0 }, | |
723 | { "RequisiteOf", "as", NULL, 0 }, | |
724 | { "WantedBy", "as", NULL, 0 }, | |
725 | { "PartOf", "as", NULL, 0 }, | |
726 | { "BoundBy", "as", NULL, 0 }, | |
727 | {} | |
728 | }, | |
729 | [DEPENDENCY_AFTER] = { | |
730 | { "After", "as", NULL, 0 }, | |
731 | {} | |
732 | }, | |
733 | [DEPENDENCY_BEFORE] = { | |
734 | { "Before", "as", NULL, 0 }, | |
735 | {} | |
736 | }, | |
737 | }; | |
738 | ||
739 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
740 | _cleanup_free_ char *dbus_path = NULL; | |
741 | int r; | |
742 | ||
743 | assert(bus); | |
744 | assert(name); | |
745 | assert(ret); | |
746 | ||
747 | dbus_path = unit_dbus_path_from_name(name); | |
748 | if (!dbus_path) | |
749 | return log_oom(); | |
750 | ||
751 | r = bus_map_all_properties(bus, | |
752 | "org.freedesktop.systemd1", | |
753 | dbus_path, | |
754 | map[arg_dependency], | |
755 | BUS_MAP_STRDUP, | |
756 | &error, | |
757 | NULL, | |
758 | &deps); | |
759 | if (r < 0) | |
760 | return log_error_errno(r, "Failed to get properties of %s: %s", name, bus_error_message(&error, r)); | |
761 | ||
762 | strv_uniq(deps); /* Sometimes a unit might have multiple deps on the other unit, | |
763 | * but we still want to show it just once. */ | |
764 | *ret = TAKE_PTR(deps); | |
765 | ||
766 | return 0; | |
767 | } | |
768 | ||
769 | const char* unit_type_suffix(const char *unit) { | |
770 | const char *dot; | |
771 | ||
772 | dot = strrchr(unit, '.'); | |
773 | if (!dot) | |
774 | return ""; | |
775 | ||
776 | return dot + 1; | |
777 | } | |
778 | ||
779 | bool output_show_unit(const UnitInfo *u, char **patterns) { | |
780 | assert(u); | |
781 | ||
782 | if (!strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) | |
783 | return false; | |
784 | ||
785 | if (arg_types && !strv_find(arg_types, unit_type_suffix(u->id))) | |
786 | return false; | |
787 | ||
788 | if (arg_all) | |
789 | return true; | |
790 | ||
791 | /* Note that '--all' is not purely a state filter, but also a filter that hides units that "follow" | |
792 | * other units (which is used for device units that appear under different names). */ | |
793 | if (!isempty(u->following)) | |
794 | return false; | |
795 | ||
796 | if (!strv_isempty(arg_states)) | |
797 | return true; | |
798 | ||
799 | /* By default show all units except the ones in inactive state and with no pending job */ | |
800 | if (u->job_id > 0) | |
801 | return true; | |
802 | ||
803 | if (streq(u->active_state, "inactive")) | |
804 | return false; | |
805 | ||
806 | return true; | |
807 | } | |
808 | ||
809 | bool install_client_side(void) { | |
810 | /* Decides when to execute enable/disable/... operations client-side rather than server-side. */ | |
811 | ||
812 | if (running_in_chroot_or_offline()) | |
813 | return true; | |
814 | ||
815 | if (sd_booted() <= 0) | |
816 | return true; | |
817 | ||
818 | if (!isempty(arg_root)) | |
819 | return true; | |
820 | ||
821 | if (arg_scope == UNIT_FILE_GLOBAL) | |
822 | return true; | |
823 | ||
824 | /* Unsupported environment variable, mostly for debugging purposes */ | |
825 | if (getenv_bool("SYSTEMCTL_INSTALL_CLIENT_SIDE") > 0) | |
826 | return true; | |
827 | ||
828 | return false; | |
829 | } | |
830 | ||
831 | int output_table(Table *table) { | |
832 | int r; | |
833 | ||
834 | assert(table); | |
835 | ||
836 | if (OUTPUT_MODE_IS_JSON(arg_output)) | |
837 | r = table_print_json(table, NULL, output_mode_to_json_format_flags(arg_output) | JSON_FORMAT_COLOR_AUTO); | |
838 | else | |
839 | r = table_print(table, NULL); | |
840 | if (r < 0) | |
841 | return table_log_print_error(r); | |
842 | ||
843 | return 0; | |
844 | } | |
845 | ||
846 | bool show_preset_for_state(UnitFileState state) { | |
847 | /* Don't show preset state in those unit file states, it'll only confuse users. */ | |
848 | return !IN_SET(state, | |
849 | UNIT_FILE_ALIAS, | |
850 | UNIT_FILE_STATIC, | |
851 | UNIT_FILE_GENERATED, | |
852 | UNIT_FILE_TRANSIENT); | |
853 | } | |
854 | ||
855 | UnitFileFlags unit_file_flags_from_args(void) { | |
856 | return (arg_runtime ? UNIT_FILE_RUNTIME : 0) | | |
857 | (arg_force ? UNIT_FILE_FORCE : 0); | |
858 | } | |
859 | ||
860 | int mangle_names(const char *operation, char **original_names, char ***ret_mangled_names) { | |
861 | _cleanup_strv_free_ char **l = NULL; | |
862 | char **i, **name; | |
863 | int r; | |
864 | ||
865 | assert(ret_mangled_names); | |
866 | ||
867 | l = i = new(char*, strv_length(original_names) + 1); | |
868 | if (!l) | |
869 | return log_oom(); | |
870 | ||
871 | STRV_FOREACH(name, original_names) { | |
872 | ||
873 | /* When enabling units qualified path names are OK, too, hence allow them explicitly. */ | |
874 | ||
875 | if (is_path(*name)) { | |
876 | *i = strdup(*name); | |
877 | if (!*i) | |
878 | return log_oom(); | |
879 | } else { | |
880 | r = unit_name_mangle_with_suffix(*name, operation, | |
881 | arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, | |
882 | ".service", i); | |
883 | if (r < 0) { | |
884 | *i = NULL; | |
885 | return log_error_errno(r, "Failed to mangle unit name: %m"); | |
886 | } | |
887 | } | |
888 | ||
889 | i++; | |
890 | } | |
891 | ||
892 | *i = NULL; | |
893 | *ret_mangled_names = TAKE_PTR(l); | |
894 | ||
895 | return 0; | |
896 | } | |
897 | ||
898 | int halt_now(enum action a) { | |
899 | /* The kernel will automatically flush ATA disks and suchlike on reboot(), but the file systems need | |
900 | * to be synced explicitly in advance. */ | |
901 | if (!arg_no_sync && !arg_dry_run) | |
902 | (void) sync(); | |
903 | ||
904 | /* Make sure C-A-D is handled by the kernel from this point on... */ | |
905 | if (!arg_dry_run) | |
906 | (void) reboot(RB_ENABLE_CAD); | |
907 | ||
908 | switch (a) { | |
909 | ||
910 | case ACTION_HALT: | |
911 | if (!arg_quiet) | |
912 | log_info("Halting."); | |
913 | if (arg_dry_run) | |
914 | return 0; | |
915 | (void) reboot(RB_HALT_SYSTEM); | |
916 | return -errno; | |
917 | ||
918 | case ACTION_POWEROFF: | |
919 | if (!arg_quiet) | |
920 | log_info("Powering off."); | |
921 | if (arg_dry_run) | |
922 | return 0; | |
923 | (void) reboot(RB_POWER_OFF); | |
924 | return -errno; | |
925 | ||
926 | case ACTION_KEXEC: | |
927 | case ACTION_REBOOT: | |
928 | return reboot_with_parameter(REBOOT_FALLBACK | | |
929 | (arg_quiet ? 0 : REBOOT_LOG) | | |
930 | (arg_dry_run ? REBOOT_DRY_RUN : 0)); | |
931 | ||
932 | default: | |
933 | assert_not_reached("Unknown action."); | |
934 | } | |
935 | } |