1 // Copyright 2009 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <errno.h> 6 #include <fcntl.h> 7 #include <signal.h> 8 #include <stdlib.h> 9 #include <string.h> 10 #include <sys/select.h> 11 #include <sys/stat.h> 12 #include <sys/time.h> 13 #include <sys/types.h> 14 #include <sys/wait.h> 15 #include <unistd.h> 16 17 #include "src/d8.h" 18 19 namespace v8 { 20 21 22 // If the buffer ends in the middle of a UTF-8 sequence then we return 23 // the length of the string up to but not including the incomplete UTF-8 24 // sequence. If the buffer ends with a valid UTF-8 sequence then we 25 // return the whole buffer. 26 static int LengthWithoutIncompleteUtf8(char* buffer, int len) { 27 int answer = len; 28 // 1-byte encoding. 29 static const int kUtf8SingleByteMask = 0x80; 30 static const int kUtf8SingleByteValue = 0x00; 31 // 2-byte encoding. 32 static const int kUtf8TwoByteMask = 0xe0; 33 static const int kUtf8TwoByteValue = 0xc0; 34 // 3-byte encoding. 35 static const int kUtf8ThreeByteMask = 0xf0; 36 static const int kUtf8ThreeByteValue = 0xe0; 37 // 4-byte encoding. 38 static const int kUtf8FourByteMask = 0xf8; 39 static const int kUtf8FourByteValue = 0xf0; 40 // Subsequent bytes of a multi-byte encoding. 41 static const int kMultiByteMask = 0xc0; 42 static const int kMultiByteValue = 0x80; 43 int multi_byte_bytes_seen = 0; 44 while (answer > 0) { 45 int c = buffer[answer - 1]; 46 // Ends in valid single-byte sequence? 47 if ((c & kUtf8SingleByteMask) == kUtf8SingleByteValue) return answer; 48 // Ends in one or more subsequent bytes of a multi-byte value? 49 if ((c & kMultiByteMask) == kMultiByteValue) { 50 multi_byte_bytes_seen++; 51 answer--; 52 } else { 53 if ((c & kUtf8TwoByteMask) == kUtf8TwoByteValue) { 54 if (multi_byte_bytes_seen >= 1) { 55 return answer + 2; 56 } 57 return answer - 1; 58 } else if ((c & kUtf8ThreeByteMask) == kUtf8ThreeByteValue) { 59 if (multi_byte_bytes_seen >= 2) { 60 return answer + 3; 61 } 62 return answer - 1; 63 } else if ((c & kUtf8FourByteMask) == kUtf8FourByteValue) { 64 if (multi_byte_bytes_seen >= 3) { 65 return answer + 4; 66 } 67 return answer - 1; 68 } else { 69 return answer; // Malformed UTF-8. 70 } 71 } 72 } 73 return 0; 74 } 75 76 77 // Suspends the thread until there is data available from the child process. 78 // Returns false on timeout, true on data ready. 79 static bool WaitOnFD(int fd, 80 int read_timeout, 81 int total_timeout, 82 const struct timeval& start_time) { 83 fd_set readfds, writefds, exceptfds; 84 struct timeval timeout; 85 int gone = 0; 86 if (total_timeout != -1) { 87 struct timeval time_now; 88 gettimeofday(&time_now, NULL); 89 time_t seconds = time_now.tv_sec - start_time.tv_sec; 90 gone = static_cast<int>(seconds * 1000 + 91 (time_now.tv_usec - start_time.tv_usec) / 1000); 92 if (gone >= total_timeout) return false; 93 } 94 FD_ZERO(&readfds); 95 FD_ZERO(&writefds); 96 FD_ZERO(&exceptfds); 97 FD_SET(fd, &readfds); 98 FD_SET(fd, &exceptfds); 99 if (read_timeout == -1 || 100 (total_timeout != -1 && total_timeout - gone < read_timeout)) { 101 read_timeout = total_timeout - gone; 102 } 103 timeout.tv_usec = (read_timeout % 1000) * 1000; 104 timeout.tv_sec = read_timeout / 1000; 105 int number_of_fds_ready = select(fd + 1, 106 &readfds, 107 &writefds, 108 &exceptfds, 109 read_timeout != -1 ? &timeout : NULL); 110 return number_of_fds_ready == 1; 111 } 112 113 114 // Checks whether we ran out of time on the timeout. Returns true if we ran out 115 // of time, false if we still have time. 116 static bool TimeIsOut(const struct timeval& start_time, const int& total_time) { 117 if (total_time == -1) return false; 118 struct timeval time_now; 119 gettimeofday(&time_now, NULL); 120 // Careful about overflow. 121 int seconds = static_cast<int>(time_now.tv_sec - start_time.tv_sec); 122 if (seconds > 100) { 123 if (seconds * 1000 > total_time) return true; 124 return false; 125 } 126 int useconds = static_cast<int>(time_now.tv_usec - start_time.tv_usec); 127 if (seconds * 1000000 + useconds > total_time * 1000) { 128 return true; 129 } 130 return false; 131 } 132 133 134 // A utility class that does a non-hanging waitpid on the child process if we 135 // bail out of the System() function early. If you don't ever do a waitpid on 136 // a subprocess then it turns into one of those annoying 'zombie processes'. 137 class ZombieProtector { 138 public: 139 explicit ZombieProtector(int pid): pid_(pid) { } 140 ~ZombieProtector() { if (pid_ != 0) waitpid(pid_, NULL, 0); } 141 void ChildIsDeadNow() { pid_ = 0; } 142 private: 143 int pid_; 144 }; 145 146 147 // A utility class that closes a file descriptor when it goes out of scope. 148 class OpenFDCloser { 149 public: 150 explicit OpenFDCloser(int fd): fd_(fd) { } 151 ~OpenFDCloser() { close(fd_); } 152 private: 153 int fd_; 154 }; 155 156 157 // A utility class that takes the array of command arguments and puts then in an 158 // array of new[]ed UTF-8 C strings. Deallocates them again when it goes out of 159 // scope. 160 class ExecArgs { 161 public: 162 ExecArgs() { 163 exec_args_[0] = NULL; 164 } 165 bool Init(Isolate* isolate, Local<Value> arg0, Local<Array> command_args) { 166 String::Utf8Value prog(arg0); 167 if (*prog == NULL) { 168 const char* message = 169 "os.system(): String conversion of program name failed"; 170 isolate->ThrowException( 171 String::NewFromUtf8(isolate, message, NewStringType::kNormal) 172 .ToLocalChecked()); 173 return false; 174 } 175 int len = prog.length() + 3; 176 char* c_arg = new char[len]; 177 snprintf(c_arg, len, "%s", *prog); 178 exec_args_[0] = c_arg; 179 int i = 1; 180 for (unsigned j = 0; j < command_args->Length(); i++, j++) { 181 Local<Value> arg( 182 command_args->Get(isolate->GetCurrentContext(), 183 Integer::New(isolate, j)).ToLocalChecked()); 184 String::Utf8Value utf8_arg(arg); 185 if (*utf8_arg == NULL) { 186 exec_args_[i] = NULL; // Consistent state for destructor. 187 const char* message = 188 "os.system(): String conversion of argument failed."; 189 isolate->ThrowException( 190 String::NewFromUtf8(isolate, message, NewStringType::kNormal) 191 .ToLocalChecked()); 192 return false; 193 } 194 int len = utf8_arg.length() + 1; 195 char* c_arg = new char[len]; 196 snprintf(c_arg, len, "%s", *utf8_arg); 197 exec_args_[i] = c_arg; 198 } 199 exec_args_[i] = NULL; 200 return true; 201 } 202 ~ExecArgs() { 203 for (unsigned i = 0; i < kMaxArgs; i++) { 204 if (exec_args_[i] == NULL) { 205 return; 206 } 207 delete [] exec_args_[i]; 208 exec_args_[i] = 0; 209 } 210 } 211 static const unsigned kMaxArgs = 1000; 212 char* const* arg_array() const { return exec_args_; } 213 const char* arg0() const { return exec_args_[0]; } 214 215 private: 216 char* exec_args_[kMaxArgs + 1]; 217 }; 218 219 220 // Gets the optional timeouts from the arguments to the system() call. 221 static bool GetTimeouts(const v8::FunctionCallbackInfo<v8::Value>& args, 222 int* read_timeout, 223 int* total_timeout) { 224 if (args.Length() > 3) { 225 if (args[3]->IsNumber()) { 226 *total_timeout = args[3] 227 ->Int32Value(args.GetIsolate()->GetCurrentContext()) 228 .FromJust(); 229 } else { 230 args.GetIsolate()->ThrowException( 231 String::NewFromUtf8(args.GetIsolate(), 232 "system: Argument 4 must be a number", 233 NewStringType::kNormal).ToLocalChecked()); 234 return false; 235 } 236 } 237 if (args.Length() > 2) { 238 if (args[2]->IsNumber()) { 239 *read_timeout = args[2] 240 ->Int32Value(args.GetIsolate()->GetCurrentContext()) 241 .FromJust(); 242 } else { 243 args.GetIsolate()->ThrowException( 244 String::NewFromUtf8(args.GetIsolate(), 245 "system: Argument 3 must be a number", 246 NewStringType::kNormal).ToLocalChecked()); 247 return false; 248 } 249 } 250 return true; 251 } 252 253 254 static const int kReadFD = 0; 255 static const int kWriteFD = 1; 256 257 258 // This is run in the child process after fork() but before exec(). It normally 259 // ends with the child process being replaced with the desired child program. 260 // It only returns if an error occurred. 261 static void ExecSubprocess(int* exec_error_fds, 262 int* stdout_fds, 263 const ExecArgs& exec_args) { 264 close(exec_error_fds[kReadFD]); // Don't need this in the child. 265 close(stdout_fds[kReadFD]); // Don't need this in the child. 266 close(1); // Close stdout. 267 dup2(stdout_fds[kWriteFD], 1); // Dup pipe fd to stdout. 268 close(stdout_fds[kWriteFD]); // Don't need the original fd now. 269 fcntl(exec_error_fds[kWriteFD], F_SETFD, FD_CLOEXEC); 270 execvp(exec_args.arg0(), exec_args.arg_array()); 271 // Only get here if the exec failed. Write errno to the parent to tell 272 // them it went wrong. If it went well the pipe is closed. 273 int err = errno; 274 ssize_t bytes_written; 275 do { 276 bytes_written = write(exec_error_fds[kWriteFD], &err, sizeof(err)); 277 } while (bytes_written == -1 && errno == EINTR); 278 // Return (and exit child process). 279 } 280 281 282 // Runs in the parent process. Checks that the child was able to exec (closing 283 // the file desriptor), or reports an error if it failed. 284 static bool ChildLaunchedOK(Isolate* isolate, int* exec_error_fds) { 285 ssize_t bytes_read; 286 int err; 287 do { 288 bytes_read = read(exec_error_fds[kReadFD], &err, sizeof(err)); 289 } while (bytes_read == -1 && errno == EINTR); 290 if (bytes_read != 0) { 291 isolate->ThrowException( 292 String::NewFromUtf8(isolate, strerror(err), NewStringType::kNormal) 293 .ToLocalChecked()); 294 return false; 295 } 296 return true; 297 } 298 299 300 // Accumulates the output from the child in a string handle. Returns true if it 301 // succeeded or false if an exception was thrown. 302 static Local<Value> GetStdout(Isolate* isolate, int child_fd, 303 const struct timeval& start_time, 304 int read_timeout, int total_timeout) { 305 Local<String> accumulator = String::Empty(isolate); 306 307 int fullness = 0; 308 static const int kStdoutReadBufferSize = 4096; 309 char buffer[kStdoutReadBufferSize]; 310 311 if (fcntl(child_fd, F_SETFL, O_NONBLOCK) != 0) { 312 return isolate->ThrowException( 313 String::NewFromUtf8(isolate, strerror(errno), NewStringType::kNormal) 314 .ToLocalChecked()); 315 } 316 317 int bytes_read; 318 do { 319 bytes_read = static_cast<int>( 320 read(child_fd, buffer + fullness, kStdoutReadBufferSize - fullness)); 321 if (bytes_read == -1) { 322 if (errno == EAGAIN) { 323 if (!WaitOnFD(child_fd, 324 read_timeout, 325 total_timeout, 326 start_time) || 327 (TimeIsOut(start_time, total_timeout))) { 328 return isolate->ThrowException( 329 String::NewFromUtf8(isolate, "Timed out waiting for output", 330 NewStringType::kNormal).ToLocalChecked()); 331 } 332 continue; 333 } else if (errno == EINTR) { 334 continue; 335 } else { 336 break; 337 } 338 } 339 if (bytes_read + fullness > 0) { 340 int length = bytes_read == 0 ? 341 bytes_read + fullness : 342 LengthWithoutIncompleteUtf8(buffer, bytes_read + fullness); 343 Local<String> addition = 344 String::NewFromUtf8(isolate, buffer, NewStringType::kNormal, length) 345 .ToLocalChecked(); 346 accumulator = String::Concat(accumulator, addition); 347 fullness = bytes_read + fullness - length; 348 memcpy(buffer, buffer + length, fullness); 349 } 350 } while (bytes_read != 0); 351 return accumulator; 352 } 353 354 355 // Modern Linux has the waitid call, which is like waitpid, but more useful 356 // if you want a timeout. If we don't have waitid we can't limit the time 357 // waiting for the process to exit without losing the information about 358 // whether it exited normally. In the common case this doesn't matter because 359 // we don't get here before the child has closed stdout and most programs don't 360 // do that before they exit. 361 // 362 // We're disabling usage of waitid in Mac OS X because it doens't work for us: 363 // a parent process hangs on waiting while a child process is already a zombie. 364 // See http://code.google.com/p/v8/issues/detail?id=401. 365 #if defined(WNOWAIT) && !defined(ANDROID) && !defined(__APPLE__) \ 366 && !defined(__NetBSD__) 367 #if !defined(__FreeBSD__) 368 #define HAS_WAITID 1 369 #endif 370 #endif 371 372 373 // Get exit status of child. 374 static bool WaitForChild(Isolate* isolate, 375 int pid, 376 ZombieProtector& child_waiter, // NOLINT 377 const struct timeval& start_time, 378 int read_timeout, 379 int total_timeout) { 380 #ifdef HAS_WAITID 381 382 siginfo_t child_info; 383 child_info.si_pid = 0; 384 int useconds = 1; 385 // Wait for child to exit. 386 while (child_info.si_pid == 0) { 387 waitid(P_PID, pid, &child_info, WEXITED | WNOHANG | WNOWAIT); 388 usleep(useconds); 389 if (useconds < 1000000) useconds <<= 1; 390 if ((read_timeout != -1 && useconds / 1000 > read_timeout) || 391 (TimeIsOut(start_time, total_timeout))) { 392 isolate->ThrowException( 393 String::NewFromUtf8(isolate, 394 "Timed out waiting for process to terminate", 395 NewStringType::kNormal).ToLocalChecked()); 396 kill(pid, SIGINT); 397 return false; 398 } 399 } 400 if (child_info.si_code == CLD_KILLED) { 401 char message[999]; 402 snprintf(message, 403 sizeof(message), 404 "Child killed by signal %d", 405 child_info.si_status); 406 isolate->ThrowException( 407 String::NewFromUtf8(isolate, message, NewStringType::kNormal) 408 .ToLocalChecked()); 409 return false; 410 } 411 if (child_info.si_code == CLD_EXITED && child_info.si_status != 0) { 412 char message[999]; 413 snprintf(message, 414 sizeof(message), 415 "Child exited with status %d", 416 child_info.si_status); 417 isolate->ThrowException( 418 String::NewFromUtf8(isolate, message, NewStringType::kNormal) 419 .ToLocalChecked()); 420 return false; 421 } 422 423 #else // No waitid call. 424 425 int child_status; 426 waitpid(pid, &child_status, 0); // We hang here if the child doesn't exit. 427 child_waiter.ChildIsDeadNow(); 428 if (WIFSIGNALED(child_status)) { 429 char message[999]; 430 snprintf(message, 431 sizeof(message), 432 "Child killed by signal %d", 433 WTERMSIG(child_status)); 434 isolate->ThrowException( 435 String::NewFromUtf8(isolate, message, NewStringType::kNormal) 436 .ToLocalChecked()); 437 return false; 438 } 439 if (WEXITSTATUS(child_status) != 0) { 440 char message[999]; 441 int exit_status = WEXITSTATUS(child_status); 442 snprintf(message, 443 sizeof(message), 444 "Child exited with status %d", 445 exit_status); 446 isolate->ThrowException( 447 String::NewFromUtf8(isolate, message, NewStringType::kNormal) 448 .ToLocalChecked()); 449 return false; 450 } 451 452 #endif // No waitid call. 453 454 return true; 455 } 456 457 458 // Implementation of the system() function (see d8.h for details). 459 void Shell::System(const v8::FunctionCallbackInfo<v8::Value>& args) { 460 HandleScope scope(args.GetIsolate()); 461 int read_timeout = -1; 462 int total_timeout = -1; 463 if (!GetTimeouts(args, &read_timeout, &total_timeout)) return; 464 Local<Array> command_args; 465 if (args.Length() > 1) { 466 if (!args[1]->IsArray()) { 467 args.GetIsolate()->ThrowException( 468 String::NewFromUtf8(args.GetIsolate(), 469 "system: Argument 2 must be an array", 470 NewStringType::kNormal).ToLocalChecked()); 471 return; 472 } 473 command_args = Local<Array>::Cast(args[1]); 474 } else { 475 command_args = Array::New(args.GetIsolate(), 0); 476 } 477 if (command_args->Length() > ExecArgs::kMaxArgs) { 478 args.GetIsolate()->ThrowException( 479 String::NewFromUtf8(args.GetIsolate(), "Too many arguments to system()", 480 NewStringType::kNormal).ToLocalChecked()); 481 return; 482 } 483 if (args.Length() < 1) { 484 args.GetIsolate()->ThrowException( 485 String::NewFromUtf8(args.GetIsolate(), "Too few arguments to system()", 486 NewStringType::kNormal).ToLocalChecked()); 487 return; 488 } 489 490 struct timeval start_time; 491 gettimeofday(&start_time, NULL); 492 493 ExecArgs exec_args; 494 if (!exec_args.Init(args.GetIsolate(), args[0], command_args)) { 495 return; 496 } 497 int exec_error_fds[2]; 498 int stdout_fds[2]; 499 500 if (pipe(exec_error_fds) != 0) { 501 args.GetIsolate()->ThrowException( 502 String::NewFromUtf8(args.GetIsolate(), "pipe syscall failed.", 503 NewStringType::kNormal).ToLocalChecked()); 504 return; 505 } 506 if (pipe(stdout_fds) != 0) { 507 args.GetIsolate()->ThrowException( 508 String::NewFromUtf8(args.GetIsolate(), "pipe syscall failed.", 509 NewStringType::kNormal).ToLocalChecked()); 510 return; 511 } 512 513 pid_t pid = fork(); 514 if (pid == 0) { // Child process. 515 ExecSubprocess(exec_error_fds, stdout_fds, exec_args); 516 exit(1); 517 } 518 519 // Parent process. Ensure that we clean up if we exit this function early. 520 ZombieProtector child_waiter(pid); 521 close(exec_error_fds[kWriteFD]); 522 close(stdout_fds[kWriteFD]); 523 OpenFDCloser error_read_closer(exec_error_fds[kReadFD]); 524 OpenFDCloser stdout_read_closer(stdout_fds[kReadFD]); 525 526 Isolate* isolate = args.GetIsolate(); 527 if (!ChildLaunchedOK(isolate, exec_error_fds)) return; 528 529 Local<Value> accumulator = GetStdout(isolate, stdout_fds[kReadFD], start_time, 530 read_timeout, total_timeout); 531 if (accumulator->IsUndefined()) { 532 kill(pid, SIGINT); // On timeout, kill the subprocess. 533 args.GetReturnValue().Set(accumulator); 534 return; 535 } 536 537 if (!WaitForChild(isolate, pid, child_waiter, start_time, read_timeout, 538 total_timeout)) { 539 return; 540 } 541 542 args.GetReturnValue().Set(accumulator); 543 } 544 545 546 void Shell::ChangeDirectory(const v8::FunctionCallbackInfo<v8::Value>& args) { 547 if (args.Length() != 1) { 548 const char* message = "chdir() takes one argument"; 549 args.GetIsolate()->ThrowException( 550 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 551 .ToLocalChecked()); 552 return; 553 } 554 String::Utf8Value directory(args[0]); 555 if (*directory == NULL) { 556 const char* message = "os.chdir(): String conversion of argument failed."; 557 args.GetIsolate()->ThrowException( 558 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 559 .ToLocalChecked()); 560 return; 561 } 562 if (chdir(*directory) != 0) { 563 args.GetIsolate()->ThrowException( 564 String::NewFromUtf8(args.GetIsolate(), strerror(errno), 565 NewStringType::kNormal).ToLocalChecked()); 566 return; 567 } 568 } 569 570 571 void Shell::SetUMask(const v8::FunctionCallbackInfo<v8::Value>& args) { 572 if (args.Length() != 1) { 573 const char* message = "umask() takes one argument"; 574 args.GetIsolate()->ThrowException( 575 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 576 .ToLocalChecked()); 577 return; 578 } 579 if (args[0]->IsNumber()) { 580 int previous = umask( 581 args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust()); 582 args.GetReturnValue().Set(previous); 583 return; 584 } else { 585 const char* message = "umask() argument must be numeric"; 586 args.GetIsolate()->ThrowException( 587 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 588 .ToLocalChecked()); 589 return; 590 } 591 } 592 593 594 static bool CheckItsADirectory(Isolate* isolate, char* directory) { 595 struct stat stat_buf; 596 int stat_result = stat(directory, &stat_buf); 597 if (stat_result != 0) { 598 isolate->ThrowException( 599 String::NewFromUtf8(isolate, strerror(errno), NewStringType::kNormal) 600 .ToLocalChecked()); 601 return false; 602 } 603 if ((stat_buf.st_mode & S_IFDIR) != 0) return true; 604 isolate->ThrowException( 605 String::NewFromUtf8(isolate, strerror(EEXIST), NewStringType::kNormal) 606 .ToLocalChecked()); 607 return false; 608 } 609 610 611 // Returns true for success. Creates intermediate directories as needed. No 612 // error if the directory exists already. 613 static bool mkdirp(Isolate* isolate, char* directory, mode_t mask) { 614 int result = mkdir(directory, mask); 615 if (result == 0) return true; 616 if (errno == EEXIST) { 617 return CheckItsADirectory(isolate, directory); 618 } else if (errno == ENOENT) { // Intermediate path element is missing. 619 char* last_slash = strrchr(directory, '/'); 620 if (last_slash == NULL) { 621 isolate->ThrowException( 622 String::NewFromUtf8(isolate, strerror(errno), NewStringType::kNormal) 623 .ToLocalChecked()); 624 return false; 625 } 626 *last_slash = 0; 627 if (!mkdirp(isolate, directory, mask)) return false; 628 *last_slash = '/'; 629 result = mkdir(directory, mask); 630 if (result == 0) return true; 631 if (errno == EEXIST) { 632 return CheckItsADirectory(isolate, directory); 633 } 634 isolate->ThrowException( 635 String::NewFromUtf8(isolate, strerror(errno), NewStringType::kNormal) 636 .ToLocalChecked()); 637 return false; 638 } else { 639 isolate->ThrowException( 640 String::NewFromUtf8(isolate, strerror(errno), NewStringType::kNormal) 641 .ToLocalChecked()); 642 return false; 643 } 644 } 645 646 647 void Shell::MakeDirectory(const v8::FunctionCallbackInfo<v8::Value>& args) { 648 mode_t mask = 0777; 649 if (args.Length() == 2) { 650 if (args[1]->IsNumber()) { 651 mask = args[1] 652 ->Int32Value(args.GetIsolate()->GetCurrentContext()) 653 .FromJust(); 654 } else { 655 const char* message = "mkdirp() second argument must be numeric"; 656 args.GetIsolate()->ThrowException( 657 String::NewFromUtf8(args.GetIsolate(), message, 658 NewStringType::kNormal).ToLocalChecked()); 659 return; 660 } 661 } else if (args.Length() != 1) { 662 const char* message = "mkdirp() takes one or two arguments"; 663 args.GetIsolate()->ThrowException( 664 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 665 .ToLocalChecked()); 666 return; 667 } 668 String::Utf8Value directory(args[0]); 669 if (*directory == NULL) { 670 const char* message = "os.mkdirp(): String conversion of argument failed."; 671 args.GetIsolate()->ThrowException( 672 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 673 .ToLocalChecked()); 674 return; 675 } 676 mkdirp(args.GetIsolate(), *directory, mask); 677 } 678 679 680 void Shell::RemoveDirectory(const v8::FunctionCallbackInfo<v8::Value>& args) { 681 if (args.Length() != 1) { 682 const char* message = "rmdir() takes one or two arguments"; 683 args.GetIsolate()->ThrowException( 684 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 685 .ToLocalChecked()); 686 return; 687 } 688 String::Utf8Value directory(args[0]); 689 if (*directory == NULL) { 690 const char* message = "os.rmdir(): String conversion of argument failed."; 691 args.GetIsolate()->ThrowException( 692 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 693 .ToLocalChecked()); 694 return; 695 } 696 rmdir(*directory); 697 } 698 699 700 void Shell::SetEnvironment(const v8::FunctionCallbackInfo<v8::Value>& args) { 701 if (args.Length() != 2) { 702 const char* message = "setenv() takes two arguments"; 703 args.GetIsolate()->ThrowException( 704 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 705 .ToLocalChecked()); 706 return; 707 } 708 String::Utf8Value var(args[0]); 709 String::Utf8Value value(args[1]); 710 if (*var == NULL) { 711 const char* message = 712 "os.setenv(): String conversion of variable name failed."; 713 args.GetIsolate()->ThrowException( 714 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 715 .ToLocalChecked()); 716 return; 717 } 718 if (*value == NULL) { 719 const char* message = 720 "os.setenv(): String conversion of variable contents failed."; 721 args.GetIsolate()->ThrowException( 722 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 723 .ToLocalChecked()); 724 return; 725 } 726 setenv(*var, *value, 1); 727 } 728 729 730 void Shell::UnsetEnvironment(const v8::FunctionCallbackInfo<v8::Value>& args) { 731 if (args.Length() != 1) { 732 const char* message = "unsetenv() takes one argument"; 733 args.GetIsolate()->ThrowException( 734 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 735 .ToLocalChecked()); 736 return; 737 } 738 String::Utf8Value var(args[0]); 739 if (*var == NULL) { 740 const char* message = 741 "os.setenv(): String conversion of variable name failed."; 742 args.GetIsolate()->ThrowException( 743 String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal) 744 .ToLocalChecked()); 745 return; 746 } 747 unsetenv(*var); 748 } 749 750 751 void Shell::AddOSMethods(Isolate* isolate, Local<ObjectTemplate> os_templ) { 752 os_templ->Set(String::NewFromUtf8(isolate, "system", NewStringType::kNormal) 753 .ToLocalChecked(), 754 FunctionTemplate::New(isolate, System)); 755 os_templ->Set(String::NewFromUtf8(isolate, "chdir", NewStringType::kNormal) 756 .ToLocalChecked(), 757 FunctionTemplate::New(isolate, ChangeDirectory)); 758 os_templ->Set(String::NewFromUtf8(isolate, "setenv", NewStringType::kNormal) 759 .ToLocalChecked(), 760 FunctionTemplate::New(isolate, SetEnvironment)); 761 os_templ->Set(String::NewFromUtf8(isolate, "unsetenv", NewStringType::kNormal) 762 .ToLocalChecked(), 763 FunctionTemplate::New(isolate, UnsetEnvironment)); 764 os_templ->Set(String::NewFromUtf8(isolate, "umask", NewStringType::kNormal) 765 .ToLocalChecked(), 766 FunctionTemplate::New(isolate, SetUMask)); 767 os_templ->Set(String::NewFromUtf8(isolate, "mkdirp", NewStringType::kNormal) 768 .ToLocalChecked(), 769 FunctionTemplate::New(isolate, MakeDirectory)); 770 os_templ->Set(String::NewFromUtf8(isolate, "rmdir", NewStringType::kNormal) 771 .ToLocalChecked(), 772 FunctionTemplate::New(isolate, RemoveDirectory)); 773 } 774 775 void Shell::Exit(int exit_code) { 776 // Use _exit instead of exit to avoid races between isolate 777 // threads and static destructors. 778 fflush(stdout); 779 fflush(stderr); 780 _exit(exit_code); 781 } 782 783 } // namespace v8 784