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