]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/systemctl/systemctl-start-unit.c
dbus-wait-for-jobs: change 'quiet' flag to enum
[thirdparty/systemd.git] / src / systemctl / systemctl-start-unit.c
CommitLineData
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
19static 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
38static 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
46static 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
54static 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
160fail:
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
187static 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
221const 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
242enum 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
250static 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 271int 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}