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