]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/exec-util.c
fstab-generator: fix check for /sys
[thirdparty/systemd.git] / src / shared / exec-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <dirent.h>
4 #include <errno.h>
5 #include <sys/prctl.h>
6 #include <sys/types.h>
7 #include <unistd.h>
8 #include <stdio.h>
9
10 #include "alloc-util.h"
11 #include "conf-files.h"
12 #include "env-file.h"
13 #include "env-util.h"
14 #include "exec-util.h"
15 #include "fd-util.h"
16 #include "fileio.h"
17 #include "hashmap.h"
18 #include "macro.h"
19 #include "process-util.h"
20 #include "rlimit-util.h"
21 #include "serialize.h"
22 #include "set.h"
23 #include "signal-util.h"
24 #include "stat-util.h"
25 #include "string-util.h"
26 #include "strv.h"
27 #include "terminal-util.h"
28 #include "tmpfile-util.h"
29 #include "util.h"
30
31 /* Put this test here for a lack of better place */
32 assert_cc(EAGAIN == EWOULDBLOCK);
33
34 static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) {
35
36 pid_t _pid;
37 int r;
38
39 if (null_or_empty_path(path)) {
40 log_debug("%s is empty (a mask).", path);
41 return 0;
42 }
43
44 r = safe_fork("(direxec)", FORK_DEATHSIG|FORK_LOG, &_pid);
45 if (r < 0)
46 return r;
47 if (r == 0) {
48 char *_argv[2];
49
50 if (stdout_fd >= 0) {
51 r = rearrange_stdio(STDIN_FILENO, stdout_fd, STDERR_FILENO);
52 if (r < 0)
53 _exit(EXIT_FAILURE);
54 }
55
56 (void) rlimit_nofile_safe();
57
58 if (!argv) {
59 _argv[0] = (char*) path;
60 _argv[1] = NULL;
61 argv = _argv;
62 } else
63 argv[0] = (char*) path;
64
65 execv(path, argv);
66 log_error_errno(errno, "Failed to execute %s: %m", path);
67 _exit(EXIT_FAILURE);
68 }
69
70 *pid = _pid;
71 return 1;
72 }
73
74 static int do_execute(
75 char **directories,
76 usec_t timeout,
77 gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
78 void* const callback_args[_STDOUT_CONSUME_MAX],
79 int output_fd,
80 char *argv[],
81 char *envp[]) {
82
83 _cleanup_hashmap_free_free_ Hashmap *pids = NULL;
84 _cleanup_strv_free_ char **paths = NULL;
85 char **path, **e;
86 int r;
87
88 /* We fork this all off from a child process so that we can somewhat cleanly make
89 * use of SIGALRM to set a time limit.
90 *
91 * If callbacks is nonnull, execution is serial. Otherwise, we default to parallel.
92 */
93
94 r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE|CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char* const*) directories);
95 if (r < 0)
96 return log_error_errno(r, "Failed to enumerate executables: %m");
97
98 if (!callbacks) {
99 pids = hashmap_new(NULL);
100 if (!pids)
101 return log_oom();
102 }
103
104 /* Abort execution of this process after the timout. We simply rely on SIGALRM as
105 * default action terminating the process, and turn on alarm(). */
106
107 if (timeout != USEC_INFINITY)
108 alarm(DIV_ROUND_UP(timeout, USEC_PER_SEC));
109
110 STRV_FOREACH(e, envp)
111 if (putenv(*e) != 0)
112 return log_error_errno(errno, "Failed to set environment variable: %m");
113
114 STRV_FOREACH(path, paths) {
115 _cleanup_free_ char *t = NULL;
116 _cleanup_close_ int fd = -1;
117 pid_t pid;
118
119 t = strdup(*path);
120 if (!t)
121 return log_oom();
122
123 if (callbacks) {
124 fd = open_serialization_fd(basename(*path));
125 if (fd < 0)
126 return log_error_errno(fd, "Failed to open serialization file: %m");
127 }
128
129 r = do_spawn(t, argv, fd, &pid);
130 if (r <= 0)
131 continue;
132
133 if (pids) {
134 r = hashmap_put(pids, PID_TO_PTR(pid), t);
135 if (r < 0)
136 return log_oom();
137 t = NULL;
138 } else {
139 r = wait_for_terminate_and_check(t, pid, WAIT_LOG);
140 if (r < 0)
141 continue;
142
143 if (lseek(fd, 0, SEEK_SET) < 0)
144 return log_error_errno(errno, "Failed to seek on serialization fd: %m");
145
146 r = callbacks[STDOUT_GENERATE](fd, callback_args[STDOUT_GENERATE]);
147 fd = -1;
148 if (r < 0)
149 return log_error_errno(r, "Failed to process output from %s: %m", *path);
150 }
151 }
152
153 if (callbacks) {
154 r = callbacks[STDOUT_COLLECT](output_fd, callback_args[STDOUT_COLLECT]);
155 if (r < 0)
156 return log_error_errno(r, "Callback two failed: %m");
157 }
158
159 while (!hashmap_isempty(pids)) {
160 _cleanup_free_ char *t = NULL;
161 pid_t pid;
162
163 pid = PTR_TO_PID(hashmap_first_key(pids));
164 assert(pid > 0);
165
166 t = hashmap_remove(pids, PID_TO_PTR(pid));
167 assert(t);
168
169 (void) wait_for_terminate_and_check(t, pid, WAIT_LOG);
170 }
171
172 return 0;
173 }
174
175 int execute_directories(
176 const char* const* directories,
177 usec_t timeout,
178 gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
179 void* const callback_args[_STDOUT_CONSUME_MAX],
180 char *argv[],
181 char *envp[]) {
182
183 char **dirs = (char**) directories;
184 _cleanup_close_ int fd = -1;
185 char *name;
186 int r;
187
188 assert(!strv_isempty(dirs));
189
190 name = basename(dirs[0]);
191 assert(!isempty(name));
192
193 if (callbacks) {
194 assert(callback_args);
195 assert(callbacks[STDOUT_GENERATE]);
196 assert(callbacks[STDOUT_COLLECT]);
197 assert(callbacks[STDOUT_CONSUME]);
198
199 fd = open_serialization_fd(name);
200 if (fd < 0)
201 return log_error_errno(fd, "Failed to open serialization file: %m");
202 }
203
204 /* Executes all binaries in the directories serially or in parallel and waits for
205 * them to finish. Optionally a timeout is applied. If a file with the same name
206 * exists in more than one directory, the earliest one wins. */
207
208 r = safe_fork("(sd-executor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL);
209 if (r < 0)
210 return r;
211 if (r == 0) {
212 r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv, envp);
213 _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
214 }
215
216 if (!callbacks)
217 return 0;
218
219 if (lseek(fd, 0, SEEK_SET) < 0)
220 return log_error_errno(errno, "Failed to rewind serialization fd: %m");
221
222 r = callbacks[STDOUT_CONSUME](fd, callback_args[STDOUT_CONSUME]);
223 fd = -1;
224 if (r < 0)
225 return log_error_errno(r, "Failed to parse returned data: %m");
226 return 0;
227 }
228
229 static int gather_environment_generate(int fd, void *arg) {
230 char ***env = arg, **x, **y;
231 _cleanup_fclose_ FILE *f = NULL;
232 _cleanup_strv_free_ char **new = NULL;
233 int r;
234
235 /* Read a series of VAR=value assignments from fd, use them to update the list of
236 * variables in env. Also update the exported environment.
237 *
238 * fd is always consumed, even on error.
239 */
240
241 assert(env);
242
243 f = fdopen(fd, "r");
244 if (!f) {
245 safe_close(fd);
246 return -errno;
247 }
248
249 r = load_env_file_pairs(f, NULL, &new);
250 if (r < 0)
251 return r;
252
253 STRV_FOREACH_PAIR(x, y, new) {
254 char *p;
255
256 if (!env_name_is_valid(*x)) {
257 log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x);
258 continue;
259 }
260
261 p = strjoin(*x, "=", *y);
262 if (!p)
263 return -ENOMEM;
264
265 r = strv_env_replace(env, p);
266 if (r < 0)
267 return r;
268
269 if (setenv(*x, *y, true) < 0)
270 return -errno;
271 }
272
273 return r;
274 }
275
276 static int gather_environment_collect(int fd, void *arg) {
277 _cleanup_fclose_ FILE *f = NULL;
278 char ***env = arg;
279 int r;
280
281 /* Write out a series of env=cescape(VAR=value) assignments to fd. */
282
283 assert(env);
284
285 f = fdopen(fd, "w");
286 if (!f) {
287 safe_close(fd);
288 return -errno;
289 }
290
291 r = serialize_strv(f, "env", *env);
292 if (r < 0)
293 return r;
294
295 r = fflush_and_check(f);
296 if (r < 0)
297 return r;
298
299 return 0;
300 }
301
302 static int gather_environment_consume(int fd, void *arg) {
303 _cleanup_fclose_ FILE *f = NULL;
304 char ***env = arg;
305 int r = 0;
306
307 /* Read a series of env=cescape(VAR=value) assignments from fd into env. */
308
309 assert(env);
310
311 f = fdopen(fd, "re");
312 if (!f) {
313 safe_close(fd);
314 return -errno;
315 }
316
317 for (;;) {
318 _cleanup_free_ char *line = NULL;
319 const char *v;
320 int k;
321
322 k = read_line(f, LONG_LINE_MAX, &line);
323 if (k < 0)
324 return k;
325 if (k == 0)
326 break;
327
328 v = startswith(line, "env=");
329 if (!v) {
330 log_debug("Serialization line \"%s\" unexpectedly didn't start with \"env=\".", line);
331 if (r == 0)
332 r = -EINVAL;
333
334 continue;
335 }
336
337 k = deserialize_environment(v, env);
338 if (k < 0) {
339 log_debug_errno(k, "Invalid serialization line \"%s\": %m", line);
340
341 if (r == 0)
342 r = k;
343 }
344 }
345
346 return r;
347 }
348
349 const gather_stdout_callback_t gather_environment[] = {
350 gather_environment_generate,
351 gather_environment_collect,
352 gather_environment_consume,
353 };