| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 _exit(int status) native "Exit"; | |
| 6 | |
| 7 class _ProcessStartStatus { | |
| 8 int _errorCode; // Set to OS error code if process start failed. | |
| 9 String _errorMessage; // Set to OS error message if process start failed. | |
| 10 } | |
| 11 | |
| 12 | |
| 13 class _Process extends NativeFieldWrapperClass1 implements Process { | |
| 14 static Future<ProcessResult> run(String path, | |
| 15 List<String> arguments, | |
| 16 [ProcessOptions options]) { | |
| 17 return new _NonInteractiveProcess(path, arguments, options)._result; | |
| 18 } | |
| 19 | |
| 20 static Future<Process> start(String path, | |
| 21 List<String> arguments, | |
| 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) { | |
| 28 if (path is !String) { | |
| 29 throw new ArgumentError("Path is not a String: $path"); | |
| 30 } | |
| 31 _path = path; | |
| 32 | |
| 33 if (arguments is !List) { | |
| 34 throw new ArgumentError("Arguments is not a List: $arguments"); | |
| 35 } | |
| 36 int len = arguments.length; | |
| 37 _arguments = new List<String>(len); | |
| 38 for (int i = 0; i < len; i++) { | |
| 39 var arg = arguments[i]; | |
| 40 if (arg is !String) { | |
| 41 throw new ArgumentError("Non-string argument: $arg"); | |
| 42 } | |
| 43 _arguments[i] = arguments[i]; | |
| 44 if (Platform.operatingSystem == 'windows') { | |
| 45 _arguments[i] = _windowsArgumentEscape(_arguments[i]); | |
| 46 } | |
| 47 } | |
| 48 | |
| 49 if (options !== null && options.workingDirectory !== null) { | |
| 50 _workingDirectory = options.workingDirectory; | |
| 51 if (_workingDirectory is !String) { | |
| 52 throw new ArgumentError( | |
| 53 "WorkingDirectory is not a String: $_workingDirectory"); | |
| 54 } | |
| 55 } | |
| 56 | |
| 57 if (options !== null && options.environment !== null) { | |
| 58 var env = options.environment; | |
| 59 if (env is !Map) { | |
| 60 throw new ArgumentError("Environment is not a map: $env"); | |
| 61 } | |
| 62 _environment = []; | |
| 63 env.forEach((key, value) { | |
| 64 if (key is !String || value is !String) { | |
| 65 throw new ArgumentError( | |
| 66 "Environment key or value is not a string: ($key, $value)"); | |
| 67 } | |
| 68 _environment.add('$key=$value'); | |
| 69 }); | |
| 70 } | |
| 71 | |
| 72 _in = new _Socket._internalReadOnly(); // stdout coming from process. | |
| 73 _out = new _Socket._internalWriteOnly(); // stdin going to process. | |
| 74 _err = new _Socket._internalReadOnly(); // stderr coming from process. | |
| 75 _exitHandler = new _Socket._internalReadOnly(); | |
| 76 _ended = false; | |
| 77 _started = false; | |
| 78 _onExit = null; | |
| 79 } | |
| 80 | |
| 81 String _windowsArgumentEscape(String argument) { | |
| 82 var result = argument; | |
| 83 if (argument.contains('\t') || argument.contains(' ')) { | |
| 84 // Produce something that the C runtime on Windows will parse | |
| 85 // back as this string. | |
| 86 | |
| 87 // Replace any number of '\' followed by '"' with | |
| 88 // twice as many '\' followed by '\"'. | |
| 89 var backslash = '\\'.charCodeAt(0); | |
| 90 var sb = new StringBuffer(); | |
| 91 var nextPos = 0; | |
| 92 var quotePos = argument.indexOf('"', nextPos); | |
| 93 while (quotePos != -1) { | |
| 94 var numBackslash = 0; | |
| 95 var pos = quotePos - 1; | |
| 96 while (pos >= 0 && argument.charCodeAt(pos) == backslash) { | |
| 97 numBackslash++; | |
| 98 pos--; | |
| 99 } | |
| 100 sb.add(argument.substring(nextPos, quotePos - numBackslash)); | |
| 101 for (var i = 0; i < numBackslash; i++) { | |
| 102 sb.add(r'\\'); | |
| 103 } | |
| 104 sb.add(r'\"'); | |
| 105 nextPos = quotePos + 1; | |
| 106 quotePos = argument.indexOf('"', nextPos); | |
| 107 } | |
| 108 sb.add(argument.substring(nextPos, argument.length)); | |
| 109 result = sb.toString(); | |
| 110 | |
| 111 // Add '"' at the beginning and end and replace all '\' at | |
| 112 // the end with two '\'. | |
| 113 sb = new StringBuffer('"'); | |
| 114 sb.add(result); | |
| 115 nextPos = argument.length - 1; | |
| 116 while (argument.charCodeAt(nextPos) == backslash) { | |
| 117 sb.add('\\'); | |
| 118 nextPos--; | |
| 119 } | |
| 120 sb.add('"'); | |
| 121 result = sb.toString(); | |
| 122 } | |
| 123 | |
| 124 return result; | |
| 125 } | |
| 126 | |
| 127 int _intFromBytes(List<int> bytes, int offset) { | |
| 128 return (bytes[offset] + | |
| 129 (bytes[offset + 1] << 8) + | |
| 130 (bytes[offset + 2] << 16) + | |
| 131 (bytes[offset + 3] << 24)); | |
| 132 } | |
| 133 | |
| 134 Future<Process> _start() { | |
| 135 var completer = new Completer(); | |
| 136 // TODO(ager): Make the actual process starting really async instead of | |
| 137 // simulating it with a timer. | |
| 138 new Timer(0, (_) { | |
| 139 var status = new _ProcessStartStatus(); | |
| 140 bool success = _startNative(_path, | |
| 141 _arguments, | |
| 142 _workingDirectory, | |
| 143 _environment, | |
| 144 _in, | |
| 145 _out, | |
| 146 _err, | |
| 147 _exitHandler, | |
| 148 status); | |
| 149 if (!success) { | |
| 150 _in.close(); | |
| 151 _out.close(); | |
| 152 _err.close(); | |
| 153 _exitHandler.close(); | |
| 154 completer.completeException( | |
| 155 new ProcessException(status._errorMessage, status._errorCode)); | |
| 156 return; | |
| 157 } | |
| 158 _started = true; | |
| 159 | |
| 160 _in._closed = false; | |
| 161 _out._closed = false; | |
| 162 _err._closed = false; | |
| 163 _exitHandler._closed = false; | |
| 164 | |
| 165 // Make sure to activate socket handlers now that the file | |
| 166 // descriptors have been set. | |
| 167 _in._activateHandlers(); | |
| 168 _out._activateHandlers(); | |
| 169 _err._activateHandlers(); | |
| 170 | |
| 171 // Setup an exit handler to handle internal cleanup and possible | |
| 172 // callback when a process terminates. | |
| 173 int exitDataRead = 0; | |
| 174 final int EXIT_DATA_SIZE = 8; | |
| 175 List<int> exitDataBuffer = new List<int>(EXIT_DATA_SIZE); | |
| 176 _exitHandler.inputStream.onData = () { | |
| 177 | |
| 178 int exitCode(List<int> ints) { | |
| 179 var code = _intFromBytes(ints, 0); | |
| 180 var negative = _intFromBytes(ints, 4); | |
| 181 assert(negative == 0 || negative == 1); | |
| 182 return (negative == 0) ? code : -code; | |
| 183 } | |
| 184 | |
| 185 void handleExit() { | |
| 186 _ended = true; | |
| 187 if (_onExit !== null) { | |
| 188 _onExit(exitCode(exitDataBuffer)); | |
| 189 } | |
| 190 _out.close(); | |
| 191 } | |
| 192 | |
| 193 exitDataRead += _exitHandler.inputStream.readInto( | |
| 194 exitDataBuffer, exitDataRead, EXIT_DATA_SIZE - exitDataRead); | |
| 195 if (exitDataRead == EXIT_DATA_SIZE) handleExit(); | |
| 196 }; | |
| 197 | |
| 198 completer.complete(this); | |
| 199 }); | |
| 200 return completer.future; | |
| 201 } | |
| 202 | |
| 203 bool _startNative(String path, | |
| 204 List<String> arguments, | |
| 205 String workingDirectory, | |
| 206 List<String> environment, | |
| 207 Socket input, | |
| 208 Socket output, | |
| 209 Socket error, | |
| 210 Socket exitHandler, | |
| 211 _ProcessStartStatus status) native "Process_Start"; | |
| 212 | |
| 213 InputStream get stdout { | |
| 214 return _in.inputStream; | |
| 215 } | |
| 216 | |
| 217 InputStream get stderr { | |
| 218 return _err.inputStream; | |
| 219 } | |
| 220 | |
| 221 OutputStream get stdin { | |
| 222 return _out.outputStream; | |
| 223 } | |
| 224 | |
| 225 bool kill([ProcessSignal signal = ProcessSignal.SIGTERM]) { | |
| 226 if (signal is! ProcessSignal) { | |
| 227 throw new ArgumentError( | |
| 228 "Argument 'signal' must be a ProcessSignal"); | |
| 229 } | |
| 230 assert(_started); | |
| 231 if (_ended) return false; | |
| 232 return _kill(this, signal._signalNumber); | |
| 233 } | |
| 234 | |
| 235 bool _kill(Process p, int signal) native "Process_Kill"; | |
| 236 | |
| 237 void set onExit(void callback(int exitCode)) { | |
| 238 if (_ended) { | |
| 239 throw new ProcessException("Process killed"); | |
| 240 } | |
| 241 _onExit = callback; | |
| 242 } | |
| 243 | |
| 244 String _path; | |
| 245 List<String> _arguments; | |
| 246 String _workingDirectory; | |
| 247 List<String> _environment; | |
| 248 // Private methods of _Socket are used by _in, _out, and _err. | |
| 249 _Socket _in; | |
| 250 _Socket _out; | |
| 251 _Socket _err; | |
| 252 Socket _exitHandler; | |
| 253 bool _ended; | |
| 254 bool _started; | |
| 255 Function _onExit; | |
| 256 } | |
| 257 | |
| 258 | |
| 259 // _NonInteractiveProcess is a wrapper around an interactive process | |
| 260 // that buffers output so it can be delivered when the process exits. | |
| 261 // _NonInteractiveProcess is used to implement the Process.run | |
| 262 // method. | |
| 263 class _NonInteractiveProcess { | |
| 264 _NonInteractiveProcess(String path, | |
| 265 List<String> arguments, | |
| 266 ProcessOptions options) { | |
| 267 _completer = new Completer<ProcessResult>(); | |
| 268 // Extract output encoding options and verify arguments. | |
| 269 var stdoutEncoding = Encoding.UTF_8; | |
| 270 var stderrEncoding = Encoding.UTF_8; | |
| 271 if (options !== null) { | |
| 272 if (options.stdoutEncoding !== null) { | |
| 273 stdoutEncoding = options.stdoutEncoding; | |
| 274 if (stdoutEncoding is !Encoding) { | |
| 275 throw new ArgumentError( | |
| 276 'stdoutEncoding option is not an encoding: $stdoutEncoding'); | |
| 277 } | |
| 278 } | |
| 279 if (options.stderrEncoding !== null) { | |
| 280 stderrEncoding = options.stderrEncoding; | |
| 281 if (stderrEncoding is !Encoding) { | |
| 282 throw new ArgumentError( | |
| 283 'stderrEncoding option is not an encoding: $stderrEncoding'); | |
| 284 } | |
| 285 } | |
| 286 } | |
| 287 | |
| 288 // Start the underlying process. | |
| 289 var processFuture = new _Process(path, arguments, options)._start(); | |
| 290 | |
| 291 processFuture.then((Process p) { | |
| 292 // Make sure the process stdin is closed. | |
| 293 p.stdin.close; | |
| 294 | |
| 295 // Setup process exit handling. | |
| 296 p.onExit = (exitCode) { | |
| 297 _exitCode = exitCode; | |
| 298 _checkDone(); | |
| 299 }; | |
| 300 | |
| 301 // Setup stdout handling. | |
| 302 _stdoutBuffer = new StringBuffer(); | |
| 303 var stdoutStream = new StringInputStream(p.stdout, stdoutEncoding); | |
| 304 stdoutStream.onData = () { | |
| 305 var data = stdoutStream.read(); | |
| 306 if (data != null) _stdoutBuffer.add(data); | |
| 307 }; | |
| 308 stdoutStream.onClosed = () { | |
| 309 _stdoutClosed = true; | |
| 310 _checkDone(); | |
| 311 }; | |
| 312 | |
| 313 // Setup stderr handling. | |
| 314 _stderrBuffer = new StringBuffer(); | |
| 315 var stderrStream = new StringInputStream(p.stderr, stderrEncoding); | |
| 316 stderrStream.onData = () { | |
| 317 var data = stderrStream.read(); | |
| 318 if (data != null) _stderrBuffer.add(data); | |
| 319 }; | |
| 320 stderrStream.onClosed = () { | |
| 321 _stderrClosed = true; | |
| 322 _checkDone(); | |
| 323 }; | |
| 324 }); | |
| 325 | |
| 326 processFuture.handleException((error) { | |
| 327 _completer.completeException(error); | |
| 328 return true; | |
| 329 }); | |
| 330 } | |
| 331 | |
| 332 void _checkDone() { | |
| 333 if (_exitCode != null && _stderrClosed && _stdoutClosed) { | |
| 334 _completer.complete(new _ProcessResult(_exitCode, | |
| 335 _stdoutBuffer.toString(), | |
| 336 _stderrBuffer.toString())); | |
| 337 } | |
| 338 } | |
| 339 | |
| 340 Future<ProcessResult> get _result => _completer.future; | |
| 341 | |
| 342 Completer<ProcessResult> _completer; | |
| 343 StringBuffer _stdoutBuffer; | |
| 344 StringBuffer _stderrBuffer; | |
| 345 int _exitCode; | |
| 346 bool _stdoutClosed = false; | |
| 347 bool _stderrClosed = false; | |
| 348 } | |
| 349 | |
| 350 | |
| 351 class _ProcessResult implements ProcessResult { | |
| 352 const _ProcessResult(int this.exitCode, | |
| 353 String this.stdout, | |
| 354 String this.stderr); | |
| 355 | |
| 356 final int exitCode; | |
| 357 final String stdout; | |
| 358 final String stderr; | |
| 359 } | |
| OLD | NEW |