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