[OE-core] [RFC PATCH] oe/gpg_sign: use our own immplementation of pexpect

Markus Lehtonen markus.lehtonen at linux.intel.com
Mon Mar 21 16:15:07 UTC 2016


Implement a simple python-expect replacement in order to get rid of the
(currently undocumented) external dependency. Pexpect is only used for
rpm package signing.

[YOCTO #9304]

Signed-off-by: Markus Lehtonen <markus.lehtonen at linux.intel.com>
---
 meta/lib/oe/gpg_sign.py | 104 +++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 90 insertions(+), 14 deletions(-)

diff --git a/meta/lib/oe/gpg_sign.py b/meta/lib/oe/gpg_sign.py
index e738397..9887c1d 100644
--- a/meta/lib/oe/gpg_sign.py
+++ b/meta/lib/oe/gpg_sign.py
@@ -1,9 +1,87 @@
 """Helper module for GPG signing"""
 import os
+import time
 
 import bb
 import oe.utils
 
+
+class Pexpect(object):
+    """Naive and limited (p)expect functionality"""
+    class PexpectErr(Exception):
+        """Pexpect error"""
+        pass
+
+    def __init__(self, cmd):
+        import pty
+        self.pid, self.fd = pty.fork()
+        if self.pid == 0:
+            os.execv(cmd[0], cmd)
+        self.status = None
+        self.buf = ''
+        self.buf_readp = 0
+
+    def check_exitstatus(self):
+        """Return child status or None if still alive"""
+        if self.status is None:
+            pid, status = os.waitpid(self.pid, os.WNOHANG)
+            if pid != 0:
+                self.status = status
+        return self.status
+
+    def close(self):
+        """Close connection and terminate our child"""
+        import signal
+        if self.fd == -1:
+            return
+        os.close(self.fd)
+        self.fd = -1
+        time.sleep(0.1)
+
+        # Kill child process if it's still alive
+        if self.check_exitstatus() is None:
+            os.kill(self.pid, signal.SIGHUP)
+            # Give the process some time to terminate peacefully
+            time.sleep(0.5)
+            if self.check_exitstatus() is None:
+                os.kill(self.pid, signal.SIGKILL)
+                time.sleep(0.5)
+                if self.check_exitstatus() is None:
+                    bb.warn('Failed to kill PID %d' % self.pid)
+
+    def expect_exact(self, expected, timeout):
+        """Wait until expected output is detected. Use None to wait until EOF"""
+        import errno
+        import select
+        end_time = time.time() + timeout
+        while time.time() < end_time:
+            ready = select.select([self.fd], [], [], end_time - time.time())
+            if ready[0]:
+                try:
+                    self.buf += os.read(self.fd, 4096)
+                except OSError as err:
+                    if err.errno == errno.EIO:
+                        if expected is None:
+                            return
+                        else:
+                            raise self.PexpectErr("Unexpected EOF")
+                    raise
+                if expected is not None:
+                    ind = self.buf.find(expected, self.buf_readp)
+                    if ind >= 0:
+                        self.buf_readp = ind + len(expected)
+                        return
+                    elif len(self.buf) > len(expected):
+                        # No need to search from beginning of buf every time
+                        self.buf_readp = len(self.buf) - len(expected)
+        raise self.PexpectErr("Timeout")
+
+    def sendline(self, data):
+        """Write data to child proces stdin"""
+        os.write(self.fd, data)
+        os.write(self.fd, '\n')
+
+
 class LocalSigner(object):
     """Class for handling local (on the build host) signing"""
     def __init__(self, d):
@@ -28,28 +106,26 @@ class LocalSigner(object):
 
     def sign_rpms(self, files, keyid, passphrase_file):
         """Sign RPM files"""
-        import pexpect
-
-        cmd = self.rpm_bin + " --addsign --define '_gpg_name %s' " % keyid
+        cmd = [self.rpm_bin, '--addsign', '--define', '_gpg_name ' + keyid]
         if self.gpg_bin:
-            cmd += "--define '%%__gpg %s' " % self.gpg_bin
+            cmd += ['--define', '__gpg ' + self.gpg_bin]
         if self.gpg_path:
-            cmd += "--define '_gpg_path %s' " % self.gpg_path
-        cmd += ' '.join(files)
+            cmd += ['--define', '_gpg_path ' + self.gpg_path]
+        cmd += files
 
         # Need to use pexpect for feeding the passphrase
-        proc = pexpect.spawn(cmd)
+        proc = Pexpect(cmd)
         try:
             proc.expect_exact('Enter pass phrase:', timeout=15)
             with open(passphrase_file) as fobj:
                 proc.sendline(fobj.readline().rstrip('\n'))
-            proc.expect(pexpect.EOF, timeout=900)
-            proc.close()
-        except pexpect.TIMEOUT as err:
-            bb.error('rpmsign timeout: %s' % err)
-            proc.terminate()
-        if os.WEXITSTATUS(proc.status) or not os.WIFEXITED(proc.status):
-            bb.error('rpmsign failed: %s' % proc.before.strip())
+            proc.expect_exact(None, timeout=900)
+        except Pexpect.PexpectErr as err:
+            bb.error('rpmsign unexpected output: %s' % err)
+        proc.close()
+        status = proc.check_exitstatus()
+        if os.WEXITSTATUS(status) or not os.WIFEXITED(status):
+            bb.error('rpmsign failed: %s' % proc.buf[proc.buf_readp:])
             raise bb.build.FuncFailed("Failed to sign RPM packages")
 
 
-- 
2.6.2




More information about the Openembedded-core mailing list