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