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