This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,25 @@
(library
(name dune_spawn)
(foreign_stubs
(language c)
(flags (:standard (:include flags.sexp)))
(names spawn_stubs))
(libraries unix threads.posix))
(rule
(target flags.sexp)
(enabled_if
(= %{os_type} "Win32"))
(action
(with-stdout-to
%{target}
(echo "(-DUNICODE -D_UNICODE)"))))
(rule
(target flags.sexp)
(enabled_if
(<> %{os_type} "Win32"))
(action
(with-stdout-to
%{target}
(echo "()"))))

View file

@ -0,0 +1,187 @@
open StdLabels
external is_osx : unit -> bool = "spawn_is_osx" [@@noalloc]
let is_osx = is_osx ()
module Working_dir = struct
type t =
| Path of string
| Fd of Unix.file_descr
| Inherit
end
module Unix_backend = struct
type t =
| Fork
| Vfork
let default =
match Sys.getenv "SPAWN_USE_FORK" with
| _ -> Fork
| exception Not_found ->
(* We observed issues in the past when using [vfork] on OSX. More
precisely, it seems that [chdir]/[fchdir] is not taken into account
after a vfork. We tried working around this by not doing the directory
change in the sub-process when using [vfork] on OSX, and instead doing
it in the parent via [pthread_chdir]/[pthread_fchdir]. This was
unsuccessful.
In the end we decided not to default to [vfork] on OSX. *)
if is_osx then Fork else Vfork
;;
end
let no_null s =
if String.contains s '\000'
then
Printf.ksprintf
invalid_arg
"Spawn.Env.of_list: NUL bytes are not allowed in the environment but found one in \
%S"
s
;;
module type Env = sig
type t
val of_list : string list -> t
end
module Env_win32 : Env = struct
type t = string
let of_list env =
if env = []
then "\000\000"
else (
let len = List.fold_left env ~init:1 ~f:(fun acc s -> acc + String.length s + 1) in
let buf = Buffer.create len in
List.iter env ~f:(fun s ->
no_null s;
Buffer.add_string buf s;
Buffer.add_char buf '\000');
Buffer.add_char buf '\000';
Buffer.contents buf)
;;
end
module Env_unix : Env = struct
type t = string list
let of_list l =
List.iter l ~f:no_null;
l
;;
end
module Env : Env = (val if Sys.win32 then (module Env_win32) else (module Env_unix) : Env)
module Pgid = struct
type t = int
let new_process_group = 0
let of_pid = function
| 0 -> raise (Invalid_argument "bad pid: 0 (hint: use [Pgid.new_process_group])")
| t -> if t < 0 then raise (Invalid_argument ("bad pid: " ^ string_of_int t)) else t
;;
end
external spawn_unix
: env:Env.t option
-> cwd:Working_dir.t
-> prog:string
-> argv:string list
-> stdin:Unix.file_descr
-> stdout:Unix.file_descr
-> stderr:Unix.file_descr
-> use_vfork:bool
-> setpgid:int option
-> sigprocmask:(Unix.sigprocmask_command * int list) option
-> int
= "spawn_unix_byte" "spawn_unix"
external spawn_windows
: env:Env.t option
-> cwd:string option
-> prog:string
-> cmdline:string
-> stdin:Unix.file_descr
-> stdout:Unix.file_descr
-> stderr:Unix.file_descr
-> int
= "spawn_windows_byte" "spawn_windows"
let maybe_quote f =
if String.contains f ' ' || String.contains f '\"' || String.contains f '\t' || f = ""
then Filename.quote f
else f
;;
let spawn_windows
~env
~cwd
~prog
~argv
~stdin
~stdout
~stderr
~use_vfork:_
~setpgid:_
~sigprocmask:_
=
let cwd =
match (cwd : Working_dir.t) with
| Path p -> Some p
| Fd _ -> invalid_arg "Spawn.spawn: [cwd=Fd _] is not supported on Windows"
| Inherit -> None
in
let cmdline = String.concat (List.map argv ~f:maybe_quote) ~sep:" " in
let prog =
match Filename.is_relative prog, cwd with
| true, Some p -> Filename.concat p prog
| _ -> prog
in
spawn_windows ~env ~cwd ~prog ~cmdline ~stdin ~stdout ~stderr
;;
let no_null s =
if String.contains s '\000'
then
Printf.ksprintf
invalid_arg
"Spawn.spawn: NUL bytes are not allowed in any of the arguments but found one in %S"
s
;;
let spawn
?env
?(cwd = Working_dir.Inherit)
~prog
~argv
?(stdin = Unix.stdin)
?(stdout = Unix.stdout)
?(stderr = Unix.stderr)
?(unix_backend = Unix_backend.default)
?setpgid
?sigprocmask
()
=
(match cwd with
| Path s -> no_null s
| Fd _ | Inherit -> ());
no_null prog;
List.iter argv ~f:no_null;
let backend = if Sys.win32 then spawn_windows else spawn_unix in
let use_vfork =
match unix_backend with
| Vfork -> true
| Fork -> false
in
backend ~env ~cwd ~prog ~argv ~stdin ~stdout ~stderr ~use_vfork ~setpgid ~sigprocmask
;;
external safe_pipe : unit -> Unix.file_descr * Unix.file_descr = "spawn_pipe"
let safe_pipe = if Sys.win32 then fun () -> Unix.pipe ~cloexec:true () else safe_pipe

View file

@ -0,0 +1,143 @@
(** Mini spawn library *)
(** Note: on Unix, spawn uses vfork by default. It has been tested, but if you
believe this is causing a problem in your application, you can change this
default at runtime by setting the environment variable [SPAWN_USE_FORK]. *)
module Working_dir : sig
type t =
| Path of string (** Path in the filesystem *)
| Fd of Unix.file_descr
(** File descriptor pointing to a directory. Not supported on Windows. *)
| Inherit (** Inherit the working directory of the current process *)
end
module Unix_backend : sig
(** System call to use on Unix. *)
type t =
| Fork
| Vfork
(** [Fork] if the [SPAWN_USE_FORK] environment variable is set, [Vfork]
otherwise. *)
val default : t
end
module Env : sig
(** Representation of an environment *)
type t
(** Create an environment from a list of strings of the form ["KEY=VALUE"]. *)
val of_list : string list -> t
end
(** Process group IDs *)
module Pgid : sig
(** Representation of the second parameter to [setpgid]. If a value of this
type is provided to [spawn], the child will immediately set its pgid
accordingly. *)
type t
(** Sets the child's pgid to the same as its process id. Equivalent to calling
[setpgid(0, 0)]. *)
val new_process_group : t
(** Raises [Invalid_arg] if the value is not strictly positive. *)
val of_pid : int -> t
end
(** Spawn a sub-command and return its PID. This function is low-level and
should be used to build higher-level APIs.
In case of errors, it raises [Unix.Unix_error].
{b Binary}
[prog] is not searched in [PATH]. It is up to the caller to do the path
resolution before calling this function. Note that there is no special
treatment of executable text files without a proper #!. The execvp function
from the C library calls [/bin/sh] in this case to imitate the behaviors of
a shell but this function doesn't.
Note that when [prog] is a relative filename, it is interpreted as a path
relative to the working directory specified by the [cwd] argument. On
Windows, this differs from what the underlying [CreateProcess] function
does.
{b Command line arguments}
[argv] is the full command line. The first element should be the program
name and subsequent elements the command line arguments. Note that the head
of [argv] doesn't necessarily have to be equal to [prog]. For instance it
might be [foo] while [prog] might be [/usr/bin/foo].
{b Environment}
[env] represents the environment in which the sub-process is executed. If
not specified, the environment from the process calling this function is
used.
{b Working directory}
[cwd] describes what the current working directory of the sub-process should
be. It defaults to [Inherit]. It is an error to pass [Fd _] on Windows.
{b Standard input/outputs}
[stdin], [stdout] and [stderr] are the file descriptors used as standard
input, output and error output of the sub-process. When not specified, they
default to the ones from the calling process.
{b Process groups}
If [setpgid] is provided, the child will immediately call [setpgid(0,pid)],
where [pid] is a [pid_t] determined from the [Pgid.t] given (see that
module). This parameter has no effect on Windows platforms.
{b Signals}
On Unix, by default, the sub-process will have all its signals unblocked. If
[sigprocmask] is passed, the sub-process will have its sigprocmask modified
with the given [sigprocmask_command], relative to the calling thread. At no
point will any OCaml function observe any intermediate signal mask.
Attempts to unblock a signal that is not blocked, to block a signal that is
already blocked, or to block a signal that cannot be blocked (e.g., SIGSTOP,
SIGKILL) are allowed and will be silently ignored.
{b Implementation}
[unix_backend] describes what backend to use on Unix. If set to [Default],
[vfork] is used unless the environment variable [SPAWN_USE_FORK] is set. On
Windows, [CreateProcess] is used. *)
val spawn
: ?env:Env.t
-> ?cwd:Working_dir.t (* default: [Inherit] *)
-> prog:string
-> argv:string list
-> ?stdin:Unix.file_descr
-> ?stdout:Unix.file_descr
-> ?stderr:Unix.file_descr
-> ?unix_backend:Unix_backend.t (* default: [Unix_backend.default] *)
-> ?setpgid:Pgid.t
-> ?sigprocmask:Unix.sigprocmask_command * int list
(** default: unblock all signals in child *)
-> unit
-> int
(**/**)
(* Create a pipe with [O_CLOEXEC] sets for both fds. This is the same as
creating a pipe and setting [O_CLOEXEC] manually on both ends, with the
difference that there is no race condition between [spawn] and [safe_pipe].
I.e. if a thread calls [safe_pipe] and another calls [spawn], it is
guaranteed that the sub-process doesn't have the pipe without [O_CLOEXEC] set
on one or the two file descriptors. The latter situation is problematic as
one often reads a pipe until it is closed, however if some random process
keeps a handle of it because it inherited it from its parent by mistake, the
pipe will never be closed.
It is implemented using the [pipe2] system calls, except on OSX where [pipe2]
is not available. On OSX, both [safe_pipe] and [spawn] lock the same mutex to
prevent race conditions. *)
val safe_pipe : unit -> Unix.file_descr * Unix.file_descr

View file

@ -0,0 +1,949 @@
#define _GNU_SOURCE
/* Must come before any other caml/ headers are included */
#define CAML_INTERNALS
#ifdef _WIN32
/* for [caml_win32_multi_byte_to_wide_char] */
#include <caml/osdeps.h>
/* Prior to OCaml 5.0, the function was called win_multi_byte_to_wide_char */
#include <caml/version.h>
#if OCAML_VERSION_MAJOR < 5
#define caml_win32_multi_byte_to_wide_char win_multi_byte_to_wide_char
#define caml_win32_maperr win32_maperr
#endif
#endif
#include <caml/mlvalues.h>
#include <caml/memory.h>
#include <caml/alloc.h>
#include <caml/unixsupport.h>
#include <caml/fail.h>
/* for [caml_convert_signal_number]; must come after public caml headers */
#include <caml/signals.h>
#include <errno.h>
#if defined(__APPLE__)
# if defined(__MAC_OS_X_VERSION_MAX_ALLOWED)
# define USE_POSIX_SPAWN
# define vfork fork
# endif
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef SYS___pthread_chdir
#define SYS___pthread_chdir 348
#endif
#ifndef SYS___pthread_fchdir
#define SYS___pthread_fchdir 349
#endif
static int __pthread_chdir(const char *path) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
return syscall(SYS___pthread_chdir, path);
#pragma clang diagnostic pop
}
static int __pthread_fchdir(int fd) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
return syscall(SYS___pthread_fchdir, fd);
#pragma clang diagnostic pop
}
CAMLprim value spawn_is_osx()
{
return Val_true;
}
#else
CAMLprim value spawn_is_osx()
{
return Val_false;
}
#endif
#if !defined(_WIN32)
# if defined(USE_POSIX_SPAWN)
# include <spawn.h>
# if !defined(__APPLE__)
# define posix_spawn_file_actions_addchdir_np(...) ENOSYS
# define posix_spawn_file_actions_addfchdir_np(...) ENOSYS
# endif
# endif
#include <assert.h>
#include <string.h>
#if !defined(__CYGWIN__) && !defined(__HAIKU__)
#include <sys/syscall.h>
#endif
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <signal.h>
/* +-----------------------------------------------------------------+
| pipe2 |
+-----------------------------------------------------------------+ */
#if defined(__APPLE__) || defined(__HAIKU__)
/* vfork(2) is deprecated on macOS >= 12, so we use fork(2) instead. */
# if defined(__MAC_OS_X_VERSION_MAX_ALLOWED)
# if __MAC_OS_X_VERSION_MAX_ALLOWED >= 120000
# define vfork fork
# endif
# endif
static int safe_pipe(int fd[2])
{
int i;
if (pipe(fd) == -1) return -1;
for (i = 0; i < 2; i++) {
int retcode = fcntl(fd[i], F_GETFD, 0);
if (retcode == -1 ||
fcntl(fd[i], F_SETFD, retcode | FD_CLOEXEC) == -1) {
int error = errno;
close(fd[0]);
close(fd[1]);
errno = error;
return -1;
}
}
return 0;
}
static pthread_mutex_t safe_pipe_mutex = PTHREAD_MUTEX_INITIALIZER;
#define enter_safe_pipe_section() pthread_mutex_lock(&safe_pipe_mutex)
#define leave_safe_pipe_section() pthread_mutex_unlock(&safe_pipe_mutex)
CAMLprim value spawn_pipe()
{
int fd[2];
int ret;
value res;
caml_enter_blocking_section();
enter_safe_pipe_section();
ret = safe_pipe(fd);
leave_safe_pipe_section();
caml_leave_blocking_section();
if (ret == -1)
uerror("pipe", Nothing);
res = caml_alloc_small(2, 0);
Field(res, 0) = Val_int(fd[0]);
Field(res, 1) = Val_int(fd[1]);
return res;
}
#else
#define enter_safe_pipe_section()
#define leave_safe_pipe_section()
static int safe_pipe(int fd[2])
{
return pipe2(fd, O_CLOEXEC);
}
CAMLprim value spawn_pipe()
{
int fd[2];
value res;
if (safe_pipe(fd) == -1) uerror("safe_pipe", Nothing);
res = caml_alloc_small(2, 0);
Field(res, 0) = Val_int(fd[0]);
Field(res, 1) = Val_int(fd[1]);
return res;
}
#endif
/* +-----------------------------------------------------------------+
| Code executed in the child |
+-----------------------------------------------------------------+ */
enum error_arg { NOTHING, CWD, PROG };
/* Structure used to communicate errors from the child to the
parent. */
struct subprocess_failure {
/* Value of [errno]. */
int error;
/* System call that failed */
char function[32];
/* What to pass as third argument of the Unix_error exception. */
enum error_arg arg;
};
/* Compile time asserts as described here:
http://stackoverflow.com/questions/807244/c-compiler-asserts-how-to-implement
*/
#define CASSERT(predicate) _impl_CASSERT_LINE(predicate,__LINE__,__FILE__)
#define _impl_PASTE(a,b) a##b
#define _impl_CASSERT_LINE(predicate, line, file) \
typedef char __attribute__((unused)) \
_impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1];
/* Fill a [subprocess_failure] structure. */
static void set_error(struct subprocess_failure *fp,
int error,
char *function,
enum error_arg error_arg)
{
size_t len = strlen(function);
assert(len + 1 <= sizeof(fp->function));
memset(fp, 0, sizeof(*fp));
memcpy(fp->function, function, len + 1);
fp->error = error;
fp->arg = error_arg;
}
/* Report an error to the parent. Use the current value of [errno] as
error number. */
static void subprocess_failure(int failure_fd,
char *function,
enum error_arg error_arg)
{
struct subprocess_failure failure;
sigset_t sigset;
ssize_t written;
#ifdef PIPE_BUF
CASSERT(sizeof(failure) < PIPE_BUF)
#else
CASSERT(sizeof(failure) < _POSIX_PIPE_BUF)
#endif
set_error(&failure, errno, function, error_arg);
/* Block all signals to avoid being interrupted in write.
Although most of the call sites of [subprocess_failure] already block
signals, the one after the [exec] might not. */
sigfillset(&sigset);
pthread_sigmask(SIG_SETMASK, &sigset, NULL);
/* Write is atomic as buffer is smaller than PIPE_BUF
(required by POSIX.1-2001, as claimed in [man 7 pipe]).
We only store the result of [write] to avoid a warning.
*/
written = write(failure_fd, &failure, sizeof(failure));
(void)written;
_exit(127);
}
/* same as [dup] but ensures the result is -1 or >= 3. */
static int safe_dup(int fd)
{
int new_fd = dup(fd);
if (new_fd == -1 || new_fd >= 3)
return new_fd;
else {
int result = safe_dup(fd);
close(new_fd);
return result;
}
}
/* same as [safe_dup] but writes errors to a file descriptor,
as from a subprocess. */
static int safe_dup_failure_fd(int failure_fd, int fd)
{
int new_fd = safe_dup(fd);
if (new_fd == -1) subprocess_failure(failure_fd, "dup", NOTHING);
return new_fd;
}
enum working_dir_kind { PATH, FD, INHERIT };
struct spawn_info {
char **env; /* can be 0, in which case the current environment is used */
enum working_dir_kind cwd_kind;
union {
int fd;
char *path;
} cwd; /* Only filled if [cwd_kind != INHERIT] */
char *prog;
char **argv;
int std_fds[3];
int set_pgid;
pid_t pgid;
sigset_t child_sigmask;
};
static void subprocess(int failure_fd, struct spawn_info *info)
{
int i, fd, tmp_fds[3];
struct sigaction sa;
if (info->set_pgid) {
if (setpgid(0, info->pgid) == -1) {
subprocess_failure(failure_fd, "setpgid", NOTHING);
return;
}
}
/* Restore all signals to their default behavior before setting the
desired signal mask for the subprocess to avoid invoking handlers
from the parent */
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
/* Ignore errors as there is no interesting way it can fail. */
for (i = 1; i < NSIG; i++) sigaction(i, &sa, NULL);
switch (info->cwd_kind) {
case INHERIT: break;
case PATH:
if (chdir(info->cwd.path) == -1)
subprocess_failure(failure_fd, "chdir", CWD);
break;
case FD:
if (fchdir(info->cwd.fd) == -1)
subprocess_failure(failure_fd, "fchdir", NOTHING);
close(info->cwd.fd);
break;
}
/* Use temporary file descriptors for redirections to avoid problems
when redirecting stdout to stderr for instance. */
for (fd = 0; fd < 3; fd++)
tmp_fds[fd] = safe_dup_failure_fd(failure_fd, info->std_fds[fd]);
for (fd = 0; fd < 3; fd++)
close(info->std_fds[fd]);
for (fd = 0; fd < 3; fd++) {
/* here we rely on [dup2] clearing the O_CLOEXEC flag */
if (dup2(tmp_fds[fd], fd) == -1)
subprocess_failure(failure_fd, "dup2", NOTHING);
close(tmp_fds[fd]);
}
pthread_sigmask(SIG_SETMASK, &info->child_sigmask, NULL);
execve(info->prog, info->argv, info->env);
subprocess_failure(failure_fd, "execve", PROG);
}
/* +-----------------------------------------------------------------+
| Parent code |
+-----------------------------------------------------------------+ */
/* Raise a Unix error based on the contents of a [subprocess_failure] structure
(and some context arguments). */
void raise_subprocess_failure(struct subprocess_failure* failure,
value v_cwd,
value v_prog)
{
value arg = Nothing;
switch (failure->arg) {
case NOTHING: arg = Nothing; break;
case CWD : arg = Field(v_cwd, 0); break;
case PROG : arg = v_prog; break;
}
assert(memchr(failure->function, 0, sizeof(failure->function)));
unix_error(failure->error, failure->function, arg);
}
/* Convert a [string list] into a NULL terminated array of C
strings.
We don't reuse the [cstringvect] function from [unix_support.h] as
it doesn't copy the strings in the array.
*/
static char **alloc_string_vect(value v)
{
char **result;
mlsize_t count, i, full_size;
value x;
char *ptr;
count = 0;
full_size = sizeof(char*);
for (x = v; Is_block(x); x = Field(x, 1)) {
count++;
full_size += sizeof(char*) + caml_string_length(Field(x, 0)) + 1;
}
/* Allocate the array of pointers followed by the space to copy the
strings as one block. */
result = (char**)malloc(full_size);
if (result == NULL) caml_raise_out_of_memory();
ptr = ((char*)result) + (sizeof(char*) * (count + 1));
for (x = v, i = 0; Is_block(x); x = Field(x, 1), i++) {
value v_str = Field(x, 0);
mlsize_t len = caml_string_length(v_str) + 1;
memcpy(ptr, String_val(v_str), len);
result[i] = ptr;
ptr += len;
}
result[count] = NULL;
return result;
}
static char **copy_c_string_array(char ** strings)
{
char **result;
size_t count, i, full_size;
char *ptr;
count = 0;
full_size = sizeof(char*);
while (strings[count] != 0) {
full_size += sizeof(char*) + strlen(strings[count]) + 1;
count++;
}
/* Allocate the array of pointers followed by the space to copy the
strings as one block. */
result = (char**)malloc(full_size);
if (result == NULL) caml_raise_out_of_memory();
ptr = (char*)(result + count + 1);
for (i = 0; i < count; i++) {
size_t len = strlen(strings[i]) + 1;
memcpy(ptr, strings[i], len);
result[i] = ptr;
ptr += len;
}
result[count] = NULL;
return result;
}
static void free_spawn_info(struct spawn_info *info)
{
if (info->cwd_kind == PATH) free(info->cwd.path);
free(info->prog);
free(info->argv);
free(info->env);
}
enum caml_unix_sigprocmask_command {
CAML_SIG_SETMASK,
CAML_SIG_BLOCK,
CAML_SIG_UNBLOCK,
};
/* Initializes all fields of `*info` except for `info->child_sigmask`,
which must be initalized by `init_spawn_info_sigmask` (below). */
static void init_spawn_info(struct spawn_info *info,
value v_env,
value v_cwd,
value v_prog,
value v_argv,
value v_stdin,
value v_stdout,
value v_stderr,
value v_setpgid,
value v_sigprocmask)
{
extern char ** environ;
info->std_fds[0] = Int_val(v_stdin);
info->std_fds[1] = Int_val(v_stdout);
info->std_fds[2] = Int_val(v_stderr);
if (Is_long(v_cwd)) {
assert(v_cwd == Val_long(0));
info->cwd_kind = INHERIT;
} else {
switch (Tag_val(v_cwd)) {
case 0: /* Path of string */
assert (Tag_val(Field(v_cwd, 0)) == String_tag);
info->cwd_kind = PATH;
info->cwd.path = strdup(String_val(Field(v_cwd, 0)));
if (info->cwd.path == NULL) caml_raise_out_of_memory();
break;
case 1: /* Fd of Unix.file_descr */
assert (Is_long(Field(v_cwd, 0)));
info->cwd_kind = FD;
info->cwd.fd = Int_val(Field(v_cwd, 0));
break;
default:
assert(0);
}
}
info->prog = strdup(String_val(v_prog));
if (info->prog == NULL) caml_raise_out_of_memory();
info->argv = alloc_string_vect(v_argv);
info->env =
Is_block(v_env) ?
alloc_string_vect(Field(v_env, 0)) : copy_c_string_array(environ);
info->set_pgid = Is_block(v_setpgid);
info->pgid =
Is_block(v_setpgid) ?
Long_val(Field(v_setpgid, 0)) : 0;
if (v_sigprocmask == Val_long(0)) {
sigemptyset(&info->child_sigmask);
} else {
v_sigprocmask = Field(v_sigprocmask, 0);
value v_sigprocmask_command = Field(v_sigprocmask, 0);
enum caml_unix_sigprocmask_command sigprocmask_command = Long_val(v_sigprocmask_command);
switch (sigprocmask_command) {
case CAML_SIG_SETMASK:
sigemptyset(&info->child_sigmask);
break;
case CAML_SIG_BLOCK:
case CAML_SIG_UNBLOCK:
pthread_sigmask(SIG_SETMASK, NULL, &info->child_sigmask);
break;
default:
caml_failwith("Unknown sigprocmask action");
}
value v_signals_list = Field(v_sigprocmask, 1);
for (; v_signals_list != Val_emptylist;
v_signals_list = Field(v_signals_list, 1)) {
int signal = caml_convert_signal_number(Long_val(Field(v_signals_list, 0)));
switch (sigprocmask_command) {
case CAML_SIG_SETMASK:
case CAML_SIG_BLOCK:
sigaddset(&info->child_sigmask, signal);
break;
case CAML_SIG_UNBLOCK:
sigdelset(&info->child_sigmask, signal);
break;
default:
assert(0);
}
}
}
}
#if defined(USE_POSIX_SPAWN)
CAMLprim value spawn_unix(value v_env,
value v_cwd,
value v_prog,
value v_argv,
value v_stdin,
value v_stdout,
value v_stderr,
value v_use_vfork,
value v_setpgid,
value v_sigprocmask)
{
CAMLparam4(v_env, v_cwd, v_prog, v_argv);
CAMLlocal1(e_arg);
e_arg = Nothing;
pid_t pid;
int tmp_fds[3] = {0}; // invariant: initialized > 2
int e_error;
char *e_function = NULL;
posix_spawn_file_actions_t actions;
if (posix_spawn_file_actions_init(&actions)) {
e_function = "posix_spawn_file_actions_init";
goto cleanup;
}
posix_spawnattr_t attr;
if (posix_spawnattr_init(&attr)) {
e_function = "posix_spawnattr_init";
goto cleanup;
}
struct spawn_info info;
init_spawn_info(&info, v_env, v_cwd, v_prog, v_argv,
v_stdin, v_stdout, v_stderr, v_setpgid, v_sigprocmask);
short attr_flags = POSIX_SPAWN_SETSIGMASK;
if (info.set_pgid) attr_flags |= POSIX_SPAWN_SETPGROUP;
e_error = posix_spawnattr_setflags(&attr, attr_flags);
if (e_error) {
e_function = "posix_spawnattr_setflags";
goto cleanup;
}
e_error = posix_spawnattr_setsigmask(&attr, &info.child_sigmask);
if (e_error) {
e_function = "posix_spawnattr_setsigmask";
goto cleanup;
}
if (info.set_pgid) {
e_error = posix_spawnattr_setpgroup(&attr, info.pgid);
if (e_error) {
e_function = "posix_spawnattr_setpgroup";
goto cleanup;
}
}
for (int fd = 0; fd < 3; fd++) {
int tmp_fd = tmp_fds[fd] = safe_dup(info.std_fds[fd]);
if (tmp_fd == -1) {
e_error = errno;
e_function = "dup";
goto cleanup;
}
e_error = posix_spawn_file_actions_adddup2(&actions, tmp_fd, fd);
if (e_error) {
e_function = "posix_spawn_file_actions_adddup2";
goto cleanup;
}
e_error = posix_spawn_file_actions_addclose(&actions, tmp_fd);
if (e_error) {
e_function = "posix_spawn_file_actions_addclose";
goto cleanup;
}
}
switch (info.cwd_kind) {
case INHERIT: break;
case PATH:
e_error = __pthread_chdir(info.cwd.path);
if (e_error) {
e_function = "__pthread_chdir";
goto cleanup;
}
break;
case FD:
e_error = __pthread_fchdir(info.cwd.fd);
if (e_error) {
e_function = "__pthread_fchdir";
goto cleanup;
}
}
caml_enter_blocking_section();
e_error = posix_spawn(&pid, info.prog,
&actions, &attr,
info.argv, info.env);
if (info.cwd_kind != INHERIT) {
/* go back to inheriting the process cwd */
__pthread_fchdir(-1);
}
caml_leave_blocking_section();
if (e_error) {
e_function = "posix_spawn";
goto cleanup;
}
cleanup:
for (int fd = 0; fd < 3; fd++)
if (tmp_fds[fd] > 2)
close(tmp_fds[fd]);
free_spawn_info(&info);
posix_spawnattr_destroy(&attr);
posix_spawn_file_actions_destroy(&actions);
if (e_function) {
unix_error(e_error, e_function, e_arg);
}
CAMLreturn(Val_int(pid));
}
#else
CAMLprim value spawn_unix(value v_env,
value v_cwd,
value v_prog,
value v_argv,
value v_stdin,
value v_stdout,
value v_stderr,
value v_use_vfork,
value v_setpgid,
value v_sigprocmask)
{
CAMLparam4(v_env, v_cwd, v_prog, v_argv);
pid_t ret;
struct spawn_info info;
int result_pipe[2];
int cancel_state;
sigset_t sigset;
sigset_t saved_procmask;
struct subprocess_failure failure;
int got_error = 0;
int errno_after_forking = 0;
int status;
init_spawn_info(&info, v_env, v_cwd, v_prog, v_argv,
v_stdin, v_stdout, v_stderr, v_setpgid, v_sigprocmask);
caml_enter_blocking_section();
enter_safe_pipe_section();
/* Pipe used by the child to send errors to the parent. */
if (safe_pipe(result_pipe) == -1) {
int error = errno;
leave_safe_pipe_section();
caml_leave_blocking_section();
free_spawn_info(&info);
unix_error(error, "pipe", Nothing);
}
/* Block signals and thread cancellation. When using vfork, the
child might share the signal handlers.
It's not clear that we need the call to [pthread_setcancelstate],
but implementations of posix_spawn based on vfork are doing this.
For instance:
http://git.musl-libc.org/cgit/musl/tree/src/process/posix_spawn.c
On android, pthread_cancel is not implemented, it is typically
patched out or in certain cases reimplemented with atomic_flags
https://github.com/search?q=org%3Atermux+pthread_setcancelstate+language%3ADiff&type=code&l=Diff
*/
#if !defined(__ANDROID__)
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state);
#endif
sigfillset(&sigset);
pthread_sigmask(SIG_SETMASK, &sigset, &saved_procmask);
ret = Bool_val(v_use_vfork) ? vfork() : fork();
if (ret == 0) {
close(result_pipe[0]);
subprocess(result_pipe[1], &info);
}
errno_after_forking = errno;
leave_safe_pipe_section();
free_spawn_info(&info);
close(result_pipe[1]);
got_error = 0;
if (ret == -1) {
got_error = 1;
set_error(&failure, errno_after_forking, "vfork", NOTHING);
} else {
intnat res = read(result_pipe[0], &failure, sizeof(failure));
/* If the sub-process exec successfully, the write end of the
error pipe is closed (as it has the [O_CLOEXEC] flag set) and
[read] returns [0].
If it returns non-zero, it means something went wrong in the
sub-process and it wrote a [subprocess_failure] structure on
the pipe. */
if (res != 0) {
got_error = 1;
if (res != sizeof(failure)) {
/* It's not clear this can happen, but just to be on the safe side */
set_error(&failure,
(res == -1) ? errno : EINVAL,
"read",
NOTHING);
};
/* If [read] did fail for some reason then we might be stuck
here for a while. Other implementation of posix_spawn just
assume that [read(...) != sizeof(failure)] is a success. */
if (got_error) waitpid(ret, &status, 0);
}
}
close(result_pipe[0]);
pthread_sigmask(SIG_SETMASK, &saved_procmask, NULL);
#if !defined(__ANDROID__)
pthread_setcancelstate(cancel_state, NULL);
#endif
caml_leave_blocking_section();
if (got_error) {
raise_subprocess_failure(&failure, v_cwd, v_prog);
}
CAMLreturn(Val_int(ret));
}
#endif
CAMLprim value spawn_windows(value v_env,
value v_cwd,
value v_prog,
value v_cmdline,
value v_stdin,
value v_stdout,
value v_stderr)
{
(void)v_env;
(void)v_cwd;
(void)v_prog;
(void)v_cmdline;
(void)v_stdin;
(void)v_stdout;
(void)v_stderr;
unix_error(ENOSYS, "spawn_windows", Nothing);
}
#else
CAMLprim value spawn_unix(value v_env,
value v_cwd,
value v_prog,
value v_argv,
value v_stdin,
value v_stdout,
value v_stderr,
value v_use_vfork,
value v_setpgid,
value v_sigprocmask)
{
(void)v_env;
(void)v_cwd;
(void)v_prog;
(void)v_argv;
(void)v_stdin;
(void)v_stdout;
(void)v_stderr;
(void)v_use_vfork;
(void)v_setpgid;
(void)v_sigprocmask;
unix_error(ENOSYS, "spawn_unix", Nothing);
}
static BOOL dup2_and_clear_close_on_exec(value fd, HANDLE *res)
{
return DuplicateHandle(GetCurrentProcess(), Handle_val(fd),
GetCurrentProcess(), res,
0L,
TRUE,
DUPLICATE_SAME_ACCESS);
}
static void close_std_handles(STARTUPINFO *si)
{
if (si->hStdInput != NULL) CloseHandle(si->hStdInput );
if (si->hStdOutput != NULL) CloseHandle(si->hStdOutput);
if (si->hStdError != NULL) CloseHandle(si->hStdError );
}
CAMLprim value spawn_windows(value v_env,
value v_cwd,
value v_prog,
value v_cmdline,
value v_stdin,
value v_stdout,
value v_stderr)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
WCHAR *prog;
WCHAR *cmdline;
WCHAR *env = NULL;
WCHAR *cwd = NULL;
BOOL result;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
if (!dup2_and_clear_close_on_exec(v_stdin , &si.hStdInput ) ||
!dup2_and_clear_close_on_exec(v_stdout, &si.hStdOutput) ||
!dup2_and_clear_close_on_exec(v_stderr, &si.hStdError )) {
caml_win32_maperr(GetLastError());
close_std_handles(&si);
uerror("DuplicateHandle", Nothing);
}
prog = caml_stat_strdup_to_utf16(String_val(v_prog));
cmdline = caml_stat_strdup_to_utf16(String_val(v_cmdline));
if (Is_block(v_env)) {
v_env = Field(v_env, 0);
mlsize_t len = caml_string_length(v_env);
int size = caml_win32_multi_byte_to_wide_char(String_val(v_env), len, NULL, 0);
env = caml_stat_alloc(size * sizeof(WCHAR));
caml_win32_multi_byte_to_wide_char(String_val(v_env), len, env, size);
}
if (Is_block(v_cwd))
cwd = caml_stat_strdup_to_utf16(String_val(Field(v_cwd, 0)));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
result =
CreateProcess(prog, cmdline, NULL, NULL, TRUE, CREATE_UNICODE_ENVIRONMENT,
env, cwd, &si, &pi);
caml_stat_free(prog);
caml_stat_free(cmdline);
caml_stat_free(env);
caml_stat_free(cwd);
if (!result) {
caml_win32_maperr(GetLastError());
close_std_handles(&si);
uerror("CreateProcess", Nothing);
}
close_std_handles(&si);
CloseHandle(pi.hThread);
return Val_long(pi.hProcess);
}
CAMLprim value spawn_pipe()
{
unix_error(ENOSYS, "spawn_pipe", Nothing);
}
#endif
CAMLprim value spawn_unix_byte(value * argv)
{
return spawn_unix(argv[0],
argv[1],
argv[2],
argv[3],
argv[4],
argv[5],
argv[6],
argv[7],
argv[8],
argv[9]);
}
CAMLprim value spawn_windows_byte(value * argv)
{
return spawn_windows(argv[0],
argv[1],
argv[2],
argv[3],
argv[4],
argv[5],
argv[6]);
}