| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| // OSObject.h - os object for exposing posix system calls in the JS shell |
| |
| #include "shell/OSObject.h" |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #ifdef XP_WIN |
| #include <direct.h> |
| #include <process.h> |
| #include <string.h> |
| #else |
| #include <sys/wait.h> |
| #include <unistd.h> |
| #endif |
| |
| #include "jsapi.h" |
| // For JSFunctionSpecWithHelp |
| #include "jsfriendapi.h" |
| #include "jsobj.h" |
| #ifdef XP_WIN |
| # include "jswin.h" |
| #endif |
| #include "jswrapper.h" |
| |
| #include "js/Conversions.h" |
| #include "shell/jsshell.h" |
| #include "vm/TypedArrayObject.h" |
| |
| #include "jsobjinlines.h" |
| |
| #ifdef XP_WIN |
| # define PATH_MAX (MAX_PATH > _MAX_DIR ? MAX_PATH : _MAX_DIR) |
| # define getcwd _getcwd |
| #else |
| # include <libgen.h> |
| #endif |
| |
| using js::shell::RCFile; |
| |
| static RCFile** gErrFilePtr = nullptr; |
| static RCFile** gOutFilePtr = nullptr; |
| |
| using namespace JS; |
| |
| namespace js { |
| namespace shell { |
| |
| /* |
| * Resolve a (possibly) relative filename to an absolute path. If |
| * |scriptRelative| is true, then the result will be relative to the directory |
| * containing the currently-running script, or the current working directory if |
| * the currently-running script is "-e" (namely, you're using it from the |
| * command line.) Otherwise, it will be relative to the current working |
| * directory. |
| */ |
| JSString* |
| ResolvePath(JSContext* cx, HandleString filenameStr, PathResolutionMode resolveMode) |
| { |
| JSAutoByteString filename(cx, filenameStr); |
| if (!filename) |
| return nullptr; |
| |
| const char* pathname = filename.ptr(); |
| if (pathname[0] == '/') |
| return filenameStr; |
| #ifdef XP_WIN |
| // Various forms of absolute paths per http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx |
| // "\..." |
| if (pathname[0] == '\\') |
| return filenameStr; |
| // "C:\..." |
| if (strlen(pathname) > 3 && isalpha(pathname[0]) && pathname[1] == ':' && pathname[2] == '\\') |
| return filenameStr; |
| // "\\..." |
| if (strlen(pathname) > 2 && pathname[1] == '\\' && pathname[2] == '\\') |
| return filenameStr; |
| #endif |
| |
| /* Get the currently executing script's name. */ |
| JS::AutoFilename scriptFilename; |
| if (!DescribeScriptedCaller(cx, &scriptFilename)) |
| return nullptr; |
| |
| if (!scriptFilename.get()) |
| return nullptr; |
| |
| if (strcmp(scriptFilename.get(), "-e") == 0 || strcmp(scriptFilename.get(), "typein") == 0) |
| resolveMode = RootRelative; |
| |
| static char buffer[PATH_MAX+1]; |
| if (resolveMode == ScriptRelative) { |
| #ifdef XP_WIN |
| // The docs say it can return EINVAL, but the compiler says it's void |
| _splitpath(scriptFilename.get(), nullptr, buffer, nullptr, nullptr); |
| #else |
| strncpy(buffer, scriptFilename.get(), PATH_MAX+1); |
| if (buffer[PATH_MAX] != '\0') |
| return nullptr; |
| |
| // dirname(buffer) might return buffer, or it might return a |
| // statically-allocated string |
| memmove(buffer, dirname(buffer), strlen(buffer) + 1); |
| #endif |
| } else { |
| const char* cwd = getcwd(buffer, PATH_MAX); |
| if (!cwd) |
| return nullptr; |
| } |
| |
| size_t len = strlen(buffer); |
| buffer[len] = '/'; |
| strncpy(buffer + len + 1, pathname, sizeof(buffer) - (len+1)); |
| if (buffer[PATH_MAX] != '\0') |
| return nullptr; |
| |
| return JS_NewStringCopyZ(cx, buffer); |
| } |
| |
| static JSObject* |
| FileAsTypedArray(JSContext* cx, const char* pathname) |
| { |
| FILE* file = fopen(pathname, "rb"); |
| if (!file) { |
| JS_ReportError(cx, "can't open %s: %s", pathname, strerror(errno)); |
| return nullptr; |
| } |
| AutoCloseInputFile autoClose(file); |
| |
| RootedObject obj(cx); |
| if (fseek(file, 0, SEEK_END) != 0) { |
| JS_ReportError(cx, "can't seek end of %s", pathname); |
| } else { |
| size_t len = ftell(file); |
| if (fseek(file, 0, SEEK_SET) != 0) { |
| JS_ReportError(cx, "can't seek start of %s", pathname); |
| } else { |
| obj = JS_NewUint8Array(cx, len); |
| if (!obj) |
| return nullptr; |
| char* buf = (char*) obj->as<js::TypedArrayObject>().viewData(); |
| size_t cc = fread(buf, 1, len, file); |
| if (cc != len) { |
| JS_ReportError(cx, "can't read %s: %s", pathname, |
| (ptrdiff_t(cc) < 0) ? strerror(errno) : "short read"); |
| obj = nullptr; |
| } |
| } |
| } |
| return obj; |
| } |
| |
| static bool |
| ReadFile(JSContext* cx, unsigned argc, jsval* vp, bool scriptRelative) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() < 1 || args.length() > 2) { |
| JS_ReportErrorNumber(cx, js::shell::my_GetErrorMessage, nullptr, |
| args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS, |
| "snarf"); |
| return false; |
| } |
| |
| if (!args[0].isString() || (args.length() == 2 && !args[1].isString())) { |
| JS_ReportErrorNumber(cx, js::shell::my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "snarf"); |
| return false; |
| } |
| |
| RootedString givenPath(cx, args[0].toString()); |
| RootedString str(cx, js::shell::ResolvePath(cx, givenPath, scriptRelative ? ScriptRelative : RootRelative)); |
| if (!str) |
| return false; |
| |
| JSAutoByteString filename(cx, str); |
| if (!filename) |
| return false; |
| |
| if (args.length() > 1) { |
| JSString* opt = JS::ToString(cx, args[1]); |
| if (!opt) |
| return false; |
| bool match; |
| if (!JS_StringEqualsAscii(cx, opt, "binary", &match)) |
| return false; |
| if (match) { |
| JSObject* obj; |
| if (!(obj = FileAsTypedArray(cx, filename.ptr()))) |
| return false; |
| args.rval().setObject(*obj); |
| return true; |
| } |
| } |
| |
| if (!(str = FileAsString(cx, filename.ptr()))) |
| return false; |
| args.rval().setString(str); |
| return true; |
| } |
| |
| static bool |
| osfile_readFile(JSContext* cx, unsigned argc, jsval* vp) |
| { |
| return ReadFile(cx, argc, vp, false); |
| } |
| |
| static bool |
| osfile_readRelativeToScript(JSContext* cx, unsigned argc, jsval* vp) |
| { |
| return ReadFile(cx, argc, vp, true); |
| } |
| |
| static bool |
| Redirect(JSContext* cx, FILE* fp, HandleString relFilename) |
| { |
| RootedString filename(cx, ResolvePath(cx, relFilename, RootRelative)); |
| if (!filename) |
| return false; |
| JSAutoByteString filenameABS(cx, filename); |
| if (!filenameABS) |
| return false; |
| if (freopen(filenameABS.ptr(), "wb", fp) == nullptr) { |
| JS_ReportError(cx, "cannot redirect to %s: %s", filenameABS.ptr(), strerror(errno)); |
| return false; |
| } |
| return true; |
| } |
| |
| static bool |
| osfile_redirect(JSContext* cx, unsigned argc, jsval* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() < 1 || args.length() > 2) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "redirect"); |
| return false; |
| } |
| |
| if (args[0].isString()) { |
| RootedString stdoutPath(cx, args[0].toString()); |
| if (!stdoutPath) |
| return false; |
| if (!Redirect(cx, stdout, stdoutPath)) |
| return false; |
| } |
| |
| if (args.length() > 1 && args[1].isString()) { |
| RootedString stderrPath(cx, args[1].toString()); |
| if (!stderrPath) |
| return false; |
| if (!Redirect(cx, stderr, stderrPath)) |
| return false; |
| } |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| <<<<<<< |
| |
| ||||||| |
| ======= |
| |
| /* static */ RCFile* |
| RCFile::create(JSContext* cx, const char* filename, const char* mode) |
| { |
| FILE* fp = fopen(filename, mode); |
| if (!fp) |
| return nullptr; |
| |
| RCFile* file = cx->new_<RCFile>(fp); |
| if (!file) { |
| fclose(fp); |
| return nullptr; |
| } |
| |
| return file; |
| } |
| |
| void |
| RCFile::close() |
| { |
| if (fp) |
| fclose(fp); |
| fp = nullptr; |
| } |
| |
| bool |
| RCFile::release() |
| { |
| if (--numRefs) |
| return false; |
| this->close(); |
| return true; |
| } |
| |
| class FileObject : public JSObject { |
| enum : uint32_t { |
| FILE_SLOT = 0, |
| NUM_SLOTS |
| }; |
| |
| public: |
| static const js::Class class_; |
| |
| static FileObject* create(JSContext* cx, RCFile* file) { |
| JSObject* obj = js::NewObjectWithClassProto(cx, &class_, JS::NullPtr()); |
| if (!obj) |
| return nullptr; |
| |
| FileObject* fileObj = &obj->as<FileObject>(); |
| fileObj->setRCFile(file); |
| file->acquire(); |
| return fileObj; |
| } |
| |
| static void finalize(FreeOp* fop, JSObject* obj) { |
| FileObject* fileObj = &obj->as<FileObject>(); |
| RCFile* file = fileObj->rcFile(); |
| if (file->release()) { |
| fileObj->setRCFile(nullptr); |
| fop->delete_(file); |
| } |
| } |
| |
| bool isOpen() { |
| RCFile* file = rcFile(); |
| return file && file->isOpen(); |
| } |
| |
| void close() { |
| if (!isOpen()) |
| return; |
| rcFile()->close(); |
| } |
| |
| RCFile* rcFile() { |
| return reinterpret_cast<RCFile*>(js::GetReservedSlot(this, FILE_SLOT).toPrivate()); |
| } |
| |
| private: |
| |
| void setRCFile(RCFile* file) { |
| js::SetReservedSlot(this, FILE_SLOT, PrivateValue(file)); |
| } |
| }; |
| |
| const js::Class FileObject::class_ = { |
| "File", |
| JSCLASS_HAS_RESERVED_SLOTS(FileObject::NUM_SLOTS), |
| nullptr, /* addProperty */ |
| nullptr, /* delProperty */ |
| nullptr, /* getProperty */ |
| nullptr, /* setProperty */ |
| nullptr, /* enumerate */ |
| nullptr, /* resolve */ |
| nullptr, /* mayResolve */ |
| nullptr, /* convert */ |
| FileObject::finalize, /* finalize */ |
| nullptr, /* call */ |
| nullptr, /* hasInstance */ |
| nullptr, /* construct */ |
| nullptr /* trace */ |
| }; |
| |
| static FileObject* |
| redirect(JSContext* cx, HandleString relFilename, RCFile** globalFile) |
| { |
| RootedString filename(cx, ResolvePath(cx, relFilename, RootRelative)); |
| if (!filename) |
| return nullptr; |
| JSAutoByteString filenameABS(cx, filename); |
| if (!filenameABS) |
| return nullptr; |
| RCFile* file = RCFile::create(cx, filenameABS.ptr(), "wb"); |
| if (!file) { |
| JS_ReportError(cx, "cannot redirect to %s: %s", filenameABS.ptr(), strerror(errno)); |
| return nullptr; |
| } |
| |
| // The global gOutFile acquires ownership of the new file, releases |
| // ownership of its old file, and we return a FileObject owning the old |
| // file. |
| file->acquire(); // Global owner of new file |
| |
| FileObject* fileObj = FileObject::create(cx, *globalFile); // Newly created owner of old file |
| if (!fileObj) { |
| file->release(); |
| return nullptr; |
| } |
| |
| (*globalFile)->release(); // Release (global) ownership of old file. |
| *globalFile = file; |
| |
| return fileObj; |
| } |
| |
| static bool |
| Redirect(JSContext* cx, const CallArgs& args, RCFile** outFile) |
| { |
| if (args.length() > 1) { |
| JS_ReportErrorNumber(cx, js::shell::my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "redirect"); |
| return false; |
| } |
| |
| RCFile* oldFile = *outFile; |
| RootedObject oldFileObj(cx, FileObject::create(cx, oldFile)); |
| if (!oldFileObj) |
| return false; |
| |
| if (args.get(0).isUndefined()) { |
| args.rval().setObject(*oldFileObj); |
| return true; |
| } |
| |
| if (args[0].isObject()) { |
| RootedObject fileObj(cx, js::CheckedUnwrap(&args[0].toObject())); |
| if (!fileObj) |
| return false; |
| if (fileObj->is<FileObject>()) { |
| // Passed in a FileObject. Create a FileObject for the previous |
| // global file, and set the global file to the passed-in one. |
| *outFile = fileObj->as<FileObject>().rcFile(); |
| (*outFile)->acquire(); |
| oldFile->release(); |
| |
| args.rval().setObject(*oldFileObj); |
| return true; |
| } |
| } |
| |
| RootedString filename(cx, JS::ToString(cx, args[0])); |
| if (!filename) |
| return false; |
| |
| if (!redirect(cx, filename, outFile)) |
| return false; |
| |
| args.rv |