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