HADOOP-13434. Add bash quoting to Shell class. (Owen O'Malley)
This commit is contained in:
parent
dc0a17e828
commit
158dd2d5a2
|
@ -42,7 +42,7 @@ import org.slf4j.LoggerFactory;
|
||||||
* A base class for running a Shell command.
|
* A base class for running a Shell command.
|
||||||
*
|
*
|
||||||
* <code>Shell</code> can be used to run shell commands like <code>du</code> or
|
* <code>Shell</code> can be used to run shell commands like <code>du</code> or
|
||||||
* <code>df</code>. It also offers facilities to gate commands by
|
* <code>df</code>. It also offers facilities to gate commands by
|
||||||
* time-intervals.
|
* time-intervals.
|
||||||
*/
|
*/
|
||||||
@InterfaceAudience.Public
|
@InterfaceAudience.Public
|
||||||
|
@ -118,6 +118,21 @@ public abstract class Shell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quote the given arg so that bash will interpret it as a single value.
|
||||||
|
* Note that this quotes it for one level of bash, if you are passing it
|
||||||
|
* into a badly written shell script, you need to fix your shell script.
|
||||||
|
* @param arg the argument to quote
|
||||||
|
* @return the quoted string
|
||||||
|
*/
|
||||||
|
static String bashQuote(String arg) {
|
||||||
|
StringBuilder buffer = new StringBuilder(arg.length() + 2);
|
||||||
|
buffer.append('\'');
|
||||||
|
buffer.append(arg.replace("'", "'\\''"));
|
||||||
|
buffer.append('\'');
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/** a Unix command to get the current user's name: {@value}. */
|
/** a Unix command to get the current user's name: {@value}. */
|
||||||
public static final String USER_NAME_COMMAND = "whoami";
|
public static final String USER_NAME_COMMAND = "whoami";
|
||||||
|
|
||||||
|
@ -173,7 +188,7 @@ public abstract class Shell {
|
||||||
/** a Unix command to get the current user's groups list. */
|
/** a Unix command to get the current user's groups list. */
|
||||||
public static String[] getGroupsCommand() {
|
public static String[] getGroupsCommand() {
|
||||||
return (WINDOWS)? new String[]{"cmd", "/c", "groups"}
|
return (WINDOWS)? new String[]{"cmd", "/c", "groups"}
|
||||||
: new String[]{"bash", "-c", "groups"};
|
: new String[]{"groups"};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -184,10 +199,14 @@ public abstract class Shell {
|
||||||
*/
|
*/
|
||||||
public static String[] getGroupsForUserCommand(final String user) {
|
public static String[] getGroupsForUserCommand(final String user) {
|
||||||
//'groups username' command return is inconsistent across different unixes
|
//'groups username' command return is inconsistent across different unixes
|
||||||
return WINDOWS ?
|
if (WINDOWS) {
|
||||||
new String[]
|
return new String[]
|
||||||
{getWinUtilsPath(), "groups", "-F", "\"" + user + "\""}
|
{getWinUtilsPath(), "groups", "-F", "\"" + user + "\""};
|
||||||
: new String[] {"bash", "-c", "id -gn " + user + "; id -Gn " + user};
|
} else {
|
||||||
|
String quotedUser = bashQuote(user);
|
||||||
|
return new String[] {"bash", "-c", "id -gn " + quotedUser +
|
||||||
|
"; id -Gn " + quotedUser};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -199,17 +218,20 @@ public abstract class Shell {
|
||||||
*/
|
*/
|
||||||
public static String[] getGroupsIDForUserCommand(final String user) {
|
public static String[] getGroupsIDForUserCommand(final String user) {
|
||||||
//'groups username' command return is inconsistent across different unixes
|
//'groups username' command return is inconsistent across different unixes
|
||||||
return WINDOWS ?
|
if (WINDOWS) {
|
||||||
new String[]
|
return new String[]{getWinUtilsPath(), "groups", "-F", "\"" + user +
|
||||||
{getWinUtilsPath(), "groups", "-F", "\"" + user + "\""}
|
"\""};
|
||||||
: new String[] {"bash", "-c", "id -g " + user + "; id -G " + user};
|
} else {
|
||||||
|
String quotedUser = bashQuote(user);
|
||||||
|
return new String[] {"bash", "-c", "id -g " + quotedUser + "; id -G " +
|
||||||
|
quotedUser};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A command to get a given netgroup's user list. */
|
/** A command to get a given netgroup's user list. */
|
||||||
public static String[] getUsersForNetgroupCommand(final String netgroup) {
|
public static String[] getUsersForNetgroupCommand(final String netgroup) {
|
||||||
//'groups username' command return is non-consistent across different unixes
|
//'groups username' command return is non-consistent across different unixes
|
||||||
return WINDOWS ? new String [] {"cmd", "/c", "getent netgroup " + netgroup}
|
return new String[] {"getent", "netgroup", netgroup};
|
||||||
: new String [] {"bash", "-c", "getent netgroup " + netgroup};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return a command to get permission information. */
|
/** Return a command to get permission information. */
|
||||||
|
@ -233,14 +255,15 @@ public abstract class Shell {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a command to set permission for specific file.
|
* Return a command to set permission for specific file.
|
||||||
*
|
*
|
||||||
* @param perm String permission to set
|
* @param perm String permission to set
|
||||||
* @param recursive boolean true to apply to all sub-directories recursively
|
* @param recursive boolean true to apply to all sub-directories recursively
|
||||||
* @param file String file to set
|
* @param file String file to set
|
||||||
* @return String[] containing command and arguments
|
* @return String[] containing command and arguments
|
||||||
*/
|
*/
|
||||||
public static String[] getSetPermissionCommand(String perm,
|
public static String[] getSetPermissionCommand(String perm,
|
||||||
boolean recursive, String file) {
|
boolean recursive,
|
||||||
|
String file) {
|
||||||
String[] baseCmd = getSetPermissionCommand(perm, recursive);
|
String[] baseCmd = getSetPermissionCommand(perm, recursive);
|
||||||
String[] cmdWithFile = Arrays.copyOf(baseCmd, baseCmd.length + 1);
|
String[] cmdWithFile = Arrays.copyOf(baseCmd, baseCmd.length + 1);
|
||||||
cmdWithFile[cmdWithFile.length - 1] = file;
|
cmdWithFile[cmdWithFile.length - 1] = file;
|
||||||
|
@ -290,9 +313,9 @@ public abstract class Shell {
|
||||||
|
|
||||||
if (isSetsidAvailable) {
|
if (isSetsidAvailable) {
|
||||||
// Use the shell-builtin as it support "--" in all Hadoop supported OSes
|
// Use the shell-builtin as it support "--" in all Hadoop supported OSes
|
||||||
return new String[] { "bash", "-c", "kill -" + code + " -- -" + pid };
|
return new String[] {"kill", "-" + code, "--", "-" + pid};
|
||||||
} else {
|
} else {
|
||||||
return new String[] { "bash", "-c", "kill -" + code + " " + pid };
|
return new String[] {"kill", "-" + code, pid };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +333,7 @@ public abstract class Shell {
|
||||||
* Returns a File referencing a script with the given basename, inside the
|
* Returns a File referencing a script with the given basename, inside the
|
||||||
* given parent directory. The file extension is inferred by platform:
|
* given parent directory. The file extension is inferred by platform:
|
||||||
* <code>".cmd"</code> on Windows, or <code>".sh"</code> otherwise.
|
* <code>".cmd"</code> on Windows, or <code>".sh"</code> otherwise.
|
||||||
*
|
*
|
||||||
* @param parent File parent directory
|
* @param parent File parent directory
|
||||||
* @param basename String script file basename
|
* @param basename String script file basename
|
||||||
* @return File referencing the script in the directory
|
* @return File referencing the script in the directory
|
||||||
|
@ -342,8 +365,8 @@ public abstract class Shell {
|
||||||
public static String[] getRunScriptCommand(File script) {
|
public static String[] getRunScriptCommand(File script) {
|
||||||
String absolutePath = script.getAbsolutePath();
|
String absolutePath = script.getAbsolutePath();
|
||||||
return WINDOWS ?
|
return WINDOWS ?
|
||||||
new String[] { "cmd", "/c", absolutePath }
|
new String[] {"cmd", "/c", absolutePath }
|
||||||
: new String[] { "/bin/bash", absolutePath };
|
: new String[] {"/bin/bash", bashQuote(absolutePath) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** a Unix command to set permission: {@value}. */
|
/** a Unix command to set permission: {@value}. */
|
||||||
|
@ -527,11 +550,11 @@ public abstract class Shell {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fully qualify the path to a binary that should be in a known hadoop
|
* Fully qualify the path to a binary that should be in a known hadoop
|
||||||
* bin location. This is primarily useful for disambiguating call-outs
|
* bin location. This is primarily useful for disambiguating call-outs
|
||||||
* to executable sub-components of Hadoop to avoid clashes with other
|
* to executable sub-components of Hadoop to avoid clashes with other
|
||||||
* executables that may be in the path. Caveat: this call doesn't
|
* executables that may be in the path. Caveat: this call doesn't
|
||||||
* just format the path to the bin directory. It also checks for file
|
* just format the path to the bin directory. It also checks for file
|
||||||
* existence of the composed path. The output of this call should be
|
* existence of the composed path. The output of this call should be
|
||||||
* cached by callers.
|
* cached by callers.
|
||||||
*
|
*
|
||||||
* @param executable executable
|
* @param executable executable
|
||||||
|
@ -848,7 +871,7 @@ public abstract class Shell {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Run the command. */
|
/** Run the command. */
|
||||||
private void runCommand() throws IOException {
|
private void runCommand() throws IOException {
|
||||||
ProcessBuilder builder = new ProcessBuilder(getExecString());
|
ProcessBuilder builder = new ProcessBuilder(getExecString());
|
||||||
Timer timeOutTimer = null;
|
Timer timeOutTimer = null;
|
||||||
ShellTimeoutTimerTask timeoutTimerTask = null;
|
ShellTimeoutTimerTask timeoutTimerTask = null;
|
||||||
|
@ -877,7 +900,7 @@ public abstract class Shell {
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.redirectErrorStream(redirectErrorStream);
|
builder.redirectErrorStream(redirectErrorStream);
|
||||||
|
|
||||||
if (Shell.WINDOWS) {
|
if (Shell.WINDOWS) {
|
||||||
synchronized (WindowsProcessLaunchLock) {
|
synchronized (WindowsProcessLaunchLock) {
|
||||||
// To workaround the race condition issue with child processes
|
// To workaround the race condition issue with child processes
|
||||||
|
@ -898,7 +921,7 @@ public abstract class Shell {
|
||||||
//One time scheduling.
|
//One time scheduling.
|
||||||
timeOutTimer.schedule(timeoutTimerTask, timeOutInterval);
|
timeOutTimer.schedule(timeoutTimerTask, timeOutInterval);
|
||||||
}
|
}
|
||||||
final BufferedReader errReader =
|
final BufferedReader errReader =
|
||||||
new BufferedReader(new InputStreamReader(
|
new BufferedReader(new InputStreamReader(
|
||||||
process.getErrorStream(), Charset.defaultCharset()));
|
process.getErrorStream(), Charset.defaultCharset()));
|
||||||
BufferedReader inReader =
|
BufferedReader inReader =
|
||||||
|
@ -936,7 +959,7 @@ public abstract class Shell {
|
||||||
parseExecResult(inReader); // parse the output
|
parseExecResult(inReader); // parse the output
|
||||||
// clear the input stream buffer
|
// clear the input stream buffer
|
||||||
String line = inReader.readLine();
|
String line = inReader.readLine();
|
||||||
while(line != null) {
|
while(line != null) {
|
||||||
line = inReader.readLine();
|
line = inReader.readLine();
|
||||||
}
|
}
|
||||||
// wait for the process to finish and check the exit code
|
// wait for the process to finish and check the exit code
|
||||||
|
@ -1073,13 +1096,13 @@ public abstract class Shell {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple shell command executor.
|
* A simple shell command executor.
|
||||||
*
|
*
|
||||||
* <code>ShellCommandExecutor</code>should be used in cases where the output
|
* <code>ShellCommandExecutor</code>should be used in cases where the output
|
||||||
* of the command needs no explicit parsing and where the command, working
|
* of the command needs no explicit parsing and where the command, working
|
||||||
* directory and the environment remains unchanged. The output of the command
|
* directory and the environment remains unchanged. The output of the command
|
||||||
* is stored as-is and is expected to be small.
|
* is stored as-is and is expected to be small.
|
||||||
*/
|
*/
|
||||||
public static class ShellCommandExecutor extends Shell
|
public static class ShellCommandExecutor extends Shell
|
||||||
implements CommandExecutor {
|
implements CommandExecutor {
|
||||||
|
|
||||||
private String[] command;
|
private String[] command;
|
||||||
|
@ -1106,7 +1129,7 @@ public abstract class Shell {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance of the ShellCommandExecutor to execute a command.
|
* Create a new instance of the ShellCommandExecutor to execute a command.
|
||||||
*
|
*
|
||||||
* @param execString The command to execute with arguments
|
* @param execString The command to execute with arguments
|
||||||
* @param dir If not-null, specifies the directory which should be set
|
* @param dir If not-null, specifies the directory which should be set
|
||||||
* as the current working directory for the command.
|
* as the current working directory for the command.
|
||||||
|
@ -1120,7 +1143,7 @@ public abstract class Shell {
|
||||||
* @param inheritParentEnv Indicates if the process should inherit the env
|
* @param inheritParentEnv Indicates if the process should inherit the env
|
||||||
* vars from the parent process or not.
|
* vars from the parent process or not.
|
||||||
*/
|
*/
|
||||||
public ShellCommandExecutor(String[] execString, File dir,
|
public ShellCommandExecutor(String[] execString, File dir,
|
||||||
Map<String, String> env, long timeout, boolean inheritParentEnv) {
|
Map<String, String> env, long timeout, boolean inheritParentEnv) {
|
||||||
command = execString.clone();
|
command = execString.clone();
|
||||||
if (dir != null) {
|
if (dir != null) {
|
||||||
|
@ -1145,7 +1168,7 @@ public abstract class Shell {
|
||||||
+ StringUtils.join(" ", command));
|
+ StringUtils.join(" ", command));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.run();
|
this.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1198,7 +1221,7 @@ public abstract class Shell {
|
||||||
/**
|
/**
|
||||||
* To check if the passed script to shell command executor timed out or
|
* To check if the passed script to shell command executor timed out or
|
||||||
* not.
|
* not.
|
||||||
*
|
*
|
||||||
* @return if the script timed out.
|
* @return if the script timed out.
|
||||||
*/
|
*/
|
||||||
public boolean isTimedOut() {
|
public boolean isTimedOut() {
|
||||||
|
@ -1207,15 +1230,15 @@ public abstract class Shell {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Declare that the command has timed out.
|
* Declare that the command has timed out.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private void setTimedOut() {
|
private void setTimedOut() {
|
||||||
this.timedOut.set(true);
|
this.timedOut.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static method to execute a shell command.
|
* Static method to execute a shell command.
|
||||||
* Covers most of the simple cases without requiring the user to implement
|
* Covers most of the simple cases without requiring the user to implement
|
||||||
* the <code>Shell</code> interface.
|
* the <code>Shell</code> interface.
|
||||||
* @param cmd shell command to execute.
|
* @param cmd shell command to execute.
|
||||||
* @return the output of the executed command.
|
* @return the output of the executed command.
|
||||||
|
@ -1237,7 +1260,7 @@ public abstract class Shell {
|
||||||
|
|
||||||
public static String execCommand(Map<String, String> env, String[] cmd,
|
public static String execCommand(Map<String, String> env, String[] cmd,
|
||||||
long timeout) throws IOException {
|
long timeout) throws IOException {
|
||||||
ShellCommandExecutor exec = new ShellCommandExecutor(cmd, null, env,
|
ShellCommandExecutor exec = new ShellCommandExecutor(cmd, null, env,
|
||||||
timeout);
|
timeout);
|
||||||
exec.execute();
|
exec.execute();
|
||||||
return exec.getOutput();
|
return exec.getOutput();
|
||||||
|
@ -1275,7 +1298,7 @@ public abstract class Shell {
|
||||||
p.exitValue();
|
p.exitValue();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
//Process has not terminated.
|
//Process has not terminated.
|
||||||
//So check if it has completed
|
//So check if it has completed
|
||||||
//if not just destroy it.
|
//if not just destroy it.
|
||||||
if (p != null && !shell.completed.get()) {
|
if (p != null && !shell.completed.get()) {
|
||||||
shell.setTimedOut();
|
shell.setTimedOut();
|
||||||
|
|
|
@ -240,9 +240,9 @@ public class TestShell extends Assert {
|
||||||
expectedCommand =
|
expectedCommand =
|
||||||
new String[]{getWinUtilsPath(), "task", "isAlive", anyPid };
|
new String[]{getWinUtilsPath(), "task", "isAlive", anyPid };
|
||||||
} else if (Shell.isSetsidAvailable) {
|
} else if (Shell.isSetsidAvailable) {
|
||||||
expectedCommand = new String[] { "bash", "-c", "kill -0 -- -" + anyPid };
|
expectedCommand = new String[] {"kill", "-0", "--", "-" + anyPid };
|
||||||
} else {
|
} else {
|
||||||
expectedCommand = new String[]{ "bash", "-c", "kill -0 " + anyPid };
|
expectedCommand = new String[] {"kill", "-0", anyPid };
|
||||||
}
|
}
|
||||||
Assert.assertArrayEquals(expectedCommand, checkProcessAliveCommand);
|
Assert.assertArrayEquals(expectedCommand, checkProcessAliveCommand);
|
||||||
}
|
}
|
||||||
|
@ -260,9 +260,9 @@ public class TestShell extends Assert {
|
||||||
expectedCommand =
|
expectedCommand =
|
||||||
new String[]{getWinUtilsPath(), "task", "kill", anyPid };
|
new String[]{getWinUtilsPath(), "task", "kill", anyPid };
|
||||||
} else if (Shell.isSetsidAvailable) {
|
} else if (Shell.isSetsidAvailable) {
|
||||||
expectedCommand = new String[] { "bash", "-c", "kill -9 -- -" + anyPid };
|
expectedCommand = new String[] {"kill", "-9", "--", "-" + anyPid };
|
||||||
} else {
|
} else {
|
||||||
expectedCommand = new String[]{ "bash", "-c", "kill -9 " + anyPid };
|
expectedCommand = new String[] {"kill", "-9", anyPid };
|
||||||
}
|
}
|
||||||
Assert.assertArrayEquals(expectedCommand, checkProcessAliveCommand);
|
Assert.assertArrayEquals(expectedCommand, checkProcessAliveCommand);
|
||||||
}
|
}
|
||||||
|
@ -466,4 +466,10 @@ public class TestShell extends Assert {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBashQuote() {
|
||||||
|
assertEquals("'foobar'", Shell.bashQuote("foobar"));
|
||||||
|
assertEquals("'foo'\\''bar'", Shell.bashQuote("foo'bar"));
|
||||||
|
assertEquals("''\\''foo'\\''bar'\\'''", Shell.bashQuote("'foo'bar'"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue