blob: deb06c56f7db7238fa8a0f01d5442efb48d45370 [file] [log] [blame]
Stuart McCullochbb014372012-06-07 21:57:32 +00001package aQute.libg.command;
2
3import java.io.*;
4import java.util.*;
5import java.util.Map.Entry;
6import java.util.concurrent.*;
Stuart McCullochd4826102012-06-26 16:34:24 +00007import java.util.concurrent.atomic.*;
Stuart McCullochbb014372012-06-07 21:57:32 +00008
Stuart McCullochd4826102012-06-26 16:34:24 +00009import aQute.lib.io.*;
Stuart McCulloch81d48de2012-06-29 19:23:09 +000010import aQute.service.reporter.*;
Stuart McCullochbb014372012-06-07 21:57:32 +000011
12public class Command {
13
14 boolean trace;
15 Reporter reporter;
16 List<String> arguments = new ArrayList<String>();
Stuart McCulloch2286f232012-06-15 13:27:53 +000017 Map<String,String> variables = new LinkedHashMap<String,String>();
Stuart McCullochbb014372012-06-07 21:57:32 +000018 long timeout = 0;
19 File cwd = new File("").getAbsoluteFile();
20 static Timer timer = new Timer(Command.class.getName(), true);
21 Process process;
22 volatile boolean timedout;
23 String fullCommand;
24
25 public Command(String fullCommand) {
26 this.fullCommand = fullCommand;
27 }
28
Stuart McCulloch2286f232012-06-15 13:27:53 +000029 public Command() {}
Stuart McCullochbb014372012-06-07 21:57:32 +000030
31 public int execute(Appendable stdout, Appendable stderr) throws Exception {
32 return execute((InputStream) null, stdout, stderr);
33 }
34
35 public int execute(String input, Appendable stdout, Appendable stderr) throws Exception {
36 InputStream in = new ByteArrayInputStream(input.getBytes("UTF-8"));
37 return execute(in, stdout, stderr);
38 }
39
Stuart McCullochd4826102012-06-26 16:34:24 +000040 public int execute(final InputStream in, Appendable stdout, Appendable stderr) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +000041 if (reporter != null) {
42 reporter.trace("executing cmd: %s", arguments);
43 }
44
45 String args[] = arguments.toArray(new String[arguments.size()]);
46 String vars[] = new String[variables.size()];
47 int i = 0;
Stuart McCulloch2286f232012-06-15 13:27:53 +000048 for (Entry<String,String> s : variables.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +000049 vars[i++] = s.getKey() + "=" + s.getValue();
50 }
51
52 if (fullCommand == null)
53 process = Runtime.getRuntime().exec(args, vars.length == 0 ? null : vars, cwd);
54 else
55 process = Runtime.getRuntime().exec(fullCommand, vars.length == 0 ? null : vars, cwd);
56
Stuart McCullochbb014372012-06-07 21:57:32 +000057 // Make sure the command will not linger when we go
58 Runnable r = new Runnable() {
59 public void run() {
60 process.destroy();
61 }
62 };
63 Thread hook = new Thread(r, arguments.toString());
64 Runtime.getRuntime().addShutdownHook(hook);
65 TimerTask timer = null;
Stuart McCullochd4826102012-06-26 16:34:24 +000066 final OutputStream stdin = process.getOutputStream();
67 Thread rdInThread = null;
Stuart McCullochbb014372012-06-07 21:57:32 +000068
69 if (timeout != 0) {
70 timer = new TimerTask() {
71 public void run() {
72 timedout = true;
73 process.destroy();
Stuart McCullochbb014372012-06-07 21:57:32 +000074 }
75 };
76 Command.timer.schedule(timer, timeout);
77 }
78
Stuart McCullochd4826102012-06-26 16:34:24 +000079 final AtomicBoolean finished = new AtomicBoolean(false);
Stuart McCullochbb014372012-06-07 21:57:32 +000080 InputStream out = process.getInputStream();
81 try {
82 InputStream err = process.getErrorStream();
83 try {
Stuart McCulloch285034f2012-06-12 12:41:16 +000084 Collector cout = new Collector(out, stdout);
85 cout.start();
86 Collector cerr = new Collector(err, stderr);
87 cerr.start();
Stuart McCulloch2286f232012-06-15 13:27:53 +000088
Stuart McCullochd4826102012-06-26 16:34:24 +000089 if (in != null) {
90 if (in == System.in) {
91 rdInThread = new Thread("Read Input Thread") {
92 public void run() {
93 try {
94 while (!finished.get()) {
95 int n = in.available();
96 if (n == 0) {
97 sleep(100);
98 } else {
99 int c = in.read();
100 if (c < 0) {
101 stdin.close();
102 return;
103 }
104 stdin.write(c);
105 if (c == '\n')
106 stdin.flush();
107 }
108 }
109 }
110 catch (InterruptedIOException e) {
111 // Ignore here
112 }
113 catch (Exception e) {
114 // Who cares?
115 }
116 finally {
117 IO.close(stdin);
118 }
119 }
120 };
121 rdInThread.setDaemon(true);
122 rdInThread.start();
123 } else {
124 IO.copy(in, stdin);
125 stdin.close();
Stuart McCulloch285034f2012-06-12 12:41:16 +0000126 }
127 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000128 if (reporter != null)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000129 reporter.trace("exited process");
Stuart McCulloch2286f232012-06-15 13:27:53 +0000130
Stuart McCulloch285034f2012-06-12 12:41:16 +0000131 cerr.join();
132 cout.join();
133 if (reporter != null)
134 reporter.trace("stdout/stderr streams have finished");
Stuart McCullochbb014372012-06-07 21:57:32 +0000135 }
136 finally {
137 err.close();
138 }
139 }
140 finally {
141 out.close();
142 if (timer != null)
143 timer.cancel();
144 Runtime.getRuntime().removeShutdownHook(hook);
Stuart McCullochbb014372012-06-07 21:57:32 +0000145 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000146
Stuart McCulloch285034f2012-06-12 12:41:16 +0000147 byte exitValue = (byte) process.waitFor();
Stuart McCullochd4826102012-06-26 16:34:24 +0000148 finished.set(true);
149 if (rdInThread != null) {
150 if (in != null)
151 IO.close(in);
152 rdInThread.interrupt();
153 }
154
Stuart McCullochbb014372012-06-07 21:57:32 +0000155 if (reporter != null)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000156 reporter.trace("cmd %s executed with result=%d, result: %s/%s, timedout=%s", arguments, exitValue, stdout,
157 stderr, timedout);
Stuart McCullochbb014372012-06-07 21:57:32 +0000158
159 if (timedout)
160 return Integer.MIN_VALUE;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000161
Stuart McCullochbb014372012-06-07 21:57:32 +0000162 return exitValue;
163 }
164
165 public void add(String... args) {
166 for (String arg : args)
167 arguments.add(arg);
168 }
169
170 public void addAll(Collection<String> args) {
171 arguments.addAll(args);
172 }
173
174 public void setTimeout(long duration, TimeUnit unit) {
175 timeout = unit.toMillis(duration);
176 }
177
178 public void setTrace() {
179 this.trace = true;
180 }
181
182 public void setReporter(Reporter reporter) {
183 this.reporter = reporter;
184 }
185
186 public void setCwd(File dir) {
187 if (!dir.isDirectory())
188 throw new IllegalArgumentException("Working directory must be a directory: " + dir);
189
190 this.cwd = dir;
191 }
192
193 public void cancel() {
194 process.destroy();
195 }
196
197 class Collector extends Thread {
198 final InputStream in;
199 final Appendable sb;
200
201 Collector(InputStream inputStream, Appendable sb) {
202 this.in = inputStream;
203 this.sb = sb;
204 setDaemon(true);
205 }
206
207 public void run() {
208 try {
209 int c = in.read();
210 while (c >= 0) {
211 sb.append((char) c);
212 c = in.read();
213 }
214 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000215 catch (IOException e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000216 // We assume the socket is closed
217 }
218 catch (Exception e) {
219 try {
220 sb.append("\n**************************************\n");
221 sb.append(e.toString());
222 sb.append("\n**************************************\n");
223 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000224 catch (IOException e1) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000225 if (reporter != null) {
226 reporter.trace("cmd exec: %s", e);
227 }
228 }
229 }
230 }
231
Stuart McCullochbb014372012-06-07 21:57:32 +0000232 public Command var(String name, String value) {
233 variables.put(name, value);
234 return this;
235 }
236
237 public Command arg(String... args) {
238 add(args);
239 return this;
240 }
241
242 public Command full(String full) {
243 fullCommand = full;
244 return this;
245 }
246
247 public void inherit() {
248 ProcessBuilder pb = new ProcessBuilder();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000249 for (Entry<String,String> e : pb.environment().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000250 var(e.getKey(), e.getValue());
251 }
252 }
253
254 public String var(String name) {
255 return variables.get(name);
256 }
257
258 public String toString() {
259 StringBuilder sb = new StringBuilder();
260 String del = "";
261
262 for (String argument : arguments) {
263 sb.append(del);
264 sb.append(argument);
265 del = " ";
266 }
267 return sb.toString();
268 }
269}