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