Package io.apigee.trireme.core.modules

Source Code of io.apigee.trireme.core.modules.ProcessWrap$SpawnedTriremeProcess

/**
* Copyright 2013 Apigee Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.apigee.trireme.core.modules;

import io.apigee.trireme.core.NodeException;
import io.apigee.trireme.core.NodeRuntime;
import io.apigee.trireme.core.NodeScript;
import io.apigee.trireme.core.Sandbox;
import io.apigee.trireme.core.ScriptFuture;
import io.apigee.trireme.core.ScriptStatus;
import io.apigee.trireme.core.ScriptStatusListener;
import io.apigee.trireme.core.ScriptTask;
import io.apigee.trireme.core.SubprocessPolicy;
import io.apigee.trireme.core.internal.BitBucketInputStream;
import io.apigee.trireme.core.internal.BitBucketOutputStream;
import io.apigee.trireme.core.InternalNodeModule;
import io.apigee.trireme.core.internal.NoCloseInputStream;
import io.apigee.trireme.core.internal.NoCloseOutputStream;
import io.apigee.trireme.core.internal.NodeOSException;
import io.apigee.trireme.core.internal.ScriptRunner;
import io.apigee.trireme.core.internal.StreamPiper;
import io.apigee.trireme.core.Utils;
import io.apigee.trireme.core.internal.handles.AbstractHandle;
import io.apigee.trireme.core.internal.handles.JavaInputStreamHandle;
import io.apigee.trireme.core.internal.handles.JavaOutputStreamHandle;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.annotations.JSFunction;
import org.mozilla.javascript.annotations.JSGetter;
import org.mozilla.javascript.annotations.JSSetter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.apigee.trireme.core.ArgUtils.*;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;

/**
* Node's script code for process management uses this native class.
*/
public class ProcessWrap
    implements InternalNodeModule
{
    protected static final Logger log = LoggerFactory.getLogger(ProcessWrap.class);

    public static final String STDIO_PIPE =      "pipe";
    public static final String STDIO_FD =        "fd";
    public static final String STDIO_IGNORE =    "ignore";
    public static final String STDIO_IPC =       "ipc";

    private static final Pattern EQUALS = Pattern.compile("=");

    // A sentinel object for handling IPC "disconnect" events
    public static final Object IPC_DISCONNECT = new Object();

    /**
     * This is a global process table for all Noderunner processes in the same JVM. This way PIDs are
     * portable across spawned processes, although not across VMs.
     */
    private static final ConcurrentHashMap<Integer, SpawnedProcess> processTable =
        new ConcurrentHashMap<Integer, SpawnedProcess>();
    private static final AtomicInteger nextPid = new AtomicInteger(1);

    @Override
    public String getModuleName()
    {
        return "trireme_process_wrap";
    }

    @Override
    public Scriptable registerExports(Context cx, Scriptable scope, NodeRuntime runner)
        throws InvocationTargetException, IllegalAccessException, InstantiationException
    {
        ScriptRunner internalRunner = (ScriptRunner)runner;
        internalRunner.require("stream", cx);

        ScriptableObject.defineClass(scope, ProcessImpl.class, false, true);
        ScriptableObject.defineClass(scope, ProcessModuleImpl.class);

        ProcessModuleImpl exports = (ProcessModuleImpl)cx.newObject(scope, ProcessModuleImpl.CLASS_NAME);
        exports.initialize(internalRunner);
        return exports;
    }

    public static void kill(Context cx, Scriptable scope, int pid, String signal)
    {
        SpawnedProcess proc = processTable.get(pid);
        if (proc == null) {
            throw Utils.makeError(cx, scope, new NodeOSException(Constants.ESRCH));
        }

        if (signal != null) {
            if (log.isDebugEnabled()) {
                log.debug("Terminating pid {} ({}) with {}", pid, proc, signal);
            }
            proc.terminate(signal);
        }
    }

    public static class ProcessModuleImpl
        extends ScriptableObject
    {
        public static final String CLASS_NAME = "_processModule";

        private ScriptRunner runner;

        @Override
        public String getClassName()
        {
            return CLASS_NAME;
        }

        void initialize(ScriptRunner runner)
        {
            this.runner = runner;
        }

        @JSFunction
        public static Object createProcess(Context cx, Scriptable thisObj, Object[] args, Function func)
        {
            ProcessImpl p = (ProcessImpl)cx.newObject(thisObj, ProcessImpl.CLASS_NAME);
            p.initialize(((ProcessModuleImpl)thisObj).runner);
            return p;
        }
    }

    public static class ProcessImpl
        extends Referenceable
    {
        public static final String CLASS_NAME = "_processClass";

        private SpawnedProcess spawned;
        private Function onExit;
        private ScriptRunner runner;
        private int pid;
        private Scriptable childProcessObject;
        private Function onMessage;

        @Override
        public String getClassName()
        {
            return CLASS_NAME;
        }

        public ScriptRunner getRuntime() {
            return runner;
        }

        void initialize(ScriptRunner runner)
        {
            this.runner = runner;
            requestPin();
        }

        @Override
        @JSFunction
        public void close()
        {
            if (spawned != null) {
                spawned.close();
            }
            super.close();
        }

        @JSFunction
        @SuppressWarnings("unused")
        public static Object spawn(Context cx, Scriptable thisObj, Object[] args, Function fn)
        {
            ensureArg(args, 0);
            ensureScriptable(args[0]);
            Scriptable options = (Scriptable)args[0];
            final ProcessImpl self = (ProcessImpl)thisObj;

            if (!options.has("args", options)) {
                return Utils.makeErrorObject(cx, thisObj, Constants.EINVAL, Constants.EINVAL);
            }
            List<String> execArgs = Utils.toStringList((Scriptable)options.get("args", options));
            if (execArgs.isEmpty()) {
                return Utils.makeErrorObject(cx, thisObj, Constants.EINVAL, Constants.EINVAL);
            }

            for (int i = 0; i < execArgs.size(); i++) {
                execArgs.set(i, Utils.unquote(execArgs.get(i)));
            }

            if (self.runner.getSandbox() != null) {
                SubprocessPolicy policy = self.runner.getSandbox().getSubprocessPolicy();
                if ((policy != null) && !policy.allowSubprocess(execArgs)) {
                    return Utils.makeErrorObject(cx, thisObj, Constants.EPERM, Constants.EPERM);
                }
            }

            String procName = execArgs.get(0);
            self.pid = nextPid.getAndIncrement();
            if ("node".equals(procName) || Process.EXECUTABLE_NAME.equals(procName)) {
                self.spawned = new SpawnedTriremeProcess(self);
            } else {
                self.spawned = new SpawnedOSProcess(self);
            }
            processTable.put(self.pid, self.spawned);
            return self.spawned.spawn(cx, execArgs, options);
        }

        private void callOnExit(final int code, final int signal)
        {
            if (log.isDebugEnabled()) {
                log.debug("Process {} exited with code {} and signal {}", spawned, code, signal);
            }
            spawned.setFinished(true);
            processTable.remove(pid);
            final Scriptable domain = runner.getDomain();
            runner.enqueueTask(new ScriptTask()
            {
                @Override
                public void execute(Context cx, Scriptable scope)
                {
                    onExit.call(cx, scope, ProcessImpl.this, new Object[]{code, signal});
                }
            }, domain);
        }

        @JSFunction
        @SuppressWarnings("unused")
        public Object kill(String signal)
        {
            if (spawned != null) {
                if (log.isDebugEnabled()) {
                    log.debug("Killing {}", spawned);
                }
                spawned.terminate(signal);
            }
            return 0;
        }

        @JSFunction
        @SuppressWarnings("unused")
        public static void send(Context cx, Scriptable thisObj, Object[] args, Function fn)
        {
            ensureArg(args, 0);
            ProcessImpl self = (ProcessImpl)thisObj;
            if (self.spawned == null) {
                throw new AssertionError("Sending to closed process");
            }
            self.spawned.send(cx, args[0]);
        }

        @JSFunction
        @SuppressWarnings("unused")
        public static void disconnect(Context cx, Scriptable thisObj, Object[] args, Function fn)
        {
            ProcessImpl self = (ProcessImpl)thisObj;
            if (self.spawned == null) {
                throw new AssertionError("Sending to closed process");
            }
            self.spawned.send(cx, IPC_DISCONNECT);
        }

        @JSGetter("connected")
        @SuppressWarnings("unused")
        public boolean isConnected() {
            return spawned == null ? false : spawned.isConnected();
        }

        @JSGetter("onexit")
        @SuppressWarnings("unused")
        public Function getOnExit() {
            return onExit;
        }

        @JSSetter("onexit")
        @SuppressWarnings("unused")
        public void setOnExit(Function onExit) {
            this.onExit = onExit;
        }

        @JSGetter("pid")
        @SuppressWarnings("unused")
        public int getPid() {
            return pid;
        }

        @JSGetter("childProcess")
        @SuppressWarnings("unused")
        public Object getChildProcess()
        {
            if (childProcessObject == null) {
                childProcessObject = (spawned == null ? null : spawned.getChildProcessObject());
            }
            return childProcessObject;
        }

        @JSSetter("onMessage")
        @SuppressWarnings("unused")
        public void setOnMessage(Function om) {
            this.onMessage = om;
        }

        @JSGetter("onMessage")
        @SuppressWarnings("unused")
        public Function getOnMessage() {
            return onMessage;
        }
    }

    public abstract static class SpawnedProcess
    {
        protected boolean finished;
        protected final ProcessImpl parent;

        SpawnedProcess(ProcessImpl parent)
        {
            this.parent = parent;
        }

        abstract void terminate(String code);
        abstract void close();
        abstract Object spawn(Context cx, List<String> execArgs, Scriptable options);

        protected void send(Context cx, Object message)
        {
            throw new EvaluatorException("IPC is not enabled to the child");
        }

        protected Scriptable getChildProcessObject() {
            return null;
        }

        protected boolean isConnected() {
            return false;
        }

        void setFinished(boolean finished) {
            this.finished = finished;
        }

        protected Scriptable getStdioObj(Scriptable stdio, int arg)
        {
            if (!stdio.has(arg, stdio)) {
                throw new EvaluatorException("Missing configuration for fd " + arg);
            }
            return (Scriptable)stdio.get(arg, stdio);
        }

        protected String getStdioType(Scriptable s)
        {
            if (!s.has("type", s)) {
                throw new EvaluatorException("Missing type in stdio");
            }
            return Context.toString(s.get("type", s));
        }

        protected int getStdioFD(Scriptable s)
        {
            if (!s.has("fd", s)) {
                throw new EvaluatorException("Missing fd in fd type stdio object");
            }
            return (Integer)Context.jsToJava(s.get("fd", s), Integer.class);
        }

        protected String getCwdOption(Scriptable s)
        {
            if (s.has("cwd", s)) {
                Object val = ScriptableObject.getProperty(s, "cwd");
                if ((val != null) && !Context.getUndefinedValue().equals(val)) {
                    return Context.toString(val);
                }
            }
            return null;
        }

        protected Scriptable createStreamHandle(Context cx, AbstractHandle handle)
        {
            Scriptable module = (Scriptable)parent.runner.requireInternal("java_stream_wrap", cx);
            return cx.newObject(module, "JavaStream", new Object[] { handle });
        }

        protected void setEnvironment(List<String> pairs,
                                    Map<String, String> env)
        {
            env.clear();
            for (String pair : pairs) {
                String[] kv = EQUALS.split(pair, 2);
                env.put(kv[0], kv[1]);
            }
        }
    }

    public static class SpawnedOSProcess
        extends SpawnedProcess
    {
        private java.lang.Process proc;

        SpawnedOSProcess(ProcessImpl parent)
        {
            super(parent);
        }

        @Override
        void terminate(String signal)
        {
            if (proc != null) {
                proc.destroy();
            }
        }

        @Override
        void close()
        {
            if (!finished) {
                finished = true;
                terminate("0");
            }
        }

        /**
         * Set "socket" to a writable stream that can write to the output stream "out".
         * This will be used for stdin.
         */
        private void createOutputStream(Context cx, Scriptable stdio, int arg,
                                        OutputStream out)
        {
            Scriptable opts = getStdioObj(stdio, arg);
            String type = getStdioType(opts);
            if (STDIO_PIPE.equals(type)) {
                // Create a new handle that writes to the output stream.
                if (log.isDebugEnabled()) {
                    log.debug("Setting fd {} to output stream {}", arg, out);
                }
                JavaOutputStreamHandle streamHandle = new JavaOutputStreamHandle(out);
                Scriptable handle = createStreamHandle(cx, streamHandle);
                opts.put("handle", opts, handle);

            } else if (STDIO_IGNORE.equals(type)) {
                // Close the stream and do nothing
                if (log.isDebugEnabled()) {
                    log.debug("Setting fd {} to produce no input", arg);
                }
                try {
                    out.close();
                } catch (IOException ioe) {
                    log.debug("Output.close() threw: {}", ioe);
                }

            } else if (STDIO_FD.equals(type)) {
                // Assuming fd is zero, read from stdin and set that up.
                if (getStdioFD(opts) != 0) {
                    throw new AssertionError("Only FDs 0, 1, and 2 supported");
                }
                StreamPiper piper = new StreamPiper(parent.runner.getStdin(), out, false);
                piper.start(parent.runner.getUnboundedPool());

            } else {
                throw Utils.makeError(cx, parent, "Trireme unsupported stdio type " + type);
            }
        }

        /**
         * Set "socket" to a readable stream that can read from the input stream "in".
         * This wil be used for stdout and stderr.
         */
        private void createInputStream(Context cx, Scriptable stdio, int arg,
                                       InputStream in)
        {
            Scriptable opts = getStdioObj(stdio, arg);
            String type = getStdioType(opts);
            if (STDIO_PIPE.equals(type)) {
                if (log.isDebugEnabled()) {
                    log.debug("Setting fd {} to input stream {}", arg, in);
                }
                JavaInputStreamHandle streamHandle = new JavaInputStreamHandle(in, parent.runner);
                Scriptable handle = createStreamHandle(cx, streamHandle);
                opts.put("handle", opts, handle);

            } else if (STDIO_IGNORE.equals(type)) {
                if (log.isDebugEnabled()) {
                    log.debug("Setting fd {} to discard all output", arg);
                }
                StreamPiper piper = new StreamPiper(in, new BitBucketOutputStream(), false);
                piper.start(parent.runner.getUnboundedPool());

            } else if (STDIO_FD.equals(type)) {
                StreamPiper piper;
                switch (getStdioFD(opts)) {
                case 1:
                    piper = new StreamPiper(in, parent.runner.getStdout(), false);
                    piper.start(parent.runner.getUnboundedPool());
                    break;
                case 2:
                    piper = new StreamPiper(in, parent.runner.getStderr(), false);
                    piper.start(parent.runner.getUnboundedPool());
                    break;
                default:
                    throw new AssertionError("Only FDs 0, 1, and 2 supported");
                }
            } else {
                throw Utils.makeError(cx, parent, "Trireme unsupported stdio type " + type);
            }
        }

        @Override
        Object spawn(Context cx, List<String> execArgs, Scriptable options)
        {
            if (log.isDebugEnabled()) {
                log.debug("About to exec " + execArgs);
            }
            ProcessBuilder builder = new ProcessBuilder(execArgs);
            String cwd = getCwdOption(options);
            if (cwd != null) {
                File cwdFile = parent.runner.translatePath(cwd);
                if (!cwdFile.exists()) {
                    return Utils.makeErrorObject(cx, parent, Constants.ENOENT, Constants.ENOENT);
                } else if (!cwdFile.isDirectory()) {
                    return Utils.makeErrorObject(cx, parent, Constants.ENOTDIR, Constants.ENOTDIR);
                }

                builder.directory(cwdFile);
            }

            if (options.has("envPairs", options)) {
                setEnvironment(Utils.toStringList((Scriptable) options.get("envPairs", options)),
                               builder.environment());
            }

            try {
                proc = builder.start();
            } catch (IOException ioe) {
                if (log.isDebugEnabled()) {
                    log.debug("Error in execution: {}", ioe);
                }
                return Utils.makeErrorObject(cx, parent, ioe.toString(), Constants.ENOENT);
            }
            if (log.isDebugEnabled()) {
                log.debug("Starting {}", proc);
            }
            // Java doesn't return the actual OS PID
            options.put("pid", options, System.identityHashCode(proc) % 65536);

            if (!options.has("stdio", options)) {
                throw Utils.makeError(cx, parent, "Missing stdio in options");
            }
            Scriptable stdio = (Scriptable)options.get("stdio", options);
            createOutputStream(cx, stdio, 0, proc.getOutputStream());
            createInputStream(cx, stdio, 1, proc.getInputStream());
            createInputStream(cx, stdio, 2, proc.getErrorStream());

            parent.runner.getUnboundedPool().submit(new Runnable()
            {
                @Override
                public void run()
                {
                    try {
                        int exitCode = proc.waitFor();
                        if (log.isDebugEnabled()) {
                            log.debug("Child process exited with {}", exitCode);
                        }
                        parent.callOnExit(exitCode, 0);
                    } catch (InterruptedException ie) {
                        // TODO some signal?
                        parent.callOnExit(0, 0);
                    }
                }
            });

            return Context.getUndefinedValue();
        }
    }

    public static class SpawnedTriremeProcess
        extends SpawnedProcess
    {
        // How much space to set aside for a pipe between processes
        public static final int PROCESS_PIPE_SIZE = 64 * 1024;

        private ScriptFuture future;
        private NodeScript script;
        private boolean ipcEnabled;

        SpawnedTriremeProcess(ProcessImpl parent)
        {
            super(parent);
        }

        @Override
        void terminate(String code)
        {
            future.cancel(true);
        }

        @Override
        void close()
        {
            if (!finished) {
                future.cancel(true);
            }
        }

        /**
         * Create a readable stream, and set "socket" to be a writable stream that writes to it.
         * Used for standard input.
         */
        private void createReadableStream(Context cx, Scriptable stdio, int arg, Sandbox sandbox)
            throws IOException
        {
            Scriptable opts = getStdioObj(stdio, arg);
            String type = getStdioType(opts);
            if (STDIO_PIPE.equals(type)) {
                // Create a pipe between stdin of this new process and an output stream handle.
                // Use piped streams here for consistency so that we do everything using standard streams.
                if (log.isDebugEnabled()) {
                    log.debug("Creating input stream pipe for stdio {}", arg);
                }
                PipedInputStream pipeIn = new PipedInputStream(PROCESS_PIPE_SIZE);
                PipedOutputStream pipeOut = new PipedOutputStream(pipeIn);

                sandbox.setStdin(pipeIn);
                JavaOutputStreamHandle streamHandle = new JavaOutputStreamHandle(pipeOut);
                Scriptable handle = createStreamHandle(cx, streamHandle);
                opts.put("handle", opts, handle);

            } else if (STDIO_FD.equals(type)) {
                // Child will read directly from the stdin for this process.
                int fd = getStdioFD(opts);
                if (fd != 0) {
                    throw new AssertionError("stdin only supported on fd 0");
                }
                log.debug("Using standard input for script input");
                sandbox.setStdin(new NoCloseInputStream(parent.runner.getStdin()));

            } else if (STDIO_IGNORE.equals(type)) {
                // Just create a dummy stream in case someone needs to read from it
                sandbox.setStdin(new BitBucketInputStream());

            } else if (STDIO_IPC.equals(type)) {
                ipcEnabled = true;

            } else {
                throw Utils.makeError(cx, parent, "Trireme unsupported stdio type " + type);
            }
        }

        /**
         * Create a writable stream, and set "socket" to be a readable stream that reads from it.
         * Used for standard output and error.
         */
        private void createWritableStream(Context cx, Scriptable stdio, int arg, Sandbox sandbox)
            throws IOException
        {
            Scriptable opts = getStdioObj(stdio, arg);
            String type = getStdioType(opts);

            if (STDIO_PIPE.equals(type)) {
                // Pipe between us using a pipe that has a maximum size and can block
                if (log.isDebugEnabled()) {
                    log.debug("Creating writable stream pipe for stdio {}", arg);
                }
                PipedInputStream pipeIn = new PipedInputStream(PROCESS_PIPE_SIZE);
                PipedOutputStream pipeOut = new PipedOutputStream(pipeIn);

                JavaInputStreamHandle streamHandle = new JavaInputStreamHandle(pipeIn, parent.runner);
                Scriptable handle = createStreamHandle(cx, streamHandle);
                opts.put("handle", opts, handle);

                switch (arg) {
                case 1:
                    sandbox.setStdout(pipeOut);
                    break;
                case 2:
                    sandbox.setStderr(pipeOut);
                    break;
                default:
                    throw new AssertionError("Child process only supported on fds 1 and 2");
                }

            } else if (STDIO_FD.equals(type)) {
                // Child will write directly to either stdout or stderr of this process.
                int fd = getStdioFD(opts);
                OutputStream out;
                switch (fd) {
                case 1:
                    log.debug("Using standard output for script output");
                    out = new NoCloseOutputStream(parent.runner.getStdout());
                    break;
                case 2:
                    log.debug("Using standard error for script output");
                    out = new NoCloseOutputStream(parent.runner.getStderr());
                    break;
                default:
                    throw new AssertionError("Child process only supported on fds 1 and 2");
                }
                switch (arg) {
                case 1:
                    sandbox.setStdout(out);
                    break;
                case 2:
                    sandbox.setStderr(out);
                    break;
                default:
                    throw new AssertionError("Child process only supported on fds 1 and 2");
                }

            } else if (STDIO_IGNORE.equals(type)) {
                // Just swallow all the output
                switch (arg) {
                case 1:
                    sandbox.setStdout(new BitBucketOutputStream());
                    break;
                case 2:
                    sandbox.setStderr(new BitBucketOutputStream());
                    break;
                default:
                    throw new AssertionError("Child process only supported on fds 1 and 2");
                }

            } else if (STDIO_IPC.equals(type)) {
                ipcEnabled = true;

            } else {
                throw Utils.makeError(cx, parent, "Trireme unsupported stdio type " + type);
            }
        }

        // If the name of the process is the same as "process.execPath" or if it is
        // "node" or "./node", then use the Environment to launch another script.
        // that means that we have to block for the process using a future rather than using the
        // thread that we have here. Use the environment to set stdin/out/err.
        // Also, this means that we will have to somehow modify Sandbox to add inheritance
        // and we will also have to modify Process to take differnet types of streams
        // for these streams.

        @Override
        Object spawn(Context cx, List<String> execArgs, Scriptable options)
        {
            if (log.isDebugEnabled()) {
                log.debug("About to launch another ScriptRunner thread");
            }

            String scriptPath = null;
            int i;
            // Look at the args but slip the first one
            for (i = 1; i < execArgs.size(); i++) {
                if (!execArgs.get(i).startsWith("-")) {
                    // Skip any parameters to "node" itself
                    scriptPath = execArgs.get(i);
                    i++;
                    break;
                }
            }
            if (scriptPath == null) {
                throw new EvaluatorException("No script path to spawn");
            }

            ArrayList<String> args = new ArrayList<String>(execArgs.size() - i);
            for (; i < execArgs.size(); i++) {
                String arg = execArgs.get(i);
                if (arg != null) {
                    args.add(arg);
                }
            }

            if (!options.has("stdio", options)) {
                throw new EvaluatorException("Missing stdio in options");
            }
            Scriptable stdio = (Scriptable)options.get("stdio", options);
            Sandbox scriptSandbox = new Sandbox(parent.runner.getSandbox());

            try {
                createReadableStream(cx, stdio, 0, scriptSandbox);
                createWritableStream(cx, stdio, 1, scriptSandbox);
                createWritableStream(cx, stdio, 2, scriptSandbox);
            } catch (IOException ioe) {
                throw Utils.makeError(cx, parent, ioe.toString());
            }

            for (int si = 3; ; si++) {
                if (stdio.has(si, stdio)) {
                    Scriptable stdioObj = getStdioObj(stdio, si);
                    if (STDIO_IPC.equals(getStdioType(stdioObj))) {
                        ipcEnabled = true;
                    } else {
                        throw Utils.makeError(cx, stdioObj, "Invalid stdio type " + getStdioType(stdioObj) +
                                              " for stdio index " + si);
                    }
                } else {
                    break;
                }
            }

            String cwd = getCwdOption(options);
            if (cwd != null) {
                File cwdFile = parent.runner.translatePath(cwd);
                if (!cwdFile.exists()) {
                    return Utils.makeErrorObject(cx, parent, Constants.ENOENT, Constants.ENOENT);
                } else if (!cwdFile.isDirectory()) {
                    return Utils.makeErrorObject(cx, parent, Constants.ENOTDIR, Constants.ENOTDIR);
                }

                File scriptPathFile = new File(scriptPath);
                if (!scriptPathFile.isAbsolute()) {
                    // Re-do script path to be relative to cwd
                    scriptPath = new File(cwdFile, scriptPath).getPath();
                }
            }

            HashMap<String, String> env = null;
            if (options.has("envPairs", options)) {
                env = new HashMap<String, String>();
                setEnvironment(Utils.toStringList((Scriptable) options.get("envPairs", options)),
                               env);
            }

            try {
                script =
                    parent.runner.getEnvironment().createScript(scriptPath, parent.runner.translatePath(scriptPath),
                                                                args.toArray(new String[args.size()]));
                script.setSandbox(scriptSandbox);
                script.setWorkingDirectory(cwd);
                if (env != null) {
                    script.setEnvironment(env);
                }

                script._setChildProcess(true);
                if (ipcEnabled) {
                    script._setParentProcess(parent);
                }

                future = script.execute();
            } catch (NodeException ne) {
                if (log.isDebugEnabled()) {
                    log.debug("Error starting internal script: {}", ne);
                }
                return Constants.EIO;
            }

            future.setListener(new ScriptStatusListener()
            {
                @Override
                public void onComplete(NodeScript script, ScriptStatus status)
                {
                    if (log.isDebugEnabled()) {
                        log.debug("Child ScriptRunner exited: {}", status);
                    }
                    finished = true;
                    script.close();
                    parent.callOnExit(status.getExitCode(), 0);
                }
            });
            return Context.getUndefinedValue();
        }

        @Override
        protected Scriptable getChildProcessObject() {
            return script._getProcessObject();
        }

        @Override
        protected void send(Context cx, Object message)
        {
            script._getRuntime().enqueueIpc(cx, message, null);
        }
    }
}
TOP

Related Classes of io.apigee.trireme.core.modules.ProcessWrap$SpawnedTriremeProcess

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.