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