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