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 }