--- /dev/null
+package com.sun.electric.tool.io;
+
+import java.net.*;
+import java.util.*;
+import java.io.*;
+
+/**
+ * This class provides the same functionality as Runtime.exec(), but
+ * with extra safeguards and utilities. Includes the ability to
+ * execute a command on a remote machine via ssh, optionally rsync'ing
+ * the working directory to the remote machine before execution and
+ * back afterwards.
+ *
+ * This class should not depend on other Electric classes.
+ *
+ * @author megacz (heavily influenced by gainsley's ExecProcess)
+ */
+public class ExecProcess {
+
+ /** an OutputStream that discards anything written to it */
+ public static final OutputStream devNull = new OutputStream() {
+ public void write(int b) { }
+ public void write(byte[] b, int ofs, int len) { }
+ };
+
+ /** an InputStream that always returns EOF */
+ public static final InputStream eofInputStream = new InputStream() {
+ public int read() { return -1; }
+ public int read(byte[] buf, int ofs, int len) { return -1; }
+ public long skip(long ofs) { return 0; }
+ public int available() { return 0; }
+ };
+
+ /**
+ * @param command the command to run (separated into argv[])
+ * @param workingDirectory the working directory on the LOCAL machine
+ */
+ public ExecProcess(String[] command, File workingDirectory) {
+ this.command = command;
+
+ // Using java.io.tmpdir as the default working directory leads
+ // to far more predictable behavior than simply using the
+ // JVM's working directory. Electric already has a lot of
+ // bugs and quirks that result from doing that -- let's not
+ // add more!
+ if (workingDirectory==null)
+ workingDirectory = new File(System.getProperty("java.io.tmpdir"));
+
+ this.workingDirectory = workingDirectory;
+ }
+
+ /**
+ * @param host the hostname to run on
+ * @param user the username on the remote machine (or null to use
+ * whatever default ssh chooses)
+ * @param remoteWorkingDirectory the directory to work in on the remote machine
+ * @param syncBefore if true then "rsync --delete
+ * workingDirectory host:remoteWorkingDirectory" before
+ * invoking command.
+ * @param syncAfter if true and the command terminates with exit
+ * code zero, then "rsync --delete
+ * host:remoteWorkingDirectory workingDirectory" after
+ * invoking command.
+ */
+ public synchronized void setRemote(String host, String user,
+ File remoteWorkingDirectory,
+ boolean syncBefore, boolean syncAfter) {
+ if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.setRemote() after ExecProcess.start()");
+ throw new RuntimeException("not implemented");
+ }
+
+ /** undoes setRemote() */
+ public synchronized void setLocal() { }
+
+ public synchronized void redirectStdin(InputStream in) {
+ if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.redirectStdin() after ExecProcess.start()");
+ this.redirectStdin = in;
+ }
+
+ public synchronized void redirectStdout(OutputStream os) {
+ if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.redirectStdout() after ExecProcess.start()");
+ this.redirectStdout = os;
+ }
+
+ public synchronized void redirectStderr(OutputStream os) {
+ if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.redirectStderr() after ExecProcess.start()");
+ this.redirectStderr = os;
+ }
+
+ public synchronized void start() throws IOException {
+ if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.start() twice");
+ proc = Runtime.getRuntime().exec(command, null, workingDirectory);
+ if (redirectStdin != null) new StreamCopier(redirectStdin, proc.getOutputStream()).start();
+ if (redirectStdout != null) new StreamCopier(proc.getInputStream(), redirectStdout).start();
+ if (redirectStderr != null) new StreamCopier(proc.getErrorStream(), redirectStderr).start();
+ }
+
+ public synchronized void destroy() throws IOException {
+ if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
+ proc.destroy();
+ }
+
+ public int waitFor() throws IOException {
+ if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
+ try {
+ return proc.waitFor();
+ } catch (InterruptedException ie) {
+ throw new RuntimeException(ie);
+ }
+ }
+
+ public synchronized OutputStream getStdin() {
+ if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
+ if (redirectStdin!=null) throw new RuntimeException("you cannot invoke getStdin() after redirectStdin()");
+ return proc.getOutputStream();
+ }
+
+ public synchronized InputStream getStdout() {
+ if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
+ if (redirectStdout!=null) throw new RuntimeException("you cannot invoke getStdout() after redirectStdout()");
+ return proc.getInputStream();
+ }
+
+ public synchronized InputStream getStderr() {
+ if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
+ if (redirectStderr!=null) throw new RuntimeException("you cannot invoke getStderr() after redirectStderr()");
+ return proc.getErrorStream();
+ }
+
+ private Process proc;
+ private InputStream redirectStdin;
+ private OutputStream redirectStdout;
+ private OutputStream redirectStderr;
+ private String[] command;
+ private File workingDirectory;
+
+ /**
+ * Copies from an InputStream to an OutputStream; used to implement redirectXXX().
+ */
+ private static class StreamCopier extends Thread {
+ private final byte[] buf = new byte[16 * 1024];
+ private final InputStream is;
+ private final OutputStream os;
+ public StreamCopier(InputStream is, OutputStream os) {
+ setDaemon(true);
+ this.is = is;
+ this.os = os;
+ }
+ public void run() {
+ try {
+ while(true) {
+ int numread = is.read(buf, 0, buf.length);
+ if (numread==-1) break;
+ os.write(buf, 0, numread);
+ }
+ } catch (Exception e) { throw new RuntimeException(e); }
+ }
+ }
+
+}