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