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