@@ -26,7 +26,18 @@ typealias ProcessID = HANDLE
26
26
typealias ProcessID = Never
27
27
#endif
28
28
29
- #if os(Linux) && !SWT_NO_DYNAMIC_LINKING
29
+ /// A platform-specific wrapper type for various types used by `posix_spawn()`.
30
+ ///
31
+ /// Darwin and Linux differ in their optionality for the `posix_spawn()` types
32
+ /// we use, so we use this typealias to paper over the differences.
33
+ #if SWT_TARGET_OS_APPLE || os(FreeBSD) || os(OpenBSD)
34
+ fileprivate typealias P < T> = T ?
35
+ #elseif os(Linux)
36
+ fileprivate typealias P < T> = T
37
+ #endif
38
+
39
+ #if !SWT_NO_DYNAMIC_LINKING
40
+ #if os(Linux)
30
41
/// Close file descriptors above a given value when spawing a new process.
31
42
///
32
43
/// This symbol is provided because the underlying function was added to glibc
@@ -38,13 +49,30 @@ private let _posix_spawn_file_actions_addclosefrom_np = symbol(named: "posix_spa
38
49
}
39
50
#endif
40
51
41
- /// Spawn a process and wait for it to terminate.
52
+ #if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD)
53
+ /// Change the current directory in the new process.
54
+ ///
55
+ /// This symbol is provided because `posix_spawn_file_actions_addchdir()` and
56
+ /// its non-portable equivalent `posix_spawn_file_actions_addchdir_np()` are not
57
+ /// consistently available across all platforms that implement the
58
+ /// `posix_spawn()` API.
59
+ private let _posix_spawn_file_actions_addchdir = symbol ( named: " posix_spawn_file_actions_addchdir " ) . map {
60
+ castCFunction ( at: $0, to: ( @convention( c) ( UnsafeMutablePointer < P < posix_spawn_file_actions_t > > , UnsafePointer < CChar > ) - > CInt) . self)
61
+ } ?? symbol ( named: " posix_spawn_file_actions_addchdir_np " ) . map {
62
+ castCFunction ( at: $0, to: ( @convention( c) ( UnsafeMutablePointer < P < posix_spawn_file_actions_t > > , UnsafePointer < CChar > ) - > CInt) . self)
63
+ }
64
+ #endif
65
+ #endif
66
+
67
+ /// Spawn a child process.
42
68
///
43
69
/// - Parameters:
44
70
/// - executablePath: The path to the executable to spawn.
45
71
/// - arguments: The arguments to pass to the executable, not including the
46
72
/// executable path.
47
73
/// - environment: The environment block to pass to the executable.
74
+ /// - currentDirectoryPath: The path to use as the executable's initial
75
+ /// working directory.
48
76
/// - standardInput: If not `nil`, a file handle the child process should
49
77
/// inherit as its standard input stream. This file handle must be backed
50
78
/// by a file descriptor and be open for reading.
@@ -61,39 +89,33 @@ private let _posix_spawn_file_actions_addclosefrom_np = symbol(named: "posix_spa
61
89
/// eventually pass this value to ``wait(for:)`` to avoid leaking system
62
90
/// resources.
63
91
///
64
- /// - Throws: Any error that prevented the process from spawning or its exit
65
- /// condition from being read.
92
+ /// - Throws: Any error that prevented the process from spawning.
66
93
func spawnExecutable(
67
94
atPath executablePath: String ,
68
95
arguments: [ String ] ,
69
96
environment: [ String : String ] ,
97
+ currentDirectoryPath: String ? = nil ,
70
98
standardInput: borrowing FileHandle ? = nil ,
71
99
standardOutput: borrowing FileHandle ? = nil ,
72
100
standardError: borrowing FileHandle ? = nil ,
73
101
additionalFileHandles: [ UnsafePointer < FileHandle > ] = [ ]
74
102
) throws -> ProcessID {
75
- // Darwin and Linux differ in their optionality for the posix_spawn types we
76
- // use, so use this typealias to paper over the differences.
77
- #if SWT_TARGET_OS_APPLE || os(FreeBSD) || os(OpenBSD)
78
- typealias P < T> = T ?
79
- #elseif os(Linux)
80
- typealias P < T> = T
81
- #endif
82
-
83
103
#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD)
84
104
return try withUnsafeTemporaryAllocation ( of: P< posix_spawn_file_actions_t> . self , capacity: 1 ) { fileActions in
85
105
let fileActions = fileActions. baseAddress!
86
- guard 0 == posix_spawn_file_actions_init ( fileActions) else {
87
- throw CError ( rawValue: swt_errno ( ) )
106
+ let fileActionsInitialized = posix_spawn_file_actions_init ( fileActions)
107
+ guard 0 == fileActionsInitialized else {
108
+ throw CError ( rawValue: fileActionsInitialized)
88
109
}
89
110
defer {
90
111
_ = posix_spawn_file_actions_destroy ( fileActions)
91
112
}
92
113
93
114
return try withUnsafeTemporaryAllocation ( of: P< posix_spawnattr_t> . self , capacity: 1 ) { attrs in
94
115
let attrs = attrs. baseAddress!
95
- guard 0 == posix_spawnattr_init ( attrs) else {
96
- throw CError ( rawValue: swt_errno ( ) )
116
+ let attrsInitialized = posix_spawnattr_init ( attrs)
117
+ guard 0 == attrsInitialized else {
118
+ throw CError ( rawValue: attrsInitialized)
97
119
}
98
120
defer {
99
121
_ = posix_spawnattr_destroy ( attrs)
@@ -116,6 +138,32 @@ func spawnExecutable(
116
138
flags |= CShort ( POSIX_SPAWN_SETSIGDEF)
117
139
}
118
140
141
+ // Set the current working directory (do this as early as possible, so
142
+ // before inheriting or opening any file descriptors.)
143
+ if let currentDirectoryPath {
144
+ var chdirNeeded = true
145
+
146
+ #if !SWT_NO_DYNAMIC_LINKING
147
+ if let _posix_spawn_file_actions_addchdir {
148
+ chdirNeeded = false
149
+ let directoryChanged = _posix_spawn_file_actions_addchdir ( fileActions, currentDirectoryPath)
150
+ guard 0 == directoryChanged else {
151
+ throw CError ( rawValue: directoryChanged)
152
+ }
153
+ }
154
+ #endif
155
+
156
+ if chdirNeeded {
157
+ // This platform does not support setting the current directory via
158
+ // posix_spawn(), so set it here in the parent process and hope
159
+ // another thread does not stomp on the change (as Foundation does.)
160
+ // Platforms known to take this path: Amazon Linux 2, OpenBSD, QNX
161
+ guard 0 == chdir ( currentDirectoryPath) else {
162
+ throw CError ( rawValue: swt_errno ( ) )
163
+ }
164
+ }
165
+ }
166
+
119
167
// Forward standard I/O streams and any explicitly added file handles.
120
168
var highestFD = max ( STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO)
121
169
func inherit( _ fileHandle: borrowing FileHandle , as standardFD: CInt ? = nil ) throws {
@@ -262,6 +310,7 @@ func spawnExecutable(
262
310
263
311
let commandLine = _escapeCommandLine ( CollectionOfOne ( executablePath) + arguments)
264
312
let environ = environment. map { " \( $0. key) = \( $0. value) " } . joined ( separator: " \0 " ) + " \0 \0 "
313
+ let currentDirectoryPath = currentDirectoryPath. map { Array ( $0. utf16) }
265
314
266
315
var flags = DWORD ( CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT)
267
316
#if DEBUG
@@ -281,7 +330,7 @@ func spawnExecutable(
281
330
true , // bInheritHandles
282
331
flags,
283
332
. init( mutating: environ) ,
284
- nil ,
333
+ currentDirectoryPath ,
285
334
startupInfo. pointer ( to: \. StartupInfo) !,
286
335
& processInfo
287
336
) else {
@@ -396,4 +445,32 @@ private func _escapeCommandLine(_ arguments: [String]) -> String {
396
445
} . joined ( separator: " " )
397
446
}
398
447
#endif
448
+
449
+ /// Spawn a child process and wait for it to terminate.
450
+ ///
451
+ /// - Parameters:
452
+ /// - executablePath: The path to the executable to spawn.
453
+ /// - arguments: The arguments to pass to the executable, not including the
454
+ /// executable path.
455
+ /// - environment: The environment block to pass to the executable.
456
+ /// - currentDirectoryPath: The path to use as the executable's initial
457
+ /// working directory.
458
+ ///
459
+ /// - Returns: The exit status of the spawned process.
460
+ ///
461
+ /// - Throws: Any error that prevented the process from spawning or its exit
462
+ /// condition from being read.
463
+ ///
464
+ /// This function is a convenience that spawns the given process and waits for
465
+ /// it to terminate. It is primarily for use by other targets in this package
466
+ /// such as its cross-import overlays.
467
+ package func spawnExecutableAtPathAndWait(
468
+ atPath executablePath: String ,
469
+ arguments: [ String ] = [ ] ,
470
+ environment: [ String : String ] = [ : ] ,
471
+ currentDirectoryPath: String ? = nil
472
+ ) async throws -> ExitStatus {
473
+ let processID = try spawnExecutable ( atPath: executablePath, arguments: arguments, environment: environment, currentDirectoryPath: currentDirectoryPath)
474
+ return try await wait ( for: processID)
475
+ }
399
476
#endif
0 commit comments