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