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