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