package org.zstack.utils; import org.apache.commons.lang.time.StopWatch; import org.zstack.utils.logging.CLogger; import java.io.*; import java.lang.reflect.Field; import java.util.Arrays; import java.util.concurrent.TimeUnit; public class ShellUtils { private static final CLogger logger = Utils.getLogger(ShellUtils.class); public static class ShellException extends RuntimeException { public ShellException(String msg, Throwable t) { super(msg, t); } public ShellException(String msg) { super(msg); } public ShellException(Throwable t) { super(t); } } private static class StreamConsumer extends Thread { final InputStream in; final PrintWriter out; final boolean flush; StreamConsumer(InputStream in, PrintWriter out, boolean flushEveryWrite) { this.in = in; this.out = out; flush = flushEveryWrite; } @Override public void run() { BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(in)); String line; while ( (line = br.readLine()) != null) { out.println(line); if (flush) { out.flush(); } } } catch (Exception e) { logger.warn(e.getMessage(), e); } finally { try { if (br != null) { br.close(); } } catch (IOException e) { logger.warn(e.getMessage(), e); } } } } public static String runVerbose(String cmdstr) { return runVerbose(cmdstr, null); } public static String runVerbose(String cmdstr, String baseDir) { return doRun(cmdstr, baseDir, true); } public static String runVerbose(String cmdstr, String baseDir, boolean withSudo) { return doRun(cmdstr, baseDir, withSudo, true); } public static String run(String cmdstr, String baseDir) { return doRun(cmdstr, baseDir, false); } public static String run(String cmdstr, String baseDir, boolean withSudo) { return doRun(cmdstr, baseDir, withSudo, false); } public static String run(String cmdsr) { return run(cmdsr, null); } public static String run(String cmdsr, boolean withSudo) { return run(cmdsr, null, withSudo); } public static class ShellRunner { private String command; private String baseDir; private boolean verbose; private boolean suppressTraceLog; private String stderrFile; private String stdoutFile; private Process process; private boolean withSudo = true; public void terminate() { DebugUtils.Assert(process!=null, String.format("you can only can call terminate() after calling run()")); process.destroy(); } public boolean isWithSudo() { return withSudo; } public void setWithSudo(boolean withSudo) { this.withSudo = withSudo; } public String getStderrFile() { return stderrFile; } public void setStderrFile(String stderrFile) { this.stderrFile = stderrFile; } public String getStdoutFile() { return stdoutFile; } public void setStdoutFile(String stdoutFile) { this.stdoutFile = stdoutFile; } public String getCommand() { return command; } public void setCommand(String command) { this.command = command; } public String getBaseDir() { return baseDir; } public void setBaseDir(String baseDir) { this.baseDir = baseDir; } public boolean isVerbose() { return verbose; } public void setVerbose(boolean verbose) { this.verbose = verbose; } public boolean isSuppressTraceLog() { return suppressTraceLog; } public void setSuppressTraceLog(boolean suppressTraceLog) { this.suppressTraceLog = suppressTraceLog; } private static final int LOG_TO_FILE = 0; private static final int LOG_TO_SCREEN = 1; private static final int LOG_TO_STRING = 2; private int logStrategy(String fileToCheck) { if (fileToCheck != null) { return LOG_TO_FILE; } else if (verbose) { return LOG_TO_SCREEN; } else { return LOG_TO_STRING; } } private int stdoutLogStrategy() { return logStrategy(stdoutFile); } private int stderrLogStrategy() { return logStrategy(stderrFile); } public Integer obtainUnixPid() { try { Class clz = process.getClass(); if (!clz.getName().equals("java.lang.UNIXProcess")) { return null; } Field pidField = clz.getDeclaredField("pid"); pidField.setAccessible(true); Object value = pidField.get(process); return (Integer) value; } catch (Exception e) { throw new RuntimeException(e); } } public ShellResult run() { StopWatch watch = new StopWatch(); watch.start(); try { if (withSudo) { command = String.format("sudo %s", command); } ProcessBuilder pb = new ProcessBuilder(Arrays.asList("/bin/bash", "-c", command)); if (baseDir == null) { baseDir = System.getProperty("user.home"); } pb.directory(new File(baseDir)); process = pb.start(); if (!suppressTraceLog && logger.isTraceEnabled()) { logger.debug(String.format("exec shell command[%s]", command)); } Writer stdout; int stdoutLog = stdoutLogStrategy(); if (stdoutLog == LOG_TO_FILE) { stdout = new BufferedWriter(new FileWriter(stdoutFile)); } else if (stdoutLog == LOG_TO_SCREEN) { stdout = new BufferedWriter(new OutputStreamWriter(System.out)); } else { stdout = new StringWriter(); } Writer stderr; int stderrLog = stderrLogStrategy(); if (stderrLog == LOG_TO_FILE) { stderr = new BufferedWriter(new FileWriter(stderrFile)); } else if (stderrLog == LOG_TO_SCREEN) { stderr = new BufferedWriter(new OutputStreamWriter(System.err)); } else { stderr = new StringWriter(); } StreamConsumer stdoutConsumer = new StreamConsumer(process.getInputStream(), new PrintWriter(stdout, true), stdoutLog != LOG_TO_FILE); StreamConsumer stderrConsumer = new StreamConsumer(process.getErrorStream(), new PrintWriter(stderr, true), stderrLog != LOG_TO_FILE); stderrConsumer.start(); stdoutConsumer.start(); process.waitFor(); stderrConsumer.join(TimeUnit.SECONDS.toMillis(30)); stdoutConsumer.join(TimeUnit.SECONDS.toMillis(30)); ShellResult ret = new ShellResult(); ret.setCommand(command); ret.setRetCode(process.exitValue()); if (stderrLog == LOG_TO_STRING) { ret.setStderr(stderr.toString()); } else if (stderrLog == LOG_TO_FILE) { stderr.close(); } if (stdoutLog == LOG_TO_STRING) { ret.setStdout(stdout.toString()); } else if (stdoutLog == LOG_TO_FILE) { stdout.close(); } return ret; } catch (Exception e) { StringBuilder sb = new StringBuilder(); sb.append("Shell command failed:\n"); sb.append(command); throw new ShellException(sb.toString(), e); } finally { if (process != null) { process.destroy(); } watch.stop(); if (!suppressTraceLog && logger.isTraceEnabled()) { logger.trace(String.format("shell command[%s] costs %sms to finish", command, watch.getTime())); } } } } private static String doRun(String cmdstr, String baseDir, boolean isVerbose) { return doRun(cmdstr, baseDir, true, isVerbose); } private static String doRun(String cmdstr, String baseDir, boolean withRoot, boolean isVerbose) { ShellRunner runner = new ShellRunner(); runner.command = cmdstr; runner.baseDir = baseDir; runner.verbose = isVerbose; runner.withSudo = withRoot; ShellResult ret = runner.run(); ret.raiseExceptionIfFail(); StringBuilder sb = new StringBuilder(String.format("exec shell: %s\n", cmdstr)); // if isVerbose is set, there is nothing to read as stream has been closed if (!isVerbose) { sb.append(String.format("stdout: %s\n", ret.getStdout())); sb.append(String.format("stderr: %s\n", ret.getStderr())); } return sb.toString(); } public static ShellResult runAndReturn(String cmdstr, String baseDir, boolean withSudo) { return doRunAndReturn(cmdstr, baseDir, withSudo); } public static ShellResult runAndReturn(String cmdstr, String baseDir) { return doRunAndReturn(cmdstr, baseDir, true); } public static ShellResult runAndReturn(String cmdsr) { return runAndReturn(cmdsr, null); } public static ShellResult runAndReturn(String cmdsr, boolean withSudo) { return doRunAndReturn(cmdsr, null, withSudo); } private static ShellResult doRunAndReturn(String cmdstr, String baseDir, boolean withSudo) { ShellRunner runner = new ShellRunner(); runner.command = cmdstr; runner.baseDir = baseDir; runner.withSudo = withSudo; return runner.run(); } }