]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/systemctl/systemctl-start-unit.c
Merge pull request #17185 from yuwata/ethtool-update
[thirdparty/systemd.git] / src / systemctl / systemctl-start-unit.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
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) {
39 size_t i;
40
41 for (i = 0; i < ELEMENTSOF(unit_actions); i++)
42 if (streq_ptr(unit_actions[i].verb, verb))
43 return unit_actions[i].method;
44
45 return "StartUnit";
46 }
47
48 static const char *verb_to_job_type(const char *verb) {
49 size_t i;
50
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;
54
55 return "start";
56 }
57
58 static int start_unit_one(
59 sd_bus *bus,
60 const char *method, /* When using classic per-job bus methods */
61 const char *job_type, /* When using new-style EnqueueUnitJob() */
62 const char *name,
63 const char *mode,
64 sd_bus_error *error,
65 BusWaitForJobs *w,
66 BusWaitForUnits *wu) {
67
68 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
69 const char *path;
70 bool done = false;
71 int r;
72
73 assert(method);
74 assert(name);
75 assert(mode);
76 assert(error);
77
78 log_debug("%s dbus call org.freedesktop.systemd1.Manager %s(%s, %s)",
79 arg_dry_run ? "Would execute" : "Executing",
80 method, name, mode);
81
82 if (arg_dry_run)
83 return 0;
84
85 if (arg_show_transaction) {
86 _cleanup_(sd_bus_error_free) sd_bus_error enqueue_error = SD_BUS_ERROR_NULL;
87
88 /* Use the new, fancy EnqueueUnitJob() API if the user wants us to print the transaction */
89 r = bus_call_method(
90 bus,
91 bus_systemd_mgr,
92 "EnqueueUnitJob",
93 &enqueue_error,
94 &reply,
95 "sss",
96 name, job_type, mode);
97 if (r < 0) {
98 if (!sd_bus_error_has_name(&enqueue_error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
99 (void) sd_bus_error_move(error, &enqueue_error);
100 goto fail;
101 }
102
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.");
105 } else {
106 const char *u, *jt;
107 uint32_t id;
108
109 r = sd_bus_message_read(reply, "uosos", &id, &path, &u, NULL, &jt);
110 if (r < 0)
111 return bus_log_parse_error(r);
112
113 log_info("Enqueued anchor job %" PRIu32 " %s/%s.", id, u, jt);
114
115 r = sd_bus_message_enter_container(reply, 'a', "(uosos)");
116 if (r < 0)
117 return bus_log_parse_error(r);
118 for (;;) {
119 r = sd_bus_message_read(reply, "(uosos)", &id, NULL, &u, NULL, &jt);
120 if (r < 0)
121 return bus_log_parse_error(r);
122 if (r == 0)
123 break;
124
125 log_info("Enqueued auxiliary job %" PRIu32 " %s/%s.", id, u, jt);
126 }
127
128 r = sd_bus_message_exit_container(reply);
129 if (r < 0)
130 return bus_log_parse_error(r);
131
132 done = true;
133 }
134 }
135
136 if (!done) {
137 r = bus_call_method(bus, bus_systemd_mgr, method, error, &reply, "ss", name, mode);
138 if (r < 0)
139 goto fail;
140
141 r = sd_bus_message_read(reply, "o", &path);
142 if (r < 0)
143 return bus_log_parse_error(r);
144 }
145
146 if (need_daemon_reload(bus, name) > 0)
147 warn_unit_file_changed(name);
148
149 if (w) {
150 log_debug("Adding %s to the set", path);
151 r = bus_wait_for_jobs_add(w, path);
152 if (r < 0)
153 return log_error_errno(r, "Failed to watch job for %s: %m", name);
154 }
155
156 if (wu) {
157 r = bus_wait_for_units_add_unit(wu, name, BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB, NULL, NULL);
158 if (r < 0)
159 return log_error_errno(r, "Failed to watch unit %s: %m", name);
160 }
161
162 return 0;
163
164 fail:
165 /* There's always a fallback possible for legacy actions. */
166 if (arg_action != ACTION_SYSTEMCTL)
167 return r;
168
169 log_error_errno(r, "Failed to %s %s: %s", job_type, name, bus_error_message(error, r));
170
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] == '-' ? " --" : "",
178 name);
179
180 return r;
181 }
182
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" },
200 };
201
202 enum action verb_to_action(const char *verb) {
203 enum action i;
204
205 for (i = 0; i < _ACTION_MAX; i++)
206 if (streq_ptr(action_table[i].verb, verb))
207 return i;
208
209 return _ACTION_INVALID;
210 }
211
212 static const char** make_extra_args(const char *extra_args[static 4]) {
213 size_t n = 0;
214
215 assert(extra_args);
216
217 if (arg_scope != UNIT_FILE_SYSTEM)
218 extra_args[n++] = "--user";
219
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;
226 } else
227 assert(arg_transport == BUS_TRANSPORT_LOCAL);
228
229 extra_args[n] = NULL;
230 return extra_args;
231 }
232
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;
240 sd_bus *bus;
241 char **name;
242
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.");
246
247 /* We cannot do sender tracking on the private bus, so we need the full one for RefUnit to implement
248 * --wait */
249 r = acquire_bus(arg_wait ? BUS_FULL : BUS_MANAGER, &bus);
250 if (r < 0)
251 return r;
252
253 ask_password_agent_open_maybe();
254 polkit_agent_open_maybe();
255
256 if (arg_action == ACTION_SYSTEMCTL) {
257 enum action action;
258
259 action = verb_to_action(argv[0]);
260
261 if (action != _ACTION_INVALID) {
262 /* A command in style "systemctl reboot", "systemctl poweroff", … */
263 method = "StartUnit";
264 job_type = "start";
265 mode = action_table[action].mode;
266 one_name = action_table[action].target;
267 } else {
268 if (streq(argv[0], "isolate")) {
269 /* A "systemctl isolate <unit1> <unit2> …" command */
270 method = "StartUnit";
271 job_type = "start";
272 mode = "isolate";
273 suffix = ".target";
274 } else {
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]);
278 mode = arg_job_mode;
279 }
280 one_name = NULL;
281 }
282 } else {
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);
287
288 method = "StartUnit";
289 job_type = "start";
290 mode = action_table[arg_action].mode;
291 one_name = action_table[arg_action].target;
292 }
293
294 if (one_name) {
295 names = strv_new(one_name);
296 if (!names)
297 return log_oom();
298 } else {
299 bool expanded;
300
301 r = expand_unit_names(bus, strv_skip(argv, 1), suffix, &names, &expanded);
302 if (r < 0)
303 return log_error_errno(r, "Failed to expand names: %m");
304
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(),
308 ansi_normal());
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.");
312 }
313 }
314
315 if (!arg_no_block) {
316 r = bus_wait_for_jobs_new(bus, &w);
317 if (r < 0)
318 return log_error_errno(r, "Could not watch jobs: %m");
319 }
320
321 if (arg_wait) {
322 r = bus_call_method_async(bus, NULL, bus_systemd_mgr, "Subscribe", NULL, NULL, NULL);
323 if (r < 0)
324 return log_error_errno(r, "Failed to enable subscription: %m");
325
326 r = bus_wait_for_units_new(bus, &wu);
327 if (r < 0)
328 return log_error_errno(r, "Failed to allocate unit watch context: %m");
329 }
330
331 STRV_FOREACH(name, names) {
332 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
333
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);
337
338 if (r >= 0 && streq(method, "StopUnit")) {
339 r = strv_push(&stopped_units, *name);
340 if (r < 0)
341 return log_oom();
342 }
343 }
344
345 if (!arg_no_block) {
346 const char* extra_args[4];
347
348 r = bus_wait_for_jobs(w, arg_quiet, make_extra_args(extra_args));
349 if (r < 0)
350 return r;
351
352 /* When stopping units, warn if they can still be triggered by
353 * another active unit (socket, path, timer) */
354 if (!arg_quiet)
355 STRV_FOREACH(name, stopped_units)
356 (void) check_triggering_units(bus, *name);
357 }
358
359 if (arg_wait) {
360 r = bus_wait_for_units_run(wu);
361 if (r < 0)
362 return log_error_errno(r, "Failed to wait for units: %m");
363 if (r == BUS_WAIT_FAILURE && ret == EXIT_SUCCESS)
364 ret = EXIT_FAILURE;
365 }
366
367 return ret;
368 }