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