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