]>
Commit | Line | Data |
---|---|---|
89711996 ZJS |
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> | |
c6e47247 | 25 | #include <stdio.h> |
89711996 ZJS |
26 | |
27 | #include "alloc-util.h" | |
2e4cfe65 | 28 | #include "conf-files.h" |
c6e47247 | 29 | #include "env-util.h" |
89711996 | 30 | #include "exec-util.h" |
c6e47247 ZJS |
31 | #include "fd-util.h" |
32 | #include "fileio.h" | |
89711996 ZJS |
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" | |
c6e47247 | 41 | #include "terminal-util.h" |
89711996 ZJS |
42 | #include "util.h" |
43 | ||
44 | /* Put this test here for a lack of better place */ | |
45 | assert_cc(EAGAIN == EWOULDBLOCK); | |
46 | ||
c6e47247 ZJS |
47 | static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) { |
48 | ||
cf55fc18 ZJS |
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 | ||
c6e47247 ZJS |
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 | ||
cf55fc18 ZJS |
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 | ||
c6e47247 ZJS |
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 | ||
89711996 | 98 | _cleanup_hashmap_free_free_ Hashmap *pids = NULL; |
2e4cfe65 ZJS |
99 | _cleanup_strv_free_ char **paths = NULL; |
100 | char **path; | |
101 | int r; | |
89711996 | 102 | |
c6e47247 ZJS |
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 | */ | |
89711996 ZJS |
108 | |
109 | (void) reset_all_signal_handlers(); | |
110 | (void) reset_signal_mask(); | |
111 | ||
112 | assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); | |
113 | ||
b5084605 | 114 | r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE, (const char* const*) directories); |
2e4cfe65 ZJS |
115 | if (r < 0) |
116 | return r; | |
117 | ||
c6e47247 ZJS |
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); | |
89711996 | 129 | |
2e4cfe65 ZJS |
130 | STRV_FOREACH(path, paths) { |
131 | _cleanup_free_ char *t = NULL; | |
c6e47247 | 132 | _cleanup_close_ int fd = -1; |
2e4cfe65 | 133 | pid_t pid; |
89711996 | 134 | |
2e4cfe65 ZJS |
135 | t = strdup(*path); |
136 | if (!t) | |
137 | return log_oom(); | |
89711996 | 138 | |
c6e47247 ZJS |
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); | |
2e4cfe65 ZJS |
146 | if (r <= 0) |
147 | continue; | |
89711996 | 148 | |
c6e47247 ZJS |
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 | } | |
89711996 ZJS |
167 | } |
168 | ||
c6e47247 ZJS |
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 | } | |
89711996 ZJS |
174 | |
175 | while (!hashmap_isempty(pids)) { | |
2e4cfe65 | 176 | _cleanup_free_ char *t = NULL; |
89711996 ZJS |
177 | pid_t pid; |
178 | ||
179 | pid = PTR_TO_PID(hashmap_first_key(pids)); | |
180 | assert(pid > 0); | |
181 | ||
2e4cfe65 ZJS |
182 | t = hashmap_remove(pids, PID_TO_PTR(pid)); |
183 | assert(t); | |
89711996 | 184 | |
2e4cfe65 | 185 | wait_for_terminate_and_warn(t, pid, true); |
89711996 ZJS |
186 | } |
187 | ||
188 | return 0; | |
189 | } | |
190 | ||
c6e47247 ZJS |
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 | ||
89711996 | 198 | pid_t executor_pid; |
89711996 ZJS |
199 | char *name; |
200 | char **dirs = (char**) directories; | |
c6e47247 ZJS |
201 | _cleanup_close_ int fd = -1; |
202 | int r; | |
89711996 ZJS |
203 | |
204 | assert(!strv_isempty(dirs)); | |
205 | ||
206 | name = basename(dirs[0]); | |
207 | assert(!isempty(name)); | |
208 | ||
c6e47247 ZJS |
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. */ | |
89711996 ZJS |
223 | |
224 | executor_pid = fork(); | |
c6e47247 ZJS |
225 | if (executor_pid < 0) |
226 | return log_error_errno(errno, "Failed to fork: %m"); | |
89711996 | 227 | |
c6e47247 ZJS |
228 | if (executor_pid == 0) { |
229 | r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv); | |
89711996 ZJS |
230 | _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); |
231 | } | |
232 | ||
c6e47247 ZJS |
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; | |
89711996 | 253 | } |
3303d1b2 ZJS |
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 | ||
184d1904 ZJS |
282 | if (!env_name_is_valid(*x)) { |
283 | log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x); | |
284 | continue; | |
285 | } | |
286 | ||
3303d1b2 ZJS |
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 | }; |