blob: 9fc61b1d2594a8a91fcf7f32d48015cb99415843 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Oak Ridge National Laboratory and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.remote.internal.proxy.server.core.commands;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.remote.proxy.protocol.core.Protocol;
import org.eclipse.remote.proxy.protocol.core.StreamChannel;
import org.eclipse.remote.proxy.protocol.core.exceptions.ProxyException;
public abstract class AbstractServerExecCommand extends AbstractServerCommand {
private class CommandRunner implements Runnable {
@Override
public void run() {
try {
int exit = 0;
try {
proc = doRun();
Forwarder stdoutFwd = startForwarder("stdout", proc.getInputStream(), stdoutChan); //$NON-NLS-1$
Forwarder stderrFwd = null;
if (!redirect) {
stderrFwd = startForwarder("stderr", proc.getErrorStream(), stderrChan); //$NON-NLS-1$
}
startForwarder("stdin", stdinChan, proc.getOutputStream()); //$NON-NLS-1$
new Thread(new ProcMonitor(), "process monitor").start(); //$NON-NLS-1$
exit = proc.waitFor();
/*
* After the process has finished, wait for the stdout and stderr forwarders to finish to
* ensure that all output is flushed.
*/
stdoutFwd.waitFor();
if (stderrFwd != null) {
stderrFwd.waitFor();
}
} catch (IOException e) {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stderrChan));
try {
writer.write(e.getMessage());
writer.flush();
} catch (IOException e1) {
// Things look pretty hopeless
}
exit = -1;
}
try {
resultStream.writeInt(exit);
resultStream.flush();
} catch (IOException e) {
// We're finished anyway
}
} catch (InterruptedException e) {
// Ignore?
}
}
}
private class ProcMonitor implements Runnable {
@Override
public void run() {
try {
switch (cmdStream.readByte()) {
case Protocol.CONTROL_KILL:
doKill(proc);
break;
case Protocol.CONTROL_SETTERMINALSIZE:
int cols = cmdStream.readInt();
int rows = cmdStream.readInt();
cmdStream.readInt(); // pixel dimensions not supported
cmdStream.readInt(); // pixel dimensions not supported
doSetTerminalSize(proc, cols, rows);
break;
}
} catch (IOException e) {
// Finish
}
}
}
private class Forwarder implements Runnable {
private final InputStream in;
private final OutputStream out;
private final String name;
private boolean running = true;
private final Lock lock = new ReentrantLock();
private final Condition cond = lock.newCondition();
public Forwarder(String name, InputStream in, OutputStream out) {
this.name = name;
this.in = new BufferedInputStream(in);
this.out = new BufferedOutputStream(out);
}
@Override
public void run() {
byte[] buf = new byte[8192];
int n;
try {
while (running) {
n = in.read(buf);
if (n > 0) {
out.write(buf, 0, n);
out.flush();
}
if (n < 0) break;
}
} catch (IOException e) {
// Finish
}
lock.lock();
try {
running = false;
try {
out.close();
} catch (IOException e) {
// Best effort
}
cond.signalAll();
} finally {
lock.unlock();
}
}
public String getName() {
return name;
}
public synchronized void waitFor() {
lock.lock();
try {
while (running) {
try {
cond.await();
} catch (InterruptedException e) {
// Check terminated flag
}
}
} finally {
lock.unlock();
}
}
}
private final List<String> command;
private final Map<String, String> env;
private final boolean redirect;
private final boolean appendEnv;
private final String directory;
private final InputStream stdinChan;
private final OutputStream stdoutChan;
private final OutputStream stderrChan;
private final DataInputStream cmdStream;
private final DataOutputStream resultStream;
private Process proc;
public AbstractServerExecCommand(List<String> command, Map<String, String> env, String directory, boolean redirect, boolean appendEnv, StreamChannel cmdChan, StreamChannel ioChan, StreamChannel errChan) {
this.command = command;
this.env = env;
this.directory = directory;
this.redirect = redirect;
this.appendEnv = appendEnv;
this.stdinChan = ioChan.getInputStream();
this.stdoutChan = ioChan.getOutputStream();
this.stderrChan = errChan != null ? errChan.getOutputStream() : this.stdoutChan;
this.resultStream = new DataOutputStream(cmdChan.getOutputStream());
this.cmdStream = new DataInputStream(cmdChan.getInputStream());
}
protected abstract Process doRun() throws IOException;
protected abstract void doKill(Process proc);
protected abstract void doSetTerminalSize(Process proc, int col, int rows);
protected List<String> getCommand() {
return command;
}
protected Map<String,String> getEnv() {
return env;
}
protected boolean isRedirect() {
return redirect;
}
protected boolean isAppendEnv() {
return appendEnv;
}
protected String getDirectory() {
return directory;
}
public void exec() throws ProxyException {
new Thread(new CommandRunner()).start();
}
private Forwarder startForwarder(String name, InputStream in, OutputStream out) {
Forwarder forwarder = new Forwarder(name, in, out);
Thread thread = new Thread(forwarder, forwarder.getName());
thread.start();
return forwarder;
}
}