1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <fcntl.h>
4 #include <sys/stat.h>
5 #include <sys/types.h>
6 
7 #include "env-util.h"
8 #include "fd-util.h"
9 #include "initreq.h"
10 #include "install.h"
11 #include "io-util.h"
12 #include "parse-util.h"
13 #include "path-util.h"
14 #include "process-util.h"
15 #include "strv.h"
16 #include "systemctl-sysv-compat.h"
17 #include "systemctl.h"
18 
talk_initctl(char rl)19 int talk_initctl(char rl) {
20 #if HAVE_SYSV_COMPAT
21         _cleanup_close_ int fd = -1;
22         const char *path;
23         int r;
24 
25         /* Try to switch to the specified SysV runlevel. Returns == 0 if the operation does not apply on this
26          * system, and > 0 on success. */
27 
28         if (rl == 0)
29                 return 0;
30 
31         FOREACH_STRING(_path, "/run/initctl", "/dev/initctl") {
32                 path = _path;
33 
34                 fd = open(path, O_WRONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
35                 if (fd < 0 && errno != ENOENT)
36                         return log_error_errno(errno, "Failed to open %s: %m", path);
37                 if (fd >= 0)
38                         break;
39         }
40         if (fd < 0)
41                 return 0;
42 
43         struct init_request request = {
44                 .magic = INIT_MAGIC,
45                 .sleeptime = 0,
46                 .cmd = INIT_CMD_RUNLVL,
47                 .runlevel = rl,
48         };
49 
50         r = loop_write(fd, &request, sizeof(request), false);
51         if (r < 0)
52                 return log_error_errno(r, "Failed to write to %s: %m", path);
53 
54         return 1;
55 #else
56         return -EOPNOTSUPP;
57 #endif
58 }
59 
parse_shutdown_time_spec(const char * t,usec_t * ret)60 int parse_shutdown_time_spec(const char *t, usec_t *ret) {
61         assert(t);
62         assert(ret);
63 
64         if (streq(t, "now"))
65                 *ret = 0;
66         else if (!strchr(t, ':')) {
67                 uint64_t u;
68 
69                 if (safe_atou64(t, &u) < 0)
70                         return -EINVAL;
71 
72                 *ret = now(CLOCK_REALTIME) + USEC_PER_MINUTE * u;
73         } else {
74                 char *e = NULL;
75                 long hour, minute;
76                 struct tm tm = {};
77                 time_t s;
78                 usec_t n;
79 
80                 errno = 0;
81                 hour = strtol(t, &e, 10);
82                 if (errno > 0 || *e != ':' || hour < 0 || hour > 23)
83                         return -EINVAL;
84 
85                 minute = strtol(e+1, &e, 10);
86                 if (errno > 0 || *e != 0 || minute < 0 || minute > 59)
87                         return -EINVAL;
88 
89                 n = now(CLOCK_REALTIME);
90                 s = (time_t) (n / USEC_PER_SEC);
91 
92                 assert_se(localtime_r(&s, &tm));
93 
94                 tm.tm_hour = (int) hour;
95                 tm.tm_min = (int) minute;
96                 tm.tm_sec = 0;
97 
98                 s = mktime(&tm);
99                 assert(s >= 0);
100 
101                 *ret = (usec_t) s * USEC_PER_SEC;
102 
103                 while (*ret <= n)
104                         *ret += USEC_PER_DAY;
105         }
106 
107         return 0;
108 }
109 
enable_sysv_units(const char * verb,char ** args)110 int enable_sysv_units(const char *verb, char **args) {
111         int r = 0;
112 
113 #if HAVE_SYSV_COMPAT
114         _cleanup_(lookup_paths_free) LookupPaths paths = {};
115         unsigned f = 0;
116 
117         /* Processes all SysV units, and reshuffles the array so that afterwards only the native units remain */
118 
119         if (arg_scope != LOOKUP_SCOPE_SYSTEM)
120                 return 0;
121 
122         if (getenv_bool("SYSTEMCTL_SKIP_SYSV") > 0)
123                 return 0;
124 
125         if (!STR_IN_SET(verb,
126                         "enable",
127                         "disable",
128                         "is-enabled"))
129                 return 0;
130 
131         r = lookup_paths_init_or_warn(&paths, arg_scope, LOOKUP_PATHS_EXCLUDE_GENERATED, arg_root);
132         if (r < 0)
133                 return r;
134 
135         r = 0;
136         while (args[f]) {
137 
138                 const char *argv[] = {
139                         ROOTLIBEXECDIR "/systemd-sysv-install",
140                         NULL, /* --root= */
141                         NULL, /* verb */
142                         NULL, /* service */
143                         NULL,
144                 };
145 
146                 _cleanup_free_ char *p = NULL, *q = NULL, *l = NULL, *v = NULL;
147                 bool found_native = false, found_sysv;
148                 const char *name;
149                 unsigned c = 1;
150                 pid_t pid;
151                 int j;
152 
153                 name = args[f++];
154 
155                 if (!endswith(name, ".service"))
156                         continue;
157 
158                 if (path_is_absolute(name))
159                         continue;
160 
161                 j = unit_file_exists(arg_scope, &paths, name);
162                 if (j < 0 && !IN_SET(j, -ELOOP, -ERFKILL, -EADDRNOTAVAIL))
163                         return log_error_errno(j, "Failed to look up unit file state: %m");
164                 found_native = j != 0;
165 
166                 /* If we have both a native unit and a SysV script, enable/disable them both (below); for
167                  * is-enabled, prefer the native unit */
168                 if (found_native && streq(verb, "is-enabled"))
169                         continue;
170 
171                 p = path_join(arg_root, SYSTEM_SYSVINIT_PATH, name);
172                 if (!p)
173                         return log_oom();
174 
175                 p[strlen(p) - STRLEN(".service")] = 0;
176                 found_sysv = access(p, F_OK) >= 0;
177                 if (!found_sysv)
178                         continue;
179 
180                 if (!arg_quiet) {
181                         if (found_native)
182                                 log_info("Synchronizing state of %s with SysV service script with %s.", name, argv[0]);
183                         else
184                                 log_info("%s is not a native service, redirecting to systemd-sysv-install.", name);
185                 }
186 
187                 if (!isempty(arg_root)) {
188                         q = strjoin("--root=", arg_root);
189                         if (!q)
190                                 return log_oom();
191 
192                         argv[c++] = q;
193                 }
194 
195                 /* Let's copy the verb, since it's still pointing directly into the original argv[] array we
196                  * got passed, but safe_fork() is likely going to rewrite that for the new child */
197                 v = strdup(verb);
198                 if (!v)
199                         return log_oom();
200 
201                 argv[c++] = v;
202                 argv[c++] = basename(p);
203                 argv[c] = NULL;
204 
205                 l = strv_join((char**)argv, " ");
206                 if (!l)
207                         return log_oom();
208 
209                 if (!arg_quiet)
210                         log_info("Executing: %s", l);
211 
212                 j = safe_fork("(sysv-install)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
213                 if (j < 0)
214                         return j;
215                 if (j == 0) {
216                         /* Child */
217                         execv(argv[0], (char**) argv);
218                         log_error_errno(errno, "Failed to execute %s: %m", argv[0]);
219                         _exit(EXIT_FAILURE);
220                 }
221 
222                 j = wait_for_terminate_and_check("sysv-install", pid, WAIT_LOG_ABNORMAL);
223                 if (j < 0)
224                         return j;
225                 if (streq(verb, "is-enabled")) {
226                         if (j == EXIT_SUCCESS) {
227                                 if (!arg_quiet)
228                                         puts("enabled");
229                                 r = 1;
230                         } else {
231                                 if (!arg_quiet)
232                                         puts("disabled");
233                         }
234 
235                 } else if (j != EXIT_SUCCESS)
236                         return -EBADE; /* We don't warn here, under the assumption the script already showed an explanation */
237 
238                 if (found_native)
239                         continue;
240 
241                 /* Remove this entry, so that we don't try enabling it as native unit */
242                 assert(f > 0);
243                 f--;
244                 assert(args[f] == name);
245                 strv_remove(args + f, name);
246         }
247 
248 #endif
249         return r;
250 }
251 
action_to_runlevel(void)252 int action_to_runlevel(void) {
253 #if HAVE_SYSV_COMPAT
254         static const char table[_ACTION_MAX] = {
255                 [ACTION_HALT] =      '0',
256                 [ACTION_POWEROFF] =  '0',
257                 [ACTION_REBOOT] =    '6',
258                 [ACTION_RUNLEVEL2] = '2',
259                 [ACTION_RUNLEVEL3] = '3',
260                 [ACTION_RUNLEVEL4] = '4',
261                 [ACTION_RUNLEVEL5] = '5',
262                 [ACTION_RESCUE] =    '1'
263         };
264 
265         assert(arg_action >= 0 && arg_action < _ACTION_MAX);
266         return table[arg_action];
267 #else
268         return -EOPNOTSUPP;
269 #endif
270 }
271