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