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