package io.eguan.utils;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class to run commands.
*
* @author oodrive
* @author ebredzinski
* @author llambert
*/
public final class RunCmdUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(RunCmdUtils.class);
// Use the default Charset for console input/ouput
private static final Charset CONSOLE_CHARSET = Charset.defaultCharset();
public static class RunningCmd {
private final AtomicReference<Process> p;
private final String[] cmdArray;
private final Object peer;
private RunningCmd(final String[] cmdArray, final Object peer) {
this.p = new AtomicReference<>();
this.cmdArray = Arrays.copyOf(cmdArray, cmdArray.length); // defensive copy of input array
this.peer = peer;
}
/**
* Runs the command. Throws an exception if the command fails (return code != 0).
*
* @throws IOException
*/
public final void run() throws IOException {
final Runtime r = Runtime.getRuntime();
p.set(r.exec(cmdArray));
doWait(p.get(), cmdArray, peer, true, false);
}
/**
* Kill a running process.
*/
public final void kill() {
final Process process = p.get();
if (process != null) {
process.destroy();
}
}
}
/**
* No instance.
*/
private RunCmdUtils() {
throw new AssertionError();
}
/**
* Create a running command.
*
* @param cmdArray
* command and arguments.
* @param peer
* object on which the command is run.
* @return the Running command
*/
public static final RunningCmd newRunningCmd(final String[] cmdArray, final Object peer) {
return new RunningCmd(cmdArray, peer);
}
/**
* Runs the command. Throws an exception if the command fails (return code != 0).
*
* @param cmdArray
* command and arguments.
* @param peer
* object on which the command is run.
* @return the output of the command
*
* @throws IOException
* if the command does not exist or if it fails
*/
public static final void runCmd(final String[] cmdArray, final Object peer) throws IOException {
runCmd(cmdArray, peer, false, false);
}
/**
* Runs the command. Throws an exception if the command fails (return code != 0).
*
* @param cmdArray
* command and arguments.
* @param peer
* object on which the command is run.
* @return the output of the command
*
* @throws IOException
* if the command does not exist or if it fails
*/
public static final StringBuilder runCmd(final String[] cmdArray, final Object peer, final boolean getOutput)
throws IOException {
return runCmd(cmdArray, peer, false, getOutput);
}
/**
* Runs the command. Throws an exception if the command fails (return code != 0).
*
* @param cmdArray
* command and arguments.
* @param peer
* object on which the command is run.
* @param displayErr
* if <code>true</code>, display the command stderr in <code>System.err</code>.
* @return the output of the command
* @throws IOException
* if the command does not exist or if it fails
*/
public static final StringBuilder runCmd(final String[] cmdArray, final Object peer, final boolean displayErr,
final boolean getOutput) throws IOException {
final Runtime r = Runtime.getRuntime();
final Process p = r.exec(cmdArray);
return doWait(p, cmdArray, peer, displayErr, getOutput);
}
/**
* Runs the command. Throws an exception if the command fails (return code != 0). This version passes some input to
* the command.
*
* @param cmdArray
* command and arguments.
* @param peer
* object on which the command is run.
* @param input
* text written in the command input stream.
* @param addEnv
* additional environment variables or <code>null</code>. The array is a list of key/value pairs.
* @throws IOException
* if the command does not exist or if it fails
*/
public static void runCmd(final String[] cmdArray, final Object peer, final String input, final String[] addEnv)
throws IOException {
runCmd(cmdArray, peer, input, addEnv, false);
}
/**
* Runs the command. Throws an exception if the command fails (return code != 0). This version passes some input to
* the command.
*
* @param cmdArray
* command and arguments.
* @param peer
* object on which the command is run.
* @param input
* text written in the command input stream.
* @param addEnv
* additional environment variables or <code>null</code>. The array is a list of key/value pairs.
* @param displayErr
* if <code>true</code>, display the command stderr in <code>System.err</code>.
* @throws IOException
*/
public static void runCmd(final String[] cmdArray, final Object peer, final String input, final String[] addEnv,
final boolean displayErr) throws IOException {
final ProcessBuilder processBuilder = new ProcessBuilder(cmdArray);
if (addEnv != null) {
// Add environment variables
final int count = addEnv.length;
if ((count % 2) != 0) {
throw new IllegalArgumentException("addEnv length=" + addEnv.length);
}
final Map<String, String> env = processBuilder.environment();
for (int i = 0; i < count; i += 2) {
env.put(addEnv[i], addEnv[i + 1]);
}
}
final Process p = processBuilder.start();
try {
final PrintStream pStdin = new PrintStream(p.getOutputStream(), false, CONSOLE_CHARSET.name());
try {
// Verbose mode
if (displayErr) {
displayErr(p);
}
// Write input
pStdin.print(input);
pStdin.flush();
final int exitValue = p.waitFor();
if (exitValue != 0) {
throw new RunCmdErrorException(Arrays.toString(cmdArray) + " failed on " + peer + ", status="
+ exitValue, exitValue);
}
}
finally {
pStdin.close();
}
}
catch (final InterruptedException e) {
throw new IOException(cmdArray[0] + " interrupted", e);
}
}
/**
* Gets the output of the executed command.
*
* @param process
* the given process
* @param builder
* the builder which will contain the result
*
* @return the Thread which reads the output
*/
private static final Thread getOutput(final Process process, final StringBuilder builder) {
final InputStream is = process.getInputStream();
final Thread outputPrinter = new Thread(new Runnable() {
@Override
public final void run() {
final byte[] buf = new byte[1024];
try {
int readLen;
while ((readLen = is.read(buf)) >= 0) {
builder.append(new String(buf, 0, readLen, CONSOLE_CHARSET).trim());
}
}
catch (final IOException e) {
LOGGER.warn("Exception ignored", e);
}
finally {
try {
is.close();
}
catch (final IOException e) {
// Ignored
}
}
}
});
outputPrinter.start();
return outputPrinter;
}
/**
* Display the stderr of the given process.
*
* @param process
*/
private static final Thread displayErr(final Process process) {
final InputStream is = process.getErrorStream();
final Thread stderrPrinter = new Thread(new Runnable() {
@Override
public final void run() {
final byte[] buf = new byte[1024];
int readLen;
try {
while ((readLen = is.read(buf)) >= 0) {
System.err.write(buf, 0, readLen);
}
}
catch (final IOException e) {
LOGGER.warn("Exception ignored", e);
}
finally {
try {
is.close();
}
catch (final IOException e) {
// Ignored
}
}
}
});
stderrPrinter.start();
return stderrPrinter;
}
private final static StringBuilder doWait(final Process p, final String[] cmdArray, final Object peer,
final boolean displayErr, final boolean getOutput) throws IOException {
try {
// Verbose mode
Thread errorThread = null;
if (displayErr) {
errorThread = displayErr(p);
}
StringBuilder strBuilder = null;
Thread outputThread = null;
if (getOutput) {
strBuilder = new StringBuilder();
outputThread = getOutput(p, strBuilder);
}
final int exitValue = p.waitFor();
if (errorThread != null) {
errorThread.join();
}
if (outputThread != null) {
outputThread.join();
}
if (exitValue != 0) {
throw new RunCmdErrorException(Arrays.toString(cmdArray) + " failed on " + peer + ", status="
+ exitValue, exitValue);
}
else {
return strBuilder;
}
}
catch (final InterruptedException e) {
throw new IOException(cmdArray[0] + " interrupted", e);
}
}
}