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