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