1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include "bus-common-errors.h"
7 #include "bus-locator.h"
9 #include "bus-wait-for-jobs.h"
10 #include "bus-wait-for-units.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"
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 */
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 */
38 static const char *verb_to_method(const char *verb
) {
41 for (i
= 0; i
< ELEMENTSOF(unit_actions
); i
++)
42 if (streq_ptr(unit_actions
[i
].verb
, verb
))
43 return unit_actions
[i
].method
;
48 static const char *verb_to_job_type(const char *verb
) {
51 for (i
= 0; i
< ELEMENTSOF(unit_actions
); i
++)
52 if (streq_ptr(unit_actions
[i
].verb
, verb
))
53 return unit_actions
[i
].job_type
;
58 static int start_unit_one(
60 const char *method
, /* When using classic per-job bus methods */
61 const char *job_type
, /* When using new-style EnqueueUnitJob() */
66 BusWaitForUnits
*wu
) {
68 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
78 log_debug("%s dbus call org.freedesktop.systemd1.Manager %s(%s, %s)",
79 arg_dry_run
? "Would execute" : "Executing",
85 if (arg_show_transaction
) {
86 _cleanup_(sd_bus_error_free
) sd_bus_error enqueue_error
= SD_BUS_ERROR_NULL
;
88 /* Use the new, fancy EnqueueUnitJob() API if the user wants us to print the transaction */
96 name
, job_type
, mode
);
98 if (!sd_bus_error_has_name(&enqueue_error
, SD_BUS_ERROR_UNKNOWN_METHOD
)) {
99 (void) sd_bus_error_move(error
, &enqueue_error
);
103 /* Hmm, the API is not yet available. Let's use the classic API instead (see below). */
104 log_notice("--show-transaction not supported by this service manager, proceeding without.");
109 r
= sd_bus_message_read(reply
, "uosos", &id
, &path
, &u
, NULL
, &jt
);
111 return bus_log_parse_error(r
);
113 log_info("Enqueued anchor job %" PRIu32
" %s/%s.", id
, u
, jt
);
115 r
= sd_bus_message_enter_container(reply
, 'a', "(uosos)");
117 return bus_log_parse_error(r
);
119 r
= sd_bus_message_read(reply
, "(uosos)", &id
, NULL
, &u
, NULL
, &jt
);
121 return bus_log_parse_error(r
);
125 log_info("Enqueued auxiliary job %" PRIu32
" %s/%s.", id
, u
, jt
);
128 r
= sd_bus_message_exit_container(reply
);
130 return bus_log_parse_error(r
);
137 r
= bus_call_method(bus
, bus_systemd_mgr
, method
, error
, &reply
, "ss", name
, mode
);
141 r
= sd_bus_message_read(reply
, "o", &path
);
143 return bus_log_parse_error(r
);
146 if (need_daemon_reload(bus
, name
) > 0)
147 warn_unit_file_changed(name
);
150 log_debug("Adding %s to the set", path
);
151 r
= bus_wait_for_jobs_add(w
, path
);
153 return log_error_errno(r
, "Failed to watch job for %s: %m", name
);
157 r
= bus_wait_for_units_add_unit(wu
, name
, BUS_WAIT_FOR_INACTIVE
|BUS_WAIT_NO_JOB
, NULL
, NULL
);
159 return log_error_errno(r
, "Failed to watch unit %s: %m", name
);
165 /* There's always a fallback possible for legacy actions. */
166 if (arg_action
!= ACTION_SYSTEMCTL
)
169 log_error_errno(r
, "Failed to %s %s: %s", job_type
, name
, bus_error_message(error
, r
));
171 if (!sd_bus_error_has_names(error
, BUS_ERROR_NO_SUCH_UNIT
,
172 BUS_ERROR_UNIT_MASKED
,
173 BUS_ERROR_JOB_TYPE_NOT_APPLICABLE
))
174 log_error("See %s logs and 'systemctl%s status%s %s' for details.",
175 arg_scope
== UNIT_FILE_SYSTEM
? "system" : "user",
176 arg_scope
== UNIT_FILE_SYSTEM
? "" : " --user",
177 name
[0] == '-' ? " --" : "",
183 const struct action_metadata action_table
[_ACTION_MAX
] = {
184 [ACTION_HALT
] = { SPECIAL_HALT_TARGET
, "halt", "replace-irreversibly" },
185 [ACTION_POWEROFF
] = { SPECIAL_POWEROFF_TARGET
, "poweroff", "replace-irreversibly" },
186 [ACTION_REBOOT
] = { SPECIAL_REBOOT_TARGET
, "reboot", "replace-irreversibly" },
187 [ACTION_KEXEC
] = { SPECIAL_KEXEC_TARGET
, "kexec", "replace-irreversibly" },
188 [ACTION_RUNLEVEL2
] = { SPECIAL_MULTI_USER_TARGET
, NULL
, "isolate" },
189 [ACTION_RUNLEVEL3
] = { SPECIAL_MULTI_USER_TARGET
, NULL
, "isolate" },
190 [ACTION_RUNLEVEL4
] = { SPECIAL_MULTI_USER_TARGET
, NULL
, "isolate" },
191 [ACTION_RUNLEVEL5
] = { SPECIAL_GRAPHICAL_TARGET
, NULL
, "isolate" },
192 [ACTION_RESCUE
] = { SPECIAL_RESCUE_TARGET
, "rescue", "isolate" },
193 [ACTION_EMERGENCY
] = { SPECIAL_EMERGENCY_TARGET
, "emergency", "isolate" },
194 [ACTION_DEFAULT
] = { SPECIAL_DEFAULT_TARGET
, "default", "isolate" },
195 [ACTION_EXIT
] = { SPECIAL_EXIT_TARGET
, "exit", "replace-irreversibly" },
196 [ACTION_SUSPEND
] = { SPECIAL_SUSPEND_TARGET
, "suspend", "replace-irreversibly" },
197 [ACTION_HIBERNATE
] = { SPECIAL_HIBERNATE_TARGET
, "hibernate", "replace-irreversibly" },
198 [ACTION_HYBRID_SLEEP
] = { SPECIAL_HYBRID_SLEEP_TARGET
, "hybrid-sleep", "replace-irreversibly" },
199 [ACTION_SUSPEND_THEN_HIBERNATE
] = { SPECIAL_SUSPEND_THEN_HIBERNATE_TARGET
, "suspend-then-hibernate", "replace-irreversibly" },
202 enum action
verb_to_action(const char *verb
) {
205 for (i
= 0; i
< _ACTION_MAX
; i
++)
206 if (streq_ptr(action_table
[i
].verb
, verb
))
209 return _ACTION_INVALID
;
212 static const char** make_extra_args(const char *extra_args
[static 4]) {
217 if (arg_scope
!= UNIT_FILE_SYSTEM
)
218 extra_args
[n
++] = "--user";
220 if (arg_transport
== BUS_TRANSPORT_REMOTE
) {
221 extra_args
[n
++] = "-H";
222 extra_args
[n
++] = arg_host
;
223 } else if (arg_transport
== BUS_TRANSPORT_MACHINE
) {
224 extra_args
[n
++] = "-M";
225 extra_args
[n
++] = arg_host
;
227 assert(arg_transport
== BUS_TRANSPORT_LOCAL
);
229 extra_args
[n
] = NULL
;
233 int start_unit(int argc
, char *argv
[], void *userdata
) {
234 _cleanup_(bus_wait_for_units_freep
) BusWaitForUnits
*wu
= NULL
;
235 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*w
= NULL
;
236 const char *method
, *job_type
, *mode
, *one_name
, *suffix
= NULL
;
237 _cleanup_free_
char **stopped_units
= NULL
; /* Do not use _cleanup_strv_free_ */
238 _cleanup_strv_free_
char **names
= NULL
;
239 int r
, ret
= EXIT_SUCCESS
;
243 if (arg_wait
&& !STR_IN_SET(argv
[0], "start", "restart"))
244 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
245 "--wait may only be used with the 'start' or 'restart' commands.");
247 /* We cannot do sender tracking on the private bus, so we need the full one for RefUnit to implement
249 r
= acquire_bus(arg_wait
? BUS_FULL
: BUS_MANAGER
, &bus
);
253 ask_password_agent_open_maybe();
254 polkit_agent_open_maybe();
256 if (arg_action
== ACTION_SYSTEMCTL
) {
259 action
= verb_to_action(argv
[0]);
261 if (action
!= _ACTION_INVALID
) {
262 /* A command in style "systemctl reboot", "systemctl poweroff", … */
263 method
= "StartUnit";
265 mode
= action_table
[action
].mode
;
266 one_name
= action_table
[action
].target
;
268 if (streq(argv
[0], "isolate")) {
269 /* A "systemctl isolate <unit1> <unit2> …" command */
270 method
= "StartUnit";
275 /* A command in style of "systemctl start <unit1> <unit2> …", "sysemctl stop <unit1> <unit2> …" and so on */
276 method
= verb_to_method(argv
[0]);
277 job_type
= verb_to_job_type(argv
[0]);
283 /* A SysV legacy command such as "halt", "reboot", "poweroff", … */
284 assert(arg_action
>= 0 && arg_action
< _ACTION_MAX
);
285 assert(action_table
[arg_action
].target
);
286 assert(action_table
[arg_action
].mode
);
288 method
= "StartUnit";
290 mode
= action_table
[arg_action
].mode
;
291 one_name
= action_table
[arg_action
].target
;
295 names
= strv_new(one_name
);
301 r
= expand_unit_names(bus
, strv_skip(argv
, 1), suffix
, &names
, &expanded
);
303 return log_error_errno(r
, "Failed to expand names: %m");
305 if (!arg_all
&& expanded
&& streq(job_type
, "start") && !arg_quiet
) {
306 log_warning("Warning: %ssystemctl start called with a glob pattern.%s",
307 ansi_highlight_red(),
309 log_notice("Hint: unit globs expand to loaded units, so start will usually have no effect.\n"
310 " Passing --all will also load units which are pulled in by other units.\n"
311 " See systemctl(1) for more details.");
316 r
= bus_wait_for_jobs_new(bus
, &w
);
318 return log_error_errno(r
, "Could not watch jobs: %m");
322 r
= bus_call_method_async(bus
, NULL
, bus_systemd_mgr
, "Subscribe", NULL
, NULL
, NULL
);
324 return log_error_errno(r
, "Failed to enable subscription: %m");
326 r
= bus_wait_for_units_new(bus
, &wu
);
328 return log_error_errno(r
, "Failed to allocate unit watch context: %m");
331 STRV_FOREACH(name
, names
) {
332 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
334 r
= start_unit_one(bus
, method
, job_type
, *name
, mode
, &error
, w
, wu
);
335 if (ret
== EXIT_SUCCESS
&& r
< 0)
336 ret
= translate_bus_error_to_exit_status(r
, &error
);
338 if (r
>= 0 && streq(method
, "StopUnit")) {
339 r
= strv_push(&stopped_units
, *name
);
346 const char* extra_args
[4];
348 r
= bus_wait_for_jobs(w
, arg_quiet
, make_extra_args(extra_args
));
352 /* When stopping units, warn if they can still be triggered by
353 * another active unit (socket, path, timer) */
355 STRV_FOREACH(name
, stopped_units
)
356 (void) check_triggering_units(bus
, *name
);
360 r
= bus_wait_for_units_run(wu
);
362 return log_error_errno(r
, "Failed to wait for units: %m");
363 if (r
== BUS_WAIT_FAILURE
&& ret
== EXIT_SUCCESS
)