| /* -*- 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 |