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