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