232 233 // Apply redirects. 234 pb.redirectInput(inputRedirect); 235 pb.redirectOutput(outputRedirect); 236 pb.redirectError(errorRedirect); 237 pb.redirectErrorStream(mergeError); 238 } 239 } 240 } 241 242 /** 243 * The Piper class is responsible for copying from an InputStream to an 244 * OutputStream without blocking the current thread. 245 */ 246 private static class Piper implements java.lang.Runnable { 247 // Stream to copy from. 248 private final InputStream input; 249 // Stream to copy to. 250 private final OutputStream output; 251 252 Piper(final InputStream input, final OutputStream output) { 253 this.input = input; 254 this.output = output; 255 } 256 257 /** 258 * start - start the Piper in a new daemon thread 259 */ 260 void start() { 261 Thread thread = new Thread(this, "$EXEC Piper"); 262 thread.setDaemon(true); 263 thread.start(); 264 } 265 266 /** 267 * run - thread action 268 */ 269 @Override 270 public void run() { 271 try { 272 // Buffer for copying. 273 byte[] b = new byte[BUFFER_SIZE]; 274 // Read from the InputStream until EOF. 275 int read; 276 while (-1 < (read = input.read(b, 0, b.length))) { 277 // Write available date to OutputStream. 278 output.write(b, 0, read); 279 } 280 } catch (Exception e) { 281 // Assume the worst. 282 throw new RuntimeException("Broken pipe", e); 283 } finally { 284 // Make sure the streams are closed. 285 try { 286 input.close(); 287 } catch (IOException e) { 288 // Don't care. 289 } 290 try { 291 output.close(); 292 } catch (IOException e) { 293 // Don't care. 294 } 295 } 296 } 297 298 // Exit thread. 299 } 300 301 // Process exit statuses. 302 static final int EXIT_SUCCESS = 0; 303 static final int EXIT_FAILURE = 1; 304 305 // Copy of environment variables used by all processes. 306 private Map<String, String> environment; 307 // Input string if provided on CommandExecutor call. 308 private String inputString; 309 // Output string if required from CommandExecutor call. 310 private String outputString; 311 // Error string if required from CommandExecutor call. 312 private String errorString; 313 // Last process exit code. 314 private int exitCode; 315 316 // Input stream if provided on CommandExecutor call. 317 private InputStream inputStream; 604 for (ProcessBuilder pb : processBuilders) { 605 try { 606 processes.add(pb.start()); 607 } catch (IOException ex) { 608 reportError("unknown.command", String.join(" ", pb.command())); 609 return; 610 } 611 } 612 613 // Clear processBuilders for next command. 614 processBuilders.clear(); 615 616 // Get first and last process. 617 final Process firstProcess = processes.get(0); 618 final Process lastProcess = processes.get(processes.size() - 1); 619 620 // Prepare for string based i/o if no redirection or provided streams. 621 ByteArrayOutputStream byteOutputStream = null; 622 ByteArrayOutputStream byteErrorStream = null; 623 624 // If input is not redirected. 625 if (inputIsPipe) { 626 // If inputStream other than System.in is provided. 627 if (inputStream != null) { 628 // Pipe inputStream to first process output stream. 629 new Piper(inputStream, firstProcess.getOutputStream()).start(); 630 } else { 631 // Otherwise assume an input string has been provided. 632 new Piper(new ByteArrayInputStream(inputString.getBytes()), firstProcess.getOutputStream()).start(); 633 } 634 } 635 636 // If output is not redirected. 637 if (outputIsPipe) { 638 // If outputStream other than System.out is provided. 639 if (outputStream != null ) { 640 // Pipe outputStream from last process input stream. 641 new Piper(lastProcess.getInputStream(), outputStream).start(); 642 } else { 643 // Otherwise assume an output string needs to be prepared. 644 byteOutputStream = new ByteArrayOutputStream(BUFFER_SIZE); 645 new Piper(lastProcess.getInputStream(), byteOutputStream).start(); 646 } 647 } 648 649 // If error is not redirected. 650 if (errorIsPipe) { 651 // If errorStream other than System.err is provided. 652 if (errorStream != null) { 653 new Piper(lastProcess.getErrorStream(), errorStream).start(); 654 } else { 655 // Otherwise assume an error string needs to be prepared. 656 byteErrorStream = new ByteArrayOutputStream(BUFFER_SIZE); 657 new Piper(lastProcess.getErrorStream(), byteErrorStream).start(); 658 } 659 } 660 661 // Pipe commands in between. 662 for (int i = 0, n = processes.size() - 1; i < n; i++) { 663 final Process prev = processes.get(i); 664 final Process next = processes.get(i + 1); 665 new Piper(prev.getInputStream(), next.getOutputStream()).start(); 666 } 667 668 // Wind up processes. 669 try { 670 // Get the user specified timeout. 671 long timeout = envVarLongValue("JJS_TIMEOUT"); 672 673 // If user specified timeout (milliseconds.) 674 if (timeout != 0) { 675 // Wait for last process, with timeout. 676 if (lastProcess.waitFor(timeout, TimeUnit.MILLISECONDS)) { 677 // Get exit code of last process. 678 exitCode = lastProcess.exitValue(); 679 } else { 680 reportError("timeout", Long.toString(timeout)); 681 } 682 } else { 683 // Wait for last process and get exit code. 684 exitCode = lastProcess.waitFor(); 685 } 686 687 // Accumulate the output and error streams. 688 outputString += byteOutputStream != null ? byteOutputStream.toString() : ""; 689 errorString += byteErrorStream != null ? byteErrorStream.toString() : ""; 690 } catch (InterruptedException ex) { 691 // Kill any living processes. 692 processes.stream().forEach(p -> { 693 if (p.isAlive()) { 694 p.destroy(); 695 } 696 697 // Get the first error code. 698 exitCode = exitCode == 0 ? p.exitValue() : exitCode; 699 }); 700 } 701 702 // If we got a non-zero exit code then possibly throw an exception. 703 if (exitCode != 0 && envVarBooleanValue("JJS_THROW_ON_EXIT")) { 704 throw rangeError("exec.returned.non.zero", ScriptRuntime.safeToString(exitCode)); | 232 233 // Apply redirects. 234 pb.redirectInput(inputRedirect); 235 pb.redirectOutput(outputRedirect); 236 pb.redirectError(errorRedirect); 237 pb.redirectErrorStream(mergeError); 238 } 239 } 240 } 241 242 /** 243 * The Piper class is responsible for copying from an InputStream to an 244 * OutputStream without blocking the current thread. 245 */ 246 private static class Piper implements java.lang.Runnable { 247 // Stream to copy from. 248 private final InputStream input; 249 // Stream to copy to. 250 private final OutputStream output; 251 252 private final Thread thread; 253 254 Piper(final InputStream input, final OutputStream output) { 255 this.input = input; 256 this.output = output; 257 this.thread = new Thread(this, "$EXEC Piper"); 258 } 259 260 /** 261 * start - start the Piper in a new daemon thread 262 * @return this Piper 263 */ 264 Piper start() { 265 thread.setDaemon(true); 266 thread.start(); 267 return this; 268 } 269 270 /** 271 * run - thread action 272 */ 273 @Override 274 public void run() { 275 try { 276 // Buffer for copying. 277 byte[] b = new byte[BUFFER_SIZE]; 278 // Read from the InputStream until EOF. 279 int read; 280 while (-1 < (read = input.read(b, 0, b.length))) { 281 // Write available date to OutputStream. 282 output.write(b, 0, read); 283 } 284 } catch (Exception e) { 285 // Assume the worst. 286 throw new RuntimeException("Broken pipe", e); 287 } finally { 288 // Make sure the streams are closed. 289 try { 290 input.close(); 291 } catch (IOException e) { 292 // Don't care. 293 } 294 try { 295 output.close(); 296 } catch (IOException e) { 297 // Don't care. 298 } 299 } 300 } 301 302 public void join() throws InterruptedException { 303 thread.join(); 304 } 305 306 // Exit thread. 307 } 308 309 // Process exit statuses. 310 static final int EXIT_SUCCESS = 0; 311 static final int EXIT_FAILURE = 1; 312 313 // Copy of environment variables used by all processes. 314 private Map<String, String> environment; 315 // Input string if provided on CommandExecutor call. 316 private String inputString; 317 // Output string if required from CommandExecutor call. 318 private String outputString; 319 // Error string if required from CommandExecutor call. 320 private String errorString; 321 // Last process exit code. 322 private int exitCode; 323 324 // Input stream if provided on CommandExecutor call. 325 private InputStream inputStream; 612 for (ProcessBuilder pb : processBuilders) { 613 try { 614 processes.add(pb.start()); 615 } catch (IOException ex) { 616 reportError("unknown.command", String.join(" ", pb.command())); 617 return; 618 } 619 } 620 621 // Clear processBuilders for next command. 622 processBuilders.clear(); 623 624 // Get first and last process. 625 final Process firstProcess = processes.get(0); 626 final Process lastProcess = processes.get(processes.size() - 1); 627 628 // Prepare for string based i/o if no redirection or provided streams. 629 ByteArrayOutputStream byteOutputStream = null; 630 ByteArrayOutputStream byteErrorStream = null; 631 632 final List<Piper> piperThreads = new ArrayList<>(); 633 634 // If input is not redirected. 635 if (inputIsPipe) { 636 // If inputStream other than System.in is provided. 637 if (inputStream != null) { 638 // Pipe inputStream to first process output stream. 639 piperThreads.add(new Piper(inputStream, firstProcess.getOutputStream()).start()); 640 } else { 641 // Otherwise assume an input string has been provided. 642 piperThreads.add(new Piper(new ByteArrayInputStream(inputString.getBytes()), firstProcess.getOutputStream()).start()); 643 } 644 } 645 646 // If output is not redirected. 647 if (outputIsPipe) { 648 // If outputStream other than System.out is provided. 649 if (outputStream != null ) { 650 // Pipe outputStream from last process input stream. 651 piperThreads.add(new Piper(lastProcess.getInputStream(), outputStream).start()); 652 } else { 653 // Otherwise assume an output string needs to be prepared. 654 byteOutputStream = new ByteArrayOutputStream(BUFFER_SIZE); 655 piperThreads.add(new Piper(lastProcess.getInputStream(), byteOutputStream).start()); 656 } 657 } 658 659 // If error is not redirected. 660 if (errorIsPipe) { 661 // If errorStream other than System.err is provided. 662 if (errorStream != null) { 663 piperThreads.add(new Piper(lastProcess.getErrorStream(), errorStream).start()); 664 } else { 665 // Otherwise assume an error string needs to be prepared. 666 byteErrorStream = new ByteArrayOutputStream(BUFFER_SIZE); 667 piperThreads.add(new Piper(lastProcess.getErrorStream(), byteErrorStream).start()); 668 } 669 } 670 671 // Pipe commands in between. 672 for (int i = 0, n = processes.size() - 1; i < n; i++) { 673 final Process prev = processes.get(i); 674 final Process next = processes.get(i + 1); 675 piperThreads.add(new Piper(prev.getInputStream(), next.getOutputStream()).start()); 676 } 677 678 // Wind up processes. 679 try { 680 // Get the user specified timeout. 681 final long timeout = envVarLongValue("JJS_TIMEOUT"); 682 683 // If user specified timeout (milliseconds.) 684 if (timeout != 0) { 685 // Wait for last process, with timeout. 686 if (lastProcess.waitFor(timeout, TimeUnit.MILLISECONDS)) { 687 // Get exit code of last process. 688 exitCode = lastProcess.exitValue(); 689 } else { 690 reportError("timeout", Long.toString(timeout)); 691 } 692 } else { 693 // Wait for last process and get exit code. 694 exitCode = lastProcess.waitFor(); 695 } 696 // Wait for all piper threads to terminate 697 for (final Piper piper : piperThreads) { 698 piper.join(); 699 } 700 701 // Accumulate the output and error streams. 702 outputString += byteOutputStream != null ? byteOutputStream.toString() : ""; 703 errorString += byteErrorStream != null ? byteErrorStream.toString() : ""; 704 } catch (InterruptedException ex) { 705 // Kill any living processes. 706 processes.stream().forEach(p -> { 707 if (p.isAlive()) { 708 p.destroy(); 709 } 710 711 // Get the first error code. 712 exitCode = exitCode == 0 ? p.exitValue() : exitCode; 713 }); 714 } 715 716 // If we got a non-zero exit code then possibly throw an exception. 717 if (exitCode != 0 && envVarBooleanValue("JJS_THROW_ON_EXIT")) { 718 throw rangeError("exec.returned.non.zero", ScriptRuntime.safeToString(exitCode)); |