blob: f48e01674ad8a9ab1b8ed9b3c65264030dbd7484 [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 McCulloch99e00542012-12-30 21:53:09 +000040 public static boolean needsWindowsQuoting(String s) {
41 int len = s.length();
42 if (len == 0) // empty string have to be quoted
43 return true;
44 for (int i = 0; i < len; i++) {
45 switch (s.charAt(i)) {
46 case ' ' :
47 case '\t' :
48 case '\\' :
49 case '"' :
50 return true;
51 }
52 }
53 return false;
54 }
55
56 public static String windowsQuote(String s) {
57 if (!needsWindowsQuoting(s))
58 return s;
59 s = s.replaceAll("([\\\\]*)\"", "$1$1\\\\\"");
60 s = s.replaceAll("([\\\\]*)\\z", "$1$1");
61 return "\"" + s + "\"";
62 }
63
Stuart McCullochd4826102012-06-26 16:34:24 +000064 public int execute(final InputStream in, Appendable stdout, Appendable stderr) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +000065 if (reporter != null) {
66 reporter.trace("executing cmd: %s", arguments);
67 }
Stuart McCulloch99e00542012-12-30 21:53:09 +000068
69 ProcessBuilder p;
70 if (fullCommand != null) {
71 p = new ProcessBuilder(fullCommand);
72 } else {
73 //[cs] Arguments on windows aren't processed correctly. Thus the below junk
74 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6511002
75
76 if (System.getProperty("os.name").startsWith("Windows")) {
77 List<String> adjustedStrings = new LinkedList<String>();
78 for (String a : arguments) {
79 adjustedStrings.add(windowsQuote(a));
80 }
81 p = new ProcessBuilder(adjustedStrings);
82 } else {
83 p = new ProcessBuilder(arguments);
84 }
Stuart McCullochbb014372012-06-07 21:57:32 +000085 }
Stuart McCulloch99e00542012-12-30 21:53:09 +000086
87 Map<String, String> env = p.environment();
88 for (Entry<String,String> s : variables.entrySet()) {
89 env.put(s.getKey(), s.getValue());
90 }
91
92 p.directory(cwd);
93 process = p.start();
Stuart McCullochbb014372012-06-07 21:57:32 +000094
Stuart McCullochbb014372012-06-07 21:57:32 +000095 // Make sure the command will not linger when we go
96 Runnable r = new Runnable() {
97 public void run() {
98 process.destroy();
99 }
100 };
101 Thread hook = new Thread(r, arguments.toString());
102 Runtime.getRuntime().addShutdownHook(hook);
103 TimerTask timer = null;
Stuart McCullochd4826102012-06-26 16:34:24 +0000104 final OutputStream stdin = process.getOutputStream();
105 Thread rdInThread = null;
Stuart McCullochbb014372012-06-07 21:57:32 +0000106
107 if (timeout != 0) {
108 timer = new TimerTask() {
Stuart McCullochb215bfd2012-09-06 18:28:06 +0000109 //@Override TODO why did this not work? TimerTask implements Runnable
Stuart McCullochbb014372012-06-07 21:57:32 +0000110 public void run() {
111 timedout = true;
112 process.destroy();
Stuart McCullochbb014372012-06-07 21:57:32 +0000113 }
114 };
115 Command.timer.schedule(timer, timeout);
116 }
117
Stuart McCullochd4826102012-06-26 16:34:24 +0000118 final AtomicBoolean finished = new AtomicBoolean(false);
Stuart McCullochbb014372012-06-07 21:57:32 +0000119 InputStream out = process.getInputStream();
120 try {
121 InputStream err = process.getErrorStream();
122 try {
Stuart McCulloch285034f2012-06-12 12:41:16 +0000123 Collector cout = new Collector(out, stdout);
124 cout.start();
125 Collector cerr = new Collector(err, stderr);
126 cerr.start();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000127
Stuart McCullochd4826102012-06-26 16:34:24 +0000128 if (in != null) {
129 if (in == System.in) {
130 rdInThread = new Thread("Read Input Thread") {
Stuart McCulloch55d4dfe2012-08-07 10:57:21 +0000131 @Override
Stuart McCullochd4826102012-06-26 16:34:24 +0000132 public void run() {
133 try {
134 while (!finished.get()) {
135 int n = in.available();
136 if (n == 0) {
137 sleep(100);
138 } else {
139 int c = in.read();
140 if (c < 0) {
141 stdin.close();
142 return;
143 }
144 stdin.write(c);
145 if (c == '\n')
146 stdin.flush();
147 }
148 }
149 }
150 catch (InterruptedIOException e) {
151 // Ignore here
152 }
153 catch (Exception e) {
154 // Who cares?
155 }
156 finally {
157 IO.close(stdin);
158 }
159 }
160 };
161 rdInThread.setDaemon(true);
162 rdInThread.start();
163 } else {
164 IO.copy(in, stdin);
165 stdin.close();
Stuart McCulloch285034f2012-06-12 12:41:16 +0000166 }
167 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000168 if (reporter != null)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000169 reporter.trace("exited process");
Stuart McCulloch2286f232012-06-15 13:27:53 +0000170
Stuart McCulloch285034f2012-06-12 12:41:16 +0000171 cerr.join();
172 cout.join();
173 if (reporter != null)
174 reporter.trace("stdout/stderr streams have finished");
Stuart McCullochbb014372012-06-07 21:57:32 +0000175 }
176 finally {
177 err.close();
178 }
179 }
180 finally {
181 out.close();
182 if (timer != null)
183 timer.cancel();
184 Runtime.getRuntime().removeShutdownHook(hook);
Stuart McCullochbb014372012-06-07 21:57:32 +0000185 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000186
Stuart McCulloch285034f2012-06-12 12:41:16 +0000187 byte exitValue = (byte) process.waitFor();
Stuart McCullochd4826102012-06-26 16:34:24 +0000188 finished.set(true);
189 if (rdInThread != null) {
190 if (in != null)
191 IO.close(in);
192 rdInThread.interrupt();
193 }
194
Stuart McCullochbb014372012-06-07 21:57:32 +0000195 if (reporter != null)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000196 reporter.trace("cmd %s executed with result=%d, result: %s/%s, timedout=%s", arguments, exitValue, stdout,
197 stderr, timedout);
Stuart McCullochbb014372012-06-07 21:57:32 +0000198
199 if (timedout)
200 return Integer.MIN_VALUE;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000201
Stuart McCullochbb014372012-06-07 21:57:32 +0000202 return exitValue;
203 }
204
205 public void add(String... args) {
206 for (String arg : args)
207 arguments.add(arg);
208 }
209
210 public void addAll(Collection<String> args) {
211 arguments.addAll(args);
212 }
213
214 public void setTimeout(long duration, TimeUnit unit) {
215 timeout = unit.toMillis(duration);
216 }
217
218 public void setTrace() {
219 this.trace = true;
220 }
221
222 public void setReporter(Reporter reporter) {
223 this.reporter = reporter;
224 }
225
226 public void setCwd(File dir) {
227 if (!dir.isDirectory())
228 throw new IllegalArgumentException("Working directory must be a directory: " + dir);
229
230 this.cwd = dir;
231 }
232
233 public void cancel() {
234 process.destroy();
235 }
236
237 class Collector extends Thread {
238 final InputStream in;
239 final Appendable sb;
240
241 Collector(InputStream inputStream, Appendable sb) {
242 this.in = inputStream;
243 this.sb = sb;
244 setDaemon(true);
245 }
246
Stuart McCulloch55d4dfe2012-08-07 10:57:21 +0000247 @Override
Stuart McCullochbb014372012-06-07 21:57:32 +0000248 public void run() {
249 try {
250 int c = in.read();
251 while (c >= 0) {
252 sb.append((char) c);
253 c = in.read();
254 }
255 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000256 catch (IOException e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000257 // We assume the socket is closed
258 }
259 catch (Exception e) {
260 try {
261 sb.append("\n**************************************\n");
262 sb.append(e.toString());
263 sb.append("\n**************************************\n");
264 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000265 catch (IOException e1) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000266 if (reporter != null) {
267 reporter.trace("cmd exec: %s", e);
268 }
269 }
270 }
271 }
272
Stuart McCullochbb014372012-06-07 21:57:32 +0000273 public Command var(String name, String value) {
274 variables.put(name, value);
275 return this;
276 }
277
278 public Command arg(String... args) {
279 add(args);
280 return this;
281 }
282
283 public Command full(String full) {
284 fullCommand = full;
285 return this;
286 }
287
288 public void inherit() {
289 ProcessBuilder pb = new ProcessBuilder();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000290 for (Entry<String,String> e : pb.environment().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000291 var(e.getKey(), e.getValue());
292 }
293 }
294
295 public String var(String name) {
296 return variables.get(name);
297 }
298
Stuart McCulloch55d4dfe2012-08-07 10:57:21 +0000299 @Override
Stuart McCullochbb014372012-06-07 21:57:32 +0000300 public String toString() {
301 StringBuilder sb = new StringBuilder();
302 String del = "";
303
304 for (String argument : arguments) {
305 sb.append(del);
306 sb.append(argument);
307 del = " ";
308 }
309 return sb.toString();
310 }
311}