PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Shell.java

https://gitlab.com/xiaoliuliu2050/hadoop
Java | 859 lines | 512 code | 109 blank | 238 comment | 68 complexity | 1b5db67c8c9b1059ebea601ab8fc3d18 MD5 | raw file
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.apache.hadoop.util;
  19. import java.io.BufferedReader;
  20. import java.io.File;
  21. import java.io.IOException;
  22. import java.io.InputStreamReader;
  23. import java.io.InputStream;
  24. import java.nio.charset.Charset;
  25. import java.util.Arrays;
  26. import java.util.Map;
  27. import java.util.Timer;
  28. import java.util.TimerTask;
  29. import java.util.concurrent.atomic.AtomicBoolean;
  30. import org.apache.commons.logging.Log;
  31. import org.apache.commons.logging.LogFactory;
  32. import org.apache.hadoop.classification.InterfaceAudience;
  33. import org.apache.hadoop.classification.InterfaceStability;
  34. /**
  35. * A base class for running a Unix command.
  36. *
  37. * <code>Shell</code> can be used to run unix commands like <code>du</code> or
  38. * <code>df</code>. It also offers facilities to gate commands by
  39. * time-intervals.
  40. */
  41. @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
  42. @InterfaceStability.Unstable
  43. abstract public class Shell {
  44. public static final Log LOG = LogFactory.getLog(Shell.class);
  45. private static boolean IS_JAVA7_OR_ABOVE =
  46. System.getProperty("java.version").substring(0, 3).compareTo("1.7") >= 0;
  47. public static boolean isJava7OrAbove() {
  48. return IS_JAVA7_OR_ABOVE;
  49. }
  50. /**
  51. * Maximum command line length in Windows
  52. * KB830473 documents this as 8191
  53. */
  54. public static final int WINDOWS_MAX_SHELL_LENGHT = 8191;
  55. /**
  56. * Checks if a given command (String[]) fits in the Windows maximum command line length
  57. * Note that the input is expected to already include space delimiters, no extra count
  58. * will be added for delimiters.
  59. *
  60. * @param commands command parts, including any space delimiters
  61. */
  62. public static void checkWindowsCommandLineLength(String...commands)
  63. throws IOException {
  64. int len = 0;
  65. for (String s: commands) {
  66. len += s.length();
  67. }
  68. if (len > WINDOWS_MAX_SHELL_LENGHT) {
  69. throw new IOException(String.format(
  70. "The command line has a length of %d exceeds maximum allowed length of %d. " +
  71. "Command starts with: %s",
  72. len, WINDOWS_MAX_SHELL_LENGHT,
  73. StringUtils.join("", commands).substring(0, 100)));
  74. }
  75. }
  76. /** a Unix command to get the current user's name */
  77. public final static String USER_NAME_COMMAND = "whoami";
  78. /** Windows CreateProcess synchronization object */
  79. public static final Object WindowsProcessLaunchLock = new Object();
  80. // OSType detection
  81. public enum OSType {
  82. OS_TYPE_LINUX,
  83. OS_TYPE_WIN,
  84. OS_TYPE_SOLARIS,
  85. OS_TYPE_MAC,
  86. OS_TYPE_FREEBSD,
  87. OS_TYPE_OTHER
  88. }
  89. public static final OSType osType = getOSType();
  90. static private OSType getOSType() {
  91. String osName = System.getProperty("os.name");
  92. if (osName.startsWith("Windows")) {
  93. return OSType.OS_TYPE_WIN;
  94. } else if (osName.contains("SunOS") || osName.contains("Solaris")) {
  95. return OSType.OS_TYPE_SOLARIS;
  96. } else if (osName.contains("Mac")) {
  97. return OSType.OS_TYPE_MAC;
  98. } else if (osName.contains("FreeBSD")) {
  99. return OSType.OS_TYPE_FREEBSD;
  100. } else if (osName.startsWith("Linux")) {
  101. return OSType.OS_TYPE_LINUX;
  102. } else {
  103. // Some other form of Unix
  104. return OSType.OS_TYPE_OTHER;
  105. }
  106. }
  107. // Helper static vars for each platform
  108. public static final boolean WINDOWS = (osType == OSType.OS_TYPE_WIN);
  109. public static final boolean SOLARIS = (osType == OSType.OS_TYPE_SOLARIS);
  110. public static final boolean MAC = (osType == OSType.OS_TYPE_MAC);
  111. public static final boolean FREEBSD = (osType == OSType.OS_TYPE_FREEBSD);
  112. public static final boolean LINUX = (osType == OSType.OS_TYPE_LINUX);
  113. public static final boolean OTHER = (osType == OSType.OS_TYPE_OTHER);
  114. public static final boolean PPC_64
  115. = System.getProperties().getProperty("os.arch").contains("ppc64");
  116. /** a Unix command to get the current user's groups list */
  117. public static String[] getGroupsCommand() {
  118. return (WINDOWS)? new String[]{"cmd", "/c", "groups"}
  119. : new String[]{"bash", "-c", "groups"};
  120. }
  121. /**
  122. * a Unix command to get a given user's groups list.
  123. * If the OS is not WINDOWS, the command will get the user's primary group
  124. * first and finally get the groups list which includes the primary group.
  125. * i.e. the user's primary group will be included twice.
  126. */
  127. public static String[] getGroupsForUserCommand(final String user) {
  128. //'groups username' command return is non-consistent across different unixes
  129. return (WINDOWS)? new String[] { WINUTILS, "groups", "-F", "\"" + user + "\""}
  130. : new String [] {"bash", "-c", "id -gn " + user
  131. + "&& id -Gn " + user};
  132. }
  133. /** a Unix command to get a given netgroup's user list */
  134. public static String[] getUsersForNetgroupCommand(final String netgroup) {
  135. //'groups username' command return is non-consistent across different unixes
  136. return (WINDOWS)? new String [] {"cmd", "/c", "getent netgroup " + netgroup}
  137. : new String [] {"bash", "-c", "getent netgroup " + netgroup};
  138. }
  139. /** Return a command to get permission information. */
  140. public static String[] getGetPermissionCommand() {
  141. return (WINDOWS) ? new String[] { WINUTILS, "ls", "-F" }
  142. : new String[] { "/bin/ls", "-ld" };
  143. }
  144. /** Return a command to set permission */
  145. public static String[] getSetPermissionCommand(String perm, boolean recursive) {
  146. if (recursive) {
  147. return (WINDOWS) ? new String[] { WINUTILS, "chmod", "-R", perm }
  148. : new String[] { "chmod", "-R", perm };
  149. } else {
  150. return (WINDOWS) ? new String[] { WINUTILS, "chmod", perm }
  151. : new String[] { "chmod", perm };
  152. }
  153. }
  154. /**
  155. * Return a command to set permission for specific file.
  156. *
  157. * @param perm String permission to set
  158. * @param recursive boolean true to apply to all sub-directories recursively
  159. * @param file String file to set
  160. * @return String[] containing command and arguments
  161. */
  162. public static String[] getSetPermissionCommand(String perm, boolean recursive,
  163. String file) {
  164. String[] baseCmd = getSetPermissionCommand(perm, recursive);
  165. String[] cmdWithFile = Arrays.copyOf(baseCmd, baseCmd.length + 1);
  166. cmdWithFile[cmdWithFile.length - 1] = file;
  167. return cmdWithFile;
  168. }
  169. /** Return a command to set owner */
  170. public static String[] getSetOwnerCommand(String owner) {
  171. return (WINDOWS) ? new String[] { WINUTILS, "chown", "\"" + owner + "\"" }
  172. : new String[] { "chown", owner };
  173. }
  174. /** Return a command to create symbolic links */
  175. public static String[] getSymlinkCommand(String target, String link) {
  176. return WINDOWS ? new String[] { WINUTILS, "symlink", link, target }
  177. : new String[] { "ln", "-s", target, link };
  178. }
  179. /** Return a command to read the target of the a symbolic link*/
  180. public static String[] getReadlinkCommand(String link) {
  181. return WINDOWS ? new String[] { WINUTILS, "readlink", link }
  182. : new String[] { "readlink", link };
  183. }
  184. /** Return a command for determining if process with specified pid is alive. */
  185. public static String[] getCheckProcessIsAliveCommand(String pid) {
  186. return Shell.WINDOWS ?
  187. new String[] { Shell.WINUTILS, "task", "isAlive", pid } :
  188. new String[] { "kill", "-0", isSetsidAvailable ? "-" + pid : pid };
  189. }
  190. /** Return a command to send a signal to a given pid */
  191. public static String[] getSignalKillCommand(int code, String pid) {
  192. return Shell.WINDOWS ? new String[] { Shell.WINUTILS, "task", "kill", pid } :
  193. new String[] { "kill", "-" + code, isSetsidAvailable ? "-" + pid : pid };
  194. }
  195. /** Return a regular expression string that match environment variables */
  196. public static String getEnvironmentVariableRegex() {
  197. return (WINDOWS) ? "%([A-Za-z_][A-Za-z0-9_]*?)%" :
  198. "\\$([A-Za-z_][A-Za-z0-9_]*)";
  199. }
  200. /**
  201. * Returns a File referencing a script with the given basename, inside the
  202. * given parent directory. The file extension is inferred by platform: ".cmd"
  203. * on Windows, or ".sh" otherwise.
  204. *
  205. * @param parent File parent directory
  206. * @param basename String script file basename
  207. * @return File referencing the script in the directory
  208. */
  209. public static File appendScriptExtension(File parent, String basename) {
  210. return new File(parent, appendScriptExtension(basename));
  211. }
  212. /**
  213. * Returns a script file name with the given basename. The file extension is
  214. * inferred by platform: ".cmd" on Windows, or ".sh" otherwise.
  215. *
  216. * @param basename String script file basename
  217. * @return String script file name
  218. */
  219. public static String appendScriptExtension(String basename) {
  220. return basename + (WINDOWS ? ".cmd" : ".sh");
  221. }
  222. /**
  223. * Returns a command to run the given script. The script interpreter is
  224. * inferred by platform: cmd on Windows or bash otherwise.
  225. *
  226. * @param script File script to run
  227. * @return String[] command to run the script
  228. */
  229. public static String[] getRunScriptCommand(File script) {
  230. String absolutePath = script.getAbsolutePath();
  231. return WINDOWS ? new String[] { "cmd", "/c", absolutePath } :
  232. new String[] { "/bin/bash", absolutePath };
  233. }
  234. /** a Unix command to set permission */
  235. public static final String SET_PERMISSION_COMMAND = "chmod";
  236. /** a Unix command to set owner */
  237. public static final String SET_OWNER_COMMAND = "chown";
  238. /** a Unix command to set the change user's groups list */
  239. public static final String SET_GROUP_COMMAND = "chgrp";
  240. /** a Unix command to create a link */
  241. public static final String LINK_COMMAND = "ln";
  242. /** a Unix command to get a link target */
  243. public static final String READ_LINK_COMMAND = "readlink";
  244. /**Time after which the executing script would be timedout*/
  245. protected long timeOutInterval = 0L;
  246. /** If or not script timed out*/
  247. private AtomicBoolean timedOut;
  248. /** Centralized logic to discover and validate the sanity of the Hadoop
  249. * home directory. Returns either NULL or a directory that exists and
  250. * was specified via either -Dhadoop.home.dir or the HADOOP_HOME ENV
  251. * variable. This does a lot of work so it should only be called
  252. * privately for initialization once per process.
  253. **/
  254. private static String checkHadoopHome() {
  255. // first check the Dflag hadoop.home.dir with JVM scope
  256. String home = System.getProperty("hadoop.home.dir");
  257. // fall back to the system/user-global env variable
  258. if (home == null) {
  259. home = System.getenv("HADOOP_HOME");
  260. }
  261. try {
  262. // couldn't find either setting for hadoop's home directory
  263. if (home == null) {
  264. throw new IOException("HADOOP_HOME or hadoop.home.dir are not set.");
  265. }
  266. if (home.startsWith("\"") && home.endsWith("\"")) {
  267. home = home.substring(1, home.length()-1);
  268. }
  269. // check that the home setting is actually a directory that exists
  270. File homedir = new File(home);
  271. if (!homedir.isAbsolute() || !homedir.exists() || !homedir.isDirectory()) {
  272. throw new IOException("Hadoop home directory " + homedir
  273. + " does not exist, is not a directory, or is not an absolute path.");
  274. }
  275. home = homedir.getCanonicalPath();
  276. } catch (IOException ioe) {
  277. if (LOG.isDebugEnabled()) {
  278. LOG.debug("Failed to detect a valid hadoop home directory", ioe);
  279. }
  280. home = null;
  281. }
  282. return home;
  283. }
  284. private static String HADOOP_HOME_DIR = checkHadoopHome();
  285. // Public getter, throws an exception if HADOOP_HOME failed validation
  286. // checks and is being referenced downstream.
  287. public static final String getHadoopHome() throws IOException {
  288. if (HADOOP_HOME_DIR == null) {
  289. throw new IOException("Misconfigured HADOOP_HOME cannot be referenced.");
  290. }
  291. return HADOOP_HOME_DIR;
  292. }
  293. /** fully qualify the path to a binary that should be in a known hadoop
  294. * bin location. This is primarily useful for disambiguating call-outs
  295. * to executable sub-components of Hadoop to avoid clashes with other
  296. * executables that may be in the path. Caveat: this call doesn't
  297. * just format the path to the bin directory. It also checks for file
  298. * existence of the composed path. The output of this call should be
  299. * cached by callers.
  300. * */
  301. public static final String getQualifiedBinPath(String executable)
  302. throws IOException {
  303. // construct hadoop bin path to the specified executable
  304. String fullExeName = HADOOP_HOME_DIR + File.separator + "bin"
  305. + File.separator + executable;
  306. File exeFile = new File(fullExeName);
  307. if (!exeFile.exists()) {
  308. throw new IOException("Could not locate executable " + fullExeName
  309. + " in the Hadoop binaries.");
  310. }
  311. return exeFile.getCanonicalPath();
  312. }
  313. /** a Windows utility to emulate Unix commands */
  314. public static final String WINUTILS = getWinUtilsPath();
  315. public static final String getWinUtilsPath() {
  316. String winUtilsPath = null;
  317. try {
  318. if (WINDOWS) {
  319. winUtilsPath = getQualifiedBinPath("winutils.exe");
  320. }
  321. } catch (IOException ioe) {
  322. LOG.error("Failed to locate the winutils binary in the hadoop binary path",
  323. ioe);
  324. }
  325. return winUtilsPath;
  326. }
  327. public static final boolean isSetsidAvailable = isSetsidSupported();
  328. private static boolean isSetsidSupported() {
  329. if (Shell.WINDOWS) {
  330. return false;
  331. }
  332. ShellCommandExecutor shexec = null;
  333. boolean setsidSupported = true;
  334. try {
  335. String[] args = {"setsid", "bash", "-c", "echo $$"};
  336. shexec = new ShellCommandExecutor(args);
  337. shexec.execute();
  338. } catch (IOException ioe) {
  339. LOG.debug("setsid is not available on this machine. So not using it.");
  340. setsidSupported = false;
  341. } finally { // handle the exit code
  342. if (LOG.isDebugEnabled()) {
  343. LOG.debug("setsid exited with exit code "
  344. + (shexec != null ? shexec.getExitCode() : "(null executor)"));
  345. }
  346. }
  347. return setsidSupported;
  348. }
  349. /** Token separator regex used to parse Shell tool outputs */
  350. public static final String TOKEN_SEPARATOR_REGEX
  351. = WINDOWS ? "[|\n\r]" : "[ \t\n\r\f]";
  352. private long interval; // refresh interval in msec
  353. private long lastTime; // last time the command was performed
  354. final private boolean redirectErrorStream; // merge stdout and stderr
  355. private Map<String, String> environment; // env for the command execution
  356. private File dir;
  357. private Process process; // sub process used to execute the command
  358. private int exitCode;
  359. /**If or not script finished executing*/
  360. private volatile AtomicBoolean completed;
  361. public Shell() {
  362. this(0L);
  363. }
  364. public Shell(long interval) {
  365. this(interval, false);
  366. }
  367. /**
  368. * @param interval the minimum duration to wait before re-executing the
  369. * command.
  370. */
  371. public Shell(long interval, boolean redirectErrorStream) {
  372. this.interval = interval;
  373. this.lastTime = (interval<0) ? 0 : -interval;
  374. this.redirectErrorStream = redirectErrorStream;
  375. }
  376. /** set the environment for the command
  377. * @param env Mapping of environment variables
  378. */
  379. protected void setEnvironment(Map<String, String> env) {
  380. this.environment = env;
  381. }
  382. /** set the working directory
  383. * @param dir The directory where the command would be executed
  384. */
  385. protected void setWorkingDirectory(File dir) {
  386. this.dir = dir;
  387. }
  388. /** check to see if a command needs to be executed and execute if needed */
  389. protected void run() throws IOException {
  390. if (lastTime + interval > Time.monotonicNow())
  391. return;
  392. exitCode = 0; // reset for next run
  393. runCommand();
  394. }
  395. /** Run a command */
  396. private void runCommand() throws IOException {
  397. ProcessBuilder builder = new ProcessBuilder(getExecString());
  398. Timer timeOutTimer = null;
  399. ShellTimeoutTimerTask timeoutTimerTask = null;
  400. timedOut = new AtomicBoolean(false);
  401. completed = new AtomicBoolean(false);
  402. if (environment != null) {
  403. builder.environment().putAll(this.environment);
  404. }
  405. if (dir != null) {
  406. builder.directory(this.dir);
  407. }
  408. builder.redirectErrorStream(redirectErrorStream);
  409. if (Shell.WINDOWS) {
  410. synchronized (WindowsProcessLaunchLock) {
  411. // To workaround the race condition issue with child processes
  412. // inheriting unintended handles during process launch that can
  413. // lead to hangs on reading output and error streams, we
  414. // serialize process creation. More info available at:
  415. // http://support.microsoft.com/kb/315939
  416. process = builder.start();
  417. }
  418. } else {
  419. process = builder.start();
  420. }
  421. if (timeOutInterval > 0) {
  422. timeOutTimer = new Timer("Shell command timeout");
  423. timeoutTimerTask = new ShellTimeoutTimerTask(
  424. this);
  425. //One time scheduling.
  426. timeOutTimer.schedule(timeoutTimerTask, timeOutInterval);
  427. }
  428. final BufferedReader errReader =
  429. new BufferedReader(new InputStreamReader(
  430. process.getErrorStream(), Charset.defaultCharset()));
  431. BufferedReader inReader =
  432. new BufferedReader(new InputStreamReader(
  433. process.getInputStream(), Charset.defaultCharset()));
  434. final StringBuffer errMsg = new StringBuffer();
  435. // read error and input streams as this would free up the buffers
  436. // free the error stream buffer
  437. Thread errThread = new Thread() {
  438. @Override
  439. public void run() {
  440. try {
  441. String line = errReader.readLine();
  442. while((line != null) && !isInterrupted()) {
  443. errMsg.append(line);
  444. errMsg.append(System.getProperty("line.separator"));
  445. line = errReader.readLine();
  446. }
  447. } catch(IOException ioe) {
  448. LOG.warn("Error reading the error stream", ioe);
  449. }
  450. }
  451. };
  452. try {
  453. errThread.start();
  454. } catch (IllegalStateException ise) {
  455. } catch (OutOfMemoryError oe) {
  456. LOG.error("Caught " + oe + ". One possible reason is that ulimit"
  457. + " setting of 'max user processes' is too low. If so, do"
  458. + " 'ulimit -u <largerNum>' and try again.");
  459. throw oe;
  460. }
  461. try {
  462. parseExecResult(inReader); // parse the output
  463. // clear the input stream buffer
  464. String line = inReader.readLine();
  465. while(line != null) {
  466. line = inReader.readLine();
  467. }
  468. // wait for the process to finish and check the exit code
  469. exitCode = process.waitFor();
  470. // make sure that the error thread exits
  471. joinThread(errThread);
  472. completed.set(true);
  473. //the timeout thread handling
  474. //taken care in finally block
  475. if (exitCode != 0) {
  476. throw new ExitCodeException(exitCode, errMsg.toString());
  477. }
  478. } catch (InterruptedException ie) {
  479. throw new IOException(ie.toString());
  480. } finally {
  481. if (timeOutTimer != null) {
  482. timeOutTimer.cancel();
  483. }
  484. // close the input stream
  485. try {
  486. // JDK 7 tries to automatically drain the input streams for us
  487. // when the process exits, but since close is not synchronized,
  488. // it creates a race if we close the stream first and the same
  489. // fd is recycled. the stream draining thread will attempt to
  490. // drain that fd!! it may block, OOM, or cause bizarre behavior
  491. // see: https://bugs.openjdk.java.net/browse/JDK-8024521
  492. // issue is fixed in build 7u60
  493. InputStream stdout = process.getInputStream();
  494. synchronized (stdout) {
  495. inReader.close();
  496. }
  497. } catch (IOException ioe) {
  498. LOG.warn("Error while closing the input stream", ioe);
  499. }
  500. if (!completed.get()) {
  501. errThread.interrupt();
  502. joinThread(errThread);
  503. }
  504. try {
  505. InputStream stderr = process.getErrorStream();
  506. synchronized (stderr) {
  507. errReader.close();
  508. }
  509. } catch (IOException ioe) {
  510. LOG.warn("Error while closing the error stream", ioe);
  511. }
  512. process.destroy();
  513. lastTime = Time.monotonicNow();
  514. }
  515. }
  516. private static void joinThread(Thread t) {
  517. while (t.isAlive()) {
  518. try {
  519. t.join();
  520. } catch (InterruptedException ie) {
  521. if (LOG.isWarnEnabled()) {
  522. LOG.warn("Interrupted while joining on: " + t, ie);
  523. }
  524. t.interrupt(); // propagate interrupt
  525. }
  526. }
  527. }
  528. /** return an array containing the command name & its parameters */
  529. protected abstract String[] getExecString();
  530. /** Parse the execution result */
  531. protected abstract void parseExecResult(BufferedReader lines)
  532. throws IOException;
  533. /**
  534. * Get the environment variable
  535. */
  536. public String getEnvironment(String env) {
  537. return environment.get(env);
  538. }
  539. /** get the current sub-process executing the given command
  540. * @return process executing the command
  541. */
  542. public Process getProcess() {
  543. return process;
  544. }
  545. /** get the exit code
  546. * @return the exit code of the process
  547. */
  548. public int getExitCode() {
  549. return exitCode;
  550. }
  551. /**
  552. * This is an IOException with exit code added.
  553. */
  554. public static class ExitCodeException extends IOException {
  555. private final int exitCode;
  556. public ExitCodeException(int exitCode, String message) {
  557. super(message);
  558. this.exitCode = exitCode;
  559. }
  560. public int getExitCode() {
  561. return exitCode;
  562. }
  563. @Override
  564. public String toString() {
  565. final StringBuilder sb =
  566. new StringBuilder("ExitCodeException ");
  567. sb.append("exitCode=").append(exitCode)
  568. .append(": ");
  569. sb.append(super.getMessage());
  570. return sb.toString();
  571. }
  572. }
  573. public interface CommandExecutor {
  574. void execute() throws IOException;
  575. int getExitCode() throws IOException;
  576. String getOutput() throws IOException;
  577. void close();
  578. }
  579. /**
  580. * A simple shell command executor.
  581. *
  582. * <code>ShellCommandExecutor</code>should be used in cases where the output
  583. * of the command needs no explicit parsing and where the command, working
  584. * directory and the environment remains unchanged. The output of the command
  585. * is stored as-is and is expected to be small.
  586. */
  587. public static class ShellCommandExecutor extends Shell
  588. implements CommandExecutor {
  589. private String[] command;
  590. private StringBuffer output;
  591. public ShellCommandExecutor(String[] execString) {
  592. this(execString, null);
  593. }
  594. public ShellCommandExecutor(String[] execString, File dir) {
  595. this(execString, dir, null);
  596. }
  597. public ShellCommandExecutor(String[] execString, File dir,
  598. Map<String, String> env) {
  599. this(execString, dir, env , 0L);
  600. }
  601. /**
  602. * Create a new instance of the ShellCommandExecutor to execute a command.
  603. *
  604. * @param execString The command to execute with arguments
  605. * @param dir If not-null, specifies the directory which should be set
  606. * as the current working directory for the command.
  607. * If null, the current working directory is not modified.
  608. * @param env If not-null, environment of the command will include the
  609. * key-value pairs specified in the map. If null, the current
  610. * environment is not modified.
  611. * @param timeout Specifies the time in milliseconds, after which the
  612. * command will be killed and the status marked as timedout.
  613. * If 0, the command will not be timed out.
  614. */
  615. public ShellCommandExecutor(String[] execString, File dir,
  616. Map<String, String> env, long timeout) {
  617. command = execString.clone();
  618. if (dir != null) {
  619. setWorkingDirectory(dir);
  620. }
  621. if (env != null) {
  622. setEnvironment(env);
  623. }
  624. timeOutInterval = timeout;
  625. }
  626. /** Execute the shell command. */
  627. public void execute() throws IOException {
  628. this.run();
  629. }
  630. @Override
  631. public String[] getExecString() {
  632. return command;
  633. }
  634. @Override
  635. protected void parseExecResult(BufferedReader lines) throws IOException {
  636. output = new StringBuffer();
  637. char[] buf = new char[512];
  638. int nRead;
  639. while ( (nRead = lines.read(buf, 0, buf.length)) > 0 ) {
  640. output.append(buf, 0, nRead);
  641. }
  642. }
  643. /** Get the output of the shell command.*/
  644. public String getOutput() {
  645. return (output == null) ? "" : output.toString();
  646. }
  647. /**
  648. * Returns the commands of this instance.
  649. * Arguments with spaces in are presented with quotes round; other
  650. * arguments are presented raw
  651. *
  652. * @return a string representation of the object.
  653. */
  654. @Override
  655. public String toString() {
  656. StringBuilder builder = new StringBuilder();
  657. String[] args = getExecString();
  658. for (String s : args) {
  659. if (s.indexOf(' ') >= 0) {
  660. builder.append('"').append(s).append('"');
  661. } else {
  662. builder.append(s);
  663. }
  664. builder.append(' ');
  665. }
  666. return builder.toString();
  667. }
  668. @Override
  669. public void close() {
  670. }
  671. }
  672. /**
  673. * To check if the passed script to shell command executor timed out or
  674. * not.
  675. *
  676. * @return if the script timed out.
  677. */
  678. public boolean isTimedOut() {
  679. return timedOut.get();
  680. }
  681. /**
  682. * Set if the command has timed out.
  683. *
  684. */
  685. private void setTimedOut() {
  686. this.timedOut.set(true);
  687. }
  688. /**
  689. * Static method to execute a shell command.
  690. * Covers most of the simple cases without requiring the user to implement
  691. * the <code>Shell</code> interface.
  692. * @param cmd shell command to execute.
  693. * @return the output of the executed command.
  694. */
  695. public static String execCommand(String ... cmd) throws IOException {
  696. return execCommand(null, cmd, 0L);
  697. }
  698. /**
  699. * Static method to execute a shell command.
  700. * Covers most of the simple cases without requiring the user to implement
  701. * the <code>Shell</code> interface.
  702. * @param env the map of environment key=value
  703. * @param cmd shell command to execute.
  704. * @param timeout time in milliseconds after which script should be marked timeout
  705. * @return the output of the executed command.o
  706. */
  707. public static String execCommand(Map<String, String> env, String[] cmd,
  708. long timeout) throws IOException {
  709. ShellCommandExecutor exec = new ShellCommandExecutor(cmd, null, env,
  710. timeout);
  711. exec.execute();
  712. return exec.getOutput();
  713. }
  714. /**
  715. * Static method to execute a shell command.
  716. * Covers most of the simple cases without requiring the user to implement
  717. * the <code>Shell</code> interface.
  718. * @param env the map of environment key=value
  719. * @param cmd shell command to execute.
  720. * @return the output of the executed command.
  721. */
  722. public static String execCommand(Map<String,String> env, String ... cmd)
  723. throws IOException {
  724. return execCommand(env, cmd, 0L);
  725. }
  726. /**
  727. * Timer which is used to timeout scripts spawned off by shell.
  728. */
  729. private static class ShellTimeoutTimerTask extends TimerTask {
  730. private Shell shell;
  731. public ShellTimeoutTimerTask(Shell shell) {
  732. this.shell = shell;
  733. }
  734. @Override
  735. public void run() {
  736. Process p = shell.getProcess();
  737. try {
  738. p.exitValue();
  739. } catch (Exception e) {
  740. //Process has not terminated.
  741. //So check if it has completed
  742. //if not just destroy it.
  743. if (p != null && !shell.completed.get()) {
  744. shell.setTimedOut();
  745. p.destroy();
  746. }
  747. }
  748. }
  749. }
  750. }