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