[PATCH 09/17] Add a method startProgram to handle process starting

David Shea dshea at redhat.com
Sun Sep 21 19:37:01 UTC 2014


This shares the process starting code between _run_program and
execReadlines. Additionally use startProgram to start the shell in
execConsole instead of calling Popen directly.

For greater flexibility in process starting, more subprocess.Popen
parameters are exposed by startProgram than by _run_program.
startProgram is able to override stdout and stderr and accepts an
additional preexec_fn.
---
 pyanaconda/iutil.py                  | 76 ++++++++++++++++++++----------------
 tests/pyanaconda_tests/iutil_test.py | 40 +++++++++++++++++++
 2 files changed, 83 insertions(+), 33 deletions(-)

diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py
index 254170a..a27cc21 100644
--- a/pyanaconda/iutil.py
+++ b/pyanaconda/iutil.py
@@ -99,16 +99,22 @@ def setSysroot(path):
     global _sysroot
     _sysroot = path
 
-def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, log_output=True, binary_output=False):
-    """ Run an external program, log the output and return it to the caller
+def startProgram(argv, root='/', stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+        env_prune=None, **kwargs):
+    """ Start an external program and return the Popen object.
+
+        The root argument is handled by passing a preexec_fn argument to
+        subprocess.Popen, but an additional preexec_fn can still be specified
+        and will be run. The user preexec_fn will be run last.
+
         :param argv: The command to run and argument
         :param root: The directory to chroot to before running command.
         :param stdin: The file object to read stdin from.
-        :param stdout: Optional file object to write stdout and stderr to.
+        :param stdout: The file object to write stdout to.
+        :param stderr: The file object to write stderr to.
         :param env_prune: environment variable to remove before execution
-        :param log_output: whether to log the output of command
-        :param binary_output: whether to treat the output of command as binary data
-        :return: The return code of the command and the output
+        :param kwargs: Additional parameters to pass to subprocess.Popen
+        :return: A Popen object for the running command.
     """
     if env_prune is None:
         env_prune = []
@@ -119,11 +125,19 @@ def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, log_ou
     if target_root == _root_path:
         target_root = getSysroot()
 
-    def chroot():
+    # Check for and save a preexec_fn argument
+    preexec_fn = kwargs.pop("preexec_fn", None)
+
+    def preexec():
+        # If a target root was specificed, chroot into it
         if target_root and target_root != '/':
             os.chroot(target_root)
             os.chdir("/")
 
+        # If the user specified an additional preexec_fn argument, run it
+        if preexec_fn is not None:
+            preexec_fn()
+
     with program_log_lock:
         program_log.info("Running... %s", " ".join(argv))
 
@@ -131,12 +145,25 @@ def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, log_ou
     for var in env_prune:
         env.pop(var, None)
 
+    return subprocess.Popen(argv,
+                            stdin=stdin,
+                            stdout=stdout,
+                            stderr=stderr,
+                            preexec_fn=preexec, cwd=root, env=env, **kwargs)
+
+def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, log_output=True, binary_output=False):
+    """ Run an external program, log the output and return it to the caller
+        :param argv: The command to run and argument
+        :param root: The directory to chroot to before running command.
+        :param stdin: The file object to read stdin from.
+        :param stdout: Optional file object to write stdout and stderr to.
+        :param env_prune: environment variable to remove before execution
+        :param log_output: whether to log the output of command
+        :param binary_output: whether to treat the output of command as binary data
+        :return: The return code of the command and the output
+    """
     try:
-        proc = subprocess.Popen(argv,
-                                stdin=stdin,
-                                stdout=subprocess.PIPE,
-                                stderr=subprocess.STDOUT,
-                                preexec_fn=chroot, cwd=root, env=env)
+        proc = startProgram(argv, root=root, stdin=stdin, env_prune=env_prune)
 
         output_string = proc.communicate()[0]
         if output_string:
@@ -229,36 +256,19 @@ def execReadlines(command, argv, stdin=None, root='/', env_prune=None):
         Output from the file is not logged to program.log
         This returns a generator with the lines from the command until it has finished
     """
-    if env_prune is None:
-        env_prune = []
-
     # Return the lines from stdout via a Queue
     def queue_lines(out, queue):
         for line in iter(out.readline, b''):
             queue.put(line.strip())
         out.close()
 
-    def chroot():
-        if root and root != '/':
-            os.chroot(root)
-            os.chdir("/")
-
     argv = [command] + argv
-    with program_log_lock:
-        program_log.info("Running... %s", " ".join(argv))
 
-    env = augmentEnv()
-    for var in env_prune:
-        env.pop(var, None)
     try:
-        proc = subprocess.Popen(argv,
-                                stdin=stdin,
-                                stdout=subprocess.PIPE,
-                                stderr=subprocess.STDOUT,
-                                bufsize=1,
-                                preexec_fn=chroot, cwd=root, env=env)
+        proc = startProgram(argv, root=root, stdin=stdin, env_prune=env_prune, bufsize=1)
     except OSError as e:
-        program_log.error("Error running %s: %s", argv[0], e.strerror)
+        with program_log_lock:
+            program_log.error("Error running %s: %s", argv[0], e.strerror)
         raise
 
     q = Queue()
@@ -282,7 +292,7 @@ def execReadlines(command, argv, stdin=None, root='/', env_prune=None):
 ## Run a shell.
 def execConsole():
     try:
-        proc = subprocess.Popen(["/bin/sh"])
+        proc = startProgram(["/bin/sh"], stdout=None, stderr=None)
         proc.wait()
     except OSError as e:
         raise RuntimeError("Error running /bin/sh: " + e.strerror)
diff --git a/tests/pyanaconda_tests/iutil_test.py b/tests/pyanaconda_tests/iutil_test.py
index 358a2d7..b49d5ba 100644
--- a/tests/pyanaconda_tests/iutil_test.py
+++ b/tests/pyanaconda_tests/iutil_test.py
@@ -268,6 +268,46 @@ exit 0
         finally:
             signal.signal(signal.SIGHUP, old_HUP_handler)
 
+    def start_program_preexec_fn_test(self):
+        """Test passing preexec_fn to startProgram."""
+
+        marker_text = "yo wassup man"
+        # Create a temporary file that will be written before exec
+        with tempfile.NamedTemporaryFile() as testfile:
+
+            # Write something to testfile to show this method was run
+            def preexec():
+                # Open a copy of the file here since close_fds has already closed the descriptor
+                testcopy = open(testfile.name, 'w')
+                testcopy.write(marker_text)
+                testcopy.close()
+
+            with timer(5):
+                # Start a program that does nothing, with a preexec_fn
+                proc = iutil.startProgram(["/bin/true"], preexec_fn=preexec)
+                proc.communicate()
+
+            # Rewind testfile and look for the text
+            testfile.seek(0, os.SEEK_SET)
+            self.assertEqual(testfile.read(), marker_text)
+
+    def start_program_stdout_test(self):
+        """Test redirecting stdout with startProgram."""
+
+        marker_text = "yo wassup man"
+        # Create a temporary file that will be written by the program
+        with tempfile.NamedTemporaryFile() as testfile:
+            # Open a new copy of the file so that the child doesn't close and
+            # delete the NamedTemporaryFile
+            stdout = open(testfile.name, 'w')
+            with timer(5):
+                proc = iutil.startProgram(["/bin/echo", marker_text], stdout=stdout)
+                proc.communicate()
+
+            # Rewind testfile and look for the text
+            testfile.seek(0, os.SEEK_SET)
+            self.assertEqual(testfile.read().strip(), marker_text)
+
 class MiscTests(unittest.TestCase):
     def get_dir_size_test(self):
         """Test the getDirSize."""
-- 
1.9.3



More information about the anaconda-patches mailing list