1
1
// This source file is part of the Swift.org open source project
2
2
//
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
4
4
// Licensed under Apache License v2.0 with Runtime Library Exception
5
5
//
6
6
// See http://swift.org/LICENSE.txt for license information
@@ -153,7 +153,7 @@ open class Process: NSObject {
153
153
}
154
154
}
155
155
}
156
-
156
+
157
157
// Create an Process which can be run at a later time
158
158
// An Process can only be run once. Subsequent attempts to
159
159
// run an Process will raise.
@@ -162,48 +162,36 @@ open class Process: NSObject {
162
162
//
163
163
164
164
public override init ( ) {
165
-
165
+
166
166
}
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 )
171
171
open var arguments : [ String ] ?
172
172
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 }
187
178
}
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) }
196
184
}
197
-
185
+
198
186
// Standard I/O channels; could be either a FileHandle or a Pipe
199
-
187
+
200
188
open var standardInput : Any ? {
201
189
willSet {
202
190
precondition ( newValue is Pipe || newValue is FileHandle ,
203
191
" standardInput must be either Pipe or FileHandle " )
204
192
}
205
193
}
206
-
194
+
207
195
open var standardOutput : Any ? {
208
196
willSet {
209
197
precondition ( newValue is Pipe || newValue is FileHandle ,
@@ -227,20 +215,62 @@ open class Process: NSObject {
227
215
228
216
// Actions
229
217
218
+ @available ( * , deprecated, renamed: " run " )
230
219
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 {
231
242
232
243
self . processLaunchedCondition. lock ( )
233
-
244
+ defer {
245
+ self . processLaunchedCondition. unlock ( )
246
+ self . processLaunchedCondition. broadcast ( )
247
+ }
248
+
234
249
// Dispatch the manager thread if it isn't already running
235
250
236
251
Process . setup ( )
237
252
238
253
// 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)
242
256
}
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
+ } )
244
274
// Convert the arguments array into a posix_spawn-friendly format
245
275
246
276
var args = [ launchPath]
@@ -260,7 +290,6 @@ open class Process: NSObject {
260
290
for arg in argv ..< argv + args. count {
261
291
free ( UnsafeMutableRawPointer ( arg. pointee) )
262
292
}
263
-
264
293
argv. deallocate ( )
265
294
}
266
295
@@ -294,7 +323,7 @@ open class Process: NSObject {
294
323
context. version = 0
295
324
context. retain = runLoopSourceRetain
296
325
context. release = runLoopSourceRelease
297
- context. info = Unmanaged . passUnretained ( self ) . toOpaque ( )
326
+ context. info = Unmanaged . passUnretained ( self ) . toOpaque ( )
298
327
299
328
let socket = CFSocketCreateWithNative ( nil , taskSocketPair [ 0 ] , CFOptionFlags ( kCFSocketDataCallBack) , {
300
329
( socket, type, address, data, info ) in
@@ -316,7 +345,7 @@ open class Process: NSObject {
316
345
}
317
346
#endif
318
347
var waitResult : Int32 = 0
319
-
348
+
320
349
repeat {
321
350
#if CYGWIN
322
351
waitResult = waitpid ( process. processIdentifier, exitCodePtrWrapper, 0 )
@@ -344,9 +373,9 @@ open class Process: NSObject {
344
373
}
345
374
346
375
// Set the running flag to false
347
-
348
376
process. isRunning = false
349
-
377
+ process. processIdentifier = - 1
378
+
350
379
// Invalidate the source and wake up the run loop, if it's available
351
380
352
381
CFRunLoopSourceInvalidate ( process. runLoopSource)
@@ -417,27 +446,20 @@ open class Process: NSObject {
417
446
418
447
let fileManager = FileManager ( )
419
448
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)
432
451
}
433
452
434
- // Launch
453
+ defer {
454
+ // Reset the previous working directory path.
455
+ fileManager. changeCurrentDirectoryPath ( previousDirectoryPath)
456
+ }
435
457
458
+ // Launch
436
459
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
+ }
441
463
442
464
// Close the write end of the input and output pipes.
443
465
if let pipe = standardInput as? Pipe {
@@ -451,7 +473,7 @@ open class Process: NSObject {
451
473
}
452
474
453
475
close ( taskSocketPair [ 1 ] )
454
-
476
+
455
477
self . runLoop = RunLoop . current
456
478
self . runLoopSourceContext = CFRunLoopSourceContext ( version: 0 ,
457
479
info: Unmanaged . passUnretained ( self ) . toOpaque ( ) ,
@@ -483,9 +505,6 @@ open class Process: NSObject {
483
505
isRunning = true
484
506
485
507
self . processIdentifier = pid
486
-
487
- self . processLaunchedCondition. unlock ( )
488
- self . processLaunchedCondition. broadcast ( )
489
508
}
490
509
491
510
open func interrupt( ) { NSUnimplemented ( ) } // Not always possible. Sends SIGINT.
@@ -506,10 +525,18 @@ open class Process: NSObject {
506
525
*/
507
526
open var terminationHandler : ( ( Process ) -> Void ) ?
508
527
open var qualityOfService : QualityOfService = . default // read-only after the process is launched
509
- }
510
528
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:) " )
513
540
// convenience; create and launch
514
541
open class func launchedProcess( launchPath path: String , arguments: [ String ] ) -> Process {
515
542
let process = Process ( )
@@ -519,7 +546,7 @@ extension Process {
519
546
520
547
return process
521
548
}
522
-
549
+
523
550
// poll the runLoop in defaultMode until process completes
524
551
open func waitUntilExit( ) {
525
552
0 commit comments