1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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 
verb_to_method(const char * verb)38 static const char *verb_to_method(const char *verb) {
39        for (size_t i = 0; i < ELEMENTSOF(unit_actions); i++)
40                 if (streq_ptr(unit_actions[i].verb, verb))
41                         return unit_actions[i].method;
42 
43        return "StartUnit";
44 }
45 
verb_to_job_type(const char * verb)46 static const char *verb_to_job_type(const char *verb) {
47        for (size_t i = 0; i < ELEMENTSOF(unit_actions); i++)
48                 if (streq_ptr(unit_actions[i].verb, verb))
49                         return unit_actions[i].job_type;
50 
51        return "start";
52 }
53 
start_unit_one(sd_bus * bus,const char * method,const char * job_type,const char * name,const char * mode,sd_bus_error * error,BusWaitForJobs * w,BusWaitForUnits * wu)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 == LOOKUP_SCOPE_SYSTEM ? "system" : "user",
172                           arg_scope == LOOKUP_SCOPE_SYSTEM ? "" : " --user",
173                           name[0] == '-' ? " --" : "",
174                           name);
175 
176         return r;
177 }
178 
enqueue_marked_jobs(sd_bus * bus,BusWaitForJobs * w)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 
202         if (w)
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                 }
209 
210         return 0;
211 }
212 
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 
verb_to_action(const char * verb)232 enum action verb_to_action(const char *verb) {
233         for (enum action i = 0; i < _ACTION_MAX; i++)
234                 if (streq_ptr(action_table[i].verb, verb))
235                         return i;
236 
237         return _ACTION_INVALID;
238 }
239 
make_extra_args(const char * extra_args[static4])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 != LOOKUP_SCOPE_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 
verb_start(int argc,char * argv[],void * userdata)261 int verb_start(int argc, char *argv[], void *userdata) {
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;
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";
301                         } else if (!arg_marked) {
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]);
305                                 mode = arg_job_mode();
306                         } else
307                                 method = job_type = mode = NULL;
308 
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();
327         } else if (!arg_marked) {
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 
360         if (arg_marked)
361                 ret = enqueue_marked_jobs(bus, w);
362 
363         else
364                 STRV_FOREACH(name, names) {
365                         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
366 
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                         }
376                 }
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 }
402