de.alexkamp.sandbox.ChrootSandboxProcess.java Source code

Java tutorial

Introduction

Here is the source code for de.alexkamp.sandbox.ChrootSandboxProcess.java

Source

package de.alexkamp.sandbox;

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

import com.fasterxml.jackson.core.JsonGenerator;
import de.alexkamp.sandbox.channels.NullChannel;

import java.io.IOException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A process which is running inside a sandbox.
 *
 * Created by akampmann on 2/7/15.
 */
public class ChrootSandboxProcess implements SandboxProcess {
    private static final Pattern exit = Pattern.compile("exit status ([0-9]*)");
    private static final Pattern timePattern = Pattern.compile("Message: (.*), Runtime: ([0-9]*)");

    private final ChrootSandbox sandbox;
    private final String workdir;
    private final long timeout;
    private final String executable;
    private final String[] args;

    /** This lock (and condition) indicate whether the process is running. */
    private final Lock running = new ReentrantLock();
    private boolean isRunning = false;
    private final Condition condition = running.newCondition();

    /** Those fields hold data about an already terminated process. */
    private String exitMessage;
    private long runtime;
    private boolean hadTimeout;

    private SandboxChannel stdout = new NullChannel();
    private SandboxChannel stderr = new NullChannel();

    public ChrootSandboxProcess(ChrootSandbox sandbox, String workdir, String executable, String[] args,
            long timeout) {
        this.sandbox = sandbox;
        this.workdir = workdir;
        this.executable = executable;
        this.args = args;
        this.timeout = timeout;
    }

    /**
     * start the process inside the sandbox.
     *
     * @return
     */
    @Override
    public int start() throws SandboxTimeoutException {
        try {
            running.lock();
            sandbox.start(this);
            isRunning = true;

            // condition will be signalled if the process terminates
            while (isRunning) {
                condition.await();
                sandbox.checkState();
            }

            if (hadTimeout) {
                throw new SandboxTimeoutException(exitMessage);
            }

            // try to parse the output for the exit code
            Matcher matcher = exit.matcher(exitMessage);
            if (matcher.find()) {
                return Integer.parseInt(matcher.group(1));
            }
            throw new SandboxException(exitMessage);
        } catch (InterruptedException e) {
            throw new SandboxException(e);
        } finally {
            running.unlock();
        }
    }

    public SandboxChannel getStdout() {
        return stdout;
    }

    @Override
    public void setStdout(SandboxChannel stdout) {
        this.stdout = stdout;
    }

    public SandboxChannel getStderr() {
        return stderr;
    }

    @Override
    public void setStderr(SandboxChannel stderr) {
        this.stderr = stderr;
    }

    @Override
    public String getExitMessage() {
        return exitMessage;
    }

    /**
     * Internal method to handle incoming communication.
     *
     * @param channel - identifier of the channel, either stdout, stderr or exit
     * @param message - the message on the channel
     * @return - true if more input is expected
     */
    protected boolean handle(String channel, String message) {
        switch (channel) {
        case "stdout":
            stdout.receive(message);
            return true;
        case "stderr":
            stderr.receive(message);
            return true;
        case "exit":
            handleExit(message, false);
            return false;
        case "timeout":
            handleExit(message, true);
            return false;
        case "error":
            handleExit(message, false);
            throw new SandboxException("A process error: \"" + message + "\" in " + executable);
        default:
            throw new SandboxException(
                    "A process received a message it should not have: " + channel + " \"" + message + "\"");
        }
    }

    /**
     * called if the process exits
     * @param message
     */
    private void handleExit(String message, boolean timeout) {
        try {
            running.lock();
            this.isRunning = false;
            this.hadTimeout = timeout;
            if (!timeout) {
                Matcher matcher = timePattern.matcher(message);
                if (matcher.find()) {
                    this.exitMessage = matcher.group(1);
                    this.runtime = Long.parseLong(matcher.group(2));
                } else {
                    this.exitMessage = message;
                    this.runtime = -1;
                }
            } else {
                this.exitMessage = message;
            }
        } finally {
            running.unlock();
        }
    }

    public String getWorkdir() {
        return workdir;
    }

    @Override
    public String getExecutable() {
        return executable;
    }

    @Override
    public String[] getArgs() {
        return args;
    }

    public void toJson(JsonGenerator sender) throws IOException {
        sender.writeStartObject();

        sender.writeObjectField("WorkDir", getWorkdir());
        sender.writeObjectField("Executable", getExecutable());
        sender.writeObjectField("Timeout", getTimeout());
        sender.writeArrayFieldStart("Arguments");
        for (String arg : getArgs()) {
            sender.writeString(arg);
        }
        sender.writeEndArray();

        sender.writeEndObject();
    }

    public long getTimeout() {
        return timeout;
    }

    @Override
    public boolean hadTimeout() {
        return hadTimeout;
    }

    @Override
    public long getRuntime() {
        return runtime;
    }

    public void wakeUp() {
        try {
            running.lock();
            condition.signalAll();
        } finally {
            running.unlock();
        }
    }

}