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