10
10
11
11
import TSCLibc
12
12
import Foundation
13
+ #if os(Windows)
14
+ import WinSDK
15
+ #endif
13
16
14
17
#if os(Windows)
15
18
public let executableFileSuffix = " .exe "
16
19
#else
17
20
public let executableFileSuffix = " "
18
21
#endif
19
22
23
+ #if os(Windows)
24
+ private func quote( _ arguments: [ String ] ) -> String {
25
+ func quote( argument: String ) -> String {
26
+ if !argument. contains ( where: { " \t \n \" " . contains ( $0) } ) {
27
+ return argument
28
+ }
29
+
30
+ // To escape the command line, we surround the argument with quotes.
31
+ // However, the complication comes due to how the Windows command line
32
+ // parser treats backslashes (\) and quotes (").
33
+ //
34
+ // - \ is normally treated as a literal backslash
35
+ // e.g. alpha\beta\gamma => alpha\beta\gamma
36
+ // - The sequence \" is treated as a literal "
37
+ // e.g. alpha\"beta => alpha"beta
38
+ //
39
+ // But then what if we are given a path that ends with a \?
40
+ //
41
+ // Surrounding alpha\beta\ with " would be "alpha\beta\" which would be
42
+ // an unterminated string since it ends on a literal quote. To allow
43
+ // this case the parser treats:
44
+ //
45
+ // - \\" as \ followed by the " metacharacter
46
+ // - \\\" as \ followed by a literal "
47
+ //
48
+ // In general:
49
+ // - 2n \ followed by " => n \ followed by the " metacharacter
50
+ // - 2n + 1 \ followed by " => n \ followed by a literal "
51
+
52
+ var quoted = " \" "
53
+ var unquoted = argument. unicodeScalars
54
+
55
+ while !unquoted. isEmpty {
56
+ guard let firstNonBS = unquoted. firstIndex ( where: { $0 != " \\ " } ) else {
57
+ // String ends with a backslash (e.g. first\second\), escape all
58
+ // the backslashes then add the metacharacter ".
59
+ let count = unquoted. count
60
+ quoted. append ( String ( repeating: " \\ " , count: 2 * count) )
61
+ break
62
+ }
63
+
64
+ let count = unquoted. distance ( from: unquoted. startIndex, to: firstNonBS)
65
+ if unquoted [ firstNonBS] == " \" " {
66
+ // This is a string of \ followed by a " (e.g. first\"second).
67
+ // Escape the backslashes and the quote.
68
+ quoted. append ( String ( repeating: " \\ " , count: 2 * count + 1 ) )
69
+ } else {
70
+ // These are just literal backslashes
71
+ quoted. append ( String ( repeating: " \\ " , count: count) )
72
+ }
73
+
74
+ quoted. append ( String ( unquoted [ firstNonBS] ) )
75
+
76
+ // Drop the backslashes and the following character
77
+ unquoted. removeFirst ( count + 1 )
78
+ }
79
+ quoted. append ( " \" " )
80
+
81
+ return quoted
82
+ }
83
+ return arguments. map ( quote ( argument: ) ) . joined ( separator: " " )
84
+ }
85
+ #endif
86
+
20
87
/// Replace the current process image with a new process image.
21
88
///
22
89
/// - Parameters:
@@ -25,20 +92,78 @@ public let executableFileSuffix = ""
25
92
public func exec( path: String , args: [ String ] ) throws -> Never {
26
93
let cArgs = CStringArray ( args)
27
94
#if os(Windows)
28
- guard cArgs. cArray. withUnsafeBufferPointer ( {
29
- $0. withMemoryRebound ( to: UnsafePointer< Int8>? . self , {
30
- _execv ( path, $0. baseAddress) != - 1
31
- } )
32
- } )
33
- else {
34
- throw SystemError . exec ( errno, path: path, args: args)
95
+ var hJob : HANDLE
96
+
97
+ hJob = CreateJobObjectA ( nil , nil )
98
+ if hJob == HANDLE ( bitPattern: 0 ) {
99
+ throw SystemError . exec ( Int32 ( GetLastError ( ) ) , path: path, args: args)
35
100
}
101
+ defer { CloseHandle ( hJob) }
102
+
103
+ let hPort = CreateIoCompletionPort ( INVALID_HANDLE_VALUE, nil , 0 , 1 )
104
+ if hPort == HANDLE ( bitPattern: 0 ) {
105
+ throw SystemError . exec ( Int32 ( GetLastError ( ) ) , path: path, args: args)
106
+ }
107
+
108
+ var acpAssociation : JOBOBJECT_ASSOCIATE_COMPLETION_PORT = JOBOBJECT_ASSOCIATE_COMPLETION_PORT ( )
109
+ acpAssociation. CompletionKey = hJob
110
+ acpAssociation. CompletionPort = hPort
111
+ if !SetInformationJobObject( hJob, JobObjectAssociateCompletionPortInformation,
112
+ & acpAssociation, DWORD ( MemoryLayout< JOBOBJECT_ASSOCIATE_COMPLETION_PORT> . size) ) {
113
+ throw SystemError . exec ( Int32 ( GetLastError ( ) ) , path: path, args: args)
114
+ }
115
+
116
+ var eliLimits : JOBOBJECT_EXTENDED_LIMIT_INFORMATION = JOBOBJECT_EXTENDED_LIMIT_INFORMATION ( )
117
+ eliLimits. BasicLimitInformation. LimitFlags =
118
+ DWORD ( JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) | DWORD ( JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK)
119
+ if !SetInformationJobObject( hJob, JobObjectExtendedLimitInformation, & eliLimits,
120
+ DWORD ( MemoryLayout< JOBOBJECT_EXTENDED_LIMIT_INFORMATION> . size) ) {
121
+ throw SystemError . exec ( Int32 ( GetLastError ( ) ) , path: path, args: args)
122
+ }
123
+
124
+
125
+ var siInfo : STARTUPINFOW = STARTUPINFOW ( )
126
+ siInfo. cb = DWORD ( MemoryLayout< STARTUPINFOW> . size)
127
+
128
+ var piInfo : PROCESS_INFORMATION = PROCESS_INFORMATION ( )
129
+
130
+ try quote ( args) . withCString ( encodedAs: UTF16 . self) { pwszCommandLine in
131
+ if !CreateProcessW( nil ,
132
+ UnsafeMutablePointer < WCHAR > ( mutating: pwszCommandLine) ,
133
+ nil , nil , false ,
134
+ DWORD ( CREATE_SUSPENDED) | DWORD ( CREATE_NEW_PROCESS_GROUP) ,
135
+ nil , nil , & siInfo, & piInfo) {
136
+ throw SystemError . exec ( Int32 ( GetLastError ( ) ) , path: path, args: args)
137
+ }
138
+ }
139
+
140
+ defer { CloseHandle ( piInfo. hThread) }
141
+ defer { CloseHandle ( piInfo. hProcess) }
142
+
143
+ if !AssignProcessToJobObject( hJob, piInfo. hProcess) {
144
+ throw SystemError . exec ( Int32 ( GetLastError ( ) ) , path: path, args: args)
145
+ }
146
+
147
+ _ = ResumeThread ( piInfo. hThread)
148
+
149
+ var dwCompletionCode : DWORD = 0
150
+ var ulCompletionKey : ULONG_PTR = 0
151
+ var lpOverlapped : LPOVERLAPPED ?
152
+ repeat {
153
+ } while GetQueuedCompletionStatus ( hPort, & dwCompletionCode, & ulCompletionKey,
154
+ & lpOverlapped, INFINITE) &&
155
+ !( ulCompletionKey == ULONG_PTR ( UInt ( bitPattern: hJob) ) &&
156
+ dwCompletionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)
157
+
158
+ var dwExitCode : DWORD = DWORD ( bitPattern: - 1 )
159
+ _ = GetExitCodeProcess ( piInfo. hProcess, & dwExitCode)
160
+ _exit ( Int32 ( bitPattern: dwExitCode) )
36
161
#elseif (!canImport(Darwin) || os(macOS))
37
162
guard execv ( path, cArgs. cArray) != - 1 else {
38
163
throw SystemError . exec ( errno, path: path, args: args)
39
164
}
40
- #endif
41
165
fatalError ( " unreachable " )
166
+ #endif
42
167
}
43
168
44
169
@_disfavoredOverload
0 commit comments