@@ -232,23 +232,238 @@ open class Process: NSObject {
232
232
}
233
233
}
234
234
235
+ #if os(Windows)
236
+ private func _socketpair( ) -> ( first: SOCKET , second: SOCKET ) {
237
+ let listener : SOCKET = socket ( AF_INET, SOCK_STREAM, 0 )
238
+ if listener == INVALID_SOCKET {
239
+ return ( first: INVALID_SOCKET, second: INVALID_SOCKET)
240
+ }
241
+ defer { closesocket ( listener) }
242
+
243
+ var result : Int32 = SOCKET_ERROR
244
+
245
+ var address : sockaddr_in =
246
+ sockaddr_in ( sin_family: ADDRESS_FAMILY ( AF_INET) , sin_port: USHORT ( 0 ) ,
247
+ sin_addr: IN_ADDR ( S_un: in_addr. __Unnamed_union_S_un ( S_addr: ULONG ( " 127.0.0.1 " ) !) ) ,
248
+ sin_zero: ( CHAR ( 0 ) , CHAR ( 0 ) , CHAR ( 0 ) , CHAR ( 0 ) , CHAR ( 0 ) , CHAR ( 0 ) , CHAR ( 0 ) , CHAR ( 0 ) ) )
249
+ withUnsafePointer ( to: & address) {
250
+ $0. withMemoryRebound ( to: sockaddr. self, capacity: 1 ) {
251
+ result = bind ( listener, $0, Int32 ( MemoryLayout< sockaddr_in> . size) )
252
+ }
253
+ }
254
+
255
+ if result == SOCKET_ERROR {
256
+ return ( first: INVALID_SOCKET, second: INVALID_SOCKET)
257
+ }
258
+
259
+ if listen ( listener, 1 ) == SOCKET_ERROR {
260
+ return ( first: INVALID_SOCKET, second: INVALID_SOCKET)
261
+ }
262
+
263
+ withUnsafeMutablePointer ( to: & address) {
264
+ $0. withMemoryRebound ( to: sockaddr. self, capacity: 1 ) {
265
+ var value : Int32 = Int32 ( MemoryLayout< sockaddr_in> . size)
266
+ result = getsockname ( listener, $0, & value)
267
+ }
268
+ }
269
+ if result == SOCKET_ERROR {
270
+ return ( first: INVALID_SOCKET, second: INVALID_SOCKET)
271
+ }
272
+
273
+ let first : SOCKET = socket ( AF_INET, SOCK_STREAM, 0 )
274
+ if first == INVALID_SOCKET {
275
+ return ( first: INVALID_SOCKET, second: INVALID_SOCKET)
276
+ }
277
+
278
+ var value : u_long = 1
279
+ if ioctlsocket ( first, FIONBIO, & value) == SOCKET_ERROR {
280
+ closesocket ( first)
281
+ return ( first: INVALID_SOCKET, second: INVALID_SOCKET)
282
+ }
283
+
284
+ withUnsafePointer ( to: & address) {
285
+ $0. withMemoryRebound ( to: sockaddr. self, capacity: 1 ) {
286
+ result = connect ( first, $0, Int32 ( MemoryLayout< sockaddr_in> . size) )
287
+ }
288
+ }
289
+
290
+ if result == SOCKET_ERROR {
291
+ closesocket ( first)
292
+ return ( first: INVALID_SOCKET, second: INVALID_SOCKET)
293
+ }
294
+
295
+ let second : SOCKET = accept ( listener, nil , nil )
296
+ if second == INVALID_SOCKET {
297
+ closesocket ( first)
298
+ return ( first: INVALID_SOCKET, second: INVALID_SOCKET)
299
+ }
300
+
301
+ return ( first: first, second: second)
302
+ }
303
+ #endif
304
+
235
305
open func run( ) throws {
236
-
237
306
self . processLaunchedCondition. lock ( )
238
307
defer {
239
308
self . processLaunchedCondition. broadcast ( )
240
309
self . processLaunchedCondition. unlock ( )
241
310
}
242
311
243
312
// Dispatch the manager thread if it isn't already running
244
-
245
313
Process . setup ( )
246
-
314
+
247
315
// Ensure that the launch path is set
248
316
guard let launchPath = self . executableURL? . path else {
249
317
throw NSError ( domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError)
250
318
}
251
319
320
+ #if os(Windows)
321
+ // TODO(compnerd) quote the commandline correctly
322
+ // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
323
+ var command : [ String ] = [ launchPath]
324
+ if let arguments = self . arguments {
325
+ command. append ( contentsOf: arguments)
326
+ }
327
+
328
+ var siStartupInfo : STARTUPINFOW = STARTUPINFOW ( )
329
+ siStartupInfo. cb = DWORD ( MemoryLayout< STARTUPINFOW> . size)
330
+
331
+ switch standardInput {
332
+ case let pipe as Pipe :
333
+ siStartupInfo. hStdInput = pipe. fileHandleForReading. handle
334
+ case let handle as FileHandle :
335
+ siStartupInfo. hStdInput = handle. handle
336
+ default : break
337
+ }
338
+
339
+ switch standardOutput {
340
+ case let pipe as Pipe :
341
+ siStartupInfo. hStdOutput = pipe. fileHandleForWriting. handle
342
+ case let handle as FileHandle :
343
+ siStartupInfo. hStdOutput = handle. handle
344
+ default : break
345
+ }
346
+
347
+ switch standardError {
348
+ case let pipe as Pipe :
349
+ siStartupInfo. hStdError = pipe. fileHandleForWriting. handle
350
+ case let handle as FileHandle :
351
+ siStartupInfo. hStdError = handle. handle
352
+ default : break
353
+ }
354
+
355
+ var piProcessInfo : PROCESS_INFORMATION = PROCESS_INFORMATION ( )
356
+
357
+ var environment : [ String : String ] = [ : ]
358
+ if let env = self . environment {
359
+ environment = env
360
+ } else {
361
+ environment = ProcessInfo . processInfo. environment
362
+ environment [ " PWD " ] = currentDirectoryURL. path
363
+ }
364
+
365
+ let szEnvironment : String = environment. map { $0. key + " = " + $0. value } . joined ( separator: " \0 " )
366
+
367
+ let sockets : ( first: SOCKET , second: SOCKET ) = _socketpair ( )
368
+
369
+ var context : CFSocketContext = CFSocketContext ( )
370
+ context. version = 0
371
+ context. retain = runLoopSourceRetain
372
+ context. release = runLoopSourceRelease
373
+ context. info = Unmanaged . passUnretained ( self ) . toOpaque ( )
374
+
375
+ let socket : CFSocket =
376
+ CFSocketCreateWithNative ( nil , CFSocketNativeHandle ( sockets. first) , CFOptionFlags ( kCFSocketDataCallBack) , { ( socket, type, address, data, info) in
377
+ let process : Process = NSObject . unretainedReference ( info!)
378
+ process. processLaunchedCondition. lock ( )
379
+ while process. isRunning == false {
380
+ process. processLaunchedCondition. wait ( )
381
+ }
382
+ process. processLaunchedCondition. unlock ( )
383
+
384
+ WaitForSingleObject ( process. processHandle, WinSDK . INFINITE)
385
+
386
+ var dwExitCode : DWORD = 0
387
+ // FIXME(compnerd) how do we handle errors here?
388
+ GetExitCodeProcess ( process. processHandle, & dwExitCode)
389
+
390
+ // TODO(compnerd) check if the process terminated abnormally
391
+ process. _terminationStatus = Int32 ( dwExitCode)
392
+ process. _terminationReason = . exit
393
+
394
+ if let handler = process. terminationHandler {
395
+ let thread : Thread = Thread { handler ( process) }
396
+ thread. start ( )
397
+ }
398
+
399
+ process. isRunning = false
400
+
401
+ // Invalidate the source and wake up the run loop, if it is available
402
+ CFRunLoopSourceInvalidate ( process. runLoopSource)
403
+ if let runloop = process. runLoop {
404
+ CFRunLoopWakeUp ( runloop. _cfRunLoop)
405
+ }
406
+
407
+ CFSocketInvalidate ( socket)
408
+ } , & context)
409
+ CFSocketSetSocketFlags ( socket, CFOptionFlags ( kCFSocketCloseOnInvalidate) )
410
+
411
+ let source : CFRunLoopSource =
412
+ CFSocketCreateRunLoopSource ( kCFAllocatorDefault, socket, 0 )
413
+ CFRunLoopAddSource ( managerThreadRunLoop? . _cfRunLoop, source, kCFRunLoopDefaultMode)
414
+
415
+ try command. joined ( separator: " " ) . withCString ( encodedAs: UTF16 . self) { wszCommandLine in
416
+ try currentDirectoryURL. path. withCString ( encodedAs: UTF16 . self) { wszCurrentDirectory in
417
+ try szEnvironment. withCString ( encodedAs: UTF16 . self) { wszEnvironment in
418
+ if CreateProcessW ( nil , UnsafeMutablePointer < WCHAR > ( mutating: wszCommandLine) ,
419
+ nil , nil , TRUE,
420
+ DWORD ( CREATE_UNICODE_ENVIRONMENT) , UnsafeMutableRawPointer ( mutating: wszEnvironment) ,
421
+ wszCurrentDirectory,
422
+ & siStartupInfo, & piProcessInfo) == FALSE {
423
+ throw NSError ( domain: _NSWindowsErrorDomain, code: Int ( GetLastError ( ) ) )
424
+ }
425
+ }
426
+ }
427
+ }
428
+
429
+ self . processHandle = piProcessInfo. hProcess
430
+ if CloseHandle ( piProcessInfo. hThread) == FALSE {
431
+ throw NSError ( domain: _NSWindowsErrorDomain, code: Int ( GetLastError ( ) ) )
432
+ }
433
+
434
+ if let pipe = standardInput as? Pipe {
435
+ pipe. fileHandleForReading. closeFile ( )
436
+ pipe. fileHandleForWriting. closeFile ( )
437
+ }
438
+ if let pipe = standardOutput as? Pipe {
439
+ pipe. fileHandleForReading. closeFile ( )
440
+ pipe. fileHandleForWriting. closeFile ( )
441
+ }
442
+ if let pipe = standardError as? Pipe {
443
+ pipe. fileHandleForWriting. closeFile ( )
444
+ pipe. fileHandleForReading. closeFile ( )
445
+ }
446
+
447
+ self . runLoop = RunLoop . current
448
+ self . runLoopSourceContext =
449
+ CFRunLoopSourceContext ( version: 0 ,
450
+ info: Unmanaged . passUnretained ( self ) . toOpaque ( ) ,
451
+ retain: { runLoopSourceRetain ( $0) } ,
452
+ release: { runLoopSourceRelease ( $0) } ,
453
+ copyDescription: nil ,
454
+ equal: { processIsEqual ( $0, $1) } ,
455
+ hash: nil ,
456
+ schedule: nil ,
457
+ cancel: nil ,
458
+ perform: { emptyRunLoopCallback ( $0) } )
459
+ self . runLoopSource = CFRunLoopSourceCreate ( kCFAllocatorDefault, 0 , & self . runLoopSourceContext!)
460
+
461
+ CFRunLoopAddSource ( CFRunLoopGetCurrent ( ) , runLoopSource, kCFRunLoopDefaultMode)
462
+
463
+ isRunning = true
464
+
465
+ closesocket ( sockets. second)
466
+ #else
252
467
// Initial checks that the launchPath points to an executable file. posix_spawn()
253
468
// can return success even if executing the program fails, eg fork() works but execve()
254
469
// fails, so try and check as much as possible beforehand.
@@ -258,9 +473,11 @@ open class Process: NSObject {
258
473
throw _NSErrorWithErrno ( errno, reading: true , path: launchPath)
259
474
}
260
475
261
- guard statInfo. st_mode & S_IFMT == S_IFREG else {
476
+ let isRegularFile : Bool = statInfo. st_mode & S_IFMT == S_IFREG
477
+ guard isRegularFile == true else {
262
478
throw NSError ( domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError)
263
479
}
480
+
264
481
guard access ( fsRep, X_OK) == 0 else {
265
482
throw _NSErrorWithErrno ( errno, reading: true , path: launchPath)
266
483
}
@@ -484,46 +701,99 @@ open class Process: NSObject {
484
701
isRunning = true
485
702
486
703
self . processIdentifier = pid
704
+ #endif
487
705
}
488
706
489
707
open func interrupt( ) {
490
708
precondition ( hasStarted, " task not launched " )
709
+ #if os(Windows)
710
+ TerminateProcess ( processHandle, UINT ( SIGINT) )
711
+ #else
491
712
kill ( processIdentifier, SIGINT)
713
+ #endif
492
714
}
493
715
494
716
open func terminate( ) {
495
717
precondition ( hasStarted, " task not launched " )
718
+ #if os(Windows)
719
+ TerminateProcess ( processHandle, UINT ( SIGTERM) )
720
+ #else
496
721
kill ( processIdentifier, SIGTERM)
722
+ #endif
497
723
}
498
724
499
725
// Every suspend() has to be balanced with a resume() so keep a count of both.
500
726
private var suspendCount = 0
501
727
502
728
open func suspend( ) -> Bool {
729
+ #if os(Windows)
730
+ let pNTSuspendProcess : Optional < ( HANDLE ) -> LONG > =
731
+ unsafeBitCast ( GetProcAddress ( GetModuleHandleA ( " ntdll.dll " ) ,
732
+ " NtSuspendProcess " ) ,
733
+ to: Optional < ( HANDLE ) -> LONG > . self)
734
+ if let pNTSuspendProcess = pNTSuspendProcess {
735
+ if pNTSuspendProcess ( processHandle) < 0 {
736
+ return false
737
+ }
738
+ suspendCount += 1
739
+ return true
740
+ }
741
+ return false
742
+ #else
503
743
if kill ( processIdentifier, SIGSTOP) == 0 {
504
744
suspendCount += 1
505
745
return true
506
746
} else {
507
747
return false
508
748
}
749
+ #endif
509
750
}
510
751
511
752
open func resume( ) -> Bool {
512
- var success = true
753
+ var success : Bool = true
754
+ #if os(Windows)
755
+ if suspendCount == 1 {
756
+ let pNTResumeProcess : Optional < ( HANDLE ) -> NTSTATUS > =
757
+ unsafeBitCast ( GetProcAddress ( GetModuleHandleA ( " ntdll.dll " ) ,
758
+ " NtResumeProcess " ) ,
759
+ to: Optional < ( HANDLE ) -> NTSTATUS > . self)
760
+ if let pNTResumeProcess = pNTResumeProcess {
761
+ if pNTResumeProcess ( processHandle) < 0 {
762
+ success = false
763
+ }
764
+ }
765
+ }
766
+ #else
513
767
if suspendCount == 1 {
514
768
success = kill ( processIdentifier, SIGCONT) == 0
515
769
}
770
+ #endif
516
771
if success {
517
772
suspendCount -= 1
518
773
}
519
774
return success
520
775
}
521
776
522
777
// status
778
+ #if os(Windows)
779
+ open private( set) var processHandle : HANDLE = INVALID_HANDLE_VALUE
780
+ open private( set) var processIdentifier : Int32 {
781
+ return Int32 ( GetProcessId ( processHandle) )
782
+ }
783
+ open private( set) var isRunning : Bool = false
784
+
785
+ private var hasStarted : Bool {
786
+ return processHandle != INVALID_HANDLE_VALUE
787
+ }
788
+ private var hasFinished : Bool {
789
+ return hasStarted && !isRunning
790
+ }
791
+ #else
523
792
open private( set) var processIdentifier : Int32 = 0
524
793
open private( set) var isRunning : Bool = false
525
794
private var hasStarted : Bool { return processIdentifier > 0 }
526
795
private var hasFinished : Bool { return !isRunning && processIdentifier > 0 }
796
+ #endif
527
797
528
798
private var _terminationStatus : Int32 = 0
529
799
public var terminationStatus : Int32 {
0 commit comments