diff --git a/.travis-data/code-setup-input.txt b/.travis-data/code-setup-input.txt index 1b0bcf1b72..2c5770d487 100644 --- a/.travis-data/code-setup-input.txt +++ b/.travis-data/code-setup-input.txt @@ -3,6 +3,6 @@ simple script that doubles a number and sleeps for a given number of seconds False simpleplugins.templatereplacer torquessh -/usr/local/bin/doubler.sh +/usr/local/bin/d"o'ub ler.sh diff --git a/.travis-data/torquessh-doubler/Dockerfile b/.travis-data/torquessh-doubler/Dockerfile index cd1c67b5f9..f7977fd928 100644 --- a/.travis-data/torquessh-doubler/Dockerfile +++ b/.travis-data/torquessh-doubler/Dockerfile @@ -4,7 +4,7 @@ MAINTAINER AiiDA Team # Use baseimage-docker's init system. CMD ["/sbin/my_init"] -# Install required packages COPY doubler.sh /usr/local/bin/ - +# Use messed-up filename to test quoting robustness +RUN mv /usr/local/bin/doubler.sh /usr/local/bin/d\"o\'ub\ ler.sh diff --git a/.travis-data/torquessh-doubler/README.md b/.travis-data/torquessh-doubler/README.md index 5ecbd09b4e..257482de86 100644 --- a/.travis-data/torquessh-doubler/README.md +++ b/.travis-data/torquessh-doubler/README.md @@ -3,3 +3,8 @@ This folder contains an example of an extension of the torquessh-base image, where we add a very basic script to double a number. This is meant to be a very lightweight 'code' to test daemon functionality. + +# Notes + +Inside the docker image, we use a filename including single quotes, double +quotes and spaces in order to test the robustness of AiiDA's escaping routines. diff --git a/aiida/transport/plugins/local.py b/aiida/transport/plugins/local.py index def5902968..7f5b5b0af4 100644 --- a/aiida/transport/plugins/local.py +++ b/aiida/transport/plugins/local.py @@ -704,21 +704,29 @@ def isfile(self, path): def _exec_command_internal(self, command): """ - Executes the specified command, first changing directory to the - current working directory as returned by self.getcwd(). - Does not wait for the calculation to finish. + Executes the specified command in bash login shell. + + Before the command is executed, changes directory to the current + working directory as returned by self.getcwd(). - For a higher-level exec_command that automatically waits for the - job to finish, use exec_command_wait. + For executing commands and waiting for them to finish, use + exec_command_wait. Otherwise, to end the process, use the proc.wait() method. - :param command: the command to execute + :param command: the command to execute. The command is assumed to be + already escaped using :py:func:`aiida.common.utils.escape_for_bash`. :return: a tuple with (stdin, stdout, stderr, proc), where stdin, stdout and stderr behave as file-like objects, proc is the process object as returned by the subprocess.Popen() class. """ + from aiida.common.utils import escape_for_bash + + # Note: The outer shell will eat one level of escaping, while + # 'bash -l -c ...' will eat another. Thus, we need to escape again. + command = 'bash -l -c ' + escape_for_bash(command) + proc = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.getcwd()) diff --git a/aiida/transport/plugins/ssh.py b/aiida/transport/plugins/ssh.py index 858632eff1..8a6c59cc23 100644 --- a/aiida/transport/plugins/ssh.py +++ b/aiida/transport/plugins/ssh.py @@ -1290,15 +1290,16 @@ def isfile(self,path): def _exec_command_internal(self,command,combine_stderr=False,bufsize=-1): """ - Executes the specified command, first changing directory to the - current working directory are returned by - self.getcwd(). - Does not wait for the calculation to finish. + Executes the specified command in bash login shell. + + Before the command is executed, changes directory to the current + working directory as returned by self.getcwd(). - For a higher-level _exec_command_internal that automatically waits for the - job to finish, use exec_command_wait. + For executing commands and waiting for them to finish, use + exec_command_wait. - :param command: the command to execute + :param command: the command to execute. The command is assumed to be + already escaped using :py:func:`aiida.common.utils.escape_for_bash`. :param combine_stderr: (default False) if True, combine stdout and stderr on the same buffer (i.e., stdout). Note: If combine_stderr is True, stderr will always be empty. @@ -1324,7 +1325,9 @@ def _exec_command_internal(self,command,combine_stderr=False,bufsize=-1): self.logger.debug("Command to be executed: {}".format( command_to_execute)) - channel.exec_command(command_to_execute) + # Note: The default shell will eat one level of escaping, while + # 'bash -l -c ...' will eat another. Thus, we need to escape again. + channel.exec_command('bash -l -c ' + escape_for_bash(command_to_execute)) stdin = channel.makefile('wb',bufsize) stdout = channel.makefile('rb',bufsize)