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