Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(193)

Side by Side Diff: runtime/bin/process_impl.dart

Issue 11337019: Use patching for dart:io. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Address comments Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698