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 /**
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 public interface CommandExecutor {
647
648 void execute() throws IOException;
649
650 int getExitCode() throws IOException;
651
652 String getOutput() throws IOException;
653
654 void close();
655
656 }
657
658 /**
659 * A simple shell command executor.
660 *
661 * <code>ShellCommandExecutor</code>should be used in cases where the output
662 * of the command needs no explicit parsing and where the command, working
663 * directory and the environment remains unchanged. The output of the command
664 * is stored as-is and is expected to be small.
665 */
666 public static class ShellCommandExecutor extends Shell
667 implements CommandExecutor {
668
669 private String[] command;
670 private StringBuffer output;
671
672
673 public ShellCommandExecutor(String[] execString) {
674 this(execString, null);
675 }
676
677 public ShellCommandExecutor(String[] execString, File dir) {
678 this(execString, dir, null);
679 }
680
681 public ShellCommandExecutor(String[] execString, File dir,
682 Map<String, String> env) {
683 this(execString, dir, env , 0L);
684 }
685
686 /**
687 * Create a new instance of the ShellCommandExecutor to execute a command.
688 *
689 * @param execString The command to execute with arguments
690 * @param dir If not-null, specifies the directory which should be set
691 * as the current working directory for the command.
692 * If null, the current working directory is not modified.
693 * @param env If not-null, environment of the command will include the
694 * key-value pairs specified in the map. If null, the current
695 * environment is not modified.
696 * @param timeout Specifies the time in milliseconds, after which the
697 * command will be killed and the status marked as timedout.
698 * If 0, the command will not be timed out.
699 */
700 public ShellCommandExecutor(String[] execString, File dir,
701 Map<String, String> env, long timeout) {
702 command = execString.clone();
703 if (dir != null) {
704 setWorkingDirectory(dir);
705 }
706 if (env != null) {
707 setEnvironment(env);
708 }
709 timeOutInterval = timeout;
710 }
711
712
713 /** Execute the shell command. */
714 public void execute() throws IOException {
715 this.run();
716 }
717
718 @Override
719 public String[] getExecString() {
720 return command;
721 }
722
723 @Override
724 protected void parseExecResult(BufferedReader lines) throws IOException {
725 output = new StringBuffer();
726 char[] buf = new char[512];
727 int nRead;
728 while ( (nRead = lines.read(buf, 0, buf.length)) > 0 ) {
729 output.append(buf, 0, nRead);
730 }
731 }
732
733 /** Get the output of the shell command.*/
734 public String getOutput() {
735 return (output == null) ? "" : output.toString();
736 }
737
738 /**
739 * Returns the commands of this instance.
740 * Arguments with spaces in are presented with quotes round; other
741 * arguments are presented raw
742 *
743 * @return a string representation of the object.
744 */
745 @Override
746 public String toString() {
747 StringBuilder builder = new StringBuilder();
748 String[] args = getExecString();
749 for (String s : args) {
750 if (s.indexOf(' ') >= 0) {
751 builder.append('"').append(s).append('"');
752 } else {
753 builder.append(s);
754 }
755 builder.append(' ');
756 }
757 return builder.toString();
758 }
759
760 @Override
761 public void close() {
762 }
763 }
764
765 /**
766 * To check if the passed script to shell command executor timed out or
767 * not.
768 *
769 * @return if the script timed out.
770 */
771 public boolean isTimedOut() {
772 return timedOut.get();
773 }
774
775 /**
776 * Set if the command has timed out.
777 *
778 */
779 private void setTimedOut() {
780 this.timedOut.set(true);
781 }
782
783 /**
784 * Static method to execute a shell command.
785 * Covers most of the simple cases without requiring the user to implement
786 * the <code>Shell</code> interface.
787 * @param cmd shell command to execute.
788 * @return the output of the executed command.
789 */
790 public static String execCommand(String ... cmd) throws IOException {
791 return execCommand(null, cmd, 0L);
792 }
793
794 /**
795 * Static method to execute a shell command.
796 * Covers most of the simple cases without requiring the user to implement
797 * the <code>Shell</code> interface.
798 * @param env the map of environment key=value
799 * @param cmd shell command to execute.
800 * @param timeout time in milliseconds after which script should be marked timeout
801 * @return the output of the executed command.o
802 */
803
804 public static String execCommand(Map<String, String> env, String[] cmd,
805 long timeout) throws IOException {
806 ShellCommandExecutor exec = new ShellCommandExecutor(cmd, null, env,
807 timeout);
808 exec.execute();
809 return exec.getOutput();
810 }
811
812 /**
813 * Static method to execute a shell command.
814 * Covers most of the simple cases without requiring the user to implement
815 * the <code>Shell</code> interface.
816 * @param env the map of environment key=value
817 * @param cmd shell command to execute.
818 * @return the output of the executed command.
819 */
820 public static String execCommand(Map<String,String> env, String ... cmd)
821 throws IOException {
822 return execCommand(env, cmd, 0L);
823 }
824
825 /**
826 * Timer which is used to timeout scripts spawned off by shell.
827 */
828 private static class ShellTimeoutTimerTask extends TimerTask {
829
830 private Shell shell;
831
832 public ShellTimeoutTimerTask(Shell shell) {
833 this.shell = shell;
834 }
835
836 @Override
837 public void run() {
838 Process p = shell.getProcess();
839 try {
840 p.exitValue();
841 } catch (Exception e) {
842 //Process has not terminated.
843 //So check if it has completed
844 //if not just destroy it.
845 if (p != null && !shell.completed.get()) {
846 shell.setTimedOut();
847 p.destroy();
848 }
849 }
850 }
851 }
852 }