]>
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 | ||
165 | log_error_errno(r, "Failed to %s %s: %s", job_type, name, bus_error_message(error, r)); | |
166 | ||
167 | if (!sd_bus_error_has_names(error, BUS_ERROR_NO_SUCH_UNIT, | |
168 | BUS_ERROR_UNIT_MASKED, | |
169 | BUS_ERROR_JOB_TYPE_NOT_APPLICABLE)) | |
170 | log_error("See %s logs and 'systemctl%s status%s %s' for details.", | |
171 | arg_scope == UNIT_FILE_SYSTEM ? "system" : "user", | |
172 | arg_scope == UNIT_FILE_SYSTEM ? "" : " --user", | |
173 | name[0] == '-' ? " --" : "", | |
174 | name); | |
175 | ||
176 | return r; | |
177 | } | |
178 | ||
c9615f73 ZJS |
179 | static int enqueue_marked_jobs( |
180 | sd_bus *bus, | |
181 | BusWaitForJobs *w) { | |
182 | ||
183 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
184 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
185 | int r; | |
186 | ||
187 | log_debug("%s dbus call org.freedesktop.systemd1.Manager EnqueueMarkedJobs()", | |
188 | arg_dry_run ? "Would execute" : "Executing"); | |
189 | ||
190 | if (arg_dry_run) | |
191 | return 0; | |
192 | ||
193 | r = bus_call_method(bus, bus_systemd_mgr, "EnqueueMarkedJobs", &error, &reply, NULL); | |
194 | if (r < 0) | |
195 | return log_error_errno(r, "Failed to start jobs: %s", bus_error_message(&error, r)); | |
196 | ||
197 | _cleanup_strv_free_ char **paths = NULL; | |
198 | r = sd_bus_message_read_strv(reply, &paths); | |
199 | if (r < 0) | |
200 | return bus_log_parse_error(r); | |
201 | ||
de010b0b | 202 | if (w) |
c9615f73 ZJS |
203 | STRV_FOREACH(path, paths) { |
204 | log_debug("Adding %s to the set", *path); | |
205 | r = bus_wait_for_jobs_add(w, *path); | |
206 | if (r < 0) | |
207 | return log_error_errno(r, "Failed to watch job %s: %m", *path); | |
208 | } | |
c9615f73 ZJS |
209 | |
210 | return 0; | |
211 | } | |
212 | ||
daf71ef6 LP |
213 | const struct action_metadata action_table[_ACTION_MAX] = { |
214 | [ACTION_HALT] = { SPECIAL_HALT_TARGET, "halt", "replace-irreversibly" }, | |
215 | [ACTION_POWEROFF] = { SPECIAL_POWEROFF_TARGET, "poweroff", "replace-irreversibly" }, | |
216 | [ACTION_REBOOT] = { SPECIAL_REBOOT_TARGET, "reboot", "replace-irreversibly" }, | |
217 | [ACTION_KEXEC] = { SPECIAL_KEXEC_TARGET, "kexec", "replace-irreversibly" }, | |
218 | [ACTION_RUNLEVEL2] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, | |
219 | [ACTION_RUNLEVEL3] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, | |
220 | [ACTION_RUNLEVEL4] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, | |
221 | [ACTION_RUNLEVEL5] = { SPECIAL_GRAPHICAL_TARGET, NULL, "isolate" }, | |
222 | [ACTION_RESCUE] = { SPECIAL_RESCUE_TARGET, "rescue", "isolate" }, | |
223 | [ACTION_EMERGENCY] = { SPECIAL_EMERGENCY_TARGET, "emergency", "isolate" }, | |
224 | [ACTION_DEFAULT] = { SPECIAL_DEFAULT_TARGET, "default", "isolate" }, | |
225 | [ACTION_EXIT] = { SPECIAL_EXIT_TARGET, "exit", "replace-irreversibly" }, | |
226 | [ACTION_SUSPEND] = { SPECIAL_SUSPEND_TARGET, "suspend", "replace-irreversibly" }, | |
227 | [ACTION_HIBERNATE] = { SPECIAL_HIBERNATE_TARGET, "hibernate", "replace-irreversibly" }, | |
228 | [ACTION_HYBRID_SLEEP] = { SPECIAL_HYBRID_SLEEP_TARGET, "hybrid-sleep", "replace-irreversibly" }, | |
229 | [ACTION_SUSPEND_THEN_HIBERNATE] = { SPECIAL_SUSPEND_THEN_HIBERNATE_TARGET, "suspend-then-hibernate", "replace-irreversibly" }, | |
230 | }; | |
231 | ||
232 | enum action verb_to_action(const char *verb) { | |
deaf4b86 | 233 | for (enum action i = 0; i < _ACTION_MAX; i++) |
daf71ef6 LP |
234 | if (streq_ptr(action_table[i].verb, verb)) |
235 | return i; | |
236 | ||
237 | return _ACTION_INVALID; | |
238 | } | |
239 | ||
240 | static const char** make_extra_args(const char *extra_args[static 4]) { | |
241 | size_t n = 0; | |
242 | ||
243 | assert(extra_args); | |
244 | ||
245 | if (arg_scope != UNIT_FILE_SYSTEM) | |
246 | extra_args[n++] = "--user"; | |
247 | ||
248 | if (arg_transport == BUS_TRANSPORT_REMOTE) { | |
249 | extra_args[n++] = "-H"; | |
250 | extra_args[n++] = arg_host; | |
251 | } else if (arg_transport == BUS_TRANSPORT_MACHINE) { | |
252 | extra_args[n++] = "-M"; | |
253 | extra_args[n++] = arg_host; | |
254 | } else | |
255 | assert(arg_transport == BUS_TRANSPORT_LOCAL); | |
256 | ||
257 | extra_args[n] = NULL; | |
258 | return extra_args; | |
259 | } | |
260 | ||
32baf64d | 261 | int verb_start(int argc, char *argv[], void *userdata) { |
daf71ef6 LP |
262 | _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *wu = NULL; |
263 | _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; | |
264 | const char *method, *job_type, *mode, *one_name, *suffix = NULL; | |
265 | _cleanup_free_ char **stopped_units = NULL; /* Do not use _cleanup_strv_free_ */ | |
266 | _cleanup_strv_free_ char **names = NULL; | |
267 | int r, ret = EXIT_SUCCESS; | |
268 | sd_bus *bus; | |
daf71ef6 LP |
269 | |
270 | if (arg_wait && !STR_IN_SET(argv[0], "start", "restart")) | |
271 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
272 | "--wait may only be used with the 'start' or 'restart' commands."); | |
273 | ||
274 | /* We cannot do sender tracking on the private bus, so we need the full one for RefUnit to implement | |
275 | * --wait */ | |
276 | r = acquire_bus(arg_wait ? BUS_FULL : BUS_MANAGER, &bus); | |
277 | if (r < 0) | |
278 | return r; | |
279 | ||
280 | ask_password_agent_open_maybe(); | |
281 | polkit_agent_open_maybe(); | |
282 | ||
283 | if (arg_action == ACTION_SYSTEMCTL) { | |
284 | enum action action; | |
285 | ||
286 | action = verb_to_action(argv[0]); | |
287 | ||
288 | if (action != _ACTION_INVALID) { | |
289 | /* A command in style "systemctl reboot", "systemctl poweroff", … */ | |
290 | method = "StartUnit"; | |
291 | job_type = "start"; | |
292 | mode = action_table[action].mode; | |
293 | one_name = action_table[action].target; | |
294 | } else { | |
295 | if (streq(argv[0], "isolate")) { | |
296 | /* A "systemctl isolate <unit1> <unit2> …" command */ | |
297 | method = "StartUnit"; | |
298 | job_type = "start"; | |
299 | mode = "isolate"; | |
300 | suffix = ".target"; | |
c9615f73 | 301 | } else if (!arg_marked) { |
daf71ef6 LP |
302 | /* A command in style of "systemctl start <unit1> <unit2> …", "sysemctl stop <unit1> <unit2> …" and so on */ |
303 | method = verb_to_method(argv[0]); | |
304 | job_type = verb_to_job_type(argv[0]); | |
a88f9dba | 305 | mode = arg_job_mode(); |
703e2870 ZJS |
306 | } else |
307 | method = job_type = mode = NULL; | |
308 | ||
daf71ef6 LP |
309 | one_name = NULL; |
310 | } | |
311 | } else { | |
312 | /* A SysV legacy command such as "halt", "reboot", "poweroff", … */ | |
313 | assert(arg_action >= 0 && arg_action < _ACTION_MAX); | |
314 | assert(action_table[arg_action].target); | |
315 | assert(action_table[arg_action].mode); | |
316 | ||
317 | method = "StartUnit"; | |
318 | job_type = "start"; | |
319 | mode = action_table[arg_action].mode; | |
320 | one_name = action_table[arg_action].target; | |
321 | } | |
322 | ||
323 | if (one_name) { | |
324 | names = strv_new(one_name); | |
325 | if (!names) | |
326 | return log_oom(); | |
c9615f73 | 327 | } else if (!arg_marked) { |
daf71ef6 LP |
328 | bool expanded; |
329 | ||
330 | r = expand_unit_names(bus, strv_skip(argv, 1), suffix, &names, &expanded); | |
331 | if (r < 0) | |
332 | return log_error_errno(r, "Failed to expand names: %m"); | |
333 | ||
334 | if (!arg_all && expanded && streq(job_type, "start") && !arg_quiet) { | |
335 | log_warning("Warning: %ssystemctl start called with a glob pattern.%s", | |
336 | ansi_highlight_red(), | |
337 | ansi_normal()); | |
338 | log_notice("Hint: unit globs expand to loaded units, so start will usually have no effect.\n" | |
339 | " Passing --all will also load units which are pulled in by other units.\n" | |
340 | " See systemctl(1) for more details."); | |
341 | } | |
342 | } | |
343 | ||
344 | if (!arg_no_block) { | |
345 | r = bus_wait_for_jobs_new(bus, &w); | |
346 | if (r < 0) | |
347 | return log_error_errno(r, "Could not watch jobs: %m"); | |
348 | } | |
349 | ||
350 | if (arg_wait) { | |
351 | r = bus_call_method_async(bus, NULL, bus_systemd_mgr, "Subscribe", NULL, NULL, NULL); | |
352 | if (r < 0) | |
353 | return log_error_errno(r, "Failed to enable subscription: %m"); | |
354 | ||
355 | r = bus_wait_for_units_new(bus, &wu); | |
356 | if (r < 0) | |
357 | return log_error_errno(r, "Failed to allocate unit watch context: %m"); | |
358 | } | |
359 | ||
c9615f73 ZJS |
360 | if (arg_marked) |
361 | ret = enqueue_marked_jobs(bus, w); | |
daf71ef6 | 362 | |
c9615f73 ZJS |
363 | else |
364 | STRV_FOREACH(name, names) { | |
365 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
daf71ef6 | 366 | |
c9615f73 ZJS |
367 | r = start_unit_one(bus, method, job_type, *name, mode, &error, w, wu); |
368 | if (ret == EXIT_SUCCESS && r < 0) | |
369 | ret = translate_bus_error_to_exit_status(r, &error); | |
370 | ||
371 | if (r >= 0 && streq(method, "StopUnit")) { | |
372 | r = strv_push(&stopped_units, *name); | |
373 | if (r < 0) | |
374 | return log_oom(); | |
375 | } | |
daf71ef6 | 376 | } |
daf71ef6 LP |
377 | |
378 | if (!arg_no_block) { | |
379 | const char* extra_args[4]; | |
380 | ||
381 | r = bus_wait_for_jobs(w, arg_quiet, make_extra_args(extra_args)); | |
382 | if (r < 0) | |
383 | return r; | |
384 | ||
385 | /* When stopping units, warn if they can still be triggered by | |
386 | * another active unit (socket, path, timer) */ | |
387 | if (!arg_quiet) | |
388 | STRV_FOREACH(name, stopped_units) | |
389 | (void) check_triggering_units(bus, *name); | |
390 | } | |
391 | ||
392 | if (arg_wait) { | |
393 | r = bus_wait_for_units_run(wu); | |
394 | if (r < 0) | |
395 | return log_error_errno(r, "Failed to wait for units: %m"); | |
396 | if (r == BUS_WAIT_FAILURE && ret == EXIT_SUCCESS) | |
397 | ret = EXIT_FAILURE; | |
398 | } | |
399 | ||
400 | return ret; | |
401 | } |