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