blob: 5d100da442dc53ec8007d62cb0501753582cbc81 [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.commands;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
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.StreamChannel;
import org.eclipse.remote.proxy.protocol.core.exceptions.ProxyException;
/**
* TODO: Fix hang if command fails...
*
*/
public class ServerExecCommand extends AbstractServerCommand {
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 stdin;
private final OutputStream stdout;
private final OutputStream stderr;
private final DataOutputStream result;
private final DataInputStream cmd;
private Process proc;
private class CommandRunner implements Runnable {
@Override
public void run() {
ProcessBuilder builder = new ProcessBuilder(command);
try {
if (!appendEnv) {
builder.environment().clear();
builder.environment().putAll(env);
} else {
for (Map.Entry<String, String> entry : env.entrySet()) {
String val = builder.environment().get(entry.getKey());
if (val == null || !val.equals(entry.getValue())) {
builder.environment().put(entry.getKey(), entry.getValue());
}
}
}
} catch (UnsupportedOperationException | IllegalArgumentException e) {
// Leave environment untouched
}
File dir = new File(directory);
if (dir.exists() && dir.isAbsolute()) {
builder.directory(dir);
}
builder.redirectErrorStream(redirect);
try {
int exit = 0;
try {
proc = builder.start();
Forwarder stdoutFwd = startForwarder("stdout", proc.getInputStream(), stdout); //$NON-NLS-1$
Forwarder stderrFwd = null;
if (!redirect) {
stderrFwd = startForwarder("stderr", proc.getErrorStream(), stderr); //$NON-NLS-1$
}
startForwarder("stdin", stdin, proc.getOutputStream()); //$NON-NLS-1$
new Thread(new ProcMonitor(), "process monitor").start(); //$NON-NLS-1$
System.err.println("wait for process");
exit = proc.waitFor();
System.err.println("wait for process close in");
// stdoutFwd.terminate();
// if (!redirect) {
// stderrFwd.terminate();
// }
System.err.println("wait for readers");
/*
* After the process has finished, wait for the stdout and stderr forwarders to finish to
* ensure that all output is flushed.
*/
// stdoutFwd.waitFor();
// System.err.println("wait for process finished out");
// if (stderrFwd != null) {
// stderrFwd.waitFor();
// }
System.err.println("wait for readers done");
} catch (IOException e) {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stderr));
try {
writer.write(e.getMessage());
writer.flush();
} catch (IOException e1) {
// Things look pretty hopeless
}
exit = -1;
}
try {
result.writeInt(exit);
result.flush();
} catch (IOException e) {
// We're finished anyway
}
} catch (InterruptedException e) {
// Ignore?
}
}
}
private class ProcMonitor implements Runnable {
@Override
public void run() {
try {
cmd.readByte();
if (proc.isAlive()) {
proc.destroyForcibly();
}
} catch (IOException e) {
// Finish
}
}
}
private class Forwarder implements Runnable {
private final InputStream in;
private final OutputStream out;
private final String name;
private volatile boolean running = true;
private boolean terminated = false;
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 (true) {
// if (in.available() == 0) {
// System.err.println("avail=0");
// /* Avoid spinning if no data */
// lock.lock();
// try {
// cond.await(100, TimeUnit.MILLISECONDS);
// } catch (InterruptedException e) {
// } finally {
// lock.unlock();
// }
// continue;
// }
n = in.read(buf);
if (n > 0) {
out.write(buf, 0, n);
out.flush();
}
if (n==0) System.err.println("forwarder n=0");
if (n < 0) break;
}
} catch (IOException e) {
// Finish
System.err.println("forwarder "+e.getMessage());
}
System.err.println("forwarder closing name="+name);
try {
out.close();
} catch (IOException e) {
// Best effort
}
lock.lock();
terminated = true;
try {
cond.signalAll();
} finally {
lock.unlock();
}
}
public void terminate() {
running = false;
}
public synchronized void waitFor() {
lock.lock();
try {
if (!terminated) {
try {
cond.await();
} catch (InterruptedException e) {
}
}
} finally {
lock.unlock();
}
}
}
public ServerExecCommand(List<String> command, Map<String, String> env, String directory, boolean redirect, boolean appendEnv, StreamChannel chanA, StreamChannel chanB, StreamChannel chanC) {
this.command = command;
this.env = env;
this.directory = directory;
this.redirect = redirect;
this.appendEnv = appendEnv;
this.stdin = chanA.getInputStream();
this.stdout = chanA.getOutputStream();
this.stderr = chanB.getOutputStream();
this.result = new DataOutputStream(chanC.getOutputStream());
this.cmd = new DataInputStream(chanC.getInputStream());
}
public void exec() throws ProxyException {
new Thread(new CommandRunner(), command.get(0)).start();
}
private Forwarder startForwarder(String name, InputStream in, OutputStream out) {
Forwarder forwarder = new Forwarder(name, in, out);
Thread thread = new Thread(forwarder, command.get(0) + " " + name); //$NON-NLS-1$
thread.start();
return forwarder;
}
}