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