d8-posix.cc 26.3 KB
Newer Older
1
// Copyright 2009 the V8 project authors. All rights reserved.
2 3
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
4 5

#include <errno.h>
6
#include <fcntl.h>
7
#include <netinet/ip.h>
8
#include <signal.h>
9
#include <stdlib.h>
10
#include <string.h>
yangguo's avatar
yangguo committed
11
#include <sys/select.h>
12
#include <sys/socket.h>
13
#include <sys/stat.h>
14
#include <sys/time.h>
15 16
#include <sys/types.h>
#include <sys/wait.h>
17 18
#include <unistd.h>

19
#include "src/d8/d8.h"
20 21 22 23 24 25 26 27 28 29 30 31 32

namespace v8 {

// If the buffer ends in the middle of a UTF-8 sequence then we return
// the length of the string up to but not including the incomplete UTF-8
// sequence.  If the buffer ends with a valid UTF-8 sequence then we
// return the whole buffer.
static int LengthWithoutIncompleteUtf8(char* buffer, int len) {
  int answer = len;
  // 1-byte encoding.
  static const int kUtf8SingleByteMask = 0x80;
  static const int kUtf8SingleByteValue = 0x00;
  // 2-byte encoding.
33 34
  static const int kUtf8TwoByteMask = 0xE0;
  static const int kUtf8TwoByteValue = 0xC0;
35
  // 3-byte encoding.
36 37
  static const int kUtf8ThreeByteMask = 0xF0;
  static const int kUtf8ThreeByteValue = 0xE0;
38
  // 4-byte encoding.
39 40
  static const int kUtf8FourByteMask = 0xF8;
  static const int kUtf8FourByteValue = 0xF0;
41
  // Subsequent bytes of a multi-byte encoding.
42
  static const int kMultiByteMask = 0xC0;
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
  static const int kMultiByteValue = 0x80;
  int multi_byte_bytes_seen = 0;
  while (answer > 0) {
    int c = buffer[answer - 1];
    // Ends in valid single-byte sequence?
    if ((c & kUtf8SingleByteMask) == kUtf8SingleByteValue) return answer;
    // Ends in one or more subsequent bytes of a multi-byte value?
    if ((c & kMultiByteMask) == kMultiByteValue) {
      multi_byte_bytes_seen++;
      answer--;
    } else {
      if ((c & kUtf8TwoByteMask) == kUtf8TwoByteValue) {
        if (multi_byte_bytes_seen >= 1) {
          return answer + 2;
        }
        return answer - 1;
      } else if ((c & kUtf8ThreeByteMask) == kUtf8ThreeByteValue) {
        if (multi_byte_bytes_seen >= 2) {
          return answer + 3;
        }
        return answer - 1;
      } else if ((c & kUtf8FourByteMask) == kUtf8FourByteValue) {
        if (multi_byte_bytes_seen >= 3) {
          return answer + 4;
        }
        return answer - 1;
      } else {
        return answer;  // Malformed UTF-8.
      }
    }
  }
  return 0;
}

// Suspends the thread until there is data available from the child process.
// Returns false on timeout, true on data ready.
79
static bool WaitOnFD(int fd, int read_timeout, int total_timeout,
80
                     const struct timeval& start_time) {
81 82
  fd_set readfds, writefds, exceptfds;
  struct timeval timeout;
83 84
  int gone = 0;
  if (total_timeout != -1) {
85
    struct timeval time_now;
86
    gettimeofday(&time_now, nullptr);
87 88 89
    time_t seconds = time_now.tv_sec - start_time.tv_sec;
    gone = static_cast<int>(seconds * 1000 +
                            (time_now.tv_usec - start_time.tv_usec) / 1000);
90
    if (gone >= total_timeout) return false;
91 92 93 94 95 96 97
  }
  FD_ZERO(&readfds);
  FD_ZERO(&writefds);
  FD_ZERO(&exceptfds);
  FD_SET(fd, &readfds);
  FD_SET(fd, &exceptfds);
  if (read_timeout == -1 ||
98 99
      (total_timeout != -1 && total_timeout - gone < read_timeout)) {
    read_timeout = total_timeout - gone;
100 101 102
  }
  timeout.tv_usec = (read_timeout % 1000) * 1000;
  timeout.tv_sec = read_timeout / 1000;
103 104
  int number_of_fds_ready = select(fd + 1, &readfds, &writefds, &exceptfds,
                                   read_timeout != -1 ? &timeout : nullptr);
105 106 107 108 109 110 111 112
  return number_of_fds_ready == 1;
}

// Checks whether we ran out of time on the timeout.  Returns true if we ran out
// of time, false if we still have time.
static bool TimeIsOut(const struct timeval& start_time, const int& total_time) {
  if (total_time == -1) return false;
  struct timeval time_now;
113
  gettimeofday(&time_now, nullptr);
114
  // Careful about overflow.
115
  int seconds = static_cast<int>(time_now.tv_sec - start_time.tv_sec);
116 117 118 119
  if (seconds > 100) {
    if (seconds * 1000 > total_time) return true;
    return false;
  }
120
  int useconds = static_cast<int>(time_now.tv_usec - start_time.tv_usec);
121 122 123 124 125 126 127 128 129 130 131
  if (seconds * 1000000 + useconds > total_time * 1000) {
    return true;
  }
  return false;
}

// A utility class that does a non-hanging waitpid on the child process if we
// bail out of the System() function early.  If you don't ever do a waitpid on
// a subprocess then it turns into one of those annoying 'zombie processes'.
class ZombieProtector {
 public:
132
  explicit ZombieProtector(int pid) : pid_(pid) {}
133 134 135
  ~ZombieProtector() {
    if (pid_ != 0) waitpid(pid_, nullptr, 0);
  }
136
  void ChildIsDeadNow() { pid_ = 0; }
137

138 139 140 141 142 143 144
 private:
  int pid_;
};

// A utility class that closes a file descriptor when it goes out of scope.
class OpenFDCloser {
 public:
145
  explicit OpenFDCloser(int fd) : fd_(fd) {}
146
  ~OpenFDCloser() { close(fd_); }
147

148 149 150 151 152 153 154 155 156
 private:
  int fd_;
};

// A utility class that takes the array of command arguments and puts then in an
// array of new[]ed UTF-8 C strings.  Deallocates them again when it goes out of
// scope.
class ExecArgs {
 public:
157
  ExecArgs() { exec_args_[0] = nullptr; }
158
  bool Init(Isolate* isolate, Local<Value> arg0, Local<Array> command_args) {
159
    String::Utf8Value prog(isolate, arg0);
160
    if (*prog == nullptr) {
161 162
      isolate->ThrowException(String::NewFromUtf8Literal(
          isolate, "os.system(): String conversion of program name failed"));
163 164 165
      return false;
    }
    int len = prog.length() + 3;
166 167 168 169 170
    char* c_arg = new char[len];
    snprintf(c_arg, len, "%s", *prog);
    exec_args_[0] = c_arg;
    int i = 1;
    for (unsigned j = 0; j < command_args->Length(); i++, j++) {
171
      Local<Value> arg(
172 173 174
          command_args
              ->Get(isolate->GetCurrentContext(), Integer::New(isolate, j))
              .ToLocalChecked());
175
      String::Utf8Value utf8_arg(isolate, arg);
176 177
      if (*utf8_arg == nullptr) {
        exec_args_[i] = nullptr;  // Consistent state for destructor.
178 179
        isolate->ThrowException(String::NewFromUtf8Literal(
            isolate, "os.system(): String conversion of argument failed."));
180 181
        return false;
      }
182 183 184 185 186
      int len = utf8_arg.length() + 1;
      char* c_arg = new char[len];
      snprintf(c_arg, len, "%s", *utf8_arg);
      exec_args_[i] = c_arg;
    }
187
    exec_args_[i] = nullptr;
188
    return true;
189 190 191
  }
  ~ExecArgs() {
    for (unsigned i = 0; i < kMaxArgs; i++) {
192
      if (exec_args_[i] == nullptr) {
193 194
        return;
      }
195
      delete[] exec_args_[i];
196
      exec_args_[i] = nullptr;
197 198 199
    }
  }
  static const unsigned kMaxArgs = 1000;
200 201
  char* const* arg_array() const { return exec_args_; }
  const char* arg0() const { return exec_args_[0]; }
202

203 204 205 206 207
 private:
  char* exec_args_[kMaxArgs + 1];
};

// Gets the optional timeouts from the arguments to the system() call.
208
static bool GetTimeouts(const v8::FunctionCallbackInfo<v8::Value>& args,
209
                        int* read_timeout, int* total_timeout) {
210 211
  if (args.Length() > 3) {
    if (args[3]->IsNumber()) {
212 213 214
      *total_timeout = args[3]
                           ->Int32Value(args.GetIsolate()->GetCurrentContext())
                           .FromJust();
215
    } else {
216 217
      args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
          args.GetIsolate(), "system: Argument 4 must be a number"));
218 219 220 221 222
      return false;
    }
  }
  if (args.Length() > 2) {
    if (args[2]->IsNumber()) {
223 224 225
      *read_timeout = args[2]
                          ->Int32Value(args.GetIsolate()->GetCurrentContext())
                          .FromJust();
226
    } else {
227 228
      args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
          args.GetIsolate(), "system: Argument 3 must be a number"));
229 230 231 232 233 234 235 236 237 238 239 240
      return false;
    }
  }
  return true;
}

static const int kReadFD = 0;
static const int kWriteFD = 1;

// This is run in the child process after fork() but before exec().  It normally
// ends with the child process being replaced with the desired child program.
// It only returns if an error occurred.
241
static void ExecSubprocess(int* exec_error_fds, int* stdout_fds,
242
                           const ExecArgs& exec_args) {
243 244 245 246 247 248 249 250 251 252
  close(exec_error_fds[kReadFD]);  // Don't need this in the child.
  close(stdout_fds[kReadFD]);      // Don't need this in the child.
  close(1);                        // Close stdout.
  dup2(stdout_fds[kWriteFD], 1);   // Dup pipe fd to stdout.
  close(stdout_fds[kWriteFD]);     // Don't need the original fd now.
  fcntl(exec_error_fds[kWriteFD], F_SETFD, FD_CLOEXEC);
  execvp(exec_args.arg0(), exec_args.arg_array());
  // Only get here if the exec failed.  Write errno to the parent to tell
  // them it went wrong.  If it went well the pipe is closed.
  int err = errno;
253
  ssize_t bytes_written;
254 255 256
  do {
    bytes_written = write(exec_error_fds[kWriteFD], &err, sizeof(err));
  } while (bytes_written == -1 && errno == EINTR);
257 258 259 260 261
  // Return (and exit child process).
}

// Runs in the parent process.  Checks that the child was able to exec (closing
// the file desriptor), or reports an error if it failed.
262
static bool ChildLaunchedOK(Isolate* isolate, int* exec_error_fds) {
263
  ssize_t bytes_read;
264 265 266 267 268
  int err;
  do {
    bytes_read = read(exec_error_fds[kReadFD], &err, sizeof(err));
  } while (bytes_read == -1 && errno == EINTR);
  if (bytes_read != 0) {
269
    isolate->ThrowException(
270
        String::NewFromUtf8(isolate, strerror(err)).ToLocalChecked());
271 272 273 274 275 276 277
    return false;
  }
  return true;
}

// Accumulates the output from the child in a string handle.  Returns true if it
// succeeded or false if an exception was thrown.
278 279 280 281
static Local<Value> GetStdout(Isolate* isolate, int child_fd,
                              const struct timeval& start_time,
                              int read_timeout, int total_timeout) {
  Local<String> accumulator = String::Empty(isolate);
282 283 284 285 286 287

  int fullness = 0;
  static const int kStdoutReadBufferSize = 4096;
  char buffer[kStdoutReadBufferSize];

  if (fcntl(child_fd, F_SETFL, O_NONBLOCK) != 0) {
288
    return isolate->ThrowException(
289
        String::NewFromUtf8(isolate, strerror(errno)).ToLocalChecked());
290 291 292 293
  }

  int bytes_read;
  do {
294 295
    bytes_read = static_cast<int>(
        read(child_fd, buffer + fullness, kStdoutReadBufferSize - fullness));
296 297
    if (bytes_read == -1) {
      if (errno == EAGAIN) {
298
        if (!WaitOnFD(child_fd, read_timeout, total_timeout, start_time) ||
299
            (TimeIsOut(start_time, total_timeout))) {
300 301
          return isolate->ThrowException(String::NewFromUtf8Literal(
              isolate, "Timed out waiting for output"));
302 303 304 305 306 307 308 309 310
        }
        continue;
      } else if (errno == EINTR) {
        continue;
      } else {
        break;
      }
    }
    if (bytes_read + fullness > 0) {
311 312 313
      int length = bytes_read == 0 ? bytes_read + fullness
                                   : LengthWithoutIncompleteUtf8(
                                         buffer, bytes_read + fullness);
314 315 316
      Local<String> addition =
          String::NewFromUtf8(isolate, buffer, NewStringType::kNormal, length)
              .ToLocalChecked();
317
      accumulator = String::Concat(isolate, accumulator, addition);
318 319 320 321 322 323 324 325 326 327 328 329 330
      fullness = bytes_read + fullness - length;
      memcpy(buffer, buffer + length, fullness);
    }
  } while (bytes_read != 0);
  return accumulator;
}

// Modern Linux has the waitid call, which is like waitpid, but more useful
// if you want a timeout.  If we don't have waitid we can't limit the time
// waiting for the process to exit without losing the information about
// whether it exited normally.  In the common case this doesn't matter because
// we don't get here before the child has closed stdout and most programs don't
// do that before they exit.
331
//
332
// We're disabling usage of waitid in Mac OS X because it doesn't work for us:
333 334
// a parent process hangs on waiting while a child process is already a zombie.
// See http://code.google.com/p/v8/issues/detail?id=401.
335 336
#if defined(WNOWAIT) && !defined(ANDROID) && !defined(__APPLE__) && \
    !defined(__NetBSD__) && !defined(__Fuchsia__)
337
#if !defined(__FreeBSD__)
338 339
#define HAS_WAITID 1
#endif
340
#endif
341 342

// Get exit status of child.
343
static bool WaitForChild(Isolate* isolate, int pid,
344
                         ZombieProtector& child_waiter,  // NOLINT
345
                         const struct timeval& start_time, int read_timeout,
346 347 348 349 350 351 352 353 354 355 356 357 358
                         int total_timeout) {
#ifdef HAS_WAITID

  siginfo_t child_info;
  child_info.si_pid = 0;
  int useconds = 1;
  // Wait for child to exit.
  while (child_info.si_pid == 0) {
    waitid(P_PID, pid, &child_info, WEXITED | WNOHANG | WNOWAIT);
    usleep(useconds);
    if (useconds < 1000000) useconds <<= 1;
    if ((read_timeout != -1 && useconds / 1000 > read_timeout) ||
        (TimeIsOut(start_time, total_timeout))) {
359 360
      isolate->ThrowException(String::NewFromUtf8Literal(
          isolate, "Timed out waiting for process to terminate"));
361 362 363 364 365 366
      kill(pid, SIGINT);
      return false;
    }
  }
  if (child_info.si_code == CLD_KILLED) {
    char message[999];
367
    snprintf(message, sizeof(message), "Child killed by signal %d",
368
             child_info.si_status);
369
    isolate->ThrowException(
370
        String::NewFromUtf8(isolate, message).ToLocalChecked());
371 372 373 374
    return false;
  }
  if (child_info.si_code == CLD_EXITED && child_info.si_status != 0) {
    char message[999];
375
    snprintf(message, sizeof(message), "Child exited with status %d",
376
             child_info.si_status);
377
    isolate->ThrowException(
378
        String::NewFromUtf8(isolate, message).ToLocalChecked());
379 380 381 382 383 384 385 386 387 388
    return false;
  }

#else  // No waitid call.

  int child_status;
  waitpid(pid, &child_status, 0);  // We hang here if the child doesn't exit.
  child_waiter.ChildIsDeadNow();
  if (WIFSIGNALED(child_status)) {
    char message[999];
389
    snprintf(message, sizeof(message), "Child killed by signal %d",
390
             WTERMSIG(child_status));
391
    isolate->ThrowException(
392
        String::NewFromUtf8(isolate, message).ToLocalChecked());
393 394 395 396 397
    return false;
  }
  if (WEXITSTATUS(child_status) != 0) {
    char message[999];
    int exit_status = WEXITSTATUS(child_status);
398
    snprintf(message, sizeof(message), "Child exited with status %d",
399
             exit_status);
400
    isolate->ThrowException(
401
        String::NewFromUtf8(isolate, message).ToLocalChecked());
402 403 404 405 406 407 408 409
    return false;
  }

#endif  // No waitid call.

  return true;
}

410
#undef HAS_WAITID
411 412

// Implementation of the system() function (see d8.h for details).
413
void Shell::System(const v8::FunctionCallbackInfo<v8::Value>& args) {
414
  HandleScope scope(args.GetIsolate());
415 416
  int read_timeout = -1;
  int total_timeout = -1;
417
  if (!GetTimeouts(args, &read_timeout, &total_timeout)) return;
418
  Local<Array> command_args;
419 420
  if (args.Length() > 1) {
    if (!args[1]->IsArray()) {
421 422
      args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
          args.GetIsolate(), "system: Argument 2 must be an array"));
423
      return;
424
    }
425
    command_args = Local<Array>::Cast(args[1]);
426
  } else {
427
    command_args = Array::New(args.GetIsolate(), 0);
428 429
  }
  if (command_args->Length() > ExecArgs::kMaxArgs) {
430 431
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(), "Too many arguments to system()"));
432
    return;
433 434
  }
  if (args.Length() < 1) {
435 436
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(), "Too few arguments to system()"));
437
    return;
438 439 440
  }

  struct timeval start_time;
441
  gettimeofday(&start_time, nullptr);
442

443
  ExecArgs exec_args;
444
  if (!exec_args.Init(args.GetIsolate(), args[0], command_args)) {
445
    return;
446
  }
447 448 449 450
  int exec_error_fds[2];
  int stdout_fds[2];

  if (pipe(exec_error_fds) != 0) {
451
    args.GetIsolate()->ThrowException(
452
        String::NewFromUtf8Literal(args.GetIsolate(), "pipe syscall failed."));
453
    return;
454 455
  }
  if (pipe(stdout_fds) != 0) {
456
    args.GetIsolate()->ThrowException(
457
        String::NewFromUtf8Literal(args.GetIsolate(), "pipe syscall failed."));
458
    return;
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
  }

  pid_t pid = fork();
  if (pid == 0) {  // Child process.
    ExecSubprocess(exec_error_fds, stdout_fds, exec_args);
    exit(1);
  }

  // Parent process.  Ensure that we clean up if we exit this function early.
  ZombieProtector child_waiter(pid);
  close(exec_error_fds[kWriteFD]);
  close(stdout_fds[kWriteFD]);
  OpenFDCloser error_read_closer(exec_error_fds[kReadFD]);
  OpenFDCloser stdout_read_closer(stdout_fds[kReadFD]);

474 475
  Isolate* isolate = args.GetIsolate();
  if (!ChildLaunchedOK(isolate, exec_error_fds)) return;
476

477 478
  Local<Value> accumulator = GetStdout(isolate, stdout_fds[kReadFD], start_time,
                                       read_timeout, total_timeout);
479 480
  if (accumulator->IsUndefined()) {
    kill(pid, SIGINT);  // On timeout, kill the subprocess.
481 482
    args.GetReturnValue().Set(accumulator);
    return;
483 484
  }

485
  if (!WaitForChild(isolate, pid, child_waiter, start_time, read_timeout,
486
                    total_timeout)) {
487
    return;
488 489
  }

490
  args.GetReturnValue().Set(accumulator);
491 492
}

493
void Shell::ChangeDirectory(const v8::FunctionCallbackInfo<v8::Value>& args) {
494
  if (args.Length() != 1) {
495 496
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(), "chdir() takes one argument"));
497
    return;
498
  }
499
  String::Utf8Value directory(args.GetIsolate(), args[0]);
500
  if (*directory == nullptr) {
501 502 503
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(),
        "os.chdir(): String conversion of argument failed."));
504
    return;
505 506
  }
  if (chdir(*directory) != 0) {
507
    args.GetIsolate()->ThrowException(
508
        String::NewFromUtf8(args.GetIsolate(), strerror(errno))
509
            .ToLocalChecked());
510
    return;
511 512 513
  }
}

514
void Shell::SetUMask(const v8::FunctionCallbackInfo<v8::Value>& args) {
515
  if (args.Length() != 1) {
516 517
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(), "umask() takes one argument"));
518
    return;
519 520
  }
  if (args[0]->IsNumber()) {
521 522
    int previous = umask(
        args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust());
523 524
    args.GetReturnValue().Set(previous);
    return;
525
  } else {
526 527
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(), "umask() argument must be numeric"));
528
    return;
529 530 531
  }
}

532
static bool CheckItsADirectory(Isolate* isolate, char* directory) {
533 534 535
  struct stat stat_buf;
  int stat_result = stat(directory, &stat_buf);
  if (stat_result != 0) {
536
    isolate->ThrowException(
537
        String::NewFromUtf8(isolate, strerror(errno)).ToLocalChecked());
538 539 540
    return false;
  }
  if ((stat_buf.st_mode & S_IFDIR) != 0) return true;
541
  isolate->ThrowException(
542
      String::NewFromUtf8(isolate, strerror(EEXIST)).ToLocalChecked());
543 544 545 546 547
  return false;
}

// Returns true for success.  Creates intermediate directories as needed.  No
// error if the directory exists already.
548
static bool mkdirp(Isolate* isolate, char* directory, mode_t mask) {
549 550 551
  int result = mkdir(directory, mask);
  if (result == 0) return true;
  if (errno == EEXIST) {
552
    return CheckItsADirectory(isolate, directory);
553 554
  } else if (errno == ENOENT) {  // Intermediate path element is missing.
    char* last_slash = strrchr(directory, '/');
555
    if (last_slash == nullptr) {
556
      isolate->ThrowException(
557
          String::NewFromUtf8(isolate, strerror(errno)).ToLocalChecked());
558 559 560
      return false;
    }
    *last_slash = 0;
561
    if (!mkdirp(isolate, directory, mask)) return false;
562 563 564 565
    *last_slash = '/';
    result = mkdir(directory, mask);
    if (result == 0) return true;
    if (errno == EEXIST) {
566
      return CheckItsADirectory(isolate, directory);
567
    }
568
    isolate->ThrowException(
569
        String::NewFromUtf8(isolate, strerror(errno)).ToLocalChecked());
570 571
    return false;
  } else {
572
    isolate->ThrowException(
573
        String::NewFromUtf8(isolate, strerror(errno)).ToLocalChecked());
574 575 576 577
    return false;
  }
}

578
void Shell::MakeDirectory(const v8::FunctionCallbackInfo<v8::Value>& args) {
579 580 581
  mode_t mask = 0777;
  if (args.Length() == 2) {
    if (args[1]->IsNumber()) {
582 583 584
      mask = args[1]
                 ->Int32Value(args.GetIsolate()->GetCurrentContext())
                 .FromJust();
585
    } else {
586 587
      args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
          args.GetIsolate(), "mkdirp() second argument must be numeric"));
588
      return;
589 590
    }
  } else if (args.Length() != 1) {
591 592
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(), "mkdirp() takes one or two arguments"));
593
    return;
594
  }
595
  String::Utf8Value directory(args.GetIsolate(), args[0]);
596
  if (*directory == nullptr) {
597 598 599
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(),
        "os.mkdirp(): String conversion of argument failed."));
600
    return;
601
  }
602
  mkdirp(args.GetIsolate(), *directory, mask);
603 604
}

605
void Shell::RemoveDirectory(const v8::FunctionCallbackInfo<v8::Value>& args) {
606
  if (args.Length() != 1) {
607 608
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(), "rmdir() takes one or two arguments"));
609
    return;
610
  }
611
  String::Utf8Value directory(args.GetIsolate(), args[0]);
612
  if (*directory == nullptr) {
613 614 615
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(),
        "os.rmdir(): String conversion of argument failed."));
616
    return;
617 618 619 620
  }
  rmdir(*directory);
}

621
void Shell::SetEnvironment(const v8::FunctionCallbackInfo<v8::Value>& args) {
622
  if (args.Length() != 2) {
623 624
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(), "setenv() takes two arguments"));
625
    return;
626
  }
627 628
  String::Utf8Value var(args.GetIsolate(), args[0]);
  String::Utf8Value value(args.GetIsolate(), args[1]);
629
  if (*var == nullptr) {
630 631 632
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(),
        "os.setenv(): String conversion of variable name failed."));
633
    return;
634
  }
635
  if (*value == nullptr) {
636 637 638
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(),
        "os.setenv(): String conversion of variable contents failed."));
639
    return;
640 641 642 643
  }
  setenv(*var, *value, 1);
}

644
void Shell::UnsetEnvironment(const v8::FunctionCallbackInfo<v8::Value>& args) {
645
  if (args.Length() != 1) {
646 647
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(), "unsetenv() takes one argument"));
648
    return;
649
  }
650
  String::Utf8Value var(args.GetIsolate(), args[0]);
651
  if (*var == nullptr) {
652 653 654
    args.GetIsolate()->ThrowException(String::NewFromUtf8Literal(
        args.GetIsolate(),
        "os.setenv(): String conversion of variable name failed."));
655
    return;
656 657 658 659
  }
  unsetenv(*var);
}

660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679
char* Shell::ReadCharsFromTcpPort(const char* name, int* size_out) {
  DCHECK_GE(Shell::options.read_from_tcp_port, 0);

  int sockfd = socket(PF_INET, SOCK_STREAM, 0);
  if (sockfd < 0) {
    fprintf(stderr, "Failed to create IPv4 socket\n");
    return nullptr;
  }

  // Create an address for localhost:PORT where PORT is specified by the shell
  // option --read-from-tcp-port.
  sockaddr_in serv_addr;
  memset(&serv_addr, 0, sizeof(sockaddr_in));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  serv_addr.sin_port = htons(Shell::options.read_from_tcp_port);

  if (connect(sockfd, reinterpret_cast<sockaddr*>(&serv_addr),
              sizeof(serv_addr)) < 0) {
    fprintf(stderr, "Failed to connect to localhost:%d\n",
680
            Shell::options.read_from_tcp_port);
681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
    close(sockfd);
    return nullptr;
  }

  // The file server follows the simple protocol for requesting and receiving
  // a file with a given filename:
  //
  //   REQUEST client -> server: {filename}"\0"
  //   RESPONSE server -> client: {4-byte file-length}{file contents}
  //
  // i.e. the request sends the filename with a null terminator, and response
  // sends the file contents by sending the length (as a 4-byte big-endian
  // value) and the contents.

  // If the file length is <0, there was an error sending the file, and the
  // rest of the response is undefined (and may, in the future, contain an error
  // message). The socket should be closed to avoid trying to interpret the
  // undefined data.

  // REQUEST
  // Send the filename.
  size_t sent_len = 0;
  size_t name_len = strlen(name) + 1;  // Includes the null terminator
  while (sent_len < name_len) {
    ssize_t sent_now = send(sockfd, name + sent_len, name_len - sent_len, 0);
    if (sent_now < 0) {
      fprintf(stderr, "Failed to send %s to localhost:%d\n", name,
708
              Shell::options.read_from_tcp_port);
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
      close(sockfd);
      return nullptr;
    }
    sent_len += sent_now;
  }

  // RESPONSE
  // Receive the file.
  ssize_t received = 0;

  // First, read the (zero-terminated) file length.
  uint32_t big_endian_file_length;
  received = recv(sockfd, &big_endian_file_length, 4, 0);
  // We need those 4 bytes to read off the file length.
  if (received < 4) {
    fprintf(stderr, "Failed to receive %s's length from localhost:%d\n", name,
725
            Shell::options.read_from_tcp_port);
726 727 728 729 730 731 732 733
    close(sockfd);
    return nullptr;
  }
  // Reinterpretet the received file length as a signed big-endian integer.
  int32_t file_length = bit_cast<int32_t>(htonl(big_endian_file_length));

  if (file_length < 0) {
    fprintf(stderr, "Received length %d for %s from localhost:%d\n",
734
            file_length, name, Shell::options.read_from_tcp_port);
735
    close(sockfd);
736
    return nullptr;
737 738 739 740 741 742 743 744 745 746 747 748
  }

  // Allocate the output array.
  char* chars = new char[file_length];

  // Now keep receiving and copying until the whole file is received.
  ssize_t total_received = 0;
  while (total_received < file_length) {
    received =
        recv(sockfd, chars + total_received, file_length - total_received, 0);
    if (received < 0) {
      fprintf(stderr, "Failed to receive %s from localhost:%d\n", name,
749
              Shell::options.read_from_tcp_port);
750 751
      close(sockfd);
      delete[] chars;
752
      return nullptr;
753 754 755 756 757 758 759 760
    }
    total_received += received;
  }

  close(sockfd);
  *size_out = file_length;
  return chars;
}
761

762
void Shell::AddOSMethods(Isolate* isolate, Local<ObjectTemplate> os_templ) {
763
  if (options.enable_os_system) {
764
    os_templ->Set(isolate, "system", FunctionTemplate::New(isolate, System));
765
  }
766
  os_templ->Set(isolate, "chdir",
767
                FunctionTemplate::New(isolate, ChangeDirectory));
768
  os_templ->Set(isolate, "setenv",
769
                FunctionTemplate::New(isolate, SetEnvironment));
770
  os_templ->Set(isolate, "unsetenv",
771
                FunctionTemplate::New(isolate, UnsetEnvironment));
772 773
  os_templ->Set(isolate, "umask", FunctionTemplate::New(isolate, SetUMask));
  os_templ->Set(isolate, "mkdirp",
774
                FunctionTemplate::New(isolate, MakeDirectory));
775
  os_templ->Set(isolate, "rmdir",
776
                FunctionTemplate::New(isolate, RemoveDirectory));
777 778
}

779
}  // namespace v8