Skip to content

Commit c7fc844

Browse files
committed
Remove preExecProcessAction
It's been deemed too tricky to make this API safe and there are relatively few use cases anyways, considering we want to use posix_spawn internally as much as possible. Closes #148
1 parent 3cc99d0 commit c7fc844

File tree

6 files changed

+10
-172
lines changed

6 files changed

+10
-172
lines changed

Sources/Subprocess/Platforms/Subprocess+Darwin.swift

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -81,29 +81,6 @@ public struct PlatformOptions: Sendable {
8181
inout posix_spawn_file_actions_t?
8282
) throws -> Void
8383
)? = nil
84-
/// A closure to configure platform-specific
85-
/// spawning constructs. This closure enables direct
86-
/// configuration or override of underlying platform-specific
87-
/// spawn settings that `Subprocess` utilizes internally,
88-
/// in cases where Subprocess does not provide higher-level
89-
/// APIs for such modifications.
90-
///
91-
/// On Darwin, Subprocess uses `posix_spawn()` as the
92-
/// underlying spawning mechanism, but may require an initial `fork()`
93-
/// depending on the configured `PlatformOptions`.
94-
/// This closure is called after `fork()` but before `posix_spawn()`
95-
/// (with the `POSIX_SPAWN_SETEXEC` flag set).
96-
/// You may use it to call any necessary process setup functions.
97-
///
98-
/// - note: You can set both `preExecProcessAction` and
99-
/// `preSpawnProcessConfigurator` and both will be called.
100-
/// Setting `preExecProcessAction` will always cause Subprocess
101-
/// to pre-`fork()` before calling `posix_spawn()` (with the
102-
/// `POSIX_SPAWN_SETEXEC` flag set) even if it would not have otherwise
103-
/// done so based on the configured `PlatformOptions`.
104-
///
105-
/// - warning: You may ONLY call [async-signal-safe functions](https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html) within this closure (note _"The following table defines a set of functions and function-like macros that shall be async-signal-safe."_).
106-
public var preExecProcessAction: (@convention(c) @Sendable () -> Void)? = nil
10784

10885
public init() {}
10986
}
@@ -161,7 +138,6 @@ extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible
161138
\(indent) processGroupID: \(String(describing: processGroupID)),
162139
\(indent) createSession: \(createSession),
163140
\(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set")
164-
\(indent) preExecProcessAction: \(self.preExecProcessAction == nil ? "not set" : "set")
165141
\(indent))
166142
"""
167143
}
@@ -426,8 +402,7 @@ extension Configuration {
426402
gidPtr,
427403
Int32(supplementaryGroups?.count ?? 0),
428404
sgroups?.baseAddress,
429-
self.platformOptions.createSession ? 1 : 0,
430-
self.platformOptions.preExecProcessAction,
405+
self.platformOptions.createSession ? 1 : 0
431406
)
432407
}
433408
}

Sources/Subprocess/Platforms/Subprocess+Linux.swift

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,7 @@ extension Configuration {
151151
processGroupIDPtr,
152152
CInt(supplementaryGroups?.count ?? 0),
153153
sgroups?.baseAddress,
154-
self.platformOptions.createSession ? 1 : 0,
155-
self.platformOptions.preExecProcessAction
154+
self.platformOptions.createSession ? 1 : 0
156155
)
157156
}
158157
}
@@ -284,20 +283,6 @@ public struct PlatformOptions: Sendable {
284283
/// the child process terminates.
285284
/// Always ends in sending a `.kill` signal at the end.
286285
public var teardownSequence: [TeardownStep] = []
287-
/// A closure to configure platform-specific
288-
/// spawning constructs. This closure enables direct
289-
/// configuration or override of underlying platform-specific
290-
/// spawn settings that `Subprocess` utilizes internally,
291-
/// in cases where Subprocess does not provide higher-level
292-
/// APIs for such modifications.
293-
///
294-
/// On Linux, Subprocess uses `fork/exec` as the
295-
/// underlying spawning mechanism. This closure is called
296-
/// after `fork()` but before `exec()`. You may use it to
297-
/// call any necessary process setup functions.
298-
///
299-
/// - warning: You may ONLY call [async-signal-safe functions](https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html) within this closure (note _"The following table defines a set of functions and function-like macros that shall be async-signal-safe."_).
300-
public var preExecProcessAction: (@convention(c) @Sendable () -> Void)? = nil
301286

302287
public init() {}
303288
}
@@ -311,8 +296,7 @@ extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible
311296
\(indent) groupID: \(String(describing: groupID)),
312297
\(indent) supplementaryGroups: \(String(describing: supplementaryGroups)),
313298
\(indent) processGroupID: \(String(describing: processGroupID)),
314-
\(indent) createSession: \(createSession),
315-
\(indent) preExecProcessAction: \(self.preExecProcessAction == nil ? "not set" : "set")
299+
\(indent) createSession: \(createSession)
316300
\(indent))
317301
"""
318302
}

Sources/_SubprocessCShims/include/process_shims.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ int _subprocess_spawn(
4343
uid_t * _Nullable uid,
4444
gid_t * _Nullable gid,
4545
int number_of_sgroups, const gid_t * _Nullable sgroups,
46-
int create_session,
47-
void (* _Nullable configurator)(void)
46+
int create_session
4847
);
4948
#endif // TARGET_OS_MAC
5049

@@ -60,8 +59,7 @@ int _subprocess_fork_exec(
6059
gid_t * _Nullable gid,
6160
gid_t * _Nullable process_group_id,
6261
int number_of_sgroups, const gid_t * _Nullable sgroups,
63-
int create_session,
64-
void (* _Nullable configurator)(void)
62+
int create_session
6563
);
6664

6765
int _was_process_exited(int status);

Sources/_SubprocessCShims/process_shims.c

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,7 @@ static int _subprocess_spawn_prefork(
9797
uid_t * _Nullable uid,
9898
gid_t * _Nullable gid,
9999
int number_of_sgroups, const gid_t * _Nullable sgroups,
100-
int create_session,
101-
void (* _Nullable configurator)(void)
100+
int create_session
102101
) {
103102
#define write_error_and_exit int error = errno; \
104103
write(pipefd[1], &error, sizeof(error));\
@@ -191,11 +190,6 @@ static int _subprocess_spawn_prefork(
191190
(void)setsid();
192191
}
193192

194-
// Run custom configuratior
195-
if (configurator != NULL) {
196-
configurator();
197-
}
198-
199193
// Use posix_spawnas exec
200194
int error = posix_spawn(pid, exec_path, file_actions, spawn_attrs, args, env);
201195
// If we reached this point, something went wrong
@@ -244,22 +238,20 @@ int _subprocess_spawn(
244238
uid_t * _Nullable uid,
245239
gid_t * _Nullable gid,
246240
int number_of_sgroups, const gid_t * _Nullable sgroups,
247-
int create_session,
248-
void (* _Nullable configurator)(void)
241+
int create_session
249242
) {
250243
int require_pre_fork = uid != NULL ||
251244
gid != NULL ||
252245
number_of_sgroups > 0 ||
253-
create_session > 0 ||
254-
configurator != NULL;
246+
create_session > 0;
255247

256248
if (require_pre_fork != 0) {
257249
int rc = _subprocess_spawn_prefork(
258250
pid,
259251
exec_path,
260252
file_actions, spawn_attrs,
261253
args, env,
262-
uid, gid, number_of_sgroups, sgroups, create_session, configurator
254+
uid, gid, number_of_sgroups, sgroups, create_session
263255
);
264256
return rc;
265257
}
@@ -486,8 +478,7 @@ int _subprocess_fork_exec(
486478
gid_t * _Nullable gid,
487479
gid_t * _Nullable process_group_id,
488480
int number_of_sgroups, const gid_t * _Nullable sgroups,
489-
int create_session,
490-
void (* _Nullable configurator)(void)
481+
int create_session
491482
) {
492483
#define write_error_and_exit int error = errno; \
493484
write(pipefd[1], &error, sizeof(error));\
@@ -681,10 +672,6 @@ int _subprocess_fork_exec(
681672
}
682673
}
683674

684-
// Run custom configuratior
685-
if (configurator != NULL) {
686-
configurator();
687-
}
688675
// Finally, exec
689676
execve(exec_path, args, env);
690677
// If we reached this point, something went wrong

Tests/SubprocessTests/DarwinTests.swift

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -26,78 +26,6 @@ import Testing
2626
// MARK: PlatformOptions Tests
2727
@Suite(.serialized)
2828
struct SubprocessDarwinTests {
29-
@Test func testSubprocessPlatformOptionsPreExecProcessAction() async throws {
30-
var platformOptions = PlatformOptions()
31-
platformOptions.preExecProcessAction = {
32-
exit(1234567)
33-
}
34-
let idResult = try await Subprocess.run(
35-
.path("/bin/pwd"),
36-
platformOptions: platformOptions,
37-
output: .discarded
38-
)
39-
#expect(idResult.terminationStatus == .exited(1234567))
40-
}
41-
42-
@Test(
43-
.disabled("Constantly fails on macOS 26 and Swift 6.2"),
44-
.bug("https://github.com/swiftlang/swift-subprocess/issues/148")
45-
) func testSubprocessPlatformOptionsPreExecProcessActionAndProcessConfigurator() async throws {
46-
let (readFD, writeFD) = try FileDescriptor.pipe()
47-
try await readFD.closeAfter {
48-
let childPID = try await writeFD.closeAfter {
49-
// Allocate some constant high-numbered FD that's unlikely to be used.
50-
let specialFD = try writeFD.duplicate(as: FileDescriptor(rawValue: 9000))
51-
return try await specialFD.closeAfter {
52-
// Make the fd non-blocking just to avoid the test hanging if it fails
53-
let opts = fcntl(specialFD.rawValue, F_GETFD)
54-
#expect(opts >= 0)
55-
#expect(fcntl(specialFD.rawValue, F_SETFD, opts | O_NONBLOCK) >= 0)
56-
57-
var platformOptions = PlatformOptions()
58-
platformOptions.preExecProcessAction = {
59-
var pid: Int32 = getpid()
60-
if write(9000, &pid, 4) != 4 {
61-
exit(EXIT_FAILURE)
62-
}
63-
}
64-
platformOptions.preSpawnProcessConfigurator = { spawnAttr, _ in
65-
// Set POSIX_SPAWN_SETSID flag, which implies calls
66-
// to setsid
67-
var flags: Int16 = 0
68-
posix_spawnattr_getflags(&spawnAttr, &flags)
69-
posix_spawnattr_setflags(&spawnAttr, flags | Int16(POSIX_SPAWN_SETSID))
70-
}
71-
// Check the process ID (pid), process group ID (pgid), and
72-
// controlling terminal's process group ID (tpgid)
73-
let psResult = try await Subprocess.run(
74-
.path("/bin/bash"),
75-
arguments: ["-c", "ps -o pid,pgid,tpgid -p $$"],
76-
platformOptions: platformOptions,
77-
input: .none,
78-
error: .discarded
79-
) { execution, standardOutput in
80-
var buffer = Data()
81-
for try await chunk in standardOutput {
82-
let currentChunk = chunk.withUnsafeBytes { Data($0) }
83-
buffer += currentChunk
84-
}
85-
return (pid: execution.processIdentifier.value, standardOutput: buffer)
86-
}
87-
try assertNewSessionCreated(terminationStatus: psResult.terminationStatus, output: String(decoding: psResult.value.standardOutput, as: UTF8.self))
88-
return psResult.value.pid
89-
}
90-
}
91-
92-
let bytes = try await readFD.readUntilEOF(upToLength: 4)
93-
var pid: Int32 = -1
94-
_ = withUnsafeMutableBytes(of: &pid) { ptr in
95-
bytes.copyBytes(to: ptr)
96-
}
97-
#expect(pid == childPID)
98-
}
99-
}
100-
10129
@Test func testSubprocessPlatformOptionsProcessConfiguratorUpdateSpawnAttr() async throws {
10230
var platformOptions = PlatformOptions()
10331
platformOptions.preSpawnProcessConfigurator = { spawnAttr, _ in

Tests/SubprocessTests/LinuxTests.swift

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,40 +29,6 @@ import _SubprocessCShims
2929
// MARK: PlatformOption Tests
3030
@Suite(.serialized)
3131
struct SubprocessLinuxTests {
32-
@Test(
33-
.enabled(
34-
if: getgid() == 0,
35-
"This test requires root privileges"
36-
)
37-
)
38-
func testSubprocessPlatformOptionsPreExecProcessAction() async throws {
39-
var platformOptions = PlatformOptions()
40-
platformOptions.preExecProcessAction = {
41-
guard setgid(4321) == 0 else {
42-
// Returns EPERM when:
43-
// The calling process is not privileged (does not have the
44-
// CAP_SETGID capability in its user namespace), and gid does
45-
// not match the real group ID or saved set-group-ID of the
46-
// calling process.
47-
abort()
48-
}
49-
}
50-
let idResult = try await Subprocess.run(
51-
.path("/usr/bin/id"),
52-
arguments: ["-g"],
53-
platformOptions: platformOptions,
54-
output: .string(limit: 32),
55-
error: .string(limit: 32)
56-
)
57-
let error = try #require(idResult.standardError)
58-
try #require(error == "")
59-
#expect(idResult.terminationStatus.isSuccess)
60-
let id = try #require(idResult.standardOutput)
61-
#expect(
62-
id.trimmingCharacters(in: .whitespacesAndNewlines) == "\(4321)"
63-
)
64-
}
65-
6632
@Test func testSuspendResumeProcess() async throws {
6733
func blockAndWaitForStatus(
6834
pid: pid_t,

0 commit comments

Comments
 (0)