]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
daf71ef6 LP |
2 | |
3 | #include "sd-bus.h" | |
4 | ||
5 | #include "bus-common-errors.h" | |
6 | #include "bus-error.h" | |
7 | #include "bus-locator.h" | |
8 | #include "bus-util.h" | |
9 | #include "bus-wait-for-jobs.h" | |
10 | #include "bus-wait-for-units.h" | |
11 | #include "macro.h" | |
12 | #include "special.h" | |
13 | #include "string-util.h" | |
14 | #include "systemctl-start-unit.h" | |
15 | #include "systemctl-util.h" | |
16 | #include "systemctl.h" | |
17 | #include "terminal-util.h" | |
18 | ||
19 | static const struct { | |
20 | const char *verb; /* systemctl verb */ | |
21 | const char *method; /* Name of the specific D-Bus method */ | |
22 | const char *job_type; /* Job type when passing to the generic EnqueueUnitJob() method */ | |
23 | } unit_actions[] = { | |
24 | { "start", "StartUnit", "start" }, | |
25 | { "stop", "StopUnit", "stop" }, | |
26 | { "condstop", "StopUnit", "stop" }, /* legacy alias */ | |
27 | { "reload", "ReloadUnit", "reload" }, | |
28 | { "restart", "RestartUnit", "restart" }, | |
29 | { "try-restart", "TryRestartUnit", "try-restart" }, | |
30 | { "condrestart", "TryRestartUnit", "try-restart" }, /* legacy alias */ | |
31 | { "reload-or-restart", "ReloadOrRestartUnit", "reload-or-restart" }, | |
32 | { "try-reload-or-restart", "ReloadOrTryRestartUnit", "reload-or-try-restart" }, | |
33 | { "reload-or-try-restart", "ReloadOrTryRestartUnit", "reload-or-try-restart" }, /* legacy alias */ | |
34 | { "condreload", "ReloadOrTryRestartUnit", "reload-or-try-restart" }, /* legacy alias */ | |
35 | { "force-reload", "ReloadOrTryRestartUnit", "reload-or-try-restart" }, /* legacy alias */ | |
36 | }; | |
37 | ||
38 | static const char *verb_to_method(const char *verb) { | |
deaf4b86 | 39 | for (size_t i = 0; i < ELEMENTSOF(unit_actions); i++) |
daf71ef6 LP |
40 | if (streq_ptr(unit_actions[i].verb, verb)) |
41 | return unit_actions[i].method; | |
42 | ||
43 | return "StartUnit"; | |
44 | } | |
45 | ||
46 | static const char *verb_to_job_type(const char *verb) { | |
deaf4b86 | 47 | for (size_t i = 0; i < ELEMENTSOF(unit_actions); i++) |
daf71ef6 LP |
48 | if (streq_ptr(unit_actions[i].verb, verb)) |
49 | return unit_actions[i].job_type; | |
50 | ||
51 | return "start"; | |
52 | } | |
53 | ||
54 | static int start_unit_one( | |
55 | sd_bus *bus, | |
56 | const char *method, /* When using classic per-job bus methods */ | |
57 | const char *job_type, /* When using new-style EnqueueUnitJob() */ | |
58 | const char *name, | |
59 | const char *mode, | |
60 | sd_bus_error *error, | |
61 | BusWaitForJobs *w, | |
62 | BusWaitForUnits *wu) { | |
63 | ||
64 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
65 | const char *path; | |
66 | bool done = false; | |
67 | int r; | |
68 | ||
69 | assert(method); | |
70 | assert(name); | |
71 | assert(mode); | |
72 | assert(error); | |
73 | ||
74 | log_debug("%s dbus call org.freedesktop.systemd1.Manager %s(%s, %s)", | |
75 | arg_dry_run ? "Would execute" : "Executing", | |
76 | method, name, mode); | |
77 | ||
78 | if (arg_dry_run) | |
79 | return 0; | |
80 | ||
81 | if (arg_show_transaction) { | |
82 | _cleanup_(sd_bus_error_free) sd_bus_error enqueue_error = SD_BUS_ERROR_NULL; | |
83 | ||
84 | /* Use the new, fancy EnqueueUnitJob() API if the user wants us to print the transaction */ | |
85 | r = bus_call_method( | |
86 | bus, | |
87 | bus_systemd_mgr, | |
88 | "EnqueueUnitJob", | |
89 | &enqueue_error, | |
90 | &reply, | |
91 | "sss", | |
92 | name, job_type, mode); | |
93 | if (r < 0) { | |
94 | if (!sd_bus_error_has_name(&enqueue_error, SD_BUS_ERROR_UNKNOWN_METHOD)) { | |
95 | (void) sd_bus_error_move(error, &enqueue_error); | |
96 | goto fail; | |
97 | } | |
98 | ||
99 | /* Hmm, the API is not yet available. Let's use the classic API instead (see below). */ | |
100 | log_notice("--show-transaction not supported by this service manager, proceeding without."); | |
101 | } else { | |
102 | const char *u, *jt; | |
103 | uint32_t id; | |
104 | ||
105 | r = sd_bus_message_read(reply, "uosos", &id, &path, &u, NULL, &jt); | |
106 | if (r < 0) | |
107 | return bus_log_parse_error(r); | |
108 | ||
109 | log_info("Enqueued anchor job %" PRIu32 " %s/%s.", id, u, jt); | |
110 | ||
111 | r = sd_bus_message_enter_container(reply, 'a', "(uosos)"); | |
112 | if (r < 0) | |
113 | return bus_log_parse_error(r); | |
114 | for (;;) { | |
115 | r = sd_bus_message_read(reply, "(uosos)", &id, NULL, &u, NULL, &jt); | |
116 | if (r < 0) | |
117 | return bus_log_parse_error(r); | |
118 | if (r == 0) | |
119 | break; | |
120 | ||
121 | log_info("Enqueued auxiliary job %" PRIu32 " %s/%s.", id, u, jt); | |
122 | } | |
123 | ||
124 | r = sd_bus_message_exit_container(reply); | |
125 | if (r < 0) | |
126 | return bus_log_parse_error(r); | |
127 | ||
128 | done = true; | |
129 | } | |
130 | } | |
131 | ||
132 | if (!done) { | |
133 | r = bus_call_method(bus, bus_systemd_mgr, method, error, &reply, "ss", name, mode); | |
134 | if (r < 0) | |
135 | goto fail; | |
136 | ||
137 | r = sd_bus_message_read(reply, "o", &path); | |
138 | if (r < 0) | |
139 | return bus_log_parse_error(r); | |
140 | } | |
141 | ||
142 | if (need_daemon_reload(bus, name) > 0) | |
143 | warn_unit_file_changed(name); | |
144 | ||
145 | if (w) { | |
146 | log_debug("Adding %s to the set", path); | |
147 | r = bus_wait_for_jobs_add(w, path); | |
148 | if (r < 0) | |
149 | return log_error_errno(r, "Failed to watch job for %s: %m", name); | |
150 | } | |
151 | ||
152 | if (wu) { | |
153 | r = bus_wait_for_units_add_unit(wu, name, BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB, NULL, NULL); | |
154 | if (r < 0) | |
155 | return log_error_errno(r, "Failed to watch unit %s: %m", name); | |
156 | } | |
157 | ||
158 | return 0; | |
159 | ||
160 | fail: | |
161 | /* There's always a fallback possible for legacy actions. */ | |
162 | if (arg_action != ACTION_SYSTEMCTL) | |
163 | return r; | |
164 | ||
8a826a97 MY |
165 | if (sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED) && |
166 | STR_IN_SET(method, "TryRestartUnit", "ReloadOrTryRestartUnit")) { | |
167 | /* Ignore masked unit if try-* is requested */ | |
168 | ||
169 | log_debug_errno(r, "Failed to %s %s, ignoring: %s", job_type, name, bus_error_message(error, r)); | |
170 | return 0; | |
11da6165 MY |
171 | } |
172 | ||
173 | log_error_errno(r, "Failed to %s %s: %s", job_type, name, bus_error_message(error, r)); | |
daf71ef6 LP |
174 | |
175 | if (!sd_bus_error_has_names(error, BUS_ERROR_NO_SUCH_UNIT, | |
176 | BUS_ERROR_UNIT_MASKED, | |
177 | BUS_ERROR_JOB_TYPE_NOT_APPLICABLE)) | |
178 | log_error("See %s logs and 'systemctl%s status%s %s' for details.", | |
4870133b LP |
179 | runtime_scope_to_string(arg_runtime_scope), |
180 | arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? "" : " --user", | |
daf71ef6 LP |
181 | name[0] == '-' ? " --" : "", |
182 | name); | |
183 | ||
184 | return r; | |
185 | } | |
186 | ||
c9615f73 ZJS |
187 | static int enqueue_marked_jobs( |
188 | sd_bus *bus, | |
189 | BusWaitForJobs *w) { | |
190 | ||
191 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
192 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
193 | int r; | |
194 | ||
195 | log_debug("%s dbus call org.freedesktop.systemd1.Manager EnqueueMarkedJobs()", | |
196 | arg_dry_run ? "Would execute" : "Executing"); | |
197 | ||
198 | if (arg_dry_run) | |
199 | return 0; | |
200 | ||
201 | r = bus_call_method(bus, bus_systemd_mgr, "EnqueueMarkedJobs", &error, &reply, NULL); | |
202 | if (r < 0) | |
203 | return log_error_errno(r, "Failed to start jobs: %s", bus_error_message(&error, r)); | |
204 | ||
205 | _cleanup_strv_free_ char **paths = NULL; | |
206 | r = sd_bus_message_read_strv(reply, &paths); | |
207 | if (r < 0) | |
208 | return bus_log_parse_error(r); | |
209 | ||
de010b0b | 210 | if (w) |
c9615f73 ZJS |
211 | STRV_FOREACH(path, paths) { |
212 | log_debug("Adding %s to the set", *path); | |
213 | r = bus_wait_for_jobs_add(w, *path); | |
214 | if (r < 0) | |
215 | return log_error_errno(r, "Failed to watch job %s: %m", *path); | |
216 | } | |
c9615f73 ZJS |
217 | |
218 | return 0; | |
219 | } | |
220 | ||
daf71ef6 LP |
221 | const struct action_metadata action_table[_ACTION_MAX] = { |
222 | [ACTION_HALT] = { SPECIAL_HALT_TARGET, "halt", "replace-irreversibly" }, | |
223 | [ACTION_POWEROFF] = { SPECIAL_POWEROFF_TARGET, "poweroff", "replace-irreversibly" }, | |
224 | [ACTION_REBOOT] = { SPECIAL_REBOOT_TARGET, "reboot", "replace-irreversibly" }, | |
225 | [ACTION_KEXEC] = { SPECIAL_KEXEC_TARGET, "kexec", "replace-irreversibly" }, | |
34f21ff6 | 226 | [ACTION_SOFT_REBOOT] = { SPECIAL_SOFT_REBOOT_TARGET, "soft-reboot", "replace-irreversibly" }, |
daf71ef6 LP |
227 | [ACTION_RUNLEVEL2] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, |
228 | [ACTION_RUNLEVEL3] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, | |
229 | [ACTION_RUNLEVEL4] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, | |
230 | [ACTION_RUNLEVEL5] = { SPECIAL_GRAPHICAL_TARGET, NULL, "isolate" }, | |
231 | [ACTION_RESCUE] = { SPECIAL_RESCUE_TARGET, "rescue", "isolate" }, | |
232 | [ACTION_EMERGENCY] = { SPECIAL_EMERGENCY_TARGET, "emergency", "isolate" }, | |
233 | [ACTION_DEFAULT] = { SPECIAL_DEFAULT_TARGET, "default", "isolate" }, | |
234 | [ACTION_EXIT] = { SPECIAL_EXIT_TARGET, "exit", "replace-irreversibly" }, | |
235 | [ACTION_SUSPEND] = { SPECIAL_SUSPEND_TARGET, "suspend", "replace-irreversibly" }, | |
236 | [ACTION_HIBERNATE] = { SPECIAL_HIBERNATE_TARGET, "hibernate", "replace-irreversibly" }, | |
237 | [ACTION_HYBRID_SLEEP] = { SPECIAL_HYBRID_SLEEP_TARGET, "hybrid-sleep", "replace-irreversibly" }, | |
238 | [ACTION_SUSPEND_THEN_HIBERNATE] = { SPECIAL_SUSPEND_THEN_HIBERNATE_TARGET, "suspend-then-hibernate", "replace-irreversibly" }, | |
5b356289 | 239 | [ACTION_SLEEP] = { NULL, /* handled only by logind */ "sleep", NULL }, |
daf71ef6 LP |
240 | }; |
241 | ||
242 | enum action verb_to_action(const char *verb) { | |
deaf4b86 | 243 | for (enum action i = 0; i < _ACTION_MAX; i++) |
daf71ef6 LP |
244 | if (streq_ptr(action_table[i].verb, verb)) |
245 | return i; | |
246 | ||
247 | return _ACTION_INVALID; | |
248 | } | |
249 | ||
250 | static const char** make_extra_args(const char *extra_args[static 4]) { | |
251 | size_t n = 0; | |
252 | ||
253 | assert(extra_args); | |
254 | ||
4870133b | 255 | if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) |
daf71ef6 LP |
256 | extra_args[n++] = "--user"; |
257 | ||
258 | if (arg_transport == BUS_TRANSPORT_REMOTE) { | |
259 | extra_args[n++] = "-H"; | |
260 | extra_args[n++] = arg_host; | |
261 | } else if (arg_transport == BUS_TRANSPORT_MACHINE) { | |
262 | extra_args[n++] = "-M"; | |
263 | extra_args[n++] = arg_host; | |
264 | } else | |
265 | assert(arg_transport == BUS_TRANSPORT_LOCAL); | |
266 | ||
267 | extra_args[n] = NULL; | |
268 | return extra_args; | |
269 | } | |
270 | ||
32baf64d | 271 | int verb_start(int argc, char *argv[], void *userdata) { |
daf71ef6 LP |
272 | _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *wu = NULL; |
273 | _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; | |
274 | const char *method, *job_type, *mode, *one_name, *suffix = NULL; | |
275 | _cleanup_free_ char **stopped_units = NULL; /* Do not use _cleanup_strv_free_ */ | |
276 | _cleanup_strv_free_ char **names = NULL; | |
277 | int r, ret = EXIT_SUCCESS; | |
278 | sd_bus *bus; | |
daf71ef6 LP |
279 | |
280 | if (arg_wait && !STR_IN_SET(argv[0], "start", "restart")) | |
281 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
282 | "--wait may only be used with the 'start' or 'restart' commands."); | |
283 | ||
284 | /* We cannot do sender tracking on the private bus, so we need the full one for RefUnit to implement | |
285 | * --wait */ | |
286 | r = acquire_bus(arg_wait ? BUS_FULL : BUS_MANAGER, &bus); | |
287 | if (r < 0) | |
288 | return r; | |
289 | ||
290 | ask_password_agent_open_maybe(); | |
291 | polkit_agent_open_maybe(); | |
292 | ||
293 | if (arg_action == ACTION_SYSTEMCTL) { | |
294 | enum action action; | |
295 | ||
296 | action = verb_to_action(argv[0]); | |
297 | ||
5b356289 MY |
298 | assert(action != ACTION_SLEEP); |
299 | ||
daf71ef6 LP |
300 | if (action != _ACTION_INVALID) { |
301 | /* A command in style "systemctl reboot", "systemctl poweroff", … */ | |
302 | method = "StartUnit"; | |
303 | job_type = "start"; | |
304 | mode = action_table[action].mode; | |
305 | one_name = action_table[action].target; | |
306 | } else { | |
307 | if (streq(argv[0], "isolate")) { | |
308 | /* A "systemctl isolate <unit1> <unit2> …" command */ | |
309 | method = "StartUnit"; | |
310 | job_type = "start"; | |
311 | mode = "isolate"; | |
312 | suffix = ".target"; | |
c9615f73 | 313 | } else if (!arg_marked) { |
09ed55c2 | 314 | /* A command in style of "systemctl start <unit1> <unit2> …", "systemctl stop <unit1> <unit2> …" and so on */ |
daf71ef6 LP |
315 | method = verb_to_method(argv[0]); |
316 | job_type = verb_to_job_type(argv[0]); | |
a88f9dba | 317 | mode = arg_job_mode(); |
703e2870 ZJS |
318 | } else |
319 | method = job_type = mode = NULL; | |
320 | ||
daf71ef6 LP |
321 | one_name = NULL; |
322 | } | |
323 | } else { | |
324 | /* A SysV legacy command such as "halt", "reboot", "poweroff", … */ | |
325 | assert(arg_action >= 0 && arg_action < _ACTION_MAX); | |
326 | assert(action_table[arg_action].target); | |
327 | assert(action_table[arg_action].mode); | |
328 | ||
329 | method = "StartUnit"; | |
330 | job_type = "start"; | |
331 | mode = action_table[arg_action].mode; | |
332 | one_name = action_table[arg_action].target; | |
333 | } | |
334 | ||
335 | if (one_name) { | |
336 | names = strv_new(one_name); | |
337 | if (!names) | |
338 | return log_oom(); | |
c9615f73 | 339 | } else if (!arg_marked) { |
daf71ef6 LP |
340 | bool expanded; |
341 | ||
342 | r = expand_unit_names(bus, strv_skip(argv, 1), suffix, &names, &expanded); | |
343 | if (r < 0) | |
344 | return log_error_errno(r, "Failed to expand names: %m"); | |
345 | ||
346 | if (!arg_all && expanded && streq(job_type, "start") && !arg_quiet) { | |
347 | log_warning("Warning: %ssystemctl start called with a glob pattern.%s", | |
348 | ansi_highlight_red(), | |
349 | ansi_normal()); | |
350 | log_notice("Hint: unit globs expand to loaded units, so start will usually have no effect.\n" | |
351 | " Passing --all will also load units which are pulled in by other units.\n" | |
352 | " See systemctl(1) for more details."); | |
353 | } | |
354 | } | |
355 | ||
356 | if (!arg_no_block) { | |
357 | r = bus_wait_for_jobs_new(bus, &w); | |
358 | if (r < 0) | |
359 | return log_error_errno(r, "Could not watch jobs: %m"); | |
360 | } | |
361 | ||
362 | if (arg_wait) { | |
363 | r = bus_call_method_async(bus, NULL, bus_systemd_mgr, "Subscribe", NULL, NULL, NULL); | |
364 | if (r < 0) | |
365 | return log_error_errno(r, "Failed to enable subscription: %m"); | |
366 | ||
367 | r = bus_wait_for_units_new(bus, &wu); | |
368 | if (r < 0) | |
369 | return log_error_errno(r, "Failed to allocate unit watch context: %m"); | |
370 | } | |
371 | ||
c9615f73 ZJS |
372 | if (arg_marked) |
373 | ret = enqueue_marked_jobs(bus, w); | |
c9615f73 ZJS |
374 | else |
375 | STRV_FOREACH(name, names) { | |
376 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
daf71ef6 | 377 | |
c9615f73 ZJS |
378 | r = start_unit_one(bus, method, job_type, *name, mode, &error, w, wu); |
379 | if (ret == EXIT_SUCCESS && r < 0) | |
380 | ret = translate_bus_error_to_exit_status(r, &error); | |
381 | ||
382 | if (r >= 0 && streq(method, "StopUnit")) { | |
383 | r = strv_push(&stopped_units, *name); | |
384 | if (r < 0) | |
385 | return log_oom(); | |
386 | } | |
daf71ef6 | 387 | } |
daf71ef6 LP |
388 | |
389 | if (!arg_no_block) { | |
390 | const char* extra_args[4]; | |
e22ad53d | 391 | WaitJobsFlags flags = 0; |
daf71ef6 | 392 | |
e22ad53d MC |
393 | SET_FLAG(flags, BUS_WAIT_JOBS_LOG_ERROR, !arg_quiet); |
394 | r = bus_wait_for_jobs(w, flags, make_extra_args(extra_args)); | |
daf71ef6 LP |
395 | if (r < 0) |
396 | return r; | |
397 | ||
398 | /* When stopping units, warn if they can still be triggered by | |
399 | * another active unit (socket, path, timer) */ | |
0b675f97 | 400 | if (!arg_quiet && !arg_no_warn) |
002db03f MY |
401 | STRV_FOREACH(unit, stopped_units) |
402 | warn_triggering_units(bus, *unit, "Stopping", /* ignore_masked = */ true); | |
daf71ef6 LP |
403 | } |
404 | ||
405 | if (arg_wait) { | |
406 | r = bus_wait_for_units_run(wu); | |
407 | if (r < 0) | |
408 | return log_error_errno(r, "Failed to wait for units: %m"); | |
409 | if (r == BUS_WAIT_FAILURE && ret == EXIT_SUCCESS) | |
410 | ret = EXIT_FAILURE; | |
411 | } | |
412 | ||
413 | return ret; | |
414 | } |