11// This source file is part of the Swift.org open source project
22//
3- // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
3+ // Copyright (c) 2014 - 2016, 2018 Apple Inc. and the Swift project authors
44// Licensed under Apache License v2.0 with Runtime Library Exception
55//
66// See http://swift.org/LICENSE.txt for license information
@@ -153,7 +153,7 @@ open class Process: NSObject {
153153 }
154154 }
155155 }
156-
156+
157157 // Create an Process which can be run at a later time
158158 // An Process can only be run once. Subsequent attempts to
159159 // run an Process will raise.
@@ -162,48 +162,36 @@ open class Process: NSObject {
162162 //
163163
164164 public override init ( ) {
165-
165+
166166 }
167-
168- // These methods can only be set before a launch.
169-
170- open var launchPath : String ?
167+
168+ // These properties can only be set before a launch.
169+ open var executableURL : URL ?
170+ open var currentDirectoryURL = URL ( fileURLWithPath : FileManager . default . currentDirectoryPath , isDirectory : true )
171171 open var arguments : [ String ] ?
172172 open var environment : [ String : String ] ? // if not set, use current
173-
174- open var currentDirectoryPath : String = FileManager . default. currentDirectoryPath
175-
176- open var executableURL : URL ? {
177- get {
178- guard let launchPath = self . launchPath else {
179- return nil
180- }
181-
182- return URL ( fileURLWithPath: launchPath)
183- }
184- set {
185- self . launchPath = newValue? . path
186- }
173+
174+ @available ( * , deprecated, renamed: " executableURL " )
175+ open var launchPath : String ? {
176+ get { return executableURL? . path }
177+ set { executableURL = ( newValue != nil ) ? URL ( fileURLWithPath: newValue!) : nil }
187178 }
188-
189- open var currentDirectoryURL : URL {
190- get {
191- return URL ( fileURLWithPath: self . currentDirectoryPath)
192- }
193- set {
194- self . currentDirectoryPath = newValue. path
195- }
179+
180+ @available ( * , deprecated, renamed: " currentDirectoryURL " )
181+ open var currentDirectoryPath : String {
182+ get { return currentDirectoryURL. path }
183+ set { currentDirectoryURL = URL ( fileURLWithPath: newValue) }
196184 }
197-
185+
198186 // Standard I/O channels; could be either a FileHandle or a Pipe
199-
187+
200188 open var standardInput : Any ? {
201189 willSet {
202190 precondition ( newValue is Pipe || newValue is FileHandle ,
203191 " standardInput must be either Pipe or FileHandle " )
204192 }
205193 }
206-
194+
207195 open var standardOutput : Any ? {
208196 willSet {
209197 precondition ( newValue is Pipe || newValue is FileHandle ,
@@ -227,20 +215,62 @@ open class Process: NSObject {
227215
228216 // Actions
229217
218+ @available ( * , deprecated, renamed: " run " )
230219 open func launch( ) {
220+ do {
221+ try run ( )
222+ } catch let nserror as NSError {
223+ if let path = nserror. userInfo [ NSFilePathErrorKey] as? String , path == currentDirectoryPath {
224+ // Foundation throws an NSException when changing the working directory fails,
225+ // and unfortunately launch() is not marked `throws`, so we get away with a
226+ // fatalError.
227+ switch CocoaError . Code ( rawValue: nserror. code) {
228+ case . fileReadNoSuchFile:
229+ fatalError ( " Process: The specified working directory does not exist. " )
230+ case . fileReadNoPermission:
231+ fatalError ( " Process: The specified working directory cannot be accessed. " )
232+ default :
233+ fatalError ( " Process: The specified working directory cannot be set. " )
234+ }
235+ }
236+ } catch {
237+ fatalError ( String ( describing: error) )
238+ }
239+ }
240+
241+ open func run( ) throws {
231242
232243 self . processLaunchedCondition. lock ( )
233-
244+ defer {
245+ self . processLaunchedCondition. unlock ( )
246+ self . processLaunchedCondition. broadcast ( )
247+ }
248+
234249 // Dispatch the manager thread if it isn't already running
235250
236251 Process . setup ( )
237252
238253 // Ensure that the launch path is set
239-
240- guard let launchPath = self . launchPath else {
241- fatalError ( )
254+ guard let launchPath = self . executableURL? . path else {
255+ throw NSError ( domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError)
242256 }
243-
257+
258+ // Initial checks that the launchPath points to an executable file. posix_spawn()
259+ // can return success even if executing the program fails, eg fork() works but execve()
260+ // fails, so try and check as much as possible beforehand.
261+ try FileManager . default. _fileSystemRepresentation ( withPath: launchPath, { fsRep in
262+ var statInfo = stat ( )
263+ guard stat ( fsRep, & statInfo) == 0 else {
264+ throw _NSErrorWithErrno ( errno, reading: true , path: launchPath)
265+ }
266+
267+ guard statInfo. st_mode & S_IFMT == S_IFREG else {
268+ throw NSError ( domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError)
269+ }
270+ guard access ( fsRep, X_OK) == 0 else {
271+ throw _NSErrorWithErrno ( errno, reading: true , path: launchPath)
272+ }
273+ } )
244274 // Convert the arguments array into a posix_spawn-friendly format
245275
246276 var args = [ launchPath]
@@ -260,7 +290,6 @@ open class Process: NSObject {
260290 for arg in argv ..< argv + args. count {
261291 free ( UnsafeMutableRawPointer ( arg. pointee) )
262292 }
263-
264293 argv. deallocate ( )
265294 }
266295
@@ -294,7 +323,7 @@ open class Process: NSObject {
294323 context. version = 0
295324 context. retain = runLoopSourceRetain
296325 context. release = runLoopSourceRelease
297- context. info = Unmanaged . passUnretained ( self ) . toOpaque ( )
326+ context. info = Unmanaged . passUnretained ( self ) . toOpaque ( )
298327
299328 let socket = CFSocketCreateWithNative ( nil , taskSocketPair [ 0 ] , CFOptionFlags ( kCFSocketDataCallBack) , {
300329 ( socket, type, address, data, info ) in
@@ -316,7 +345,7 @@ open class Process: NSObject {
316345 }
317346#endif
318347 var waitResult : Int32 = 0
319-
348+
320349 repeat {
321350#if CYGWIN
322351 waitResult = waitpid ( process. processIdentifier, exitCodePtrWrapper, 0 )
@@ -344,9 +373,9 @@ open class Process: NSObject {
344373 }
345374
346375 // Set the running flag to false
347-
348376 process. isRunning = false
349-
377+ process. processIdentifier = - 1
378+
350379 // Invalidate the source and wake up the run loop, if it's available
351380
352381 CFRunLoopSourceInvalidate ( process. runLoopSource)
@@ -417,27 +446,20 @@ open class Process: NSObject {
417446
418447 let fileManager = FileManager ( )
419448 let previousDirectoryPath = fileManager. currentDirectoryPath
420- if !fileManager. changeCurrentDirectoryPath ( currentDirectoryPath) {
421- // Foundation throws an NSException when changing the working directory fails,
422- // and unfortunately launch() is not marked `throws`, so we get away with a
423- // fatalError.
424- switch errno {
425- case ENOENT:
426- fatalError ( " Process: The specified working directory does not exist. " )
427- case EACCES:
428- fatalError ( " Process: The specified working directory cannot be accessed. " )
429- default :
430- fatalError ( " Process: The specified working directory cannot be set. " )
431- }
449+ if !fileManager. changeCurrentDirectoryPath ( currentDirectoryURL. path) {
450+ throw _NSErrorWithErrno ( errno, reading: true , url: currentDirectoryURL)
432451 }
433452
434- // Launch
453+ defer {
454+ // Reset the previous working directory path.
455+ fileManager. changeCurrentDirectoryPath ( previousDirectoryPath)
456+ }
435457
458+ // Launch
436459 var pid = pid_t ( )
437- posix ( posix_spawn ( & pid, launchPath, & fileActions, nil , argv, envp) )
438-
439- // Reset the previous working directory path.
440- fileManager. changeCurrentDirectoryPath ( previousDirectoryPath)
460+ guard posix_spawn ( & pid, launchPath, & fileActions, nil , argv, envp) == 0 else {
461+ throw _NSErrorWithErrno ( errno, reading: true , path: launchPath)
462+ }
441463
442464 // Close the write end of the input and output pipes.
443465 if let pipe = standardInput as? Pipe {
@@ -451,7 +473,7 @@ open class Process: NSObject {
451473 }
452474
453475 close ( taskSocketPair [ 1 ] )
454-
476+
455477 self . runLoop = RunLoop . current
456478 self . runLoopSourceContext = CFRunLoopSourceContext ( version: 0 ,
457479 info: Unmanaged . passUnretained ( self ) . toOpaque ( ) ,
@@ -483,9 +505,6 @@ open class Process: NSObject {
483505 isRunning = true
484506
485507 self . processIdentifier = pid
486-
487- self . processLaunchedCondition. unlock ( )
488- self . processLaunchedCondition. broadcast ( )
489508 }
490509
491510 open func interrupt( ) { NSUnimplemented ( ) } // Not always possible. Sends SIGINT.
@@ -506,10 +525,18 @@ open class Process: NSObject {
506525 */
507526 open var terminationHandler : ( ( Process ) -> Void ) ?
508527 open var qualityOfService : QualityOfService = . default // read-only after the process is launched
509- }
510528
511- extension Process {
512-
529+
530+ open class func run( _ url: URL , arguments: [ String ] , terminationHandler: ( ( Process ) -> Void ) ? = nil ) throws -> Process {
531+ let process = Process ( )
532+ process. executableURL = url
533+ process. arguments = arguments
534+ process. terminationHandler = terminationHandler
535+ try process. run ( )
536+ return process
537+ }
538+
539+ @available ( * , deprecated, renamed: " run(_:arguments:terminationHandler:) " )
513540 // convenience; create and launch
514541 open class func launchedProcess( launchPath path: String , arguments: [ String ] ) -> Process {
515542 let process = Process ( )
@@ -519,7 +546,7 @@ extension Process {
519546
520547 return process
521548 }
522-
549+
523550 // poll the runLoop in defaultMode until process completes
524551 open func waitUntilExit( ) {
525552
0 commit comments