1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.incubator.jpackage.internal; 26 27 import java.io.BufferedReader; 28 import java.io.IOException; 29 import java.io.InputStreamReader; 30 import java.util.List; 31 import java.util.function.Consumer; 32 import java.util.function.Supplier; 33 import java.util.stream.Collectors; 34 import java.util.stream.Stream; 35 36 final public class Executor { 37 38 Executor() { 39 } 40 41 Executor setOutputConsumer(Consumer<Stream<String>> v) { 42 outputConsumer = v; 43 return this; 44 } 45 46 Executor saveOutput(boolean v) { 47 saveOutput = v; 48 return this; 49 } 50 51 Executor setProcessBuilder(ProcessBuilder v) { 52 pb = v; 53 return this; 54 } 55 56 Executor setCommandLine(String... cmdline) { 57 return setProcessBuilder(new ProcessBuilder(cmdline)); 58 } 59 60 List<String> getOutput() { 61 return output; 62 } 63 64 Executor executeExpectSuccess() throws IOException { 65 int ret = execute(); 66 if (0 != ret) { 67 throw new IOException( 68 String.format("Command %s exited with %d code", 69 createLogMessage(pb), ret)); 70 } 71 return this; 72 } 73 74 int execute() throws IOException { 75 output = null; 76 77 boolean needProcessOutput = outputConsumer != null || Log.isVerbose() || saveOutput; 78 if (needProcessOutput) { 79 pb.redirectErrorStream(true); 80 } else { 81 // We are not going to read process output, so need to notify 82 // ProcessBuilder about this. Otherwise some processes might just 83 // hang up (`ldconfig -p`). 84 pb.redirectError(ProcessBuilder.Redirect.DISCARD); 85 pb.redirectOutput(ProcessBuilder.Redirect.DISCARD); 86 } 87 88 Log.verbose(String.format("Running %s", createLogMessage(pb))); 89 Process p = pb.start(); 90 91 if (needProcessOutput) { 92 try (var br = new BufferedReader(new InputStreamReader( 93 p.getInputStream()))) { 94 final List<String> savedOutput; 95 // Need to save output if explicitely requested (saveOutput=true) or 96 // if will be used used by multiple consumers 97 if ((outputConsumer != null && Log.isVerbose()) || saveOutput) { 98 savedOutput = br.lines().collect(Collectors.toList()); 99 if (saveOutput) { 100 output = savedOutput; 101 } 102 } else { 103 savedOutput = null; 104 } 105 106 Supplier<Stream<String>> outputStream = () -> { 107 if (savedOutput != null) { 108 return savedOutput.stream(); 109 } 110 return br.lines(); 111 }; 112 113 if (Log.isVerbose()) { 114 outputStream.get().forEach(Log::verbose); 115 } 116 117 if (outputConsumer != null) { 118 outputConsumer.accept(outputStream.get()); 119 } 120 121 if (savedOutput == null) { 122 // For some processes on Linux if the output stream 123 // of the process is opened but not consumed, the process 124 // would exit with code 141. 125 // It turned out that reading just a single line of process 126 // output fixes the problem, but let's process 127 // all of the output, just in case. 128 br.lines().forEach(x -> {}); 129 } 130 } 131 } 132 133 try { 134 return p.waitFor(); 135 } catch (InterruptedException ex) { 136 Log.verbose(ex); 137 throw new RuntimeException(ex); 138 } 139 } 140 141 static Executor of(String... cmdline) { 142 return new Executor().setCommandLine(cmdline); 143 } 144 145 static Executor of(ProcessBuilder pb) { 146 return new Executor().setProcessBuilder(pb); 147 } 148 149 private static String createLogMessage(ProcessBuilder pb) { 150 StringBuilder sb = new StringBuilder(); 151 sb.append(String.format("%s", pb.command())); 152 if (pb.directory() != null) { 153 sb.append(String.format("in %s", pb.directory().getAbsolutePath())); 154 } 155 return sb.toString(); 156 } 157 158 private ProcessBuilder pb; 159 private boolean saveOutput; 160 private List<String> output; 161 private Consumer<Stream<String>> outputConsumer; 162 }