blob: 4744d38bf22904601a6555b288db8896e06767e2 [file] [log] [blame]
/* -*- 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