Native Spawn Trampoline¶
spawn/run launch through sb-ext:run-program, which is enough for the
process-group isolation the rest of the library relies on but doesn't
expose lower-level POSIX setup: dropping privileges, joining a new session,
remapping arbitrary file descriptors, or setting resource limits before
exec. spawn-native covers that gap through a small fork/execve-based C
trampoline (native/spawn.c, packaged as the cl-process-kit-spawn
binary) that performs the requested setup and then execs the real
program — with typed, synchronous failure reporting for every setup phase
that can fail.
The returned process-handle is the same type spawn returns, and
supports the same operations —
spawn-native is a drop-in alternative launch path, not a separate
process-handle type. Because the trampoline execs the target program in
place, the handle's PID is the final target process's PID; there is no
lingering wrapper process.
Locating the trampoline binary¶
*native-spawn-program* defaults to the CL_PROCESS_KIT_SPAWN environment
variable, or "cl-process-kit-spawn" resolved via PATH if that variable
is unset. The Nix package builds and installs this binary automatically
($out/bin/cl-process-kit-spawn); building it directly uses a plain C11
compile:
cc -std=c11 -O2 -Wall -Wextra -Werror native/spawn.c -o cl-process-kit-spawn
export CL_PROCESS_KIT_SPAWN=$PWD/cl-process-kit-spawn
Setup options¶
spawn-native (program arguments &key search fd-mappings pass-fds session
process-group detached uid gid groups umask resource-limits
directory environment input output error external-format
status-hook) -> process-handle
searchresolvesprogramagainstPATHusing the same rules asmake-command's executable search.fd-mappingsis a list of(target . source)conses:targetis the file descriptor number the child sees;sourceis either an existing file descriptor number to duplicate onto it, or:close/:null.pass-fdsis a list of file descriptor numbers to leave open (not close-on-exec) across theexec.session(boolean) starts a new session (setsid) beforeexec.process-groupmust benilor0; joining an existing process group is unsupported by this backend — only creating the child as its own new group leader is.detached(boolean) fully detaches the child from the caller's controlling terminal.uid,gid, andgroups(a list of supplementary group IDs) set the child's credentials beforeexec;umasksets its file-creation mask.resource-limitsis a list of(resource soft hard)triples (e.g.(:nofile 256 1024)) applied viasetrlimitbeforeexec.directory,environment,input,output,error,external-format, andstatus-hookmirrorspawn's options for the launched process.
Typed launch failures¶
Every setup phase that can fail is reported synchronously and precisely, instead of surfacing as an opaque non-zero exit:
(handler-case
(process-kit:spawn-native "/definitely/missing-program" nil)
(process-kit:native-process-launch-error (condition)
(list (process-kit:native-process-launch-error-phase condition)
(process-kit:native-process-launch-error-errno condition))))
;; => (:EXEC <errno>)
native-process-launch-error is a process-launch-error subclass (so it
carries program/arguments/directory/cause too — see
Results and Conditions) with two
additional readers:
| Reader | Meaning |
|---|---|
native-process-launch-error-phase |
One of :argument, :fd-setup, :chdir, :session, :process-group, :credentials, :resource-limit, or :exec — which setup step failed. |
native-process-launch-error-errno |
The C errno value from that step. |
Internally, the trampoline reports failure by writing a small phase/errno
record down a pipe held open only until a successful exec closes it;
spawn-native reads that pipe synchronously before returning, so a launch
failure is always reported as a condition from the spawn-native call
itself rather than discovered later through process-wait/communicate.
The trampoline is launched like any spawn child, so it starts as the
leader of its own process group. setsid refuses a group leader, so for
session or detached the trampoline first moves into its parent's group
and then calls setsid. On macOS the group it vacated can stay visible for
a short time afterwards, during which setsid fails with EPERM; the
trampoline retries for up to five seconds before reporting a :session
failure. Because the child changes its group after the trampoline has
started, spawn-native checks that the child leads its own process group
only after the launch-error pipe closes. A session leader already leads its
own group, so process-group 0 combined with session or detached needs
no further step.