| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 _exit(int status) native "Exit"; | 5 _exit(int status) native "Exit"; |
| 6 | 6 |
| 7 class _ProcessStartStatus { | 7 class _ProcessStartStatus { |
| 8 int _errorCode; // Set to OS error code if process start failed. | 8 int _errorCode; // Set to OS error code if process start failed. |
| 9 String _errorMessage; // Set to OS error message if process start failed. | 9 String _errorMessage; // Set to OS error message if process start failed. |
| 10 } | 10 } |
| 11 | 11 |
| 12 | 12 |
| 13 class _Process extends NativeFieldWrapperClass1 implements Process { | 13 class _Process extends NativeFieldWrapperClass1 implements Process { |
| 14 static Future<ProcessResult> run(String path, | 14 static Future<ProcessResult> run(String path, |
| 15 List<String> arguments, | 15 List<String> arguments, |
| 16 [ProcessOptions options]) { | 16 [ProcessOptions options]) { |
| 17 return new _NonInteractiveProcess._start(path, arguments, options)._result; | 17 return new _NonInteractiveProcess(path, arguments, options)._result; |
| 18 } | 18 } |
| 19 | 19 |
| 20 _Process.start(String path, | 20 static Future<Process> start(String path, |
| 21 List<String> arguments, | 21 List<String> arguments, |
| 22 ProcessOptions options) { | 22 ProcessOptions options) { |
| 23 _Process process = new _Process(path, arguments, options); |
| 24 return process._start(); |
| 25 } |
| 26 |
| 27 _Process(String path, List<String> arguments, ProcessOptions options) { |
| 23 if (path is !String) { | 28 if (path is !String) { |
| 24 throw new ArgumentError("Path is not a String: $path"); | 29 throw new ArgumentError("Path is not a String: $path"); |
| 25 } | 30 } |
| 26 _path = path; | 31 _path = path; |
| 27 | 32 |
| 28 if (arguments is !List) { | 33 if (arguments is !List) { |
| 29 throw new ArgumentError("Arguments is not a List: $arguments"); | 34 throw new ArgumentError("Arguments is not a List: $arguments"); |
| 30 } | 35 } |
| 31 int len = arguments.length; | 36 int len = arguments.length; |
| 32 _arguments = new List<String>(len); | 37 _arguments = new List<String>(len); |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 65 } | 70 } |
| 66 | 71 |
| 67 _in = new _Socket._internalReadOnly(); // stdout coming from process. | 72 _in = new _Socket._internalReadOnly(); // stdout coming from process. |
| 68 _out = new _Socket._internalWriteOnly(); // stdin going to process. | 73 _out = new _Socket._internalWriteOnly(); // stdin going to process. |
| 69 _err = new _Socket._internalReadOnly(); // stderr coming from process. | 74 _err = new _Socket._internalReadOnly(); // stderr coming from process. |
| 70 _exitHandler = new _Socket._internalReadOnly(); | 75 _exitHandler = new _Socket._internalReadOnly(); |
| 71 _closed = false; | 76 _closed = false; |
| 72 _ended = false; | 77 _ended = false; |
| 73 _started = false; | 78 _started = false; |
| 74 _onExit = null; | 79 _onExit = null; |
| 75 // TODO(ager): Make the actual process starting really async instead of | |
| 76 // simulating it with a timer. | |
| 77 new Timer(0, (Timer ignore) => _start()); | |
| 78 } | 80 } |
| 79 | 81 |
| 80 String _windowsArgumentEscape(String argument) { | 82 String _windowsArgumentEscape(String argument) { |
| 81 var result = argument; | 83 var result = argument; |
| 82 if (argument.contains('\t') || argument.contains(' ')) { | 84 if (argument.contains('\t') || argument.contains(' ')) { |
| 83 // Produce something that the C runtime on Windows will parse | 85 // Produce something that the C runtime on Windows will parse |
| 84 // back as this string. | 86 // back as this string. |
| 85 | 87 |
| 86 // Replace any number of '\' followed by '"' with | 88 // Replace any number of '\' followed by '"' with |
| 87 // twice as many '\' followed by '\"'. | 89 // twice as many '\' followed by '\"'. |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 123 return result; | 125 return result; |
| 124 } | 126 } |
| 125 | 127 |
| 126 int _intFromBytes(List<int> bytes, int offset) { | 128 int _intFromBytes(List<int> bytes, int offset) { |
| 127 return (bytes[offset] + | 129 return (bytes[offset] + |
| 128 (bytes[offset + 1] << 8) + | 130 (bytes[offset + 1] << 8) + |
| 129 (bytes[offset + 2] << 16) + | 131 (bytes[offset + 2] << 16) + |
| 130 (bytes[offset + 3] << 24)); | 132 (bytes[offset + 3] << 24)); |
| 131 } | 133 } |
| 132 | 134 |
| 133 void _start() { | 135 Future<Process> _start() { |
| 134 var status = new _ProcessStartStatus(); | 136 var completer = new Completer(); |
| 135 bool success = _startNative(_path, | 137 // TODO(ager): Make the actual process starting really async instead of |
| 136 _arguments, | 138 // simulating it with a timer. |
| 137 _workingDirectory, | 139 new Timer(0, (_) { |
| 138 _environment, | 140 var status = new _ProcessStartStatus(); |
| 139 _in, | 141 bool success = _startNative(_path, |
| 140 _out, | 142 _arguments, |
| 141 _err, | 143 _workingDirectory, |
| 142 _exitHandler, | 144 _environment, |
| 143 status); | 145 _in, |
| 144 if (!success) { | 146 _out, |
| 145 close(); | 147 _err, |
| 146 _reportError(new ProcessException(status._errorMessage, | 148 _exitHandler, |
| 147 status._errorCode)); | 149 status); |
| 148 return; | 150 if (!success) { |
| 149 } | 151 close(); |
| 150 _started = true; | 152 completer.completeException( |
| 153 new ProcessException(status._errorMessage, status._errorCode)); |
| 154 return; |
| 155 } |
| 156 _started = true; |
| 151 | 157 |
| 152 _in._closed = false; | 158 _in._closed = false; |
| 153 _out._closed = false; | 159 _out._closed = false; |
| 154 _err._closed = false; | 160 _err._closed = false; |
| 155 _exitHandler._closed = false; | 161 _exitHandler._closed = false; |
| 156 | 162 |
| 157 // Make sure to activate socket handlers now that the file | 163 // Make sure to activate socket handlers now that the file |
| 158 // descriptors have been set. | 164 // descriptors have been set. |
| 159 _in._activateHandlers(); | 165 _in._activateHandlers(); |
| 160 _out._activateHandlers(); | 166 _out._activateHandlers(); |
| 161 _err._activateHandlers(); | 167 _err._activateHandlers(); |
| 162 | 168 |
| 163 // Setup an exit handler to handle internal cleanup and possible | 169 // Setup an exit handler to handle internal cleanup and possible |
| 164 // callback when a process terminates. | 170 // callback when a process terminates. |
| 165 int exitDataRead = 0; | 171 int exitDataRead = 0; |
| 166 final int EXIT_DATA_SIZE = 8; | 172 final int EXIT_DATA_SIZE = 8; |
| 167 List<int> exitDataBuffer = new List<int>(EXIT_DATA_SIZE); | 173 List<int> exitDataBuffer = new List<int>(EXIT_DATA_SIZE); |
| 168 _exitHandler.inputStream.onData = () { | 174 _exitHandler.inputStream.onData = () { |
| 169 | 175 |
| 170 int exitCode(List<int> ints) { | 176 int exitCode(List<int> ints) { |
| 171 var code = _intFromBytes(ints, 0); | 177 var code = _intFromBytes(ints, 0); |
| 172 var negative = _intFromBytes(ints, 4); | 178 var negative = _intFromBytes(ints, 4); |
| 173 assert(negative == 0 || negative == 1); | 179 assert(negative == 0 || negative == 1); |
| 174 return (negative == 0) ? code : -code; | 180 return (negative == 0) ? code : -code; |
| 175 } | 181 } |
| 176 | 182 |
| 177 void handleExit() { | 183 void handleExit() { |
| 178 _ended = true; | 184 _ended = true; |
| 179 if (_onExit !== null) { | 185 if (_onExit !== null) { |
| 180 _onExit(exitCode(exitDataBuffer)); | 186 _onExit(exitCode(exitDataBuffer)); |
| 187 } |
| 181 } | 188 } |
| 182 } | |
| 183 | 189 |
| 184 exitDataRead += _exitHandler.inputStream.readInto( | 190 exitDataRead += _exitHandler.inputStream.readInto( |
| 185 exitDataBuffer, exitDataRead, EXIT_DATA_SIZE - exitDataRead); | 191 exitDataBuffer, exitDataRead, EXIT_DATA_SIZE - exitDataRead); |
| 186 if (exitDataRead == EXIT_DATA_SIZE) handleExit(); | 192 if (exitDataRead == EXIT_DATA_SIZE) handleExit(); |
| 187 }; | 193 }; |
| 188 | 194 |
| 189 if (_onStart !== null) { | 195 completer.complete(this); |
| 190 _onStart(); | 196 }); |
| 191 } | 197 return completer.future; |
| 192 } | 198 } |
| 193 | 199 |
| 194 bool _startNative(String path, | 200 bool _startNative(String path, |
| 195 List<String> arguments, | 201 List<String> arguments, |
| 196 String workingDirectory, | 202 String workingDirectory, |
| 197 List<String> environment, | 203 List<String> environment, |
| 198 Socket input, | 204 Socket input, |
| 199 Socket output, | 205 Socket output, |
| 200 Socket error, | 206 Socket error, |
| 201 Socket exitHandler, | 207 Socket exitHandler, |
| (...skipping 18 matching lines...) Expand all Loading... |
| 220 throw new ProcessException("Process closed"); | 226 throw new ProcessException("Process closed"); |
| 221 } | 227 } |
| 222 return _out.outputStream; | 228 return _out.outputStream; |
| 223 } | 229 } |
| 224 | 230 |
| 225 void kill([ProcessSignal signal = ProcessSignal.SIGTERM]) { | 231 void kill([ProcessSignal signal = ProcessSignal.SIGTERM]) { |
| 226 if (signal is! ProcessSignal) { | 232 if (signal is! ProcessSignal) { |
| 227 throw new ArgumentError( | 233 throw new ArgumentError( |
| 228 "Argument 'signal' must be a ProcessSignal"); | 234 "Argument 'signal' must be a ProcessSignal"); |
| 229 } | 235 } |
| 230 if (!_started) { | 236 assert(_started); |
| 231 var e = new ProcessException("Cannot kill process that is not started"); | 237 if (_ended) return; |
| 232 _reportError(e); | 238 if (_kill(this, signal._signalNumber)) return; |
| 233 return; | 239 throw new ProcessException("Could not kill process"); |
| 234 } | |
| 235 if (_ended) { | |
| 236 return; | |
| 237 } | |
| 238 if (_kill(this, signal._signalNumber)) { | |
| 239 return; | |
| 240 } | |
| 241 _reportError(new ProcessException("Could not kill process")); | |
| 242 return; | |
| 243 } | 240 } |
| 244 | 241 |
| 245 bool _kill(Process p, int signal) native "Process_Kill"; | 242 bool _kill(Process p, int signal) native "Process_Kill"; |
| 246 | 243 |
| 247 void close() { | 244 void close() { |
| 248 if (_closed) { | 245 if (_closed) { |
| 249 throw new ProcessException("Process closed"); | 246 throw new ProcessException("Process closed"); |
| 250 } | 247 } |
| 251 _in.close(); | 248 _in.close(); |
| 252 _out.close(); | 249 _out.close(); |
| 253 _err.close(); | 250 _err.close(); |
| 254 _exitHandler.close(); | 251 _exitHandler.close(); |
| 255 _closed = true; | 252 _closed = true; |
| 256 } | 253 } |
| 257 | 254 |
| 258 void set onExit(void callback(int exitCode)) { | 255 void set onExit(void callback(int exitCode)) { |
| 259 if (_closed) { | 256 if (_closed) { |
| 260 throw new ProcessException("Process closed"); | 257 throw new ProcessException("Process closed"); |
| 261 } | 258 } |
| 262 if (_ended) { | 259 if (_ended) { |
| 263 throw new ProcessException("Process killed"); | 260 throw new ProcessException("Process killed"); |
| 264 } | 261 } |
| 265 _onExit = callback; | 262 _onExit = callback; |
| 266 } | 263 } |
| 267 | 264 |
| 268 void set onError(void callback(e)) { | |
| 269 _onError = callback; | |
| 270 } | |
| 271 | |
| 272 void set onStart(void callback()) { | |
| 273 _onStart = callback; | |
| 274 } | |
| 275 | |
| 276 void _reportError(e) { | |
| 277 if (_onError != null) { | |
| 278 _onError(e); | |
| 279 } else { | |
| 280 throw e; | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 String _path; | 265 String _path; |
| 285 List<String> _arguments; | 266 List<String> _arguments; |
| 286 String _workingDirectory; | 267 String _workingDirectory; |
| 287 List<String> _environment; | 268 List<String> _environment; |
| 288 // Private methods of _Socket are used by _in, _out, and _err. | 269 // Private methods of _Socket are used by _in, _out, and _err. |
| 289 _Socket _in; | 270 _Socket _in; |
| 290 _Socket _out; | 271 _Socket _out; |
| 291 _Socket _err; | 272 _Socket _err; |
| 292 Socket _exitHandler; | 273 Socket _exitHandler; |
| 293 bool _closed; | 274 bool _closed; |
| 294 bool _ended; | 275 bool _ended; |
| 295 bool _started; | 276 bool _started; |
| 296 Function _onExit; | 277 Function _onExit; |
| 297 Function _onError; | |
| 298 Function _onStart; | |
| 299 } | 278 } |
| 300 | 279 |
| 301 | 280 |
| 302 // _NonInteractiveProcess is a wrapper around an interactive process | 281 // _NonInteractiveProcess is a wrapper around an interactive process |
| 303 // that buffers output so it can be delivered when the process exits. | 282 // that buffers output so it can be delivered when the process exits. |
| 304 // _NonInteractiveProcess is used to implement the Process.run | 283 // _NonInteractiveProcess is used to implement the Process.run |
| 305 // method. | 284 // method. |
| 306 class _NonInteractiveProcess { | 285 class _NonInteractiveProcess { |
| 307 _NonInteractiveProcess._start(String path, | 286 _NonInteractiveProcess(String path, |
| 308 List<String> arguments, | 287 List<String> arguments, |
| 309 ProcessOptions options) { | 288 ProcessOptions options) { |
| 310 _completer = new Completer<ProcessResult>(); | 289 _completer = new Completer<ProcessResult>(); |
| 311 // Extract output encoding options and verify arguments. | 290 // Extract output encoding options and verify arguments. |
| 312 var stdoutEncoding = Encoding.UTF_8; | 291 var stdoutEncoding = Encoding.UTF_8; |
| 313 var stderrEncoding = Encoding.UTF_8; | 292 var stderrEncoding = Encoding.UTF_8; |
| 314 if (options !== null) { | 293 if (options !== null) { |
| 315 if (options.stdoutEncoding !== null) { | 294 if (options.stdoutEncoding !== null) { |
| 316 stdoutEncoding = options.stdoutEncoding; | 295 stdoutEncoding = options.stdoutEncoding; |
| 317 if (stdoutEncoding is !Encoding) { | 296 if (stdoutEncoding is !Encoding) { |
| 318 throw new ArgumentError( | 297 throw new ArgumentError( |
| 319 'stdoutEncoding option is not an encoding: $stdoutEncoding'); | 298 'stdoutEncoding option is not an encoding: $stdoutEncoding'); |
| 320 } | 299 } |
| 321 } | 300 } |
| 322 if (options.stderrEncoding !== null) { | 301 if (options.stderrEncoding !== null) { |
| 323 stderrEncoding = options.stderrEncoding; | 302 stderrEncoding = options.stderrEncoding; |
| 324 if (stderrEncoding is !Encoding) { | 303 if (stderrEncoding is !Encoding) { |
| 325 throw new ArgumentError( | 304 throw new ArgumentError( |
| 326 'stderrEncoding option is not an encoding: $stderrEncoding'); | 305 'stderrEncoding option is not an encoding: $stderrEncoding'); |
| 327 } | 306 } |
| 328 } | 307 } |
| 329 } | 308 } |
| 330 | 309 |
| 331 // Start the underlying process. | 310 // Start the underlying process. |
| 332 _process = new _Process.start(path, arguments, options); | 311 var processFuture = new _Process(path, arguments, options)._start(); |
| 333 | 312 |
| 334 // Make sure stdin is closed. | 313 processFuture.then((Process p) { |
| 335 _process.onStart = _process.stdin.close; | 314 // Make sure the process stdin is closed. |
| 315 p.stdin.close; |
| 336 | 316 |
| 337 // Setup process error handling. | 317 // Setup process exit handling. |
| 338 _process.onError = (e) => _completer.completeException(e); | 318 p.onExit = (exitCode) { |
| 319 _exitCode = exitCode; |
| 320 _checkDone(); |
| 321 }; |
| 339 | 322 |
| 340 // Setup process exit handling. | 323 // Setup stdout handling. |
| 341 _process.onExit = (exitCode) { | 324 _stdoutBuffer = new StringBuffer(); |
| 342 _exitCode = exitCode; | 325 var stdoutStream = new StringInputStream(p.stdout, stdoutEncoding); |
| 343 _checkDone(); | 326 stdoutStream.onData = () { |
| 344 }; | 327 var data = stdoutStream.read(); |
| 328 if (data != null) _stdoutBuffer.add(data); |
| 329 }; |
| 330 stdoutStream.onClosed = () { |
| 331 _stdoutClosed = true; |
| 332 _checkDone(); |
| 333 }; |
| 345 | 334 |
| 346 // Setup stdout handling. | 335 // Setup stderr handling. |
| 347 _stdoutBuffer = new StringBuffer(); | 336 _stderrBuffer = new StringBuffer(); |
| 348 var stdoutStream = new StringInputStream(_process.stdout, stdoutEncoding); | 337 var stderrStream = new StringInputStream(p.stderr, stderrEncoding); |
| 349 stdoutStream.onData = () { | 338 stderrStream.onData = () { |
| 350 var data = stdoutStream.read(); | 339 var data = stderrStream.read(); |
| 351 if (data != null) _stdoutBuffer.add(data); | 340 if (data != null) _stderrBuffer.add(data); |
| 352 }; | 341 }; |
| 353 stdoutStream.onClosed = () { | 342 stderrStream.onClosed = () { |
| 354 _stdoutClosed = true; | 343 _stderrClosed = true; |
| 355 _checkDone(); | 344 _checkDone(); |
| 356 }; | 345 }; |
| 346 }); |
| 357 | 347 |
| 358 // Setup stderr handling. | 348 processFuture.handleException((error) { |
| 359 _stderrBuffer = new StringBuffer(); | 349 _completer.completeException(error); |
| 360 var stderrStream = new StringInputStream(_process.stderr, stderrEncoding); | 350 return true; |
| 361 stderrStream.onData = () { | 351 }); |
| 362 var data = stderrStream.read(); | |
| 363 if (data != null) _stderrBuffer.add(data); | |
| 364 }; | |
| 365 stderrStream.onClosed = () { | |
| 366 _stderrClosed = true; | |
| 367 _checkDone(); | |
| 368 }; | |
| 369 } | 352 } |
| 370 | 353 |
| 371 void _checkDone() { | 354 void _checkDone() { |
| 372 if (_exitCode != null && _stderrClosed && _stdoutClosed) { | 355 if (_exitCode != null && _stderrClosed && _stdoutClosed) { |
| 373 _completer.complete(new _ProcessResult(_exitCode, | 356 _completer.complete(new _ProcessResult(_exitCode, |
| 374 _stdoutBuffer.toString(), | 357 _stdoutBuffer.toString(), |
| 375 _stderrBuffer.toString())); | 358 _stderrBuffer.toString())); |
| 376 } | 359 } |
| 377 } | 360 } |
| 378 | 361 |
| 379 Future<ProcessResult> get _result => _completer.future; | 362 Future<ProcessResult> get _result => _completer.future; |
| 380 | 363 |
| 381 Completer<ProcessResult> _completer; | 364 Completer<ProcessResult> _completer; |
| 382 Process _process; | |
| 383 StringBuffer _stdoutBuffer; | 365 StringBuffer _stdoutBuffer; |
| 384 StringBuffer _stderrBuffer; | 366 StringBuffer _stderrBuffer; |
| 385 int _exitCode; | 367 int _exitCode; |
| 386 bool _stdoutClosed = false; | 368 bool _stdoutClosed = false; |
| 387 bool _stderrClosed = false; | 369 bool _stderrClosed = false; |
| 388 } | 370 } |
| 389 | 371 |
| 390 | 372 |
| 391 class _ProcessResult implements ProcessResult { | 373 class _ProcessResult implements ProcessResult { |
| 392 const _ProcessResult(int this.exitCode, | 374 const _ProcessResult(int this.exitCode, |
| 393 String this.stdout, | 375 String this.stdout, |
| 394 String this.stderr); | 376 String this.stderr); |
| 395 | 377 |
| 396 final int exitCode; | 378 final int exitCode; |
| 397 final String stdout; | 379 final String stdout; |
| 398 final String stderr; | 380 final String stderr; |
| 399 } | 381 } |
| OLD | NEW |