chiark / gitweb /
Prep v233: Add missing files from upstream and rename formats-util.[hc]
[elogind.git] / src / basic / exec-util.c
1 /***
2   This file is part of systemd.
3
4   Copyright 2010 Lennart Poettering
5
6   systemd is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   systemd is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <dirent.h>
21 #include <errno.h>
22 #include <sys/prctl.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25 #include <stdio.h>
26
27 #include "alloc-util.h"
28 #include "conf-files.h"
29 #include "env-util.h"
30 #include "exec-util.h"
31 #include "fd-util.h"
32 #include "fileio.h"
33 #include "hashmap.h"
34 #include "macro.h"
35 #include "process-util.h"
36 #include "set.h"
37 #include "signal-util.h"
38 #include "stat-util.h"
39 #include "string-util.h"
40 #include "strv.h"
41 #include "terminal-util.h"
42 #include "util.h"
43
44 /* Put this test here for a lack of better place */
45 assert_cc(EAGAIN == EWOULDBLOCK);
46
47 static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) {
48
49         pid_t _pid;
50
51         if (null_or_empty_path(path)) {
52                 log_debug("%s is empty (a mask).", path);
53                 return 0;
54         }
55
56         _pid = fork();
57         if (_pid < 0)
58                 return log_error_errno(errno, "Failed to fork: %m");
59         if (_pid == 0) {
60                 char *_argv[2];
61
62                 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
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         (void) reset_all_signal_handlers();
110         (void) reset_signal_mask();
111
112         assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
113
114         r = conf_files_list_strv(&paths, NULL, NULL, (const char* const*) directories);
115         if (r < 0)
116                 return r;
117
118         if (!callbacks) {
119                 pids = hashmap_new(NULL);
120                 if (!pids)
121                         return log_oom();
122         }
123
124         /* Abort execution of this process after the timout. We simply rely on SIGALRM as
125          * default action terminating the process, and turn on alarm(). */
126
127         if (timeout != USEC_INFINITY)
128                 alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC);
129
130         STRV_FOREACH(path, paths) {
131                 _cleanup_free_ char *t = NULL;
132                 _cleanup_close_ int fd = -1;
133                 pid_t pid;
134
135                 t = strdup(*path);
136                 if (!t)
137                         return log_oom();
138
139                 if (callbacks) {
140                         fd = open_serialization_fd(basename(*path));
141                         if (fd < 0)
142                                 return log_error_errno(fd, "Failed to open serialization file: %m");
143                 }
144
145                 r = do_spawn(t, argv, fd, &pid);
146                 if (r <= 0)
147                         continue;
148
149                 if (pids) {
150                         r = hashmap_put(pids, PID_TO_PTR(pid), t);
151                         if (r < 0)
152                                 return log_oom();
153                         t = NULL;
154                 } else {
155                         r = wait_for_terminate_and_warn(t, pid, true);
156                         if (r < 0)
157                                 continue;
158
159                         if (lseek(fd, 0, SEEK_SET) < 0)
160                                 return log_error_errno(errno, "Failed to seek on serialization fd: %m");
161
162                         r = callbacks[STDOUT_GENERATE](fd, callback_args[STDOUT_GENERATE]);
163                         fd = -1;
164                         if (r < 0)
165                                 return log_error_errno(r, "Failed to process output from %s: %m", *path);
166                 }
167         }
168
169         if (callbacks) {
170                 r = callbacks[STDOUT_COLLECT](output_fd, callback_args[STDOUT_COLLECT]);
171                 if (r < 0)
172                         return log_error_errno(r, "Callback two failed: %m");
173         }
174
175         while (!hashmap_isempty(pids)) {
176                 _cleanup_free_ char *t = NULL;
177                 pid_t pid;
178
179                 pid = PTR_TO_PID(hashmap_first_key(pids));
180                 assert(pid > 0);
181
182                 t = hashmap_remove(pids, PID_TO_PTR(pid));
183                 assert(t);
184
185                 wait_for_terminate_and_warn(t, pid, true);
186         }
187
188         return 0;
189 }
190
191 int execute_directories(
192                 const char* const* directories,
193                 usec_t timeout,
194                 gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
195                 void* const callback_args[_STDOUT_CONSUME_MAX],
196                 char *argv[]) {
197
198         pid_t executor_pid;
199         char *name;
200         char **dirs = (char**) directories;
201         _cleanup_close_ int fd = -1;
202         int r;
203
204         assert(!strv_isempty(dirs));
205
206         name = basename(dirs[0]);
207         assert(!isempty(name));
208
209         if (callbacks) {
210                 assert(callback_args);
211                 assert(callbacks[STDOUT_GENERATE]);
212                 assert(callbacks[STDOUT_COLLECT]);
213                 assert(callbacks[STDOUT_CONSUME]);
214
215                 fd = open_serialization_fd(name);
216                 if (fd < 0)
217                         return log_error_errno(fd, "Failed to open serialization file: %m");
218         }
219
220         /* Executes all binaries in the directories serially or in parallel and waits for
221          * them to finish. Optionally a timeout is applied. If a file with the same name
222          * exists in more than one directory, the earliest one wins. */
223
224         executor_pid = fork();
225         if (executor_pid < 0)
226                 return log_error_errno(errno, "Failed to fork: %m");
227
228         if (executor_pid == 0) {
229                 r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv);
230                 _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
231         }
232
233         r = wait_for_terminate_and_warn(name, executor_pid, true);
234         if (r < 0)
235                 return log_error_errno(r, "Execution failed: %m");
236         if (r > 0) {
237                 /* non-zero return code from child */
238                 log_error("Forker process failed.");
239                 return -EREMOTEIO;
240         }
241
242         if (!callbacks)
243                 return 0;
244
245         if (lseek(fd, 0, SEEK_SET) < 0)
246                 return log_error_errno(errno, "Failed to rewind serialization fd: %m");
247
248         r = callbacks[STDOUT_CONSUME](fd, callback_args[STDOUT_CONSUME]);
249         fd = -1;
250         if (r < 0)
251                 return log_error_errno(r, "Failed to parse returned data: %m");
252         return 0;
253 }
254
255 static int gather_environment_generate(int fd, void *arg) {
256         char ***env = arg, **x, **y;
257         _cleanup_fclose_ FILE *f = NULL;
258         _cleanup_strv_free_ char **new;
259         int r;
260
261         /* Read a series of VAR=value assignments from fd, use them to update the list of
262          * variables in env. Also update the exported environment.
263          *
264          * fd is always consumed, even on error.
265          */
266
267         assert(env);
268
269         f = fdopen(fd, "r");
270         if (!f) {
271                 safe_close(fd);
272                 return -errno;
273         }
274
275         r = load_env_file_pairs(f, NULL, NULL, &new);
276         if (r < 0)
277                 return r;
278
279         STRV_FOREACH_PAIR(x, y, new) {
280                 char *p;
281
282                 if (!env_name_is_valid(*x)) {
283                         log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x);
284                         continue;
285                 }
286
287                 p = strjoin(*x, "=", *y);
288                 if (!p)
289                         return -ENOMEM;
290
291                 r = strv_env_replace(env, p);
292                 if (r < 0)
293                         return r;
294
295                 if (setenv(*x, *y, true) < 0)
296                         return -errno;
297         }
298
299         return r;
300 }
301
302 static int gather_environment_collect(int fd, void *arg) {
303         char ***env = arg;
304         _cleanup_fclose_ FILE *f = NULL;
305         int r;
306
307         /* Write out a series of env=cescape(VAR=value) assignments to fd. */
308
309         assert(env);
310
311         f = fdopen(fd, "w");
312         if (!f) {
313                 safe_close(fd);
314                 return -errno;
315         }
316
317         r = serialize_environment(f, *env);
318         if (r < 0)
319                 return r;
320
321         if (ferror(f))
322                 return errno > 0 ? -errno : -EIO;
323
324         return 0;
325 }
326
327 static int gather_environment_consume(int fd, void *arg) {
328         char ***env = arg;
329         _cleanup_fclose_ FILE *f = NULL;
330         char line[LINE_MAX];
331         int r = 0, k;
332
333         /* Read a series of env=cescape(VAR=value) assignments from fd into env. */
334
335         assert(env);
336
337         f = fdopen(fd, "r");
338         if (!f) {
339                 safe_close(fd);
340                 return -errno;
341         }
342
343         FOREACH_LINE(line, f, return -EIO) {
344                 truncate_nl(line);
345
346                 k = deserialize_environment(env, line);
347                 if (k < 0)
348                         log_error_errno(k, "Invalid line \"%s\": %m", line);
349                 if (k < 0 && r == 0)
350                         r = k;
351         }
352
353         return r;
354 }
355
356 const gather_stdout_callback_t gather_environment[] = {
357         gather_environment_generate,
358         gather_environment_collect,
359         gather_environment_consume,
360 };