diff --git a/src/pylorax/__init__.py b/src/pylorax/__init__.py index b1178d07..cbe66c3c 100644 --- a/src/pylorax/__init__.py +++ b/src/pylorax/__init__.py @@ -156,7 +156,8 @@ class Lorax(BaseLoraxClass): add_templates=None, add_template_vars=None, add_arch_templates=None, - add_arch_template_vars=None): + add_arch_template_vars=None, + verify=True): assert self._configured @@ -289,6 +290,13 @@ class Lorax(BaseLoraxClass): logger.info("cleaning unneeded files") rb.cleanup() + if verify: + logger.info("verifying the installroot") + if not rb.verify(): + sys.exit(1) + else: + logger.info("Skipping verify") + if self.debug: rb.writepkgsizes(joinpaths(logdir, "final-pkgsizes.txt")) diff --git a/src/pylorax/treebuilder.py b/src/pylorax/treebuilder.py index bea9bcba..5d4f8b79 100644 --- a/src/pylorax/treebuilder.py +++ b/src/pylorax/treebuilder.py @@ -23,12 +23,14 @@ logger = logging.getLogger("pylorax.treebuilder") import os, re from os.path import basename from shutil import copytree, copy2 +from pathlib import Path +import itertools from pylorax.sysutils import joinpaths, remove from pylorax.base import DataHolder from pylorax.ltmpl import LoraxTemplateRunner import pylorax.imgutils as imgutils -from pylorax.executils import runcmd, runcmd_output +from pylorax.executils import runcmd, runcmd_output, execWithCapture templatemap = { 'i386': 'x86.tmpl', @@ -143,6 +145,54 @@ class RuntimeBuilder(object): '''Remove unneeded packages and files with runtime-cleanup.tmpl''' self._runner.run("runtime-cleanup.tmpl") + def verify(self): + '''Ensure that contents of the installroot can run''' + status = True + + ELF_MAGIC = b'\x7fELF' + + # Iterate over all files in /usr/bin and /usr/sbin + # For ELF files, gather them into a list and we'll check them all at + # the end. For files with a #!, check them as we go + elf_files = [] + usr_bin = Path(self.vars.root + '/usr/bin') + usr_sbin = Path(self.vars.root + '/usr/sbin') + for path in (str(x) for x in itertools.chain(usr_bin.iterdir(), usr_sbin.iterdir()) \ + if x.is_file()): + with open(path, "rb") as f: + magic = f.read(4) + if magic == ELF_MAGIC: + # Save the path, minus the chroot prefix + elf_files.append(path[len(self.vars.root):]) + elif magic[:2] == b'#!': + # Reopen the file as text and read the first line. + # Open as latin-1 so that stray 8-bit characters don't make + # things blow up. We only really care about ASCII parts. + with open(path, "rt", encoding="latin-1") as f_text: + # Remove the #!, split on space, and take the first part + shabang = f_text.readline()[2:].split()[0] + + # Does the path exist? + if not os.path.exists(self.vars.root + shabang): + logger.error('%s, needed by %s, does not exist', shabang, path) + status = False + + # Now, run ldd on all the ELF files + # Just run ldd once on everything so it isn't logged a million times. + # At least one thing in the list isn't going to be a dynamic executable, + # so use execWithCapture to ignore the exit code. + filename = '' + for line in execWithCapture('ldd', elf_files, root=self.vars.root, + log_output=False, filter_stderr=True).split('\n'): + if line and not line[0].isspace(): + # New filename header, strip the : at the end and save + filename = line[:-1] + elif 'not found' in line: + logger.error('%s, needed by %s, not found', line.split()[0], filename) + status = False + + return status + def writepkgsizes(self, pkgsizefile): '''debugging data: write a big list of pkg sizes''' fobj = open(pkgsizefile, "w") diff --git a/src/sbin/lorax b/src/sbin/lorax index 93e9ed7e..f92aeb96 100755 --- a/src/sbin/lorax +++ b/src/sbin/lorax @@ -114,6 +114,8 @@ def main(args): optional.add_argument("--add-arch-template-var", dest="add_arch_template_vars", action="append", help="Set variable for architecture-specific image", default=[]) + optional.add_argument("--noverify", action="store_false", default=True, dest="verify", + help="Do not verify the install root") # add the show version option parser.add_argument("-V", help="show program's version number and exit", @@ -200,7 +202,7 @@ def main(args): add_template_vars=parsed_add_template_vars, add_arch_templates=opts.add_arch_templates, add_arch_template_vars=parsed_add_arch_template_vars, - remove_temp=True) + remove_temp=True, verify=opts.verify) def get_dnf_base_object(installroot, repositories, mirrorlists=None,