Add a verification step to Lorax.run.

After the cleanup step, check that everything in /usr/bin and /usr/sbin
can still run. Currently, this just checks that ELF files have
everything they need to link, and scripts have an interpreter.

Verifying is on by default but can be skipped with --noverify
This commit is contained in:
David Shea 2015-07-17 19:22:53 -04:00
parent d6584e1d77
commit 04d170bb09
3 changed files with 63 additions and 3 deletions

View File

@ -156,7 +156,8 @@ class Lorax(BaseLoraxClass):
add_templates=None, add_templates=None,
add_template_vars=None, add_template_vars=None,
add_arch_templates=None, add_arch_templates=None,
add_arch_template_vars=None): add_arch_template_vars=None,
verify=True):
assert self._configured assert self._configured
@ -289,6 +290,13 @@ class Lorax(BaseLoraxClass):
logger.info("cleaning unneeded files") logger.info("cleaning unneeded files")
rb.cleanup() rb.cleanup()
if verify:
logger.info("verifying the installroot")
if not rb.verify():
sys.exit(1)
else:
logger.info("Skipping verify")
if self.debug: if self.debug:
rb.writepkgsizes(joinpaths(logdir, "final-pkgsizes.txt")) rb.writepkgsizes(joinpaths(logdir, "final-pkgsizes.txt"))

View File

@ -23,12 +23,14 @@ logger = logging.getLogger("pylorax.treebuilder")
import os, re import os, re
from os.path import basename from os.path import basename
from shutil import copytree, copy2 from shutil import copytree, copy2
from pathlib import Path
import itertools
from pylorax.sysutils import joinpaths, remove from pylorax.sysutils import joinpaths, remove
from pylorax.base import DataHolder from pylorax.base import DataHolder
from pylorax.ltmpl import LoraxTemplateRunner from pylorax.ltmpl import LoraxTemplateRunner
import pylorax.imgutils as imgutils import pylorax.imgutils as imgutils
from pylorax.executils import runcmd, runcmd_output from pylorax.executils import runcmd, runcmd_output, execWithCapture
templatemap = { templatemap = {
'i386': 'x86.tmpl', 'i386': 'x86.tmpl',
@ -143,6 +145,54 @@ class RuntimeBuilder(object):
'''Remove unneeded packages and files with runtime-cleanup.tmpl''' '''Remove unneeded packages and files with runtime-cleanup.tmpl'''
self._runner.run("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): def writepkgsizes(self, pkgsizefile):
'''debugging data: write a big list of pkg sizes''' '''debugging data: write a big list of pkg sizes'''
fobj = open(pkgsizefile, "w") fobj = open(pkgsizefile, "w")

View File

@ -114,6 +114,8 @@ def main(args):
optional.add_argument("--add-arch-template-var", dest="add_arch_template_vars", optional.add_argument("--add-arch-template-var", dest="add_arch_template_vars",
action="append", help="Set variable for architecture-specific image", action="append", help="Set variable for architecture-specific image",
default=[]) default=[])
optional.add_argument("--noverify", action="store_false", default=True, dest="verify",
help="Do not verify the install root")
# add the show version option # add the show version option
parser.add_argument("-V", help="show program's version number and exit", 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_template_vars=parsed_add_template_vars,
add_arch_templates=opts.add_arch_templates, add_arch_templates=opts.add_arch_templates,
add_arch_template_vars=parsed_add_arch_template_vars, 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, def get_dnf_base_object(installroot, repositories, mirrorlists=None,