Skip to content

Commit 3d88b68

Browse files
Remove preExecProcessAction (#149)
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 3d88b68

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)