add ExecProcess from Electric
[fleet.git] / src / com / sun / electric / tool / io / ExecProcess.java
1 package com.sun.electric.tool.io;
2
3 import java.net.*;
4 import java.util.*;
5 import java.io.*;
6
7 /**
8  * This class provides the same functionality as Runtime.exec(), but
9  * with extra safeguards and utilities.  Includes the ability to
10  * execute a command on a remote machine via ssh, optionally rsync'ing
11  * the working directory to the remote machine before execution and
12  * back afterwards.
13  *
14  * This class should not depend on other Electric classes.
15  *
16  * @author megacz (heavily influenced by gainsley's ExecProcess)
17  */
18 public class ExecProcess {
19
20     /** an OutputStream that discards anything written to it */
21     public static final OutputStream devNull = new OutputStream() {
22             public void write(int b) { }
23             public void write(byte[] b, int ofs, int len) { }
24         };
25
26     /** an InputStream that always returns EOF */
27     public static final InputStream eofInputStream = new InputStream() {
28             public int  read() { return -1; }
29             public int  read(byte[] buf, int ofs, int len) { return -1; }
30             public long skip(long ofs) { return 0; }
31             public int  available() { return 0; }
32         };
33
34     /**
35      *  @param command the command to run (separated into argv[])
36      *  @param workingDirectory the working directory on the LOCAL machine
37      */
38     public ExecProcess(String[] command, File workingDirectory) {
39         this.command = command;
40
41         // Using java.io.tmpdir as the default working directory leads
42         // to far more predictable behavior than simply using the
43         // JVM's working directory.  Electric already has a lot of
44         // bugs and quirks that result from doing that -- let's not
45         // add more!
46         if (workingDirectory==null)
47             workingDirectory = new File(System.getProperty("java.io.tmpdir"));
48
49         this.workingDirectory = workingDirectory;
50     }
51
52     /**
53      *  @param host the hostname to run on
54      *  @param user the username on the remote machine (or null to use
55      *         whatever default ssh chooses)
56      *  @param remoteWorkingDirectory the directory to work in on the remote machine
57      *  @param syncBefore if true then "rsync --delete
58      *         workingDirectory host:remoteWorkingDirectory" before
59      *         invoking command.
60      *  @param syncAfter if true and the command terminates with exit
61      *         code zero, then "rsync --delete
62      *         host:remoteWorkingDirectory workingDirectory" after
63      *         invoking command.
64      */
65     public synchronized void setRemote(String host, String user,
66                                        File remoteWorkingDirectory,
67                                        boolean syncBefore, boolean syncAfter) {
68         if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.setRemote() after ExecProcess.start()");
69         throw new RuntimeException("not implemented");
70     }
71
72     /** undoes setRemote() */
73     public synchronized void setLocal() { }
74
75     public synchronized void redirectStdin(InputStream in) {
76         if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.redirectStdin() after ExecProcess.start()");
77         this.redirectStdin = in;
78     }
79
80     public synchronized void redirectStdout(OutputStream os) {
81         if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.redirectStdout() after ExecProcess.start()");
82         this.redirectStdout = os;
83     }
84
85     public synchronized void redirectStderr(OutputStream os) {
86         if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.redirectStderr() after ExecProcess.start()");
87         this.redirectStderr = os;
88     }
89
90     public synchronized void start() throws IOException {
91         if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.start() twice");
92         proc = Runtime.getRuntime().exec(command, null, workingDirectory);
93         if (redirectStdin != null) new StreamCopier(redirectStdin, proc.getOutputStream()).start();
94         if (redirectStdout != null) new StreamCopier(proc.getInputStream(), redirectStdout).start();
95         if (redirectStderr != null) new StreamCopier(proc.getErrorStream(), redirectStderr).start();
96     }
97
98     public synchronized void destroy() throws IOException {
99         if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
100         proc.destroy();
101     }
102
103     public int waitFor() throws IOException {
104         if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
105         try {
106             return proc.waitFor();
107         } catch (InterruptedException ie) {
108             throw new RuntimeException(ie);
109         }
110     }
111
112     public synchronized OutputStream getStdin() {
113         if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
114         if (redirectStdin!=null) throw new RuntimeException("you cannot invoke getStdin() after redirectStdin()");
115         return proc.getOutputStream();
116     }
117
118     public synchronized InputStream getStdout() {
119         if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
120         if (redirectStdout!=null) throw new RuntimeException("you cannot invoke getStdout() after redirectStdout()");
121         return proc.getInputStream();
122     }
123
124     public synchronized InputStream getStderr() {
125         if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
126         if (redirectStderr!=null) throw new RuntimeException("you cannot invoke getStderr() after redirectStderr()");
127         return proc.getErrorStream();
128     }
129
130     private Process      proc;
131     private InputStream  redirectStdin;
132     private OutputStream redirectStdout;
133     private OutputStream redirectStderr;
134     private String[]     command;
135     private File         workingDirectory;
136
137     /**
138      *  Copies from an InputStream to an OutputStream; used to implement redirectXXX().
139      */
140     private static class StreamCopier extends Thread {
141         private final byte[] buf = new byte[16 * 1024];
142         private final InputStream is;
143         private final OutputStream os;
144         public StreamCopier(InputStream is, OutputStream os) {
145             setDaemon(true);
146             this.is = is;
147             this.os = os;
148         }
149         public void run() {
150             try {
151                 while(true) {
152                     int numread = is.read(buf, 0, buf.length);
153                     if (numread==-1) break;
154                     os.write(buf, 0, numread);
155                 }
156             } catch (Exception e) { throw new RuntimeException(e); }
157         }
158     }
159
160 }