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