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