Bug 530750 - Enable launch support using proxy. Also adds support for
command shell and fixes a number of bugs.

Change-Id: I6b9a0bf40647d6f5682c425c754371f8af6cb918
Signed-off-by: Greg Watson <g.watson@computer.org>
diff --git a/bundles/org.eclipse.remote.proxy.core/plugin.xml b/bundles/org.eclipse.remote.proxy.core/plugin.xml
index 4f56f94..2de33ec 100644
--- a/bundles/org.eclipse.remote.proxy.core/plugin.xml
+++ b/bundles/org.eclipse.remote.proxy.core/plugin.xml
@@ -40,7 +40,7 @@
       </connectionService>
       <connectionService
             connectionTypeId="org.eclipse.remote.Proxy"
-            factory="org.eclipse.remote.internal.proxy.core.ProxyCommandShellService$Factory"
+            factory="org.eclipse.remote.internal.proxy.core.ProxyConnection$Factory"
             service="org.eclipse.remote.core.IRemoteCommandShellService">
       </connectionService>
       <processService
@@ -58,6 +58,11 @@
             factory="org.eclipse.remote.internal.proxy.core.ProxyProcess$Factory"
             service="org.eclipse.remote.core.IRemoteProcessTerminalService">
       </processService>
+      <processService
+            connectionTypeId="org.eclipse.remote.Proxy"
+            factory="org.eclipse.remote.internal.proxy.core.ProxyProcess$Factory"
+            service="org.eclipse.remote.internal.proxy.core.ProxyProcess">
+      </processService>
    </extension>
    <extension
          id="org.eclipse.remote.proxy.filesystem"
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyCommandShellService.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyCommandShellService.java
deleted file mode 100644
index eb50760..0000000
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyCommandShellService.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 QNX Software Systems 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
- *
- * Contributors:
- * QNX Software Systems - Initial API and implementation
- *******************************************************************************/
-package org.eclipse.remote.internal.proxy.core;
-
-import java.io.IOException;
-
-import org.eclipse.remote.core.IRemoteCommandShellService;
-import org.eclipse.remote.core.IRemoteConnection;
-import org.eclipse.remote.core.IRemoteProcess;
-
-public class ProxyCommandShellService implements IRemoteCommandShellService {
-	private IRemoteConnection fRemoteConnection;
-
-	public ProxyCommandShellService(IRemoteConnection remoteConnection) {
-		fRemoteConnection = remoteConnection;
-	}
-
-	@Override
-	public IRemoteConnection getRemoteConnection() {
-		return fRemoteConnection;
-	}
-
-	@Override
-	public IRemoteProcess getCommandShell(int flags) throws IOException {
-		return null;
-//		return new JSchProcessBuilder(getRemoteConnection()).start(flags);
-	}
-
-	public static class Factory implements IRemoteConnection.Service.Factory {
-		/*
-		 * (non-Javadoc)
-		 *
-		 * @see org.eclipse.remote.core.IRemoteConnection.Service.Factory#getService(org.eclipse.remote.core.IRemoteConnection,
-		 * java.lang.Class)
-		 */
-		@Override
-		@SuppressWarnings("unchecked")
-		public <T extends IRemoteConnection.Service> T getService(IRemoteConnection connection, Class<T> service) {
-			if (IRemoteCommandShellService.class.equals(service)) {
-				return (T) new ProxyCommandShellService(connection);
-			}
-			return null;
-		}
-	}
-}
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyConnection.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyConnection.java
index fa2d269..20a0cfc 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyConnection.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyConnection.java
@@ -37,8 +37,8 @@
 import org.eclipse.remote.internal.proxy.core.commands.GetEnvCommand;
 import org.eclipse.remote.internal.proxy.core.commands.GetPropertiesCommand;
 import org.eclipse.remote.internal.proxy.core.messages.Messages;
-import org.eclipse.remote.proxy.protocol.core.StreamChannelManager;
 import org.eclipse.remote.proxy.protocol.core.StreamChannel;
+import org.eclipse.remote.proxy.protocol.core.StreamChannelManager;
 import org.eclipse.remote.proxy.protocol.core.exceptions.ProxyException;
 
 import com.jcraft.jsch.ChannelShell;
@@ -48,7 +48,8 @@
  */
 public class ProxyConnection implements IRemoteConnectionControlService,
 		IRemoteConnectionChangeListener, IRemoteProcessService,
-		IRemoteCommandShellService, IRemoteConnectionHostService {
+		IRemoteCommandShellService, IRemoteConnectionHostService, 
+		IRemoteConnectionPropertyService {
 	// Connection Type ID
 	public static final String JSCH_ID = "org.eclipse.remote.Proxy"; //$NON-NLS-1$
 
@@ -72,7 +73,6 @@
 	private StreamChannelManager channelMux;
 	private StreamChannel commandChannel;
 	private boolean isOpen;
-	private boolean proxyRunning;
 	
 	private final IRemoteConnection fRemoteConnection;
 
@@ -133,7 +133,8 @@
 					|| IRemoteConnectionPropertyService.class.equals(service) 
 					|| IRemoteConnectionHostService.class.equals(service) 
 					|| IRemoteProcessService.class.equals(service)
-					|| IRemoteCommandShellService.class.equals(service)) {
+					|| IRemoteCommandShellService.class.equals(service) 
+					|| IRemoteConnectionPropertyService.class.equals(service)) {
 				return (T) connection.getService(ProxyConnection.class);
 			} else {
 				return null;
@@ -348,7 +349,7 @@
 
 	@Override
 	public IRemoteProcess getCommandShell(int flags) throws IOException {
-		throw new IOException("Not implemented yet");
+		return new ProxyProcessBuilder(this).start(flags);
 	}
 
 	@Override
@@ -454,4 +455,9 @@
 			wc.setAttribute(USERNAME_ATTR, username);
 		}
 	}
+
+	@Override
+	public String getProperty(String key) {
+		return fProperties.get(key);
+	}
 }
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyConnectionBootstrap.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyConnectionBootstrap.java
index 9262b93..03b2aa2 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyConnectionBootstrap.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyConnectionBootstrap.java
@@ -211,7 +211,7 @@
 					int n;
 					while ((n = in.read(buf)) >= 0) {
 						if (n < 510) {
-							writer.write(encoder.encodeToString(Arrays.copyOf(buf, n)) + "\n");
+							writer.write(encoder.encodeToString(Arrays.copyOf(buf, n)) + "\n"); //$NON-NLS-1$
 						} else {
 							writer.write(encoder.encodeToString(buf));
 						}
@@ -311,7 +311,7 @@
 				throw new RemoteConnectionException(Messages.ProxyConnectionBootstrap_8);
 			}
 			exec = (ChannelExec) session.openChannel("exec"); //$NON-NLS-1$
-			exec.setCommand("/bin/sh -l"); //$NON-NLS-1$
+			exec.setCommand("/bin/bash -l"); //$NON-NLS-1$
 			exec.connect();
 			return exec;
 		} catch (JSchException e) {
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyProcess.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyProcess.java
index 5ac0303..95cd5a9 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyProcess.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyProcess.java
@@ -12,18 +12,18 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.List;
 
-import org.eclipse.remote.core.IRemoteConnection;
 import org.eclipse.remote.core.IRemoteProcess;
-import org.eclipse.remote.core.IRemoteProcessBuilder;
 import org.eclipse.remote.core.IRemoteProcessControlService;
 import org.eclipse.remote.core.IRemoteProcessSignalService;
 import org.eclipse.remote.core.IRemoteProcessTerminalService;
-import org.eclipse.remote.internal.core.RemoteProcess;
+import org.eclipse.remote.proxy.protocol.core.Protocol;
 import org.eclipse.remote.proxy.protocol.core.StreamChannel;
 
-public class ProxyProcess extends RemoteProcess implements IRemoteProcessControlService, IRemoteProcessTerminalService {
+public class ProxyProcess implements IRemoteProcessControlService, IRemoteProcessTerminalService {
 	private IRemoteProcess remoteProcess;
+	
 	private final StreamChannel stdIOChan;
 	private final StreamChannel stdErrChan;
 	private final StreamChannel controlChan;
@@ -44,8 +44,8 @@
 		@SuppressWarnings("unchecked")
 		@Override
 		public <T extends IRemoteProcess.Service> T getService(IRemoteProcess remoteProcess, Class<T> service) {
-			if (ProxyProcess.class.equals(service) && remoteProcess instanceof ProxyProcess) {
-				return (T) remoteProcess;
+			if (ProxyProcess.class.equals(service)) {
+				return (T) new ProxyProcess(remoteProcess);
 			}
 			if (IRemoteProcessControlService.class.equals(service) || IRemoteProcessSignalService.class.equals(service)
 					|| IRemoteProcessTerminalService.class.equals(service)) {
@@ -55,17 +55,19 @@
 		}
 	}
 
-	public ProxyProcess(IRemoteConnection connection, IRemoteProcessBuilder builder, StreamChannel stdIO, StreamChannel stdErr, StreamChannel control) {
-		super(connection, builder);
-		stdIOChan = stdIO;
-		stdErrChan = stdErr;
-		controlChan = control;
-		cmdStream = new DataOutputStream(control.getOutputStream());
-		resultStream = new DataInputStream(control.getInputStream());
+	protected ProxyProcess(IRemoteProcess process) {
+		remoteProcess = process;
+		ProxyProcessBuilder builder = (ProxyProcessBuilder)process.getProcessBuilder();
+		List<StreamChannel> streams = builder.getStreams();
+		controlChan = streams.get(0);
+		stdIOChan = streams.get(1);
+		stdErrChan = streams.size() > 2 ? streams.get(2) : null;
+		cmdStream = new DataOutputStream(controlChan.getOutputStream());
+		resultStream = new DataInputStream(controlChan.getInputStream());
 		isCompleted = false;
 		exitValue = 0;
 		
-		cmdThread = new Thread("process " + builder.command().get(0) + " result reader") { //$NON-NLS-1$ //$NON-NLS-2$
+		cmdThread = new Thread("process result reader") { //$NON-NLS-1$
 			@Override
 			public void run() {
 				try {
@@ -77,36 +79,15 @@
 				try {
 					stdIOChan.close();
 				} catch (IOException e1) {
-					// TODO Auto-generated catch block
-					e1.printStackTrace();
+					// Ignore
 				}
 				try {
-					stdErrChan.close();
+					if (stdErrChan != null) {
+						stdErrChan.close();
+					}
 				} catch (IOException e1) {
-					// TODO Auto-generated catch block
-					e1.printStackTrace();
+					// Ignore
 				}
-//				if (stdout == null) {
-//					try {
-//						stdIOChan.getInputStream().close();
-//					} catch (IOException e) {
-//						// Ignore
-//					}
-//				}
-//				if (stdin == null) {
-//					try {
-//						stdIOChan.getOutputStream().close();
-//					} catch (IOException e) {
-//						// Ignore
-//					}
-//				}
-//				if (stderr == null) {
-//					try {
-//						stdErrChan.close();
-//					} catch (IOException e) {
-//						// Ignore
-//					}
-//				}
 				try {
 					controlChan.close();
 				} catch (IOException e) {
@@ -125,7 +106,7 @@
 	@Override
 	public void destroy() {
 		try {
-			cmdStream.writeByte(0);
+			cmdStream.writeByte(Protocol.CONTROL_KILL);
 			cmdStream.flush();
 		} catch (IOException e) {
 			isCompleted = true;
@@ -152,6 +133,19 @@
 	 */
 	@Override
 	public InputStream getErrorStream() {
+		if (stdErrChan == null) {
+			return new  InputStream() {
+				@Override
+				public int read() throws IOException {
+					return -1;
+				}
+
+				@Override
+				public int available() {
+					return 0;
+				}
+			};
+		}
 		return stdErrChan.getInputStream();
 	}
 
@@ -203,6 +197,15 @@
 
 	@Override
 	public void setTerminalSize(int cols, int rows, int pwidth, int pheight) {
-		// Nothing?
+		try {
+			cmdStream.writeByte(Protocol.CONTROL_SETTERMINALSIZE);
+			cmdStream.writeInt(cols);
+			cmdStream.writeInt(rows);
+			cmdStream.writeInt(pwidth);
+			cmdStream.writeInt(pheight);
+			cmdStream.flush();
+		} catch (IOException e) {
+			// Dealt with somewhere else hopefully
+		}
 	}
 }
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyProcessBuilder.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyProcessBuilder.java
index 4d509ec..0241194 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyProcessBuilder.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/ProxyProcessBuilder.java
@@ -8,6 +8,7 @@
 package org.eclipse.remote.internal.proxy.core;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -22,6 +23,7 @@
 import org.eclipse.remote.core.IRemoteProcess;
 import org.eclipse.remote.core.IRemoteProcessBuilder;
 import org.eclipse.remote.internal.proxy.core.commands.ExecCommand;
+import org.eclipse.remote.internal.proxy.core.commands.ShellCommand;
 import org.eclipse.remote.internal.proxy.core.messages.Messages;
 import org.eclipse.remote.proxy.protocol.core.StreamChannel;
 import org.eclipse.remote.proxy.protocol.core.exceptions.ProxyException;
@@ -29,6 +31,7 @@
 public class ProxyProcessBuilder extends AbstractRemoteProcessBuilder {
 	private final ProxyConnection proxyConnection;
 	private Map<String, String> remoteEnv;
+	private List<StreamChannel> streams = new ArrayList<>();
 	
 	public ProxyProcessBuilder(ProxyConnection connection, List<String> command) {
 		super(connection.getRemoteConnection(), command);
@@ -42,6 +45,17 @@
 	public ProxyProcessBuilder(ProxyConnection connection, String... command) {
 		this(connection, Arrays.asList(command));
 	}
+	
+	/**
+	 * Constructor for creating command shell
+	 * @param connection
+	 */
+	public ProxyProcessBuilder(ProxyConnection connection) {
+		super(connection.getRemoteConnection(), (List<String>)null);
+		proxyConnection = connection;
+		redirectErrorStream(true);
+	}
+
 
 	/*
 	 * (non-Javadoc)
@@ -76,41 +90,66 @@
 	 */
 	@Override
 	public IRemoteProcess start(int flags) throws IOException {
-		final List<String> cmdArgs = command();
-		if (cmdArgs.size() < 1) {
-			throw new IndexOutOfBoundsException();
-		}
-		/*
-		 * If environment has not been touched, then don't send anything
-		 */
-		final Map<String, String> env = new HashMap<String, String>();
-		if (remoteEnv != null) {
-			env.putAll(remoteEnv);
-		}
-		final boolean append = (flags & IRemoteProcessBuilder.APPEND_ENVIRONMENT) != 0 || remoteEnv == null;
-		
 		final ProxyConnection conn = getRemoteConnection().getService(ProxyConnection.class);
 		if (conn == null) {
 			throw new IOException(Messages.ProxyProcessBuilder_0);
 		}
 		
-		final StreamChannel chanStdIO = conn.openChannel();
-		final StreamChannel chanStdErr = conn.openChannel();
-		final StreamChannel chanControl = conn.openChannel();
+		Job job;
 		
-		Job job = new Job("process executor") { //$NON-NLS-1$
-			@Override
-			protected IStatus run(IProgressMonitor monitor) {
-				ExecCommand cmd = new ExecCommand(conn, cmdArgs, env, directory().toURI().getPath(), redirectErrorStream(), append, 
-						chanStdIO.getId(), chanStdErr.getId(), chanControl.getId());
-				try {
-					cmd.getResult(monitor);
-				} catch (ProxyException e) {
-					return new Status(IStatus.ERROR, Activator.getUniqueIdentifier(), e.getMessage());
-				}
-				return monitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS;
+		final List<String> cmdArgs = command();
+		if (cmdArgs != null) {
+			if (cmdArgs.size() < 1) {
+				throw new IOException(Messages.ProxyProcessBuilder_1);
 			}
-		};
+			/*
+			 * If environment has not been touched, then don't send anything
+			 */
+			final Map<String, String> env = new HashMap<String, String>();
+			if (remoteEnv != null) {
+				env.putAll(remoteEnv);
+			}
+			
+			final boolean append = (flags & IRemoteProcessBuilder.APPEND_ENVIRONMENT) != 0 || remoteEnv == null;
+			
+			streams.add(conn.openChannel());
+			streams.add(conn.openChannel());
+			streams.add(conn.openChannel());
+			
+			job = new Job("process executor") { //$NON-NLS-1$
+				@Override
+				protected IStatus run(IProgressMonitor monitor) {
+					ExecCommand cmd = new ExecCommand(conn, cmdArgs, env, directory().toURI().getPath(), redirectErrorStream(), append, 
+							streams.get(0).getId(), streams.get(1).getId(), streams.get(2).getId());
+					try {
+						cmd.getResult(monitor);
+					} catch (ProxyException e) {
+						return new Status(IStatus.ERROR, Activator.getUniqueIdentifier(), e.getMessage());
+					}
+					return monitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS;
+				}
+			};
+		} else {
+			/*
+			 * Start command shell
+			 */
+			streams.add(conn.openChannel());
+			streams.add(conn.openChannel());
+			
+			job = new Job("process executor") { //$NON-NLS-1$
+				@Override
+				protected IStatus run(IProgressMonitor monitor) {
+					ShellCommand cmd = new ShellCommand(conn, streams.get(0).getId(), streams.get(1).getId());
+					try {
+						cmd.getResult(monitor);
+					} catch (ProxyException e) {
+						return new Status(IStatus.ERROR, Activator.getUniqueIdentifier(), e.getMessage());
+					}
+					return monitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS;
+				}
+			};
+		}
+		
 		job.schedule();
 		try {
 			job.join();
@@ -121,7 +160,10 @@
 			throw new IOException(job.getResult().getMessage());
 		}
 
-		ProxyProcess proc = new ProxyProcess(getRemoteConnection(), this, chanStdIO, chanStdErr, chanControl);
-		return proc;
+		return newRemoteProcess();
+	}
+
+	public List<StreamChannel> getStreams() {
+		return streams;
 	}
 }
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/AbstractCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/AbstractCommand.java
index 52e14cc..dec2d98 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/AbstractCommand.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/AbstractCommand.java
@@ -19,6 +19,7 @@
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.SubMonitor;
 import org.eclipse.remote.internal.proxy.core.ProxyConnection;
+import org.eclipse.remote.internal.proxy.core.messages.Messages;
 import org.eclipse.remote.proxy.protocol.core.StreamChannel;
 import org.eclipse.remote.proxy.protocol.core.exceptions.ProxyException;
 
@@ -28,15 +29,11 @@
 	private static ExecutorService executors = Executors.newSingleThreadExecutor();
 
 	private final ProxyConnection connection;
-	
 
 	private Future<T> asyncCmdInThread() throws ProxyException {
 		return executors.submit(this);
 	}
 
-	private void finalizeCmdInThread() {
-	}
-
 	public StreamChannel openChannel() throws IOException {
 		return connection.openChannel();
 	}
@@ -52,12 +49,8 @@
 	public T getResult(IProgressMonitor monitor) throws ProxyException {
 		Future<T> future = null;
 		progressMonitor = SubMonitor.convert(monitor, 10);
-		try {
-			future = asyncCmdInThread();
-			return waitCmdInThread(future);
-		} finally {
-			finalizeCmdInThread();
-		}
+		future = asyncCmdInThread();
+		return waitCmdInThread(future);
 	}
 
 	private T waitCmdInThread(Future<T> future) throws ProxyException {
@@ -78,7 +71,7 @@
 			Thread.currentThread().interrupt(); // set current thread flag
 		}
 		future.cancel(true);
-		throw new ProxyException("Operation cancelled by user");
+		throw new ProxyException(Messages.AbstractCommand_0);
 	}
 
 	@Override
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/ChildInfosCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/ChildInfosCommand.java
index 325f5c6..3e6af47 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/ChildInfosCommand.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/ChildInfosCommand.java
@@ -44,7 +44,6 @@
 			byte res = in.readByte();
 			if (res != Protocol.PROTO_OK) {
 				String errMsg = in.readUTF();
-				System.err.println("childinfos command failed:" + errMsg);
 				throw new ProxyException(errMsg);
 			}
 			
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/DeleteCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/DeleteCommand.java
index 96e44aa..bd7463c 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/DeleteCommand.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/DeleteCommand.java
@@ -41,7 +41,6 @@
 			byte res = in.readByte();
 			if (res != Protocol.PROTO_OK) {
 				String errMsg = in.readUTF();
-				System.err.println("delete command failed:" + errMsg);
 				throw new ProxyException(errMsg);
 			}
 			return null;
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/ExecCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/ExecCommand.java
index 3bf1136..c03decd 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/ExecCommand.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/ExecCommand.java
@@ -26,12 +26,12 @@
 	private final String directory;
 	private final boolean appendEnv;
 	private final boolean redirect;
-	private final int chanA;
-	private final int chanB;
-	private final int chanC;
+	private final int cmdChan;
+	private final int ioChan;
+	private final int errChan;
 
 	public ExecCommand(ProxyConnection conn, List<String> command, Map<String, String> env, String directory, boolean redirect, boolean appendEnv,
-			int chanA, int chanB, int chanC) {
+			int cmdChan, int ioChan, int errChan) {
 		super(conn);
 		this.out = new DataOutputStream(conn.getCommandChannel().getOutputStream());
 		this.in = new DataInputStream(conn.getCommandChannel().getInputStream());
@@ -40,18 +40,18 @@
 		this.directory = directory;
 		this.redirect = redirect;
 		this.appendEnv = appendEnv;
-		this.chanA = chanA;
-		this.chanB = chanB;
-		this.chanC = chanC;
+		this.cmdChan = cmdChan;
+		this.ioChan = ioChan;
+		this.errChan = errChan;
 	}
 
 	public Void call() throws ProxyException {
 		try {
 			out.writeByte(Protocol.PROTO_COMMAND);
 			out.writeShort(Protocol.CMD_EXEC);
-			out.writeByte(chanA);
-			out.writeByte(chanB);
-			out.writeByte(chanC);
+			out.writeByte(cmdChan);
+			out.writeByte(ioChan);
+			out.writeByte(errChan);
 			out.writeInt(command.size());
 			for (String arg : command) {
 				out.writeUTF(arg);
@@ -69,7 +69,6 @@
 			byte res = in.readByte();
 			if (res != Protocol.PROTO_OK) {
 				String errMsg = in.readUTF();
-				System.err.println("exec command failed:" + errMsg);
 				throw new ProxyException(errMsg);
 			}
 		} catch (IOException e) {
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/FetchInfoCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/FetchInfoCommand.java
index b04ce3f..0616371 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/FetchInfoCommand.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/FetchInfoCommand.java
@@ -44,7 +44,6 @@
 			byte res = in.readByte();
 			if (res != Protocol.PROTO_OK) {
 				String errMsg = in.readUTF();
-				System.err.println("fetchinfo command failed:" + errMsg);
 				throw new ProxyException(errMsg);
 			}
 			
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetCwdCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetCwdCommand.java
index 2d1eb02..99a373c 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetCwdCommand.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetCwdCommand.java
@@ -40,7 +40,6 @@
 			byte res = in.readByte();
 			if (res != Protocol.PROTO_OK) {
 				String errMsg = in.readUTF();
-				System.err.println("getcwd command failed:" + errMsg);
 				throw new ProxyException(errMsg);
 			}
 			
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetEnvCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetEnvCommand.java
index df9e8d3..773f39f 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetEnvCommand.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetEnvCommand.java
@@ -42,7 +42,6 @@
 			byte res = in.readByte();
 			if (res != Protocol.PROTO_OK) {
 				String errMsg = in.readUTF();
-				System.err.println("getenv command failed:" + errMsg);
 				throw new ProxyException(errMsg);
 			}
 			
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetInputStreamCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetInputStreamCommand.java
index 472868c..b93b695 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetInputStreamCommand.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetInputStreamCommand.java
@@ -14,8 +14,8 @@
 import java.io.InputStream;
 
 import org.eclipse.remote.internal.proxy.core.ProxyConnection;
-import org.eclipse.remote.proxy.protocol.core.StreamChannel;
 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 class GetInputStreamCommand extends AbstractCommand<InputStream> {
@@ -47,7 +47,6 @@
 			byte res = in.readByte();
 			if (res != Protocol.PROTO_OK) {
 				String errMsg = in.readUTF();
-				System.err.println("getinputstream command failed:" + errMsg);
 				throw new ProxyException(errMsg);
 			}
 			
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetOutputStreamCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetOutputStreamCommand.java
index 35be0bf..c1fc277 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetOutputStreamCommand.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetOutputStreamCommand.java
@@ -14,8 +14,8 @@
 import java.io.OutputStream;
 
 import org.eclipse.remote.internal.proxy.core.ProxyConnection;
-import org.eclipse.remote.proxy.protocol.core.StreamChannel;
 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 class GetOutputStreamCommand extends AbstractCommand<OutputStream> {
@@ -47,7 +47,6 @@
 			byte res = in.readByte();
 			if (res != Protocol.PROTO_OK) {
 				String errMsg = in.readUTF();
-				System.err.println("getoutputstream command failed:" + errMsg);
 				throw new ProxyException(errMsg);
 			}
 			
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetPropertiesCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetPropertiesCommand.java
index a01b27a..ad77868 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetPropertiesCommand.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/GetPropertiesCommand.java
@@ -42,7 +42,6 @@
 			byte res = in.readByte();
 			if (res != Protocol.PROTO_OK) {
 				String errMsg = in.readUTF();
-				System.err.println("getproperties command failed:" + errMsg);
 				throw new ProxyException(errMsg);
 			}
 			
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/MkdirCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/MkdirCommand.java
index a6cf33b..eebb748 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/MkdirCommand.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/MkdirCommand.java
@@ -41,7 +41,6 @@
 			byte res = in.readByte();
 			if (res != Protocol.PROTO_OK) {
 				String errMsg = in.readUTF();
-				System.err.println("mkdir command failed:" + errMsg);
 				throw new ProxyException(errMsg);
 			}
 			return null;
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/PutInfoCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/PutInfoCommand.java
index 6e5a4fc..d4a6149 100644
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/PutInfoCommand.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/PutInfoCommand.java
@@ -47,7 +47,6 @@
 			byte res = in.readByte();
 			if (res != Protocol.PROTO_OK) {
 				String errMsg = in.readUTF();
-				System.err.println("fetchinfo command failed:" + errMsg);
 				throw new ProxyException(errMsg);
 			}
 			return null;
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/ShellCommand.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/ShellCommand.java
new file mode 100644
index 0000000..66a1088
--- /dev/null
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/commands/ShellCommand.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.core.commands;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.eclipse.remote.internal.proxy.core.ProxyConnection;
+import org.eclipse.remote.proxy.protocol.core.Protocol;
+import org.eclipse.remote.proxy.protocol.core.exceptions.ProxyException;
+
+public class ShellCommand extends AbstractCommand<Void> {
+
+	private final DataOutputStream out;
+	private final DataInputStream in;
+	private final int cmdChan;
+	private final int ioChan;
+
+	public ShellCommand(ProxyConnection conn, int cmdChan, int ioChan) {
+		super(conn);
+		this.out = new DataOutputStream(conn.getCommandChannel().getOutputStream());
+		this.in = new DataInputStream(conn.getCommandChannel().getInputStream());
+		this.cmdChan = cmdChan;
+		this.ioChan = ioChan;
+	}
+
+	public Void call() throws ProxyException {
+		try {
+			out.writeByte(Protocol.PROTO_COMMAND);
+			out.writeShort(Protocol.CMD_SHELL);
+			out.writeByte(cmdChan);
+			out.writeByte(ioChan);
+			out.flush();
+			
+			byte res = in.readByte();
+			if (res != Protocol.PROTO_OK) {
+				String errMsg = in.readUTF();
+				throw new ProxyException(errMsg);
+			}
+		} catch (IOException e) {
+			throw new ProxyException(e.getMessage());
+		}
+		return null;
+	}
+}
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/messages/Messages.java b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/messages/Messages.java
index be67bf9..6d7c896 100755
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/messages/Messages.java
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/messages/Messages.java
@@ -16,6 +16,8 @@
 public class Messages extends NLS {
 	private static final String BUNDLE_ID = "org.eclipse.remote.internal.proxy.core.messages.messages"; //$NON-NLS-1$
 
+	public static String AbstractCommand_0;
+
 	public static String AbstractRemoteCommand_format1;
 	public static String AbstractRemoteCommand_format2;
 	public static String AbstractRemoteCommand_Get_symlink_target;
@@ -56,6 +58,8 @@
 	public static String JschFileStore_A_file_of_name_already_exists;
 	public static String JschFileStore_The_parent_of_directory_does_not_exist;
 
+	public static String ProxyCommandShell_0;
+
 	public static String ProxyConnection_0;
 
 	public static String ProxyConnection_2;
@@ -98,6 +102,8 @@
 
 	public static String ProxyProcessBuilder_0;
 
+	public static String ProxyProcessBuilder_1;
+
 	static {
 		// load message values from bundle file
 		NLS.initializeMessages(BUNDLE_ID, Messages.class);
diff --git a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/messages/messages.properties b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/messages/messages.properties
index a8a0511..d96b42b 100755
--- a/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/messages/messages.properties
+++ b/bundles/org.eclipse.remote.proxy.core/src/org/eclipse/remote/internal/proxy/core/messages/messages.properties
@@ -8,6 +8,7 @@
 # Contributors:
 #     IBM Corporation - initial implementation
 ###############################################################################
+AbstractCommand_0=Operation cancelled by user
 AbstractRemoteCommand_format1={0,number,integer} {1} completed
 AbstractRemoteCommand_format2={0,number,integer} {1} of {2,number,integer} {3} complete ({4,number,percent})
 AbstractRemoteCommand_Get_symlink_target=Get symlink target
@@ -43,6 +44,7 @@
 JschFileStore_The_directory_could_not_be_created=The directory {0} could not be created
 JschFileStore_A_file_of_name_already_exists=A file of name {0} already exists
 JschFileStore_The_parent_of_directory_does_not_exist=The parent of directory {0} does not exist
+ProxyCommandShell_0=Unable to locate connection for command shell
 ProxyConnection_0=Opening connection...
 ProxyConnection_2=User canceled opening connection
 ProxyConnectionBootstrap_0=Initializing
@@ -64,3 +66,4 @@
 ProxyFileStore_6=File {0} does not exist
 ProxyFileStore_7={0} is a directory
 ProxyProcessBuilder_0=Unable to located connection for this process
+ProxyProcessBuilder_1=No command to run\!
diff --git a/bundles/org.eclipse.remote.proxy.protocol.core/META-INF/MANIFEST.MF b/bundles/org.eclipse.remote.proxy.protocol.core/META-INF/MANIFEST.MF
index db3c660..fdfcbeb 100644
--- a/bundles/org.eclipse.remote.proxy.protocol.core/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.remote.proxy.protocol.core/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.remote.proxy.protocol.core;singleton:=true
-Bundle-Version: 1.0.0.qualifier
+Bundle-Version: 2.0.0.qualifier
 Bundle-Activator: org.eclipse.remote.internal.proxy.protocol.core.Activator
 Bundle-Vendor: %pluginProvider
 Bundle-ActivationPolicy: lazy
diff --git a/bundles/org.eclipse.remote.proxy.protocol.core/pom.xml b/bundles/org.eclipse.remote.proxy.protocol.core/pom.xml
index ef7267b..ac47f7a 100644
--- a/bundles/org.eclipse.remote.proxy.protocol.core/pom.xml
+++ b/bundles/org.eclipse.remote.proxy.protocol.core/pom.xml
@@ -12,5 +12,5 @@
 
   <artifactId>org.eclipse.remote.proxy.protocol.core</artifactId>
   <packaging>eclipse-plugin</packaging>
-<version>1.0.0-SNAPSHOT</version>
+<version>2.0.0-SNAPSHOT</version>
 </project>
diff --git a/bundles/org.eclipse.remote.proxy.protocol.core/src/org/eclipse/remote/proxy/protocol/core/Protocol.java b/bundles/org.eclipse.remote.proxy.protocol.core/src/org/eclipse/remote/proxy/protocol/core/Protocol.java
index e10835b..0e7257e 100644
--- a/bundles/org.eclipse.remote.proxy.protocol.core/src/org/eclipse/remote/proxy/protocol/core/Protocol.java
+++ b/bundles/org.eclipse.remote.proxy.protocol.core/src/org/eclipse/remote/proxy/protocol/core/Protocol.java
@@ -17,16 +17,25 @@
 	
 	public final static short CmdBase = 		100;
 	
-	public final static short CMD_EXEC = 				CmdBase + 1;
-	public final static short CMD_SHELL = 				CmdBase + 2;
-	public final static short CMD_GETCWD = 				CmdBase + 3;
-	public final static short CMD_GETENV = 				CmdBase + 4;
-	public final static short CMD_CHILDINFOS = 			CmdBase + 5;
-	public final static short CMD_DELETE = 				CmdBase + 6;
-	public final static short CMD_FETCHINFO = 			CmdBase + 7;
-	public final static short CMD_GETINPUTSTREAM = 		CmdBase + 8;
+	public final static short CMD_EXEC = 			CmdBase + 1;
+	public final static short CMD_SHELL = 			CmdBase + 2;
+	public final static short CMD_GETCWD = 			CmdBase + 3;
+	public final static short CMD_GETENV = 			CmdBase + 4;
+	public final static short CMD_CHILDINFOS = 		CmdBase + 5;
+	public final static short CMD_DELETE = 			CmdBase + 6;
+	public final static short CMD_FETCHINFO = 		CmdBase + 7;
+	public final static short CMD_GETINPUTSTREAM = 	CmdBase + 8;
 	public final static short CMD_GETOUTPUTSTREAM = 	CmdBase + 9;
-	public final static short CMD_MKDIR = 				CmdBase + 10;
+	public final static short CMD_MKDIR = 			CmdBase + 10;
 	public final static short CMD_PUTINFO = 			CmdBase + 11;
-	public final static short CMD_GETPROPERTIES = 		CmdBase + 12;
+	public final static short CMD_GETPROPERTIES = 	CmdBase + 12;
+	
+	/**
+	 * @since 2.0
+	 */
+	public final static byte CONTROL_KILL = 0;
+	/**
+	 * @since 2.0
+	 */
+	public final static byte CONTROL_SETTERMINALSIZE = 1;
 }
diff --git a/bundles/org.eclipse.remote.proxy.server.core/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.remote.proxy.server.core/.settings/org.eclipse.jdt.core.prefs
index 0c68a61..a698e59 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/.settings/org.eclipse.jdt.core.prefs
+++ b/bundles/org.eclipse.remote.proxy.server.core/.settings/org.eclipse.jdt.core.prefs
@@ -1,7 +1,12 @@
 eclipse.preferences.version=1
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
 org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
 org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
 org.eclipse.jdt.core.compiler.source=1.8
diff --git a/bundles/org.eclipse.remote.proxy.server.core/.settings/org.eclipse.jdt.ui.prefs b/bundles/org.eclipse.remote.proxy.server.core/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..44ccb79
--- /dev/null
+++ b/bundles/org.eclipse.remote.proxy.server.core/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,59 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=false
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=false
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=false
+sp_cleanup.remove_trailing_whitespaces=false
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/bundles/org.eclipse.remote.proxy.server.core/META-INF/MANIFEST.MF b/bundles/org.eclipse.remote.proxy.server.core/META-INF/MANIFEST.MF
index 589f2fd..e201342 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.remote.proxy.server.core/META-INF/MANIFEST.MF
@@ -15,3 +15,4 @@
  org.osgi.framework.launch
 Bundle-Vendor: %pluginProvider
 Bundle-Localization: plugin
+Require-Bundle: org.eclipse.cdt.core.native
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerExecCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerExecCommand.java
deleted file mode 100644
index 5d100da..0000000
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerExecCommand.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*******************************************************************************
- * 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;
-	}
-}
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/Application.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/Application.java
index da92a67..0ad3110 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/Application.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/Application.java
@@ -25,7 +25,7 @@
 	public Object start(IApplicationContext context) throws Exception {
 		String[] args = (String[])context.getArguments().get(IApplicationContext.APPLICATION_ARGS);
 		for (String arg : args) {
-			if (arg.equals("-magic")) {
+			if (arg.equals("-magic")) { //$NON-NLS-1$
 				ByteBuffer b = ByteBuffer.allocate(4);
 				b.putInt(Protocol.MAGIC);
 				System.out.write(b.array());
@@ -40,5 +40,6 @@
 	 * @see org.eclipse.equinox.app.IApplication#stop()
 	 */
 	public void stop() {
+		// Nothing
 	}
 }
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/CommandServer.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/CommandServer.java
index c58c2d8..e600a47 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/CommandServer.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/CommandServer.java
@@ -16,18 +16,19 @@
 import java.util.List;
 import java.util.Map;
 
-import org.eclipse.remote.internal.proxy.server.commands.AbstractServerCommand;
-import org.eclipse.remote.internal.proxy.server.commands.ServerChildInfosCommand;
-import org.eclipse.remote.internal.proxy.server.commands.ServerDeleteCommand;
-import org.eclipse.remote.internal.proxy.server.commands.ServerExecCommand;
-import org.eclipse.remote.internal.proxy.server.commands.ServerFetchInfoCommand;
-import org.eclipse.remote.internal.proxy.server.commands.ServerGetCwdCommand;
-import org.eclipse.remote.internal.proxy.server.commands.ServerGetEnvCommand;
-import org.eclipse.remote.internal.proxy.server.commands.ServerGetInputStreamCommand;
-import org.eclipse.remote.internal.proxy.server.commands.ServerGetOutputStreamCommand;
-import org.eclipse.remote.internal.proxy.server.commands.ServerGetPropertiesCommand;
-import org.eclipse.remote.internal.proxy.server.commands.ServerMkdirCommand;
-import org.eclipse.remote.internal.proxy.server.commands.ServerPutInfoCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.AbstractServerCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.ServerChildInfosCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.ServerDeleteCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.ServerExecCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.ServerFetchInfoCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.ServerGetCwdCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.ServerGetEnvCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.ServerGetInputStreamCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.ServerGetOutputStreamCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.ServerGetPropertiesCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.ServerMkdirCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.ServerPutInfoCommand;
+import org.eclipse.remote.internal.proxy.server.core.commands.ServerShellCommand;
 import org.eclipse.remote.proxy.protocol.core.Protocol;
 import org.eclipse.remote.proxy.protocol.core.SerializableFileInfo;
 import org.eclipse.remote.proxy.protocol.core.StreamChannel;
@@ -48,7 +49,7 @@
 	}
 	
 	public void run() {
-		new Thread("cmd reader") {
+		new Thread("cmd reader") { //$NON-NLS-1$
 			@Override
 			public void run() {
 				try {
@@ -117,6 +118,10 @@
 			serverCmd = cmdExec(in);
 			break;
 
+		case Protocol.CMD_SHELL:
+			serverCmd = cmdShell(in);
+			break;
+
 		case Protocol.CMD_FETCHINFO:
 			serverCmd = cmdFetchInfo(in);
 			break;
@@ -151,16 +156,16 @@
 			
 		default:
 			System.err.println("Invalid command ID: " + cmd);
-			throw new ProxyException("Invalid command ID: " + cmd);
+			throw new ProxyException("Invalid command ID: " + cmd); //$NON-NLS-1$
 		}
 		
 		serverCmd.exec();
 	}
 	
 	private AbstractServerCommand cmdExec(DataInputStream in) throws ProxyException, IOException {
-		int chanAId = in.readByte();
-		int chanBId = in.readByte();
-		int chanCId = in.readByte();
+		int cmdChanId = in.readByte();
+		int ioChanId = in.readByte();
+		int errChanId = in.readByte();
 		int length = in.readInt();
 		List<String> command = new ArrayList<String>(length);
 		for (int i = 0; i < length; i++) {
@@ -176,25 +181,30 @@
 		String dir = in.readUTF();
 		boolean redirect = in.readBoolean();
 		boolean appendEnv = in.readBoolean();
-		System.err.print("dispatch: ");
-		for (String s:command) {
-			System.err.print(" " + s);
+		StreamChannel cmdChan = server.getChannel(cmdChanId);
+		StreamChannel ioChan = server.getChannel(ioChanId);
+		StreamChannel errChan= server.getChannel(errChanId);
+		if (cmdChan == null || ioChan == null || errChan == null) {
+			throw new ProxyException("Unable to locate channels for command"); //$NON-NLS-1$
 		}
-		System.err.println(" [" + chanAId + "," + chanBId+ ","+ chanCId + "]");
-		StreamChannel chanA = server.getChannel(chanAId);
-		StreamChannel chanB = server.getChannel(chanBId);
-		StreamChannel chanC= server.getChannel(chanCId);
-		if (chanA == null || chanB == null || chanC == null) {
-			throw new ProxyException("Unable to locate channels for command");
-		}
-		return new ServerExecCommand(command, env, dir, redirect, appendEnv, chanA, chanB, chanC);
+		return new ServerExecCommand(command, env, dir, redirect, appendEnv, cmdChan, ioChan, errChan);
 	}
 
+	private AbstractServerCommand cmdShell(DataInputStream in) throws ProxyException, IOException {
+		int cmdChanId = in.readByte();
+		int ioChanId = in.readByte();
+		StreamChannel cmdChan = server.getChannel(cmdChanId);
+		StreamChannel ioChan = server.getChannel(ioChanId);
+		if (cmdChan == null || ioChan == null) {
+			throw new ProxyException("Unable to locate channels for command"); //$NON-NLS-1$
+		}
+		return new ServerShellCommand(cmdChan, ioChan);
+	}
 	private AbstractServerCommand cmdGetCwd(DataInputStream in) throws ProxyException, IOException {
 		int chanId = in.readByte();
 		StreamChannel chan = server.getChannel(chanId);
 		if (chan == null) {
-			throw new ProxyException("Unable to locate channel for command");
+			throw new ProxyException("Unable to locate channel for command"); //$NON-NLS-1$
 		}
 		return new ServerGetCwdCommand(chan);
 	}
@@ -203,7 +213,7 @@
 		int chanId = in.readByte();
 		StreamChannel chan = server.getChannel(chanId);
 		if (chan == null) {
-			throw new ProxyException("Unable to locate channel for command");
+			throw new ProxyException("Unable to locate channel for command"); //$NON-NLS-1$
 		}
 		return new ServerGetEnvCommand(chan);
 	}
@@ -212,7 +222,7 @@
 		int chanId = in.readByte();
 		StreamChannel chan = server.getChannel(chanId);
 		if (chan == null) {
-			throw new ProxyException("Unable to locate channel for command");
+			throw new ProxyException("Unable to locate channel for command"); //$NON-NLS-1$
 		}
 		return new ServerGetPropertiesCommand(chan);
 	}
@@ -221,7 +231,7 @@
 		int chanId = in.readByte();
 		StreamChannel chan = server.getChannel(chanId);
 		if (chan == null) {
-			throw new ProxyException("Unable to locate channel for command");
+			throw new ProxyException("Unable to locate channel for command"); //$NON-NLS-1$
 		}
 		String path = in.readUTF();
 		return new ServerChildInfosCommand(chan, path);
@@ -231,7 +241,7 @@
 		int chanId = in.readByte();
 		StreamChannel chan = server.getChannel(chanId);
 		if (chan == null) {
-			throw new ProxyException("Unable to locate channel for command");
+			throw new ProxyException("Unable to locate channel for command"); //$NON-NLS-1$
 		}
 		String path = in.readUTF();
 		return new ServerFetchInfoCommand(chan, path);
@@ -241,7 +251,7 @@
 		int chanId = in.readByte();
 		StreamChannel chan = server.getChannel(chanId);
 		if (chan == null) {
-			throw new ProxyException("Unable to locate channel for command");
+			throw new ProxyException("Unable to locate channel for command"); //$NON-NLS-1$
 		}
 		int options = in.readInt();
 		String path = in.readUTF();
@@ -252,7 +262,7 @@
 		int chanId = in.readByte();
 		StreamChannel chan = server.getChannel(chanId);
 		if (chan == null) {
-			throw new ProxyException("Unable to locate channel for command");
+			throw new ProxyException("Unable to locate channel for command"); //$NON-NLS-1$
 		}
 		int options = in.readInt();
 		String path = in.readUTF();
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/AbstractServerCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/AbstractServerCommand.java
similarity index 90%
rename from bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/AbstractServerCommand.java
rename to bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/AbstractServerCommand.java
index f4d2dfa..c25198a 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/AbstractServerCommand.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/AbstractServerCommand.java
@@ -5,7 +5,7 @@
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *******************************************************************************/
-package org.eclipse.remote.internal.proxy.server.commands;
+package org.eclipse.remote.internal.proxy.server.core.commands;
 
 import org.eclipse.remote.proxy.protocol.core.exceptions.ProxyException;
 
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/AbstractServerExecCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/AbstractServerExecCommand.java
new file mode 100644
index 0000000..9fc61b1
--- /dev/null
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/AbstractServerExecCommand.java
@@ -0,0 +1,232 @@
+/*******************************************************************************
+ * 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;
+	}
+}
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerChildInfosCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerChildInfosCommand.java
similarity index 93%
rename from bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerChildInfosCommand.java
rename to bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerChildInfosCommand.java
index 5d1d28a..7f471e1 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerChildInfosCommand.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerChildInfosCommand.java
@@ -5,7 +5,7 @@
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *******************************************************************************/
-package org.eclipse.remote.internal.proxy.server.commands;
+package org.eclipse.remote.internal.proxy.server.core.commands;
 
 import java.io.DataOutputStream;
 import java.io.IOException;
@@ -15,8 +15,8 @@
 import org.eclipse.core.filesystem.EFS;
 import org.eclipse.core.filesystem.IFileInfo;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.remote.proxy.protocol.core.StreamChannel;
 import org.eclipse.remote.proxy.protocol.core.SerializableFileInfo;
+import org.eclipse.remote.proxy.protocol.core.StreamChannel;
 import org.eclipse.remote.proxy.protocol.core.exceptions.ProxyException;
 
 public class ServerChildInfosCommand extends AbstractServerCommand {
@@ -45,7 +45,7 @@
 	
 	public ServerChildInfosCommand(StreamChannel chan, String path) {
 		this.out = chan.getOutputStream();
-		this.uri = URI.create("file:" + path);
+		this.uri = URI.create("file:" + path); //$NON-NLS-1$
 	}
 
 	public void exec() throws ProxyException {
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerDeleteCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerDeleteCommand.java
similarity index 90%
rename from bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerDeleteCommand.java
rename to bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerDeleteCommand.java
index ba2435e..a259e77 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerDeleteCommand.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerDeleteCommand.java
@@ -5,7 +5,7 @@
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *******************************************************************************/
-package org.eclipse.remote.internal.proxy.server.commands;
+package org.eclipse.remote.internal.proxy.server.core.commands;
 
 import java.net.URI;
 
@@ -20,7 +20,7 @@
 	
 	public ServerDeleteCommand(int options, String path) {
 		this.options = options;
-		this.uri = URI.create("file:" + path);
+		this.uri = URI.create("file:" + path); //$NON-NLS-1$
 	}
 
 	public void exec() throws ProxyException {
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerExecCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerExecCommand.java
new file mode 100644
index 0000000..f56a641
--- /dev/null
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerExecCommand.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * 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.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.remote.proxy.protocol.core.StreamChannel;
+
+/**
+ * TODO: Fix hang if command fails...
+ *
+ */
+public class ServerExecCommand extends AbstractServerExecCommand {
+	public Process doRun() throws IOException {
+		System.err.print("exec: ");
+		for (String arg:getCommand()) {
+			System.err.print(arg + " ");
+		}
+		System.err.println();
+		ProcessBuilder builder = new ProcessBuilder(getCommand());
+		try {
+			if (!isAppendEnv()) {
+				builder.environment().clear();
+				builder.environment().putAll(getEnv());
+			} else {
+				for (Map.Entry<String, String> entry : getEnv().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(getDirectory());
+		if (dir.exists() && dir.isAbsolute()) {
+			builder.directory(dir);
+		}
+		builder.redirectErrorStream(isRedirect());
+		return builder.start();
+	}
+	
+	protected void doKill(Process proc) {
+		if (proc.isAlive()) {
+			proc.destroyForcibly();
+		}
+	}
+	
+	protected void doSetTerminalSize(Process proc, int cols, int rows) {
+		// Not supported
+	}
+	
+	public ServerExecCommand(List<String> command, Map<String, String> env, String directory, boolean redirect, boolean appendEnv, StreamChannel cmdChan, StreamChannel ioChan, StreamChannel errChan) {
+		super(command, env, directory, redirect, appendEnv, cmdChan, ioChan, errChan);
+	}
+}
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerFetchInfoCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerFetchInfoCommand.java
similarity index 93%
rename from bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerFetchInfoCommand.java
rename to bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerFetchInfoCommand.java
index 0f2d15e..d03b016 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerFetchInfoCommand.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerFetchInfoCommand.java
@@ -5,7 +5,7 @@
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *******************************************************************************/
-package org.eclipse.remote.internal.proxy.server.commands;
+package org.eclipse.remote.internal.proxy.server.core.commands;
 
 import java.io.DataOutputStream;
 import java.io.IOException;
@@ -15,8 +15,8 @@
 import org.eclipse.core.filesystem.EFS;
 import org.eclipse.core.filesystem.IFileInfo;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.remote.proxy.protocol.core.StreamChannel;
 import org.eclipse.remote.proxy.protocol.core.SerializableFileInfo;
+import org.eclipse.remote.proxy.protocol.core.StreamChannel;
 import org.eclipse.remote.proxy.protocol.core.exceptions.ProxyException;
 
 public class ServerFetchInfoCommand extends AbstractServerCommand {
@@ -42,7 +42,7 @@
 	
 	public ServerFetchInfoCommand(StreamChannel chan, String path) {
 		this.out = chan.getOutputStream();
-		this.uri = URI.create("file:" + path);
+		this.uri = URI.create("file:" + path); //$NON-NLS-1$
 	}
 
 	public void exec() throws ProxyException {
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetCwdCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetCwdCommand.java
similarity index 90%
rename from bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetCwdCommand.java
rename to bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetCwdCommand.java
index 6ce4136..9bc2a5f 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetCwdCommand.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetCwdCommand.java
@@ -5,7 +5,7 @@
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *******************************************************************************/
-package org.eclipse.remote.internal.proxy.server.commands;
+package org.eclipse.remote.internal.proxy.server.core.commands;
 
 import java.io.DataOutputStream;
 import java.io.IOException;
@@ -35,7 +35,7 @@
 	}
 
 	public void exec() throws ProxyException {
-		cwd = System.getProperty("user.dir");
+		cwd = System.getProperty("user.dir"); //$NON-NLS-1$
 		new Thread(new CommandRunner()).start();
 	}
 }
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetEnvCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetEnvCommand.java
similarity index 95%
rename from bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetEnvCommand.java
rename to bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetEnvCommand.java
index d94b9a4..6249110 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetEnvCommand.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetEnvCommand.java
@@ -5,7 +5,7 @@
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *******************************************************************************/
-package org.eclipse.remote.internal.proxy.server.commands;
+package org.eclipse.remote.internal.proxy.server.core.commands;
 
 import java.io.DataOutputStream;
 import java.io.IOException;
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetInputStreamCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetInputStreamCommand.java
similarity index 97%
rename from bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetInputStreamCommand.java
rename to bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetInputStreamCommand.java
index d01ca9d..6fb2835 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetInputStreamCommand.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetInputStreamCommand.java
@@ -5,7 +5,7 @@
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *******************************************************************************/
-package org.eclipse.remote.internal.proxy.server.commands;
+package org.eclipse.remote.internal.proxy.server.core.commands;
 
 import java.io.BufferedInputStream;
 import java.io.IOException;
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetOutputStreamCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetOutputStreamCommand.java
similarity index 97%
rename from bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetOutputStreamCommand.java
rename to bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetOutputStreamCommand.java
index 7e12bb1..ab2517e 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetOutputStreamCommand.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetOutputStreamCommand.java
@@ -5,7 +5,7 @@
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *******************************************************************************/
-package org.eclipse.remote.internal.proxy.server.commands;
+package org.eclipse.remote.internal.proxy.server.core.commands;
 
 import java.io.BufferedOutputStream;
 import java.io.IOException;
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetPropertiesCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetPropertiesCommand.java
similarity index 95%
rename from bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetPropertiesCommand.java
rename to bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetPropertiesCommand.java
index 3c7168d..dab727e 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerGetPropertiesCommand.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerGetPropertiesCommand.java
@@ -5,7 +5,7 @@
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *******************************************************************************/
-package org.eclipse.remote.internal.proxy.server.commands;
+package org.eclipse.remote.internal.proxy.server.core.commands;
 
 import java.io.DataOutputStream;
 import java.io.IOException;
@@ -32,7 +32,7 @@
 				props.put(IRemoteConnection.OS_NAME_PROPERTY, System.getProperty(IRemoteConnection.OS_NAME_PROPERTY));
 				props.put(IRemoteConnection.OS_VERSION_PROPERTY, System.getProperty(IRemoteConnection.OS_VERSION_PROPERTY));
 				props.put(IRemoteConnection.OS_ARCH_PROPERTY, System.getProperty(IRemoteConnection.OS_ARCH_PROPERTY));
-				props.put(IRemoteConnection.LOCALE_CHARMAP_PROPERTY, System.getProperty("file.encoding"));
+				props.put(IRemoteConnection.LOCALE_CHARMAP_PROPERTY, System.getProperty("file.encoding")); //$NON-NLS-1$
 
 				result.writeInt(props.size());
 				for (Map.Entry<String, String> entry : props.entrySet()) {
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerMkdirCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerMkdirCommand.java
similarity index 90%
rename from bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerMkdirCommand.java
rename to bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerMkdirCommand.java
index ac18606..cb3ddb4 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerMkdirCommand.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerMkdirCommand.java
@@ -5,7 +5,7 @@
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *******************************************************************************/
-package org.eclipse.remote.internal.proxy.server.commands;
+package org.eclipse.remote.internal.proxy.server.core.commands;
 
 import java.net.URI;
 
@@ -20,7 +20,7 @@
 	
 	public ServerMkdirCommand(int options, String path) {
 		this.options = options;
-		this.uri = URI.create("file:" + path);
+		this.uri = URI.create("file:" + path); //$NON-NLS-1$
 	}
 
 	public void exec() throws ProxyException {
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerPutInfoCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerPutInfoCommand.java
similarity index 91%
rename from bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerPutInfoCommand.java
rename to bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerPutInfoCommand.java
index 9ee1c5d..61d2abf 100644
--- a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/commands/ServerPutInfoCommand.java
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerPutInfoCommand.java
@@ -5,7 +5,7 @@
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *******************************************************************************/
-package org.eclipse.remote.internal.proxy.server.commands;
+package org.eclipse.remote.internal.proxy.server.core.commands;
 
 import java.net.URI;
 
@@ -23,7 +23,7 @@
 	public ServerPutInfoCommand(IFileInfo info, int options, String path) {
 		this.info = info;
 		this.options = options;
-		this.uri = URI.create("file:" + path);
+		this.uri = URI.create("file:" + path); //$NON-NLS-1$
 	}
 
 	public void exec() throws ProxyException {
diff --git a/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerShellCommand.java b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerShellCommand.java
new file mode 100644
index 0000000..74545c6
--- /dev/null
+++ b/bundles/org.eclipse.remote.proxy.server.core/src/org/eclipse/remote/internal/proxy/server/core/commands/ServerShellCommand.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * 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.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+
+import org.eclipse.cdt.utils.pty.PTY;
+import org.eclipse.cdt.utils.pty.PTY.Mode;
+import org.eclipse.cdt.utils.spawner.ProcessFactory;
+import org.eclipse.remote.proxy.protocol.core.StreamChannel;
+
+/**
+ * TODO: Fix hang if command fails...
+ *
+ */
+public class ServerShellCommand extends AbstractServerExecCommand {
+	private class ShellProcess extends Process {
+		private final Process proc;
+		private final PTY pty;
+
+		public ShellProcess(Process proc, PTY pty) {
+			this.proc = proc;
+			this.pty = pty;
+		}
+		
+		@Override
+		public OutputStream getOutputStream() {
+			if (pty != null) {
+				return pty.getOutputStream();
+			}
+			return proc.getOutputStream();
+		}
+
+		@Override
+		public InputStream getInputStream() {
+			if (pty != null) {
+				return pty.getInputStream();
+			}
+			return proc.getInputStream();
+		}
+
+		@Override
+		public InputStream getErrorStream() {
+			if (pty != null) {
+				return pty.getInputStream();
+			}
+			return proc.getErrorStream();
+		}
+
+		@Override
+		public int waitFor() throws InterruptedException {
+			return proc.waitFor();
+		}
+
+		@Override
+		public int exitValue() {
+			return proc.exitValue();
+		}
+
+		@Override
+		public void destroy() {
+			proc.destroy();
+		}
+		
+		public void setTerminalSize(int cols, int rows) {
+			if (pty != null) {
+				pty.setTerminalSize(cols, rows);
+			}
+		}
+	}
+	
+	public ServerShellCommand(StreamChannel cmdChan, StreamChannel ioChan) {
+		super(null, null, null, true, false, cmdChan, ioChan, null);
+	}
+
+	public Process doRun() throws IOException {
+		String shell = findLoginShell();
+		
+		if (PTY.isSupported(Mode.TERMINAL)) {
+			PTY pty = new PTY(Mode.TERMINAL);
+			Process p = ProcessFactory.getFactory().exec(new String[] {shell, "-l"}, null, null, pty); //$NON-NLS-1$
+			return new ShellProcess(p, pty);
+		}
+		
+		return ProcessFactory.getFactory().exec(new String[] {shell, "-l"}, null, null); //$NON-NLS-1$
+	}
+	
+	protected void doKill(Process proc) {
+		if (proc.isAlive()) {
+			proc.destroyForcibly();
+		}
+	}
+	
+	protected void doSetTerminalSize(Process proc, int cols, int rows) {
+		if (proc.isAlive() && proc instanceof ShellProcess) {
+			ShellProcess shell = (ShellProcess)proc;
+			shell.setTerminalSize(cols, rows);
+		}
+	}
+
+	/**
+	 * Find the login shell.
+	 * 
+	 * On Linux, use `getent passwd $USER`
+	 * On Mac OSX, use `dscl . -read /Users/$USER UserShell`
+	 * 
+	 * @return
+	 */
+	private String findLoginShell() throws IOException {
+		String res;
+		
+		String osName = System.getProperty("os.name"); //$NON-NLS-1$
+		String userName = System.getProperty("user.name"); //$NON-NLS-1$
+		if (osName == null || userName == null) {
+			throw new IOException("Unable to obtain information needed to find login shell"); //$NON-NLS-1$
+		}
+		switch (osName) {
+		case "Mac OS X": //$NON-NLS-1$
+			res = executeCommand("dscl . -read /Users/" + userName + " UserShell"); //$NON-NLS-1$ //$NON-NLS-2$
+			if (res != null) {
+				String[] vals = res.split(" "); //$NON-NLS-1$
+				if (vals.length == 2) {
+					return vals[1];
+				}
+			}
+			break;
+		case "Linux": //$NON-NLS-1$
+			res = executeCommand("getent passwd " + userName); //$NON-NLS-1$
+			if (res != null) {
+				String[] vals = res.split(":"); //$NON-NLS-1$
+				if (vals.length == 7) {
+					return vals[6];
+				}
+			}
+			break;
+		default:
+			break;
+		}
+		throw new IOException("Unable to find login shell for os=" + osName + " user=" + userName); //$NON-NLS-1$ //$NON-NLS-2$
+	}
+	
+	private String executeCommand(String command) throws IOException {
+		String line;
+		StringBuffer output = new StringBuffer();
+
+		Process p;
+		try {
+			p = Runtime.getRuntime().exec(command);
+		} catch (Exception e) {
+			throw new IOException(e.getMessage());
+		}
+		try {
+			p.waitFor();
+		} catch (InterruptedException e) {
+			throw new IOException(e.getMessage());
+		}
+		BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+		line = reader.readLine();
+		while (line != null) {
+			output.append(line);
+			line = reader.readLine();
+			if (line != null) {
+				output.append("\n"); //$NON-NLS-1$
+			}
+		}
+
+		return output.toString();
+	}
+}
diff --git a/releng/org.eclipse.remote.proxy.server.product/proxy.server.product b/releng/org.eclipse.remote.proxy.server.product/proxy.server.product
index 345cc64..87104e6 100644
--- a/releng/org.eclipse.remote.proxy.server.product/proxy.server.product
+++ b/releng/org.eclipse.remote.proxy.server.product/proxy.server.product
@@ -16,7 +16,6 @@
    <windowImages/>
 
    <launcher name="proxy">
-      <solaris/>
       <win useIco="false">
          <bmp/>
       </win>
@@ -28,6 +27,8 @@
 
    <plugins>
       <plugin id="com.ibm.icu"/>
+      <plugin id="org.eclipse.cdt.core.macosx" fragment="true"/>
+      <plugin id="org.eclipse.cdt.core.linux.x86_64" fragment="true"/>
       <plugin id="org.eclipse.cdt.core.native"/>
       <plugin id="org.eclipse.core.contenttype"/>
       <plugin id="org.eclipse.core.expressions"/>
@@ -53,10 +54,6 @@
       <plugin id="org.eclipse.remote.proxy.server.core"/>
    </plugins>
 
-   <features>
-      <feature id="org.eclipse.remote.proxy"/>
-   </features>
-
    <configurations>
       <plugin id="org.eclipse.core.runtime" autoStart="true" startLevel="0" />
       <plugin id="org.eclipse.equinox.common" autoStart="true" startLevel="2" />
diff --git a/releng/org.eclipse.remote.target/oxygen.target b/releng/org.eclipse.remote.target/oxygen.target
index b0b715f..f3a2707 100644
--- a/releng/org.eclipse.remote.target/oxygen.target
+++ b/releng/org.eclipse.remote.target/oxygen.target
@@ -1,20 +1,20 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<?pde version="3.8"?>
-<target name="remote-oxygen" sequenceNumber="0">
+<?pde version="3.8"?><target name="remote-oxygen" sequenceNumber="0">
 <locations>
 <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">
-<unit id="org.eclipse.sdk.ide" version="0.0.0"/>
 <unit id="org.eclipse.equinox.executable.feature.group" version="0.0.0"/>
+<unit id="org.eclipse.sdk.ide" version="0.0.0"/>
 <repository location="http://download.eclipse.org/eclipse/updates/4.7/"/>
 </location>
 <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">
-<unit id="org.mockito" version="0.0.0"/>
 <unit id="org.hamcrest" version="0.0.0"/>
+<unit id="org.mockito" version="0.0.0"/>
 <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20170919201930/repository/"/>
 </location>
 <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">
-<unit id="org.eclipse.cdt.native.serial" version="0.0.0"/>
 <unit id="org.eclipse.cdt.core" version="0.0.0"/>
+<unit id="org.eclipse.cdt.native.feature.group" version="0.0.0"/>
+<unit id="org.eclipse.cdt.native.serial" version="0.0.0"/>
 <repository location="http://download.eclipse.org/tools/cdt/builds/master/nightly/"/>
 </location>
 <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">