Skip to content

Commit d4fb9d8

Browse files
committed
cbits/posix: Introduce posix_spawn support
This is a complete rewrite `posix/runProcess.c`. There are a few goals of this rewrite: * fix a long-standing and serious bug in the `execvpe` fallback path, which uses non-reentrant functions after `fork`ing. This is of course undefined behavior and has been causing failures under Darwin's Rosetta binary translation engine (see GHC #19994). * eliminate code duplication in the `fork/exec` implementation. * introduce support for `posix_spawn`, allowing us to unload a significant amount of complexity in some cases. This is particularly desireable as the cost of `fork` has increased considerably in some cases on recent Darwin releases (namely when `MAP_JIT` mappings are used; see [1]) While `posix_spawn` is often a win, there are unfortunately several cases where it cannot be used: * `posix_spawn_file_actions_addchdir_np` is broken on Darwin * `POSIX_SPAWN_SETSID` is only supported on mac 10.15 and later, but doesn't return a proper error code when not supported * the originally-specified semantics of `posix_spawn_file_actions_adddup2` are unsafe and have been amended (see [3]) but not all implementations have caught up (musl has [4], glibc did later [5], Darwin seemingly hasn't) there appears to be no support at all for setuid and setgid * `spawn` is significantly slower than fork on some Darwin releases (see [6]) To address this we first try using `posix_spawn`, falling back on `fork/exec` if we encounter a case which the former cannot handle. [1]: libuv/libuv#3064 [2]: https://www.austingroupbugs.net/view.php?id=411 [3]: rust-lang/rust#80537 [4]: https://git.musl-libc.org/cgit/musl/commit/?id=6fc6ca1a323bc0b6b9e9cdc8fa72221ae18fe206 [5]: https://sourceware.org/bugzilla/show_bug.cgi?id=23640 [6]: https://discuss.python.org/t/multiprocessing-spawn-default-on-macos-since-python-3-8-is-slower-than-fork-method/5910/4
1 parent 60c8894 commit d4fb9d8

File tree

11 files changed

+837
-404
lines changed

11 files changed

+837
-404
lines changed

cbits/posix/common.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#pragma once
2+
3+
#include "runProcess.h"
4+
5+
enum std_handle_behavior {
6+
// Close the handle
7+
STD_HANDLE_CLOSE,
8+
// dup2 the specified fd to standard handle
9+
STD_HANDLE_USE_FD,
10+
// dup2 the appropriate end of the given pipe to the standard handle and
11+
// close the other end.
12+
STD_HANDLE_USE_PIPE
13+
};
14+
15+
struct std_handle {
16+
enum std_handle_behavior behavior;
17+
union {
18+
int use_fd;
19+
struct {
20+
int parent_end, child_end;
21+
} use_pipe;
22+
};
23+
};
24+
25+
int get_max_fd(void);
26+
27+
// defined in find_executable.c
28+
#if !defined(HAVE_execvpe)
29+
char *find_executable(char *filename);
30+
#endif
31+
32+
// defined in fork_exec.c
33+
ProcHandle
34+
do_spawn_fork (char *const args[],
35+
char *workingDirectory, char **environment,
36+
struct std_handle *stdInHdl,
37+
struct std_handle *stdOutHdl,
38+
struct std_handle *stdErrHdl,
39+
gid_t *childGroup, uid_t *childUser,
40+
int flags,
41+
char **failed_doing);
42+
43+
// defined in posix_spawn.c
44+
ProcHandle
45+
do_spawn_posix (char *const args[],
46+
char *workingDirectory, char **environment,
47+
struct std_handle *stdInHdl,
48+
struct std_handle *stdOutHdl,
49+
struct std_handle *stdErrHdl,
50+
gid_t *childGroup, uid_t *childUser,
51+
int flags,
52+
char **failed_doing);

cbits/posix/find_executable.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* ----------------------------------------------------------------------------
2+
* search path search logic
3+
* (c) Ben Gamari 2021
4+
*/
5+
6+
#include <string.h>
7+
#include <unistd.h>
8+
#include <stdlib.h>
9+
#include <stdio.h>
10+
#include <stdbool.h>
11+
12+
#include "common.h"
13+
14+
// the below is only necessary when we don't have execvpe.
15+
#if !defined(HAVE_execvpe)
16+
17+
/* Return true if the given file exists and is an executable. */
18+
static bool is_executable(const char *path) {
19+
return access(path, X_OK) == 0;
20+
}
21+
22+
/* Find an executable with the given filename in the given search path. The
23+
* result must be freed by the caller. Returns NULL if a matching file is not
24+
* found.
25+
*/
26+
static char *find_in_search_path(char *search_path, const char *filename) {
27+
const int filename_len = strlen(filename);
28+
char *tokbuf;
29+
char *path = strtok_r(search_path, ":", &tokbuf);
30+
while (path != NULL) {
31+
const int tmp_len = filename_len + 1 + strlen(path) + 1;
32+
char *tmp = malloc(tmp_len);
33+
snprintf(tmp, tmp_len, "%s/%s", path, filename);
34+
if (is_executable(tmp)) {
35+
return tmp;
36+
} else {
37+
free(tmp);
38+
}
39+
40+
path = strtok_r(NULL, ":", &tokbuf);
41+
}
42+
return NULL;
43+
}
44+
45+
/* Identify the executable search path. The result must be freed by the caller. */
46+
static char *get_executable_search_path(void) {
47+
char *search_path;
48+
49+
search_path = getenv("PATH");
50+
if (search_path) {
51+
search_path = strdup(search_path);
52+
return search_path;
53+
}
54+
55+
#if defined(HAVE_CONFSTR)
56+
int len = confstr(_CS_PATH, NULL, 0);
57+
search_path = malloc(len + 1)
58+
if (search_path != NULL) {
59+
search_path[0] = ':';
60+
(void) confstr (_CS_PATH, search_path + 1, len);
61+
return search_path;
62+
}
63+
#endif
64+
65+
return strdup(":");
66+
}
67+
68+
/* Find the given executable in the executable search path. */
69+
char *find_executable(char *filename) {
70+
/* If it's an absolute or relative path name, it's easy. */
71+
if (strchr(filename, '/') && is_executable(filename)) {
72+
return filename;
73+
}
74+
75+
char *search_path = get_executable_search_path();
76+
return find_in_search_path(search_path, filename);
77+
}
78+
79+
#endif

0 commit comments

Comments
 (0)