Support AIX by forking where posix_spawn cannot change directory - #112
Conversation
Merges mehendarkarprajwal/subprocess.h@e6cec1b ("Initial port for AIX"). AIX provides no posix_spawn_file_actions_addchdir[_np], so process_cwd cannot be honoured through posix_spawn there; the child has to be forked so it can chdir() before exec. The branch was based on 8671cee and conflicted with five commits that have since touched subprocess.h (sheredom#102, sheredom#104, sheredom#105, sheredom#106, sheredom#110). Rather than replay the incoming restructuring of the posix_spawn path, the conflict is resolved in favour of main for that path, and the fork() implementation is added beside it. The result is +183/-7 against main instead of +227/-97: the posix_spawn code is left byte-identical, so no other platform can regress. Changes relative to the merged commit: * Select the implementation through SUBPROCESS_SPAWN_VIA_FORK, defaulting to 1 on AIX, instead of testing _AIX at each site. The path can then be forced on for testing on any POSIX platform, which is how it was validated: with -DSUBPROCESS_SPAWN_VIA_FORK=1 on glibc, all 441 tests pass. * Derive SUBPROCESS_HAVE_CWD and SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS from it. Both are true whenever the fork path is used, because the child chdir()s itself and the error pipe reports exec failures. Previously both landed on 1 for AIX through the unknown-platform #else, which happened to be right for the wrong reason. * Drop the duplicate function-scope declarations of posix_error and actions, which shadowed the ones in the new inner block and left actions_created without a use. As merged, that broke every non-AIX build under -Werror: three unused-variable errors at subprocess.h:1137-1139. * Name the child's fallback exit status SUBPROCESS_EXEC_FAILURE_STATUS rather than repeating 127 at six call sites. No header defines the value; 127 is the shell convention for a command that could not be executed, and what glibc's posix_spawn used before it learned to report exec failures. * Restart read() and waitpid() on EINTR, and require a full sizeof(int) from the error pipe. A short or interrupted read previously reported success for a child whose exec had failed. * Only close the pipe ends in the child once they have been duplicated, and only those above STDERR_FILENO, so a pipe end that already occupies 0, 1 or 2 is not closed out from under the descriptor just installed. * Route the child's failure paths through a single exit point and keep every call it makes async-signal-safe, which fork() in a threaded process requires. Not yet verified on AIX itself; the test evidence above is from glibc with the fork path forced.
Adds SUBPROCESS_ADDCHDIR_IS_POSIX, which says whether the platform provides posix_spawn_file_actions_addchdir under its POSIX 2024 name rather than the older ..._np one, and extends SUBPROCESS_SPAWN_VIA_FORK to the platforms that provide neither and therefore need fork()+exec(). The call site now asks those two macros instead of naming platforms itself. SUBPROCESS_ADDCHDIR_IS_POSIX macOS 26+, NetBSD 10+ SUBPROCESS_SPAWN_VIA_FORK AIX, OpenBSD, NetBSD < 10 The reason for two macros rather than one is that platforms differ in two independent ways: whether the chdir file action exists at all, and what it is called. Taken from each vendor's own spawn.h and libc: FreeBSD 13.1, 13.2, 14.3, 15.0 ..._addchdir_np NetBSD 10.0, 10.1 ..._addchdir, no _np NetBSD 9.3, 9.4 neither OpenBSD 7.7, 7.8, 7.9 neither AIX 7.2 TL04 neither, in header and in libc This also corrects the call site's previous test, defined(__NetBSD__) with no version check, which was right for NetBSD 10 and wrong for 9 and older, where neither spelling exists. __NetBSD_Version__ comes from <sys/param.h>, not from the compiler's predefines, so that header is now included on NetBSD before the probes run. If it is ever absent the version folds to 0 and every NetBSD takes the fork path: heavier than needed, never wrong. Verified by compiling against sysroots built from each vendor's release sets, so nothing here rests on documentation. Every C source of the test suite compiles for NetBSD 9.4 and 10.1, OpenBSD 7.9 and FreeBSD 14.3 under the flags CMakeLists assigns. macOS 10.13 through 14.0 compile through osxcross against a real 15.5 SDK, with SUBPROCESS_HAVE_CWD 0 below 10.15 and 1 from 10.15 up; the macOS 26 branch cannot be checked because no mirrored SDK goes that far. Unchanged where it was already working: Linux glibc and musl 441/441, AIX 7.2 on POWER8 441/441, Windows MSVC 387/387 in 32- and 64-bit and against both CRTs. Forcing -DSUBPROCESS_SPAWN_VIA_FORK=1 on glibc also passes 441/441, which is how the fork path is exercised without leaving Linux. clang's static analyser reports nothing on either path, and a purpose-built test spawning from eight threads, 640 spawns, passes on both paths under AddressSanitizer.
The test already excludes AArch64 because SDIV by zero yields 0 instead of trapping. Two more architectures behave the same way, and are specified to: PowerPC's divw/divd leave the result undefined and raise no exception unless OE is set, and RISC-V's DIV returns -1, all bits set. On both the child exits 0, so subprocess_fail_divzero cannot hold. Measured with the same source and compiler on each architecture rather than taken from the manuals. x86_64, i386, armv7l, armv6 and s390x die on signal 8; aarch64 exits 0 printing 0, ppc64le exits 0, and riscv64 exits 0 printing -1 — each matching what its ISA specifies. Reproduced on AIX 7.2 TL04 on POWER8, where the suite now passes 441 of 441. 32-bit ARM was checked deliberately and does trap, including ARMv6, which has no divide instruction at all and goes through __aeabi_idiv. The plausible assumption that a software divide routine returns 0 quietly is wrong, so __arm__ is not excluded. _ARCH_PPC is listed beside __powerpc__ because IBM XL C and OpenXL define only the former, and that is the compiler most AIX users reach for.
The first run of this workflow failed all four foreign-arch jobs, and for a
reason that has nothing to do with the architectures: qemu-user does not
propagate the errno that glibc's posix_spawn uses to report a failed exec, so
create_ex_subprocess_create_failure_preserves_error fails there regardless of
the target. s390x failed identically to ppc64le despite having no architectural
difference in play, which is what gives it away.
Running the whole suite under emulation therefore cannot go green, and a job
that is permanently red is worse than no job. These now build the whole suite —
which still covers compilation for each architecture — and run only the tests
whose outcome actually depends on the instruction set:
./subprocess_test '--filter=*divzero'
That is the reason the jobs exist: integer division by zero traps on x86, Arm
and s390x but not on PowerPC or RISC-V. The comment explains the narrowing so it
does not read as suppressing inconvenient failures.
Also noted in the file that the SUBPROCESS_SPAWN_VIA_FORK combination in
capability-paths is a no-op until sheredom#112 lands, so its green tick is not mistaken
for evidence today.
|
Now confirmed on real POWER10 hardware rather than emulation. I rented an IBM Cloud Power Virtual Server — AIX 7.3 TL04 SP01 Results
GCC 13.3.0, CMake 4.2.0. No capability override of any kind — no The blanket
|
| 7.2 TL04 | 7.3 TL04 | |
|---|---|---|
posix_spawn_file_actions_addchdir[_np] in spawn.h |
absent | absent |
exported from libc.a, both members |
absent | absent |
execvpe declared in unistd.h |
no | no |
So @mehendarkarprajwal's comment about execvpe still holds word for word two
releases on, and SUBPROCESS_SPAWN_VIA_FORK needs no version gate for AIX.
Divide-by-zero, third independent measurement
The architecture guard in test_shared.h now has a native data point:
| Platform | result | exit | traps |
|---|---|---|---|
| ppc64le Linux | r=42 |
0 | no |
| AIX 7.2 / POWER8, emulated | — | 0 | no |
| AIX 7.3 / POWER10, native | r=0 |
0 | no |
The returned values differ between POWER8 and POWER10 while the absence of a trap
does not — which is precisely what the ISA specifies, the result being undefined
and only the lack of an exception guaranteed. A guard keyed on the value would
have been wrong.
One dependency, unchanged
utest.h still stops at #error Unsupported platform! on 7.3, so both rows
above need sheredom/utest.h#189 — without it nothing builds on AIX at all. The
32-bit row additionally needs sheredom/utest.h#188, since AIX is rejected before
its 32-bit problems are even reached. Verified one variant at a time on this
machine. The vendored copy in this repository is deliberately untouched.
Happy to run anything else you would like checked while I have the machine.
|
Three more checks on the same POWER10 machine, each covering something emulation Negative controlThe results so far show the branch works. They do not, on their own, show why — The probes resolve as intended here, checked by printing them rather than reading fork() from threads
RepeatabilityFive consecutive runs of the full suite, all |
|
Thank youu @bernardladenthin for all the effort |
| #include <unistd.h> | ||
| #endif | ||
|
|
||
| /* __NetBSD_Version__ lives in <sys/param.h>, not in the compiler's predefines, |
There was a problem hiding this comment.
This comment is verbose and not interesting, just remove it.
| #endif | ||
| #endif | ||
|
|
||
| /* Whether to launch the child with fork()+exec() instead of posix_spawn(). |
There was a problem hiding this comment.
Make this comment more succinct please.
| XCU 2.8.2), and the value glibc's posix_spawn used before it learned to | ||
| report exec failures. The real reason travels over the error pipe; this is | ||
| only what a caller sees if that write is lost. */ | ||
| #if !defined(SUBPROCESS_EXEC_FAILURE_STATUS) |
There was a problem hiding this comment.
Why do we even need this define?
| #endif | ||
| if (subprocess_option_search_user_path == | ||
| (options & subprocess_option_search_user_path)) { | ||
| /* AIX exports execvpe from libc but declares it in no header. */ |
There was a problem hiding this comment.
Could we only extern to this in a define block for AIX above the function instead then?
There was a problem hiding this comment.
Moved it above the function, but guarded on SUBPROCESS_SPAWN_VIA_FORK
rather than _AIX.
Narrowing it to AIX breaks glibc: execvpe is a GNU extension there, so
<unistd.h> only declares it when _GNU_SOURCE is set, and anyone forcing
the fork path without that macro loses the declaration. Measured both ways
with -Werror=implicit-function-declaration — AIX-only fails without
_GNU_SOURCE and builds with it. The suite can't see either way, since
test/CMakeLists.txt defines _GNU_SOURCE on Linux. musl hides it the same
way.
The comment is no longer AIX-specific either: OpenBSD and NetBSD reach this
code too, and both do have execvpe.
Reviewer feedback on sheredom#113: the file carried explanatory prose that repeated what the job and matrix names state. The paragraphs above capability-paths, linux-32-bit, musl and macos-deployment-target are gone entirely — "no chdir file action, no exec error reporting" and "macOS deployment target 10.14" already say it. Two survive as single lines, both where deleting the comment would invite a wrong edit rather than merely lose background: foreign-arch why it runs only *divzero. Without it the narrowing looks like an oversight and the obvious "fix" is a permanently red job. bsd why NetBSD is listed twice, so the duplicate does not get tidied away and take the 9.x path with it. The note that SUBPROCESS_SPAWN_VIA_FORK is a no-op until sheredom#112 lands is dropped as well. It was accurate when written but describes a moment rather than the workflow, and it goes stale the day sheredom#112 merges. No job, step or matrix entry is touched: 7 jobs, 17 runs, before and after. The reasoning that left the file is preserved in the two commits that added it and in the PR description.
Review feedback on sheredom#112. The paragraph above the <sys/param.h> include is gone. It explained that __NetBSD_Version__ is not a compiler predefine and that omitting the header would fold the version to 0 — true, but the include sits directly under #if defined(__NetBSD__) and needs no defence. The SUBPROCESS_SPAWN_VIA_FORK comment keeps what the macro does, why forking buys anything, and that it can be overridden. Dropped: the list of which platforms lack addchdir, which the #if immediately below states in code, and the sentence about the capabilities this enables further down. Comments only. Preprocessing both revisions with -fpreprocessed -dD -E -P leaves 1563 identical lines of code. Rebuilt anyway: 441/441 on x86_64 glibc, and 441/441 again with -DSUBPROCESS_SPAWN_VIA_FORK=1.
Review feedback on sheredom#112: "Why do we even need this define?" We do not. The value was used exactly once, no test referenced it, the README never mentioned it, and it only ever reaches a caller down a path that practically cannot happen — the parent reads the real errno off the pipe and reaps the child with a discarded status, so 127 surfaces only if that 4-byte write into an empty pipe is lost. Making it configurable was worse than pointless. POSIX requires posix_spawn's child to exit with status 127 when exec fails, so 127 is what keeps the fork path indistinguishable from the posix_spawn path for a caller. The knob's only possible use was to break that equivalence, and it offered nothing on the posix_spawn side, where the value comes from libc and cannot be overridden. The macro was introduced by this PR and never released, so nothing depends on it. 441/441 on x86_64 glibc, and again with -DSUBPROCESS_SPAWN_VIA_FORK=1.
Review feedback on sheredom#112: "Could we only extern to this in a define block for AIX above the function instead then?" Moved out of the function body, but guarded on SUBPROCESS_SPAWN_VIA_FORK rather than _AIX. Restricting it to AIX breaks glibc: execvpe is a GNU extension there, so <unistd.h> only declares it when _GNU_SOURCE is set, and a consumer forcing the fork path without that macro would lose the declaration. Measured both ways with -Werror=implicit-function-declaration — the AIX-only variant fails without _GNU_SOURCE and builds with it. The suite cannot see this either way, since test/CMakeLists.txt defines _GNU_SOURCE on Linux. musl hides execvpe the same way. The declaration is also no longer AIX-specific in what it claims: OpenBSD and NetBSD reach this code too, and both do have execvpe. 441/441 on x86_64 glibc, and again with -DSUBPROCESS_SPAWN_VIA_FORK=1. The -D_AIX arm still type-checks, and the fork path compiles on glibc and musl in C and C++, with and without _GNU_SOURCE — the C++ runs matter because glibc declares execvpe __THROW, and a file-scope redeclaration without noexcept would clash where the in-body one did not.
The merge brings in sheredom#115, which creates the stdio pipes close-on-exec. The forked child relies on dup2 to strip that flag when it installs them on 0, 1 and 2 — and dup2 does clear it, except when source and target are the same descriptor, where it is a no-op. That case is reachable: with fd 0 free in the parent, the stdin pipe's read end lands on 0 and the child's dup2(stdinfd[0], STDIN_FILENO) becomes dup2(0, 0). exec then closes the child's stdin. Measured with a parent that closes fd 0 before subprocess_create: this branch before the merge works, upstream main works, the merge of the two does not, and clearing the flag fixes it. fcntl is async-signal-safe, so the child stays within what it may call before exec. Nothing in the suite could see this. It stayed at 442/442 before and after the fix, in both implementations, because no test ran with a standard descriptor closed. The test added here does: it closes fd 0 around a subprocess_create and asserts the child still has a stdin, using the process_is_fd_open helper sheredom#115 brought along. It goes red on the fork path without the fix above and green with it; on the posix_spawn path, which never had the defect, it is green either way. The suite is now 443/443. The test saves and restores fd 0 and defers its assertions until after the restore — an assertion returns early in utest, which would otherwise leave every later test in the binary without a stdin.
|
Merged current #115 now creates the stdio pipes close-on-exec, and the forked child relies on Neither change causes it on its own:
Fixed by clearing Worth noting the suite could not have caught this: it stayed at 442/442 in |
|
Ok I've merged that dependent PR. You might want to rebase and repush! |
subprocess_fds_above_std, added in sheredom#118, moves every pipe end above STDERR on both pipe-creation paths, so dup2 in the child can no longer be a no-op on a standard descriptor. Reverts the addition from this branch as promised in sheredom#118.
|
Brought up to date with current One deviation from your wording: I merged Also dropped the
Full suite, gcc 13, x86-64: 444/444 with sheredom/utest.h#188, #189 and #190 are all merged now, so the dependency note |
<sys/param.h>, included here since sheredom#112 for __NetBSD_Version__, pulls in <sys/inttypes.h>. That header gates <machine/int_fmtio.h> on __STDC_FORMAT_MACROS for C++ before C++11 and sits behind an include guard, so anything defining the macro afterwards cannot get PRId64 and PRIu64 back. Consumers including subprocess.h first lost them silently; the test suite's C++98 translation unit is one.
* Add a portability workflow beside the existing matrix
cmake.yml covers x86-64 Linux, Windows and macOS across four build types and
four compilers, and it covers them well. What it cannot reach is anything that
is not selected by the runner it happens to be on: the capability probes only
ever resolve one way per machine, every runner is 64-bit, and no BSD or non-x86
architecture is present at all.
The new workflow adds one job per dimension:
capability-paths force each combination of SUBPROCESS_HAVE_CWD,
SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS and
SUBPROCESS_SPAWN_VIA_FORK on an ordinary runner
linux-32-bit, windows-x86 ILP32, where uint64_t becomes unsigned long long
musl a second libc
foreign-arch ppc64le, riscv64, s390x, armv7 under qemu-user
bsd real FreeBSD, NetBSD 9.4 and 10.1, OpenBSD VMs
macos-deployment-target 10.14, 10.15 and 11.0, either side of the boundary
that SUBPROCESS_HAVE_CWD turns on
capability-paths is the cheapest and catches the most. The library selects its
implementation at compile time, so on any given runner the other paths are never
executed — including the ones added by #104 and #106, which as far as I can tell
had never actually run anywhere before, only compiled.
NetBSD is listed at both 9.4 and 10.1 on purpose: posix_spawn_file_actions_addchdir
arrived in 10.0 and is spelled without the _np suffix, so the two take different
paths through the header.
foreign-arch runs under qemu-user, which is faithful for instruction-set
semantics but not for posix_spawn's exec-error reporting — it does not propagate
the errno, and a create_ex failure there would be an artifact rather than a
platform property. That is noted in the file so nobody reads it the wrong way.
* Narrow the emulated-architecture jobs to what emulation reproduces
The first run of this workflow failed all four foreign-arch jobs, and for a
reason that has nothing to do with the architectures: qemu-user does not
propagate the errno that glibc's posix_spawn uses to report a failed exec, so
create_ex_subprocess_create_failure_preserves_error fails there regardless of
the target. s390x failed identically to ppc64le despite having no architectural
difference in play, which is what gives it away.
Running the whole suite under emulation therefore cannot go green, and a job
that is permanently red is worse than no job. These now build the whole suite —
which still covers compilation for each architecture — and run only the tests
whose outcome actually depends on the instruction set:
./subprocess_test '--filter=*divzero'
That is the reason the jobs exist: integer division by zero traps on x86, Arm
and s390x but not on PowerPC or RISC-V. The comment explains the narrowing so it
does not read as suppressing inconvenient failures.
Also noted in the file that the SUBPROCESS_SPAWN_VIA_FORK combination in
capability-paths is a no-op until #112 lands, so its green tick is not mistaken
for evidence today.
* Trim the workflow comments to what the YAML does not already say
Reviewer feedback on #113: the file carried explanatory prose that repeated
what the job and matrix names state. The paragraphs above capability-paths,
linux-32-bit, musl and macos-deployment-target are gone entirely — "no chdir
file action, no exec error reporting" and "macOS deployment target 10.14"
already say it.
Two survive as single lines, both where deleting the comment would invite a
wrong edit rather than merely lose background:
foreign-arch why it runs only *divzero. Without it the narrowing looks
like an oversight and the obvious "fix" is a permanently
red job.
bsd why NetBSD is listed twice, so the duplicate does not get
tidied away and take the 9.x path with it.
The note that SUBPROCESS_SPAWN_VIA_FORK is a no-op until #112 lands is
dropped as well. It was accurate when written but describes a moment rather
than the workflow, and it goes stale the day #112 merges.
No job, step or matrix entry is touched: 7 jobs, 17 runs, before and after.
The reasoning that left the file is preserved in the two commits that added
it and in the PR description.
* Refresh the vendored utest.h to sheredom/utest.h@b6230bc
The old copy is a pristine snapshot of utest.h@f4610ee. Since then upstream
fixed the ILP32 constants and the NetBSD PRIu64 problem (#188), the test
filter dropping tests it should run (#190) and added AIX support (#189).
* Keep the PRI macros visible on NetBSD in C++98
<sys/param.h>, included here since #112 for __NetBSD_Version__, pulls in
<sys/inttypes.h>. That header gates <machine/int_fmtio.h> on
__STDC_FORMAT_MACROS for C++ before C++11 and sits behind an include guard,
so anything defining the macro afterwards cannot get PRId64 and PRIu64 back.
Consumers including subprocess.h first lost them silently; the test suite's
C++98 translation unit is one.
* Fall back to the compiler default where -std=c++2a is unknown too
#105 added -std=c++2a for GCC 8 and clang 9, which do not know -std=c++20.
GCC 7 and older know neither, so the fallback passed a flag those compilers
reject and the build stopped before running anything. NetBSD 9.4 ships GCC
7.5.0 and hit exactly this.
test20.cpp now drops the standard flag when neither spelling is available,
which is what already happens for test11.c without c_std_11 and for MSVC,
where no -std flag is passed at all. The suite then runs that translation
unit at the compiler default rather than not at all.
Measured in GCC 7.5.0: -std=c++20 and -std=c++2a are both rejected, and the
file compiles clean at the default gnu++14 under the flags CMakeLists sets.
GCC 13 still selects -std=c++20 and the suite stays at 444/444.
This continues @mehendarkarprajwal's #109, whose commit is included here
unchanged. It was opened because llama.cpp adopted subprocess.h for launching
llama-server in router mode and AIX does not build. I have since had an AIX 7.2
TL04 system to test on, so this brings the branch up to current
mainandconfirms it on the real platform.
What was wrong.
SUBPROCESS_HAVE_CWDasks about glibc and macOS and hands1to everything else. AIX has noposix_spawn_file_actions_addchdirundereither spelling, so the build fails at link time:
SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORSgets it wrong the same way, and that oneis silent rather than loud — AIX's
posix_spawnreturns success for a binarythat does not exist:
On unpatched
main, AIX needs-DSUBPROCESS_HAVE_CWD=0merely to link and thenfails 27 of 441 tests: 18 from the exec-error problem above, 9 from a
PowerPC property described further down.
And AIX is not alone. Read from each vendor's own
spawn.handlibc:..._addchdir_np..._addchdir, no_npWhat this does
Three commits on top of the merge:
The merge itself. Initial port for AIX #109 was based on
8671cee; five commits have touchedsubprocess.hsince (Fix build on NetBSD. #102, Fix building where posix_spawn_file_actions_addchdir_np is unavailable #104, Fall back to -std=c++2a on compilers without -std=c++20 #105, Report a missing executable on glibc older than 2.24 #106, Handle ENOSYS subprocess errors #110). Rather than replay theincoming restructuring of the
posix_spawnpath, the conflict is resolved infavour of
mainfor that path and thefork()implementation is added besideit — +183/-7 against
mainwhere Initial port for AIX #109 is +227/-97, so theposix_spawncode is byte-identical and no other platform can regress.
Changes to @mehendarkarprajwal's code while merging: dropped the duplicate
function-scope declarations that broke every non-AIX build under
-Werror;named the child's fallback exit status instead of repeating
127six times;restarted
read()andwaitpid()onEINTRand required a fullsizeof(int)from the error pipe; and closed the pipe ends in the child onlyafter duplicating them, and only those above
STDERR_FILENO.Two questions instead of one.
SUBPROCESS_ADDCHDIR_IS_POSIXsays whetherthe POSIX 2024 spelling exists (macOS 26+, NetBSD 10+);
SUBPROCESS_SPAWN_VIA_FORKsays neither exists so fork is needed (AIX,OpenBSD, NetBSD < 10). The call site asks those rather than naming platforms,
which also fixes Fix build on NetBSD. #102's version-less
defined(__NetBSD__)guard — right forNetBSD 10, wrong for 9 and older.
An architecture guard for
create_subprocess_fail_divzero. PowerPC'sdivw/divdleave the result undefined and raise no exception, so the childexits 0. The test already excludes AArch64 for exactly this; RISC-V belongs
there too, its
DIVreturning -1. Measured per architecture rather than takenfrom manuals: x86_64, i386, armv7l, armv6 and s390x die on signal 8; aarch64,
ppc64le and riscv64 exit 0. Reproduced on ppc64le Linux, so this is
architecture, not AIX.
Testing on any Linux runner
To your question on #109, @sheredom: the fork path can be exercised without AIX,
because the implementation is selected by a macro rather than by
_AIX.That would have caught the build error #109 currently has, and it is cheap
enough to add to CI.
Results
-maix64-DSUBPROCESS_SPAWN_VIA_FORK=1-DSUBPROCESS_HAVE_CWD=0 -DSUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS=0SUBPROCESS_HAVE_CWD0 below 10.15 and 1 from 10.15The fail-closed row is worth calling out: those are the paths added by #104 and
#106, and as far as I can tell they had never actually been executed anywhere
before, only compiled.
Also run:
clang --analyzereports nothing on either path, and a purpose-builttest spawning from eight threads — 640 spawns — passes on both paths under
AddressSanitizer, since
fork()in a threaded process is the risky part and thesuite is single-threaded.
Not verified: the macOS 26 branch of
SUBPROCESS_ADDCHDIR_IS_POSIX. No publiclymirrored SDK goes past 15.5.
The utest.h dependency is resolved
The test suite could not build on AIX until
utest.hgained an AIX branch, andit did not build 32-bit at all, which is GCC's default there. Both are now
merged upstream — sheredom/utest.h#189 and sheredom/utest.h#188.
The vendored
test/utest.hin this repository is still the older copy, so thosefixes have not reached it yet. That is a separate, one-file change and
deliberately not part of this PR.