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