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