1010
1111import TSCLibc
1212import Foundation
13+ #if os(Windows)
14+ import WinSDK
15+ #endif
1316
1417#if os(Windows)
1518public let executableFileSuffix = " .exe "
1619#else
1720public let executableFileSuffix = " "
1821#endif
1922
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+
2087/// Replace the current process image with a new process image.
2188///
2289/// - Parameters:
@@ -25,20 +92,78 @@ public let executableFileSuffix = ""
2592public func exec( path: String , args: [ String ] ) throws -> Never {
2693 let cArgs = CStringArray ( args)
2794 #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)
35100 }
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) )
36161 #elseif (!canImport(Darwin) || os(macOS))
37162 guard execv ( path, cArgs. cArray) != - 1 else {
38163 throw SystemError . exec ( errno, path: path, args: args)
39164 }
40- #endif
41165 fatalError ( " unreachable " )
166+ #endif
42167}
43168
44169@_disfavoredOverload
0 commit comments