Skip to content

Commit

Permalink
cbits/posix: Introduce posix_spawn support
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bgamari committed Jul 5, 2021
1 parent 30076bb commit c7e5b06
Show file tree
Hide file tree
Showing 11 changed files with 837 additions and 404 deletions.
52 changes: 52 additions & 0 deletions cbits/posix/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#pragma once

#include "runProcess.h"

enum std_handle_behavior {
// Close the handle
STD_HANDLE_CLOSE,
// dup2 the specified fd to standard handle
STD_HANDLE_USE_FD,
// dup2 the appropriate end of the given pipe to the standard handle and
// close the other end.
STD_HANDLE_USE_PIPE
};

struct std_handle {
enum std_handle_behavior behavior;
union {
int use_fd;
struct {
int parent_end, child_end;
} use_pipe;
};
};

int get_max_fd(void);

// defined in find_executable.c
#if !defined(HAVE_execvpe)
char *find_executable(char *filename);
#endif

// defined in fork_exec.c
ProcHandle
do_spawn_fork (char *const args[],
char *workingDirectory, char **environment,
struct std_handle *stdInHdl,
struct std_handle *stdOutHdl,
struct std_handle *stdErrHdl,
gid_t *childGroup, uid_t *childUser,
int flags,
char **failed_doing);

// defined in posix_spawn.c
ProcHandle
do_spawn_posix (char *const args[],
char *workingDirectory, char **environment,
struct std_handle *stdInHdl,
struct std_handle *stdOutHdl,
struct std_handle *stdErrHdl,
gid_t *childGroup, uid_t *childUser,
int flags,
char **failed_doing);
79 changes: 79 additions & 0 deletions cbits/posix/find_executable.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* ----------------------------------------------------------------------------
* search path search logic
* (c) Ben Gamari 2021
*/

#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

#include "common.h"

// the below is only necessary when we don't have execvpe.
#if !defined(HAVE_execvpe)

/* Return true if the given file exists and is an executable. */
static bool is_executable(const char *path) {
return access(path, X_OK) == 0;
}

/* Find an executable with the given filename in the given search path. The
* result must be freed by the caller. Returns NULL if a matching file is not
* found.
*/
static char *find_in_search_path(char *search_path, const char *filename) {
const int filename_len = strlen(filename);
char *tokbuf;
char *path = strtok_r(search_path, ":", &tokbuf);
while (path != NULL) {
const int tmp_len = filename_len + 1 + strlen(path) + 1;
char *tmp = malloc(tmp_len);
snprintf(tmp, tmp_len, "%s/%s", path, filename);
if (is_executable(tmp)) {
return tmp;
} else {
free(tmp);
}

path = strtok_r(NULL, ":", &tokbuf);
}
return NULL;
}

/* Identify the executable search path. The result must be freed by the caller. */
static char *get_executable_search_path(void) {
char *search_path;

search_path = getenv("PATH");
if (search_path) {
search_path = strdup(search_path);
return search_path;
}

#if defined(HAVE_CONFSTR)
int len = confstr(_CS_PATH, NULL, 0);
search_path = malloc(len + 1)
if (search_path != NULL) {
search_path[0] = ':';
(void) confstr (_CS_PATH, search_path + 1, len);
return search_path;
}
#endif

return strdup(":");
}

/* Find the given executable in the executable search path. */
char *find_executable(char *filename) {
/* If it's an absolute or relative path name, it's easy. */
if (strchr(filename, '/') && is_executable(filename)) {
return filename;
}

char *search_path = get_executable_search_path();
return find_in_search_path(search_path, filename);
}

#endif
Loading

0 comments on commit c7e5b06

Please sign in to comment.