[core/input] Invoke commands in a subshell

add shell capability to util.cli and make sure that the input module
uses that to reliably spawn whatever command the user wants to run.

see #628
This commit is contained in:
tobi-wan-kenobi 2020-05-16 11:41:34 +02:00
parent 06fa453d71
commit eea3c758de
3 changed files with 8 additions and 3 deletions

View file

@ -38,7 +38,7 @@ def __event_id(obj_id, button):
def __execute(cmd): def __execute(cmd):
try: try:
util.cli.execute(cmd, wait=False) util.cli.execute(cmd, wait=False, shell=True)
except Exception as e: except Exception as e:
logging.error("failed to invoke callback: {}".format(e)) logging.error("failed to invoke callback: {}".format(e))

View file

@ -11,6 +11,7 @@ def execute(
include_stderr=False, include_stderr=False,
env=None, env=None,
return_exitcode=False, return_exitcode=False,
shell=False,
): ):
"""Executes a commandline utility and returns its output """Executes a commandline utility and returns its output
@ -20,13 +21,14 @@ def execute(
:param include_stderr: set to True to include stderr output in the return value, defaults to False :param include_stderr: set to True to include stderr output in the return value, defaults to False
:param env: provide a dict here to specify a custom execution environment, defaults to None :param env: provide a dict here to specify a custom execution environment, defaults to None
:param return_exitcode: set to True to return a pair, where the first member is the exit code and the message the second, defaults to False :param return_exitcode: set to True to return a pair, where the first member is the exit code and the message the second, defaults to False
:param shell: set to True to run command in a separate shell, defaults to False
:raises RuntimeError: the command either didn't exist or didn't exit cleanly, and ignore_errors was set to False :raises RuntimeError: the command either didn't exist or didn't exit cleanly, and ignore_errors was set to False
:return: output of cmd, or stderr, if ignore_errors is True and the command failed; or a tuple of exitcode and the previous, if return_exitcode is set to True :return: output of cmd, or stderr, if ignore_errors is True and the command failed; or a tuple of exitcode and the previous, if return_exitcode is set to True
:rtype: string or tuple (if return_exitcode is set to True) :rtype: string or tuple (if return_exitcode is set to True)
""" """
args = shlex.split(cmd) args = cmd if shell else shlex.split(cmd)
logging.debug(cmd) logging.debug(cmd)
try: try:
proc = subprocess.Popen( proc = subprocess.Popen(
@ -34,6 +36,7 @@ def execute(
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT if include_stderr else subprocess.PIPE, stderr=subprocess.STDOUT if include_stderr else subprocess.PIPE,
env=env, env=env,
shell=shell,
) )
except FileNotFoundError as e: except FileNotFoundError as e:
raise RuntimeError("{} not found".format(cmd)) raise RuntimeError("{} not found".format(cmd))

View file

@ -70,7 +70,9 @@ class config(unittest.TestCase):
self.inputObject, self.someEvent["button"], self.someCommand self.inputObject, self.someEvent["button"], self.someCommand
) )
core.input.trigger(self.someEvent) core.input.trigger(self.someEvent)
cli.execute.assert_called_once_with(self.someCommand, wait=False) cli.execute.assert_called_once_with(
self.someCommand, wait=False, shell=True
)
def test_non_existent_callback(self): def test_non_existent_callback(self):
with unittest.mock.patch("core.input.util.cli") as cli: with unittest.mock.patch("core.input.util.cli") as cli: