Bug 944164 - Move a bunch of file-related stuff onto an os.file object, creating aliases from the old names, r=jorendorff
authorSteve Fink <sfink@mozilla.com>
Wed, 13 May 2015 15:16:26 -0700
changeset 244068 df56de89d8db28c51d82794ecfc98642b8ffcece
parent 244067 a8f3b4f3a3b194c2862d7b7c92f5dd9235e17aa7
child 244069 4a1b5563fdccd1a174b6be3985056c845576fba1
push id59833
push usersfink@mozilla.com
push dateFri, 15 May 2015 18:45:56 +0000
treeherdermozilla-inbound@df56de89d8db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs944164
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 944164 - Move a bunch of file-related stuff onto an os.file object, creating aliases from the old names, r=jorendorff
js/src/shell/OSObject.cpp
js/src/shell/OSObject.h
js/src/shell/js.cpp
js/src/shell/jsshell.h
--- a/js/src/shell/OSObject.cpp
+++ b/js/src/shell/OSObject.cpp
@@ -6,30 +6,280 @@
 
 // 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 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 const JSFunctionSpecWithHelp osfile_functions[] = {
+    JS_FN_HELP("readFile", osfile_readFile, 1, 0,
+"readFile(filename, [\"binary\"])",
+"  Read filename into returned string. Filename is relative to the current\n"
+               "  working directory."),
+
+    JS_FN_HELP("readRelativeToScript", osfile_readRelativeToScript, 1, 0,
+"readRelativeToScript(filename, [\"binary\"])",
+"  Read filename into returned string. Filename is relative to the directory\n"
+"  containing the current script."),
+
+    JS_FN_HELP("redirect", osfile_redirect, 2, 0,
+"redirect(stdoutFilename[, stderrFilename])",
+"  Redirect stdout and/or stderr to the named file. Pass undefined to avoid\n"
+"   redirecting. Filenames are relative to the current working directory."),
+
+    JS_FS_HELP_END
+};
+
 static bool
 os_getenv(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() < 1) {
         JS_ReportError(cx, "os.getenv requires 1 argument");
         return false;
     }
@@ -287,19 +537,59 @@ static const JSFunctionSpecWithHelp os_f
 "  passed in must be numeric, if given."),
 
     JS_FN_HELP("waitpid", os_waitpid, 1, 0,
 "waitpid(pid[, nohang])",
 "  Calls waitpid(). 'nohang' is a boolean indicating whether to pass WNOHANG.\n"
 "  The return value is an object containing a 'pid' field, if a process was waitable\n"
 "  and an 'exitStatus' field if a pid exited."),
 #endif
+
     JS_FS_HELP_END
 };
 
 bool
-js::DefineOS(JSContext* cx, HandleObject global)
+DefineOS(JSContext* cx, HandleObject global, bool fuzzingSafe)
 {
     RootedObject obj(cx, JS_NewPlainObject(cx));
-    return obj &&
-           JS_DefineFunctionsWithHelp(cx, obj, os_functions) &&
-           JS_DefineProperty(cx, global, "os", obj, 0);
+    if (!obj || !JS_DefineProperty(cx, global, "os", obj, 0))
+        return false;
+
+    if (!fuzzingSafe) {
+        if (!JS_DefineFunctionsWithHelp(cx, obj, os_functions))
+            return false;
+    }
+
+    RootedObject osfile(cx, JS_NewPlainObject(cx));
+    if (!osfile ||
+        !JS_DefineFunctionsWithHelp(cx, osfile, osfile_functions) ||
+        !JS_DefineProperty(cx, obj, "file", osfile, 0))
+    {
+        return false;
+    }
+
+    // For backwards compatibility, expose various os.file.* functions as
+    // direct methods on the global.
+    RootedValue val(cx);
+
+    struct {
+        const char* src;
+        const char* dst;
+    } osfile_exports[] = {
+        { "readFile", "read" },
+        { "readFile", "snarf" },
+        { "readRelativeToScript", "readRelativeToScript" },
+        { "redirect", "redirect" }
+    };
+
+    for (auto pair : osfile_exports) {
+        if (!JS_GetProperty(cx, osfile, pair.src, &val))
+            return false;
+        RootedObject function(cx, &val.toObject());
+        if (!JS_DefineProperty(cx, global, pair.dst, function, 0))
+            return false;
+    }
+
+    return true;
 }
+
+}
+}
--- a/js/src/shell/OSObject.h
+++ b/js/src/shell/OSObject.h
@@ -7,16 +7,26 @@
 // OSObject.h - os object for exposing posix system calls in the JS shell
 
 #ifndef shell_OSObject_h
 #define shell_OSObject_h
 
 #include "jsapi.h"
 
 namespace js {
+namespace shell {
 
 /* Define an os object on the given global object. */
 bool
-DefineOS(JSContext* cx, JS::HandleObject global);
+DefineOS(JSContext* cx, JS::HandleObject global, bool fuzzingSafe);
 
+enum PathResolutionMode {
+    RootRelative,
+    ScriptRelative
+};
+
+JSString*
+ResolvePath(JSContext* cx, JS::HandleString filenameStr, PathResolutionMode resolveMode);
+
+}
 }
 
 #endif /* shell_OSObject_h */
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -81,45 +81,35 @@
 #include "vm/WrapperObject.h"
 
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
 
 #include "vm/Interpreter-inl.h"
 #include "vm/Stack-inl.h"
 
-#ifdef XP_WIN
-# define PATH_MAX (MAX_PATH > _MAX_DIR ? MAX_PATH : _MAX_DIR)
-#else
-# include <libgen.h>
-#endif
-
 using namespace js;
 using namespace js::cli;
+using namespace js::shell;
 
 using mozilla::ArrayLength;
 using mozilla::MakeUnique;
 using mozilla::Maybe;
 using mozilla::NumberEqualsInt32;
 using mozilla::PodCopy;
 using mozilla::PodEqual;
 using mozilla::UniquePtr;
 
 enum JSShellExitCode {
     EXITCODE_RUNTIME_ERROR      = 3,
     EXITCODE_FILE_NOT_FOUND     = 4,
     EXITCODE_OUT_OF_MEMORY      = 5,
     EXITCODE_TIMEOUT            = 6
 };
 
-enum PathResolutionMode {
-    RootRelative,
-    ScriptRelative
-};
-
 static size_t gStackChunkSize = 8192;
 
 /*
  * Note: This limit should match the stack limit set by the browser in
  *       js/xpconnect/src/XPCJSRuntime.cpp
  */
 #if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN))
 static size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024;
@@ -190,40 +180,26 @@ static bool reportWarnings = true;
 static bool compileOnly = false;
 static bool fuzzingSafe = false;
 
 #ifdef DEBUG
 static bool dumpEntrainedVariables = false;
 static bool OOM_printAllocationCount = false;
 #endif
 
-enum JSShellErrNum {
-#define MSG_DEF(name, count, exception, format) \
-    name,
-#include "jsshell.msg"
-#undef MSG_DEF
-    JSShellErr_Limit
-};
-
 static JSContext*
 NewContext(JSRuntime* rt);
 
 static void
 DestroyContext(JSContext* cx, bool withGC);
 
 static JSObject*
 NewGlobalObject(JSContext* cx, JS::CompartmentOptions& options,
                 JSPrincipals* principals);
 
-static const JSErrorFormatString*
-my_GetErrorMessage(void* userRef, const unsigned errorNumber);
-
-static void
-my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report);
-
 /*
  * A toy principals type for the shell.
  *
  * In the shell, a principal is simply a 32-bit mask: P subsumes Q if the
  * set bits in P are a superset of those in Q. Thus, the principal 0 is
  * subsumed by everything, and the principal ~0 subsumes everything.
  *
  * As a special case, a null pointer as a principal is treated like 0xffff.
@@ -560,28 +536,16 @@ ReadEvalPrintLoop(JSContext* cx, FILE* i
             // Catch the error, report it, and keep going.
             JS_ReportPendingException(cx);
         }
     } while (!hitEOF && !gQuitting);
 
     fprintf(out, "\n");
 }
 
-class AutoCloseInputFile
-{
-  private:
-    FILE* f_;
-  public:
-    explicit AutoCloseInputFile(FILE* f) : f_(f) {}
-    ~AutoCloseInputFile() {
-        if (f_ && f_ != stdin)
-            fclose(f_);
-    }
-};
-
 static void
 Process(JSContext* cx, const char* filename, bool forceTTY)
 {
     FILE* file;
     if (forceTTY || !filename || strcmp(filename, "-") == 0) {
         file = stdin;
     } else {
         file = fopen(filename, "r");
@@ -626,87 +590,16 @@ Version(JSContext* cx, unsigned argc, js
             return false;
         }
         JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(v));
         args.rval().setInt32(origVersion);
     }
     return true;
 }
 
-/*
- * 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.
- */
-static 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 bool
 CreateMappedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() < 1 || args.length() > 3) {
         JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
                              args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
@@ -1361,18 +1254,18 @@ Evaluate(JSContext* cx, unsigned argc, j
             return false;
 
         saveBuffer.forget();
     }
 
     return JS_WrapValue(cx, args.rval());
 }
 
-static JSString*
-FileAsString(JSContext* cx, const char* pathname)
+JSString*
+js::shell::FileAsString(JSContext* cx, const char* pathname)
 {
     FILE* file;
     RootedString str(cx);
     size_t len, cc;
     char* buf;
 
     file = fopen(pathname, "rb");
     if (!file) {
@@ -1407,49 +1300,16 @@ FileAsString(JSContext* cx, const char* 
                 JS_free(cx, buf);
             }
         }
     }
 
     return str;
 }
 
-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<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;
-}
-
 /*
  * Function to run scripts and return compilation + execution time. Semantics
  * are closely modelled after the equivalent function in WebKit, as this is used
  * to produce benchmark timings by SunSpider.
  */
 static bool
 Run(JSContext* cx, unsigned argc, jsval* vp)
 {
@@ -3587,122 +3447,16 @@ struct FreeOnReturn
         this->ptr = ptr;
     }
 
     ~FreeOnReturn() {
         JS_free(cx, (void*)ptr);
     }
 };
 
-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, 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, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "snarf");
-        return false;
-    }
-
-    RootedString givenPath(cx, args[0].toString());
-    RootedString str(cx, 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
-Snarf(JSContext* cx, unsigned argc, jsval* vp)
-{
-    return ReadFile(cx, argc, vp, false);
-}
-
-static bool
-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
-RedirectOutput(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 int sArgc;
 static char** sArgv;
 
 class AutoCStringVector
 {
     Vector<char*> argv_;
   public:
     explicit AutoCStringVector(JSContext* cx) : argv_(cx) {}
@@ -4507,16 +4261,17 @@ ReflectTrackedOptimizations(JSContext* c
     RootedValue jsonVal(cx);
     if (!JS_ParseJSON(cx, str, &jsonVal))
         return false;
 
     args.rval().set(jsonVal);
     return true;
 }
 
+namespace js {
 namespace shell {
 
 class ShellAutoEntryMonitor : JS::dbg::AutoEntryMonitor {
     Vector<UniqueChars, 1, js::SystemAllocPolicy> log;
     bool oom;
     bool enteredWithoutExit;
 
   public:
@@ -4578,16 +4333,17 @@ class ShellAutoEntryMonitor : JS::dbg::A
         }
 
         resultValue.setObject(*result.get());
         return true;
     }
 };
 
 } // namespace shell
+} // namespace js
 
 static bool
 EntryPoints(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() != 1) {
         JS_ReportError(cx, "Wrong number of arguments");
@@ -4600,17 +4356,17 @@ EntryPoints(JSContext* cx, unsigned argc
 
     // { function: f } --- Call f.
     {
         RootedValue fun(cx), dummy(cx);
 
         if (!JS_GetProperty(cx, opts, "function", &fun))
             return false;
         if (!fun.isUndefined()) {
-            shell::ShellAutoEntryMonitor sarep(cx);
+            js::shell::ShellAutoEntryMonitor sarep(cx);
             if (!Call(cx, UndefinedHandleValue, fun, JS::HandleValueArray::empty(), &dummy))
                 return false;
             return sarep.buildResult(cx, args.rval());
         }
     }
 
     // { object: o, property: p, value: v } --- Fetch o[p], or if
     // v is present, assign o[p] = v.
@@ -4630,17 +4386,17 @@ EntryPoints(JSContext* cx, unsigned argc
                 return false;
             RootedId id(cx);
             if (!JS_StringToId(cx, string, &id))
                 return false;
 
             if (!JS_GetProperty(cx, opts, "value", &valuev))
                 return false;
 
-            shell::ShellAutoEntryMonitor sarep(cx);
+            js::shell::ShellAutoEntryMonitor sarep(cx);
 
             if (!valuev.isUndefined()) {
                 if (!JS_SetPropertyById(cx, object, id, valuev))
                     return false;
             } else {
                 if (!JS_GetPropertyById(cx, object, id, &valuev))
                     return false;
             }
@@ -4651,32 +4407,32 @@ EntryPoints(JSContext* cx, unsigned argc
 
     // { ToString: v } --- Apply JS::ToString to v.
     {
         RootedValue v(cx);
 
         if (!JS_GetProperty(cx, opts, "ToString", &v))
             return false;
         if (!v.isUndefined()) {
-            shell::ShellAutoEntryMonitor sarep(cx);
+            js::shell::ShellAutoEntryMonitor sarep(cx);
             if (!JS::ToString(cx, v))
                 return false;
             return sarep.buildResult(cx, args.rval());
         }
     }
 
     // { ToNumber: v } --- Apply JS::ToNumber to v.
     {
         RootedValue v(cx);
         double dummy;
 
         if (!JS_GetProperty(cx, opts, "ToNumber", &v))
             return false;
         if (!v.isUndefined()) {
-            shell::ShellAutoEntryMonitor sarep(cx);
+            js::shell::ShellAutoEntryMonitor sarep(cx);
             if (!JS::ToNumber(cx, v, &dummy))
                 return false;
             return sarep.buildResult(cx, args.rval());
         }
     }
 
     // { eval: code } --- Apply ToString and then Evaluate to code.
     {
@@ -4694,17 +4450,17 @@ EntryPoints(JSContext* cx, unsigned argc
                 return false;
             const char16_t* chars = stableChars.twoByteRange().start().get();
             size_t length = codeString->length();
 
             CompileOptions options(cx);
             options.setIntroductionType("entryPoint eval")
                    .setFileAndLine("entryPoint eval", 1);
 
-            shell::ShellAutoEntryMonitor sarep(cx);
+            js::shell::ShellAutoEntryMonitor sarep(cx);
             if (!JS::Evaluate(cx, options, chars, length, &dummy))
                 return false;
             return sarep.buildResult(cx, args.rval());
         }
     }
 
     JS_ReportError(cx, "bad 'params' object");
     return false;
@@ -4900,30 +4656,16 @@ static const JSFunctionSpecWithHelp shel
 "arrayInfo(a1, a2, ...)",
 "  Report statistics about arrays."),
 #endif
 
     JS_FN_HELP("sleep", Sleep_fn, 1, 0,
 "sleep(dt)",
 "  Sleep for dt seconds."),
 
-    JS_FN_HELP("snarf", Snarf, 1, 0,
-"snarf(filename, [\"binary\"])",
-"  Read filename into returned string. Filename is relative to the current\n"
-               "  working directory."),
-
-    JS_FN_HELP("read", Snarf, 1, 0,
-"read(filename, [\"binary\"])",
-"  Synonym for snarf."),
-
-    JS_FN_HELP("readRelativeToScript", ReadRelativeToScript, 1, 0,
-"readRelativeToScript(filename, [\"binary\"])",
-"  Read filename into returned string. Filename is relative to the directory\n"
-"  containing the current script."),
-
     JS_FN_HELP("compile", Compile, 1, 0,
 "compile(code)",
 "  Compiles a string to bytecode, potentially throwing."),
 
     JS_FN_HELP("parse", Parse, 1, 0,
 "parse(code)",
 "  Parses a string, potentially throwing."),
 
@@ -5106,21 +4848,16 @@ static const JSFunctionSpecWithHelp fuzz
     JS_FN_HELP("line2pc", LineToPC, 0, 0,
 "line2pc([fun,] line)",
 "  Map line number to PC."),
 
     JS_FN_HELP("pc2line", PCToLine, 0, 0,
 "pc2line(fun[, pc])",
 "  Map PC to line number."),
 
-    JS_FN_HELP("redirect", RedirectOutput, 2, 0,
-"redirect(stdoutFilename[, stderrFilename])",
-"  Redirect stdout and/or stderr to the named file. Pass undefined to avoid\n"
-"   redirecting. Filenames are relative to the current working directory."),
-
     JS_FN_HELP("nestedShell", NestedShell, 0, 0,
 "nestedShell(shellArgs...)",
 "  Execute the given code in a new JS shell process, passing this nested shell\n"
 "  the arguments passed to nestedShell. argv[0] of the nested shell will be argv[0]\n"
 "  of the current shell (which is assumed to be the actual path to the shell.\n"
 "  arguments[0] (of the call to nestedShell) will be argv[1], arguments[1] will\n"
 "  be argv[2], etc."),
 
@@ -5289,27 +5026,27 @@ Help(JSContext* cx, unsigned argc, jsval
 
 static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = {
 #define MSG_DEF(name, count, exception, format) \
     { format, count, JSEXN_ERR } ,
 #include "jsshell.msg"
 #undef MSG_DEF
 };
 
-static const JSErrorFormatString*
-my_GetErrorMessage(void* userRef, const unsigned errorNumber)
+const JSErrorFormatString*
+js::shell::my_GetErrorMessage(void* userRef, const unsigned errorNumber)
 {
     if (errorNumber == 0 || errorNumber >= JSShellErr_Limit)
         return nullptr;
 
     return &jsShell_ErrorFormatString[errorNumber];
 }
 
-static void
-my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report)
+void
+js::shell::my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report)
 {
     gGotError = PrintError(cx, gErrFile, message, report, reportWarnings);
     if (report->exnType != JSEXN_NONE && !JSREPORT_IS_WARNING(report->flags)) {
         if (report->errorNumber == JSMSG_OUT_OF_MEMORY) {
             gExitCode = EXITCODE_OUT_OF_MEMORY;
         } else {
             gExitCode = EXITCODE_RUNTIME_ERROR;
         }
@@ -5884,22 +5621,23 @@ NewGlobalObject(JSContext* cx, JS::Compa
             return nullptr;
         }
         if (!js::DefineTestingFunctions(cx, glob, fuzzingSafe))
             return nullptr;
 
         if (!fuzzingSafe) {
             if (!JS_DefineFunctionsWithHelp(cx, glob, fuzzing_unsafe_functions))
                 return nullptr;
-            if (!js::DefineOS(cx, glob))
-                return nullptr;
             if (!DefineConsole(cx, glob))
                 return nullptr;
         }
 
+        if (!DefineOS(cx, glob, fuzzingSafe))
+            return nullptr;
+
         RootedObject performanceObj(cx, JS_NewObject(cx, nullptr));
         if (!performanceObj)
             return nullptr;
         RootedObject mozMemoryObj(cx, JS_NewObject(cx, nullptr));
         if (!mozMemoryObj)
             return nullptr;
         RootedObject gcObj(cx, gc::NewMemoryInfoObject(cx));
         if (!gcObj)
new file mode 100644
--- /dev/null
+++ b/js/src/shell/jsshell.h
@@ -0,0 +1,41 @@
+#ifndef jsshell_js_h
+#define jsshell_js_h
+
+#include "jsapi.h"
+
+namespace js {
+namespace shell {
+
+enum JSShellErrNum {
+#define MSG_DEF(name, count, exception, format) \
+    name,
+#include "jsshell.msg"
+#undef MSG_DEF
+    JSShellErr_Limit
+};
+
+const JSErrorFormatString*
+my_GetErrorMessage(void* userRef, const unsigned errorNumber);
+
+static void
+my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report);
+
+JSString*
+FileAsString(JSContext* cx, const char* pathname);
+
+class AutoCloseInputFile
+{
+  private:
+    FILE* f_;
+  public:
+    explicit AutoCloseInputFile(FILE* f) : f_(f) {}
+    ~AutoCloseInputFile() {
+        if (f_ && f_ != stdin)
+            fclose(f_);
+    }
+};
+
+} /* namespace shell */
+} /* namespace js */
+
+#endif