From fd48c477a4298daa6ceb76753f81016a9d184417 Mon Sep 17 00:00:00 2001 From: Bernard Ladenthin Date: Sat, 1 Aug 2026 15:04:39 +0200 Subject: [PATCH 1/4] Fix building against glibc older than 2.29 posix_spawn_file_actions_addchdir_np arrived in glibc 2.29, so subprocess_create_ex fails to compile and link on older systems such as manylinux2014. Add a SUBPROCESS_HAVE_CWD probe, report a requested cwd as ENOSYS where the call is unavailable, and skip the cwd test there. --- subprocess.h | 14 ++++++++++++++ test/test_shared.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/subprocess.h b/subprocess.h index 5e80902..c88646d 100644 --- a/subprocess.h +++ b/subprocess.h @@ -274,6 +274,18 @@ subprocess_weak int subprocess_alive(struct subprocess_s *const process); #include #endif +/* Whether subprocess_create_ex can honour process_cwd. glibc only gained + posix_spawn_file_actions_addchdir_np in 2.29. */ +#if defined(__GLIBC__) +#if __GLIBC_PREREQ(2, 29) +#define SUBPROCESS_HAVE_CWD 1 +#else +#define SUBPROCESS_HAVE_CWD 0 +#endif +#else +#define SUBPROCESS_HAVE_CWD 1 +#endif + #if defined(_WIN32) #include @@ -1207,6 +1219,8 @@ int subprocess_create_ex(const char *const commandLine[], int options, if (process_cwd) { #if defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED >= 260000 posix_error = posix_spawn_file_actions_addchdir(&actions, process_cwd); +#elif !SUBPROCESS_HAVE_CWD + posix_error = ENOSYS; #else #if defined(__APPLE__) && defined(__clang__) #pragma clang diagnostic push diff --git a/test/test_shared.h b/test/test_shared.h index a694ed6..b6a9706 100644 --- a/test/test_shared.h +++ b/test/test_shared.h @@ -1116,6 +1116,7 @@ SUBPROCESS_TEST(environment, specify_environment) { ASSERT_EQ(0, subprocess_destroy(&process)); } +#if SUBPROCESS_HAVE_CWD SUBPROCESS_TEST(create_ex, subprocess_cwd) { char current_path[4096]; char target_path[4096]; @@ -1156,6 +1157,7 @@ SUBPROCESS_TEST(create_ex, subprocess_cwd) { ASSERT_EQ(0, subprocess_destroy(&process)); } +#endif #if !defined(_MSC_VER) SUBPROCESS_TEST(executable_resolve, no_slashes_with_environment) { From 620ce442567657429578d2da7d1278c428d0066e Mon Sep 17 00:00:00 2001 From: Bernard Ladenthin Date: Sat, 1 Aug 2026 15:54:05 +0200 Subject: [PATCH 2/4] Allow overriding SUBPROCESS_HAVE_CWD and document it musl gained posix_spawn_file_actions_addchdir_np in 1.1.24 but exposes no version macro, so the detection cannot cover older releases. Skip it when the macro is already defined, letting those users set it themselves. --- README.md | 6 ++++++ subprocess.h | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e03e98b..7ee711d 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,12 @@ pass `NULL` to inherit the parent's current working directory. On Windows, custom environment entries and the current working directory string are interpreted as UTF-8. +Not every platform can honour a custom working directory. Where it cannot, +`SUBPROCESS_HAVE_CWD` is defined to `0` and passing a non-`NULL` working +directory fails with `ENOSYS`; this currently affects glibc older than 2.29. +Define `SUBPROCESS_HAVE_CWD` yourself to override the detection, for instance +on musl older than 1.1.24. + Note though that you **cannot** specify `subprocess_option_inherit_environment` with a custom environment. If you want to merge some custom environment with the parent process environment then it is up to you as the user to query the original diff --git a/subprocess.h b/subprocess.h index c88646d..dc0153c 100644 --- a/subprocess.h +++ b/subprocess.h @@ -275,7 +275,9 @@ subprocess_weak int subprocess_alive(struct subprocess_s *const process); #endif /* Whether subprocess_create_ex can honour process_cwd. glibc only gained - posix_spawn_file_actions_addchdir_np in 2.29. */ + posix_spawn_file_actions_addchdir_np in 2.29. Define this yourself to + override the detection, for instance on musl older than 1.1.24. */ +#if !defined(SUBPROCESS_HAVE_CWD) #if defined(__GLIBC__) #if __GLIBC_PREREQ(2, 29) #define SUBPROCESS_HAVE_CWD 1 @@ -285,6 +287,7 @@ subprocess_weak int subprocess_alive(struct subprocess_s *const process); #else #define SUBPROCESS_HAVE_CWD 1 #endif +#endif #if defined(_WIN32) From 71c351623dcf3ff2d1ae5a60fc0feb6f9fd0ab93 Mon Sep 17 00:00:00 2001 From: Bernard Ladenthin Date: Sat, 1 Aug 2026 17:29:30 +0200 Subject: [PATCH 3/4] Report a missing executable on glibc older than 2.24 --- subprocess.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/subprocess.h b/subprocess.h index dc0153c..6b4987e 100644 --- a/subprocess.h +++ b/subprocess.h @@ -289,6 +289,20 @@ subprocess_weak int subprocess_alive(struct subprocess_s *const process); #endif #endif +/* Whether posix_spawn reports a failed exec back to the caller. glibc only + started doing so in 2.24; before that the child silently exits with 127. */ +#if !defined(SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS) +#if defined(__GLIBC__) +#if __GLIBC_PREREQ(2, 24) +#define SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS 1 +#else +#define SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS 0 +#endif +#else +#define SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS 1 +#endif +#endif + #if defined(_WIN32) #include @@ -1346,6 +1360,17 @@ int subprocess_create_ex(const char *const commandLine[], int options, goto cleanup; } } else { +#if !SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS + /* posix_spawn cannot tell us the exec failed, so check up front */ + if (0 != access(commandLine[0], X_OK)) { + saved_errno = errno; + result = subprocess_error_from_errno(saved_errno); + if (subprocess_error_unknown == result) { + result = subprocess_error_spawn; + } + goto cleanup; + } +#endif posix_error = posix_spawn(&child, commandLine[0], &actions, SUBPROCESS_NULL, SUBPROCESS_CONST_CAST(char *const *, commandLine), From a52eae7283446a82506ba67cfc8649cd551e4ebd Mon Sep 17 00:00:00 2001 From: Bernard Ladenthin Date: Sun, 9 Aug 2026 12:56:17 +0200 Subject: [PATCH 4/4] Restore the exec-error probe lost in the merge Merging main after #104 dropped the SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS block while keeping its use site, so the macro was undefined and folded to 0. That turned the access() pre-check on unconditionally, on every platform, which is the opposite of what this change intends. --- subprocess.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/subprocess.h b/subprocess.h index 07605ea..ed05dfb 100644 --- a/subprocess.h +++ b/subprocess.h @@ -293,6 +293,20 @@ subprocess_weak int subprocess_alive(struct subprocess_s *const process); #endif #endif +/* Whether posix_spawn reports a failed exec back to the caller. glibc only + started doing so in 2.24; before that the child silently exits with 127. */ +#if !defined(SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS) +#if defined(__GLIBC__) +#if __GLIBC_PREREQ(2, 24) +#define SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS 1 +#else +#define SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS 0 +#endif +#else +#define SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS 1 +#endif +#endif + #if defined(_WIN32) #include