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

Side by Side Diff: src/d8-posix.cc

Issue 42641: Add a system() call to the d8 shell (Unix only). (Closed) Base URL: http://v8.googlecode.com/svn/branches/bleeding_edge/
Patch Set: '' Created 11 years, 9 months 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
« no previous file with comments | « src/d8.cc ('k') | src/d8-windows.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2009 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are
4 // met:
5 //
6 // * Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer.
8 // * Redistributions in binary form must reproduce the above
9 // copyright notice, this list of conditions and the following
10 // disclaimer in the documentation and/or other materials provided
11 // with the distribution.
12 // * Neither the name of Google Inc. nor the names of its
13 // contributors may be used to endorse or promote products derived
14 // from this software without specific prior written permission.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28
29 #include <stdlib.h>
30 #include <errno.h>
31 #include <sys/types.h>
32 #include <sys/time.h>
33 #include <time.h>
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <sys/wait.h>
37 #include <signal.h>
38
39
40 #include "d8.h"
41 #include "d8-debug.h"
42 #include "debug.h"
43
44
45 namespace v8 {
46
47
48 // If the buffer ends in the middle of a UTF-8 sequence then we return
49 // the length of the string up to but not including the incomplete UTF-8
50 // sequence. If the buffer ends with a valid UTF-8 sequence then we
51 // return the whole buffer.
52 static int LengthWithoutIncompleteUtf8(char* buffer, int len) {
53 int answer = len;
54 // 1-byte encoding.
55 static const int kUtf8SingleByteMask = 0x80;
56 static const int kUtf8SingleByteValue = 0x00;
57 // 2-byte encoding.
58 static const int kUtf8TwoByteMask = 0xe0;
59 static const int kUtf8TwoByteValue = 0xc0;
60 // 3-byte encoding.
61 static const int kUtf8ThreeByteMask = 0xf0;
62 static const int kUtf8ThreeByteValue = 0xe0;
63 // 4-byte encoding.
64 static const int kUtf8FourByteMask = 0xf8;
65 static const int kUtf8FourByteValue = 0xf0;
66 // Subsequent bytes of a multi-byte encoding.
67 static const int kMultiByteMask = 0xc0;
68 static const int kMultiByteValue = 0x80;
69 int multi_byte_bytes_seen = 0;
70 while (answer > 0) {
71 int c = buffer[answer - 1];
72 // Ends in valid single-byte sequence?
73 if ((c & kUtf8SingleByteMask) == kUtf8SingleByteValue) return answer;
74 // Ends in one or more subsequent bytes of a multi-byte value?
75 if ((c & kMultiByteMask) == kMultiByteValue) {
76 multi_byte_bytes_seen++;
77 answer--;
78 } else {
79 if ((c & kUtf8TwoByteMask) == kUtf8TwoByteValue) {
80 if (multi_byte_bytes_seen >= 1) {
81 return answer + 2;
82 }
83 return answer - 1;
84 } else if ((c & kUtf8ThreeByteMask) == kUtf8ThreeByteValue) {
85 if (multi_byte_bytes_seen >= 2) {
86 return answer + 3;
87 }
88 return answer - 1;
89 } else if ((c & kUtf8FourByteMask) == kUtf8FourByteValue) {
90 if (multi_byte_bytes_seen >= 3) {
91 return answer + 4;
92 }
93 return answer - 1;
94 } else {
95 return answer; // Malformed UTF-8.
96 }
97 }
98 }
99 return 0;
100 }
101
102
103 // Suspends the thread until there is data available from the child process.
104 // Returns false on timeout, true on data ready.
105 static bool WaitOnFD(int fd,
106 int read_timeout,
107 int* total_timeout,
108 struct timeval& start_time) {
109 fd_set readfds, writefds, exceptfds;
110 struct timeval timeout;
111 if (*total_timeout != -1) {
112 struct timeval time_now;
113 gettimeofday(&time_now, NULL);
114 int seconds = time_now.tv_sec - start_time.tv_sec;
115 int gone = seconds * 1000 + (time_now.tv_usec - start_time.tv_usec) / 1000;
116 if (gone >= *total_timeout) return false;
117 *total_timeout -= gone;
118 }
119 FD_ZERO(&readfds);
120 FD_ZERO(&writefds);
121 FD_ZERO(&exceptfds);
122 FD_SET(fd, &readfds);
123 FD_SET(fd, &exceptfds);
124 if (read_timeout == -1 ||
125 (*total_timeout != -1 && *total_timeout < read_timeout)) {
126 read_timeout = *total_timeout;
127 }
128 timeout.tv_usec = (read_timeout % 1000) * 1000;
129 timeout.tv_sec = read_timeout / 1000;
130 int number_of_fds_ready = select(fd + 1,
131 &readfds,
132 &writefds,
133 &exceptfds,
134 read_timeout != -1 ? &timeout : NULL);
135 return number_of_fds_ready == 1;
136 }
137
138
139 // Checks whether we ran out of time on the timeout. Returns true if we ran out
140 // of time, false if we still have time.
141 static bool TimeIsOut(const struct timeval& start_time, const int& total_time) {
142 if (total_time == -1) return false;
143 struct timeval time_now;
144 gettimeofday(&time_now, NULL);
145 // Careful about overflow.
146 int seconds = time_now.tv_sec - start_time.tv_sec;
147 if (seconds > 100) {
148 if (seconds * 1000 > total_time) return true;
149 return false;
150 }
151 int useconds = time_now.tv_usec - start_time.tv_usec;
152 if (seconds * 1000000 + useconds > total_time * 1000) {
153 return true;
154 }
155 return false;
156 }
157
158
159 // A utility class that does a non-hanging waitpid on the child process if we
160 // bail out of the System() function early. If you don't ever do a waitpid on
161 // a subprocess then it turns into one of those annoying 'zombie processes'.
162 class ZombieProtector {
163 public:
164 explicit ZombieProtector(int pid): pid_(pid) { }
165 ~ZombieProtector() { if (pid_ != 0) waitpid(pid_, NULL, WNOHANG); }
166 void ChildIsDeadNow() { pid_ = 0; }
167 private:
168 int pid_;
169 };
170
171
172 // A utility class that closes a file descriptor when it goes out of scope.
173 class OpenFDCloser {
174 public:
175 explicit OpenFDCloser(int fd): fd_(fd) { }
176 ~OpenFDCloser() { close(fd_); }
177 private:
178 int fd_;
179 };
180
181
182 // A utility class that takes the array of command arguments and puts then in an
183 // array of new[]ed UTF-8 C strings. Deallocates them again when it goes out of
184 // scope.
185 class ExecArgs {
186 public:
187 ExecArgs(Handle<Value> arg0, Handle<Array> command_args) {
188 String::Utf8Value prog(arg0);
189 int len = prog.length() + 1;
190 char* c_arg = new char[len];
191 snprintf(c_arg, len, "%s", *prog);
192 exec_args_[0] = c_arg;
193 int i = 1;
194 for (unsigned j = 0; j < command_args->Length(); i++, j++) {
195 Handle<Value> arg(command_args->Get(Integer::New(j)));
196 String::Utf8Value utf8_arg(arg);
197 int len = utf8_arg.length() + 1;
198 char* c_arg = new char[len];
199 snprintf(c_arg, len, "%s", *utf8_arg);
200 exec_args_[i] = c_arg;
201 }
202 exec_args_[i] = NULL;
203 }
204 ~ExecArgs() {
205 for (unsigned i = 0; i < kMaxArgs; i++) {
206 if (exec_args_[i] == NULL) {
207 return;
208 }
209 delete [] exec_args_[i];
210 exec_args_[i] = 0;
211 }
212 }
213 static const unsigned kMaxArgs = 1000;
214 char** arg_array() { return exec_args_; }
215 char* arg0() { return exec_args_[0]; }
216 private:
217 char* exec_args_[kMaxArgs + 1];
218 };
219
220
221 // Gets the optional timeouts from the arguments to the system() call.
222 static bool GetTimeouts(const Arguments& args,
223 int* read_timeout,
224 int* total_timeout) {
225 if (args.Length() > 3) {
226 if (args[3]->IsNumber()) {
227 *total_timeout = args[3]->Int32Value();
228 } else {
229 ThrowException(String::New("system: Argument 4 must be a number"));
230 return false;
231 }
232 }
233 if (args.Length() > 2) {
234 if (args[2]->IsNumber()) {
235 *read_timeout = args[2]->Int32Value();
236 } else {
237 ThrowException(String::New("system: Argument 3 must be a number"));
238 return false;
239 }
240 }
241 return true;
242 }
243
244
245 static const int kReadFD = 0;
246 static const int kWriteFD = 1;
247
248
249 // This is run in the child process after fork() but before exec(). It normally
250 // ends with the child process being replaced with the desired child program.
251 // It only returns if an error occurred.
252 static void ExecSubprocess(int* exec_error_fds,
253 int* stdout_fds,
254 ExecArgs& exec_args) {
255 close(exec_error_fds[kReadFD]); // Don't need this in the child.
256 close(stdout_fds[kReadFD]); // Don't need this in the child.
257 close(1); // Close stdout.
258 dup2(stdout_fds[kWriteFD], 1); // Dup pipe fd to stdout.
259 close(stdout_fds[kWriteFD]); // Don't need the original fd now.
260 fcntl(exec_error_fds[kWriteFD], F_SETFD, FD_CLOEXEC);
261 execvp(exec_args.arg0(), exec_args.arg_array());
262 // Only get here if the exec failed. Write errno to the parent to tell
263 // them it went wrong. If it went well the pipe is closed.
264 int err = errno;
265 write(exec_error_fds[kWriteFD], &err, sizeof(err));
266 // Return (and exit child process).
267 }
268
269
270 // Runs in the parent process. Checks that the child was able to exec (closing
271 // the file desriptor), or reports an error if it failed.
272 static bool ChildLaunchedOK(int* exec_error_fds) {
273 int bytes_read;
274 int err;
275 do {
276 bytes_read = read(exec_error_fds[kReadFD], &err, sizeof(err));
277 } while (bytes_read == -1 && errno == EINTR);
278 if (bytes_read != 0) {
279 ThrowException(String::New(strerror(err)));
280 return false;
281 }
282 return true;
283 }
284
285
286 // Accumulates the output from the child in a string handle. Returns true if it
287 // succeeded or false if an exception was thrown.
288 static Handle<Value> GetStdout(int child_fd,
289 struct timeval& start_time,
290 int read_timeout,
291 int* total_timeout) {
292 Handle<String> accumulator = String::Empty();
293 const char* source = "function(a, b) { return a + b; }";
294 Handle<Value> cons_as_obj(Script::Compile(String::New(source))->Run());
295 Handle<Function> cons_function(Function::Cast(*cons_as_obj));
296 Handle<Value> cons_args[2];
297
298 int fullness = 0;
299 static const int kStdoutReadBufferSize = 4096;
300 char buffer[kStdoutReadBufferSize];
301
302 if (fcntl(child_fd, F_SETFL, O_NONBLOCK) != 0) {
303 return ThrowException(String::New(strerror(errno)));
304 }
305
306 int bytes_read;
307 do {
308 bytes_read = read(child_fd,
309 buffer + fullness,
310 kStdoutReadBufferSize - fullness);
311 if (bytes_read == -1) {
312 if (errno == EAGAIN) {
313 if (!WaitOnFD(child_fd,
314 read_timeout,
315 total_timeout,
316 start_time) ||
317 (TimeIsOut(start_time, *total_timeout))) {
318 return ThrowException(String::New("Timed out waiting for output"));
319 }
320 continue;
321 } else if (errno == EINTR) {
322 continue;
323 } else {
324 break;
325 }
326 }
327 if (bytes_read + fullness > 0) {
328 int length = bytes_read == 0 ?
329 bytes_read + fullness :
330 LengthWithoutIncompleteUtf8(buffer, bytes_read + fullness);
331 Handle<String> addition = String::New(buffer, length);
332 cons_args[0] = accumulator;
333 cons_args[1] = addition;
334 accumulator = Handle<String>::Cast(cons_function->Call(
335 Shell::utility_context()->Global(),
336 2,
337 cons_args));
338 fullness = bytes_read + fullness - length;
339 memcpy(buffer, buffer + length, fullness);
340 }
341 } while (bytes_read != 0);
342 return accumulator;
343 }
344
345
346 // Modern Linux has the waitid call, which is like waitpid, but more useful
347 // if you want a timeout. If we don't have waitid we can't limit the time
348 // waiting for the process to exit without losing the information about
349 // whether it exited normally. In the common case this doesn't matter because
350 // we don't get here before the child has closed stdout and most programs don't
351 // do that before they exit.
352 #if defined(WNOWAIT) && !defined(ANDROID)
353 #define HAS_WAITID 1
354 #endif
355
356
357 // Get exit status of child.
358 static bool WaitForChild(int pid,
359 ZombieProtector& child_waiter,
360 struct timeval& start_time,
361 int read_timeout,
362 int total_timeout) {
363 #ifdef HAS_WAITID
364
365 siginfo_t child_info;
366 child_info.si_pid = 0;
367 int useconds = 1;
368 // Wait for child to exit.
369 while (child_info.si_pid == 0) {
370 waitid(P_PID, pid, &child_info, WEXITED | WNOHANG | WNOWAIT);
371 usleep(useconds);
372 if (useconds < 1000000) useconds <<= 1;
373 if ((read_timeout != -1 && useconds / 1000 > read_timeout) ||
374 (TimeIsOut(start_time, total_timeout))) {
375 ThrowException(String::New("Timed out waiting for process to terminate"));
376 kill(pid, SIGINT);
377 return false;
378 }
379 }
380 child_waiter.ChildIsDeadNow();
381 if (child_info.si_code == CLD_KILLED) {
382 char message[999];
383 snprintf(message,
384 sizeof(message),
385 "Child killed by signal %d",
386 child_info.si_status);
387 ThrowException(String::New(message));
388 return false;
389 }
390 if (child_info.si_code == CLD_EXITED && child_info.si_status != 0) {
391 char message[999];
392 snprintf(message,
393 sizeof(message),
394 "Child exited with status %d",
395 child_info.si_status);
396 ThrowException(String::New(message));
397 return false;
398 }
399
400 #else // No waitid call.
401
402 int child_status;
403 printf("waitpid");
404 waitpid(pid, &child_status, 0); // We hang here if the child doesn't exit.
405 child_waiter.ChildIsDeadNow();
406 if (WIFSIGNALED(child_status)) {
407 char message[999];
408 snprintf(message,
409 sizeof(message),
410 "Child killed by signal %d",
411 WTERMSIG(child_status));
412 ThrowException(String::New(message));
413 return false;
414 }
415 if (WEXITSTATUS(child_status) != 0) {
416 char message[999];
417 int exit_status = WEXITSTATUS(child_status);
418 snprintf(message,
419 sizeof(message),
420 "Child exited with status %d",
421 exit_status);
422 ThrowException(String::New(message));
423 return false;
424 }
425
426 #endif // No waitid call.
427
428 return true;
429 }
430
431
432 // Implementation of the system() function (see d8.h for details).
433 Handle<Value> Shell::System(const Arguments& args) {
434 HandleScope scope;
435 int read_timeout = -1;
436 int total_timeout = -1;
437 if (!GetTimeouts(args, &read_timeout, &total_timeout)) return v8::Undefined();
438 Handle<Array> command_args;
439 if (args.Length() > 1) {
440 if (!args[1]->IsArray()) {
441 return ThrowException(String::New("system: Argument 2 must be an array"));
442 }
443 command_args = Handle<Array>::Cast(args[1]);
444 } else {
445 command_args = Array::New(0);
446 }
447 if (command_args->Length() > ExecArgs::kMaxArgs) {
448 return ThrowException(String::New("Too many arguments to system()"));
449 }
450 if (args.Length() < 1) {
451 return ThrowException(String::New("Too few arguments to system()"));
452 }
453
454 struct timeval start_time;
455 gettimeofday(&start_time, NULL);
456
457 ExecArgs exec_args(args[0], command_args);
458 int exec_error_fds[2];
459 int stdout_fds[2];
460
461 if (pipe(exec_error_fds) != 0) {
462 return ThrowException(String::New("pipe syscall failed."));
463 }
464 if (pipe(stdout_fds) != 0) {
465 return ThrowException(String::New("pipe syscall failed."));
466 }
467
468 pid_t pid = fork();
469 if (pid == 0) { // Child process.
470 ExecSubprocess(exec_error_fds, stdout_fds, exec_args);
471 exit(1);
472 }
473
474 // Parent process. Ensure that we clean up if we exit this function early.
475 ZombieProtector child_waiter(pid);
476 close(exec_error_fds[kWriteFD]);
477 close(stdout_fds[kWriteFD]);
478 OpenFDCloser error_read_closer(exec_error_fds[kReadFD]);
479 OpenFDCloser stdout_read_closer(stdout_fds[kReadFD]);
480
481 if (!ChildLaunchedOK(exec_error_fds)) return v8::Undefined();
482
483 Handle<Value> accumulator = GetStdout(stdout_fds[kReadFD],
484 start_time,
485 read_timeout,
486 &total_timeout);
487 if (accumulator->IsUndefined()) {
488 kill(pid, SIGINT); // On timeout, kill the subprocess.
489 return accumulator;
490 }
491
492 if (!WaitForChild(pid,
493 child_waiter,
494 start_time,
495 read_timeout,
496 total_timeout)) {
497 return v8::Undefined();
498 }
499
500 return scope.Close(accumulator);
501 }
502
503
504 } // namespace v8
OLDNEW
« no previous file with comments | « src/d8.cc ('k') | src/d8-windows.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698