flatcar-scripts/build_library/check_root
Flatcar Buildbot c7bbb2b1e2 2345.0.0
2019-12-04 14:59:11 +01:00

341 lines
12 KiB
Python
Executable File

#!/usr/bin/python
# Copyright (c) 2015 The CoreOS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import unicode_literals
import fnmatch
import os
import stat
import sys
import portage
from portage import dep
from portage import output
from portage.dep.soname.SonameAtom import SonameAtom
from portage.dep.soname.parse import parse_soname_deps
VARDB = portage.db[portage.root]["vartree"].dbapi
# TODO(marneam): possibly accept globs for arch and sonames
IGNORE_MISSING = {
# /usr/lib/go/src/debug/elf/testdata/gcc-386-freebsd-exec
# /usr/lib/go/src/debug/elf/testdata/gcc-amd64-linux-exec
"dev-lang/go": [SonameAtom("x86_32", "libc.so.6"),
SonameAtom("x86_64", "libc.so.6")],
"dev-lang/go-bootstrap": [SonameAtom("x86_32", "libc.so.6"),
SonameAtom("x86_64", "libc.so.6")],
# RPATHs and symlinks apparently confuse the perl-5.24 package
"dev-lang/perl": [SonameAtom("arm_64", "libperl.so.5.26.2"),
SonameAtom("x86_64", "libperl.so.5.26.2")],
"dev-perl/XML-Parser": [SonameAtom("x86_64", "libc.so.6"),
SonameAtom("x86_64", "libexpat.so.1")],
"dev-perl/libintl-perl": [SonameAtom("x86_64", "libc.so.6")],
"dev-util/boost-build": [SonameAtom("x86_64", "libc.so.6")],
"net-dns/dnsmasq": [SonameAtom("x86_64", "libc.so.6")],
"sys-apps/texinfo": [SonameAtom("x86_64", "libc.so.6"),
SonameAtom("x86_64", "libperl.so.5.26")],
# https://bugs.gentoo.org/show_bug.cgi?id=554582
"net-firewall/ebtables": [SonameAtom("arm_64", "libebt_802_3.so"),
SonameAtom("arm_64", "libebt_among.so"),
SonameAtom("arm_64", "libebt_arp.so"),
SonameAtom("arm_64", "libebt_arpreply.so"),
SonameAtom("arm_64", "libebt_ip.so"),
SonameAtom("arm_64", "libebt_ip6.so"),
SonameAtom("arm_64", "libebt_limit.so"),
SonameAtom("arm_64", "libebt_log.so"),
SonameAtom("arm_64", "libebt_mark.so"),
SonameAtom("arm_64", "libebt_mark_m.so"),
SonameAtom("arm_64", "libebt_nat.so"),
SonameAtom("arm_64", "libebt_nflog.so"),
SonameAtom("arm_64", "libebt_pkttype.so"),
SonameAtom("arm_64", "libebt_redirect.so"),
SonameAtom("arm_64", "libebt_standard.so"),
SonameAtom("arm_64", "libebt_stp.so"),
SonameAtom("arm_64", "libebt_ulog.so"),
SonameAtom("arm_64", "libebt_vlan.so"),
SonameAtom("arm_64", "libebtable_broute.so"),
SonameAtom("arm_64", "libebtable_filter.so"),
SonameAtom("arm_64", "libebtable_nat.so"),
SonameAtom("x86_64", "libebt_802_3.so"),
SonameAtom("x86_64", "libebt_among.so"),
SonameAtom("x86_64", "libebt_arp.so"),
SonameAtom("x86_64", "libebt_arpreply.so"),
SonameAtom("x86_64", "libebt_ip.so"),
SonameAtom("x86_64", "libebt_ip6.so"),
SonameAtom("x86_64", "libebt_limit.so"),
SonameAtom("x86_64", "libebt_log.so"),
SonameAtom("x86_64", "libebt_mark.so"),
SonameAtom("x86_64", "libebt_mark_m.so"),
SonameAtom("x86_64", "libebt_nat.so"),
SonameAtom("x86_64", "libebt_nflog.so"),
SonameAtom("x86_64", "libebt_pkttype.so"),
SonameAtom("x86_64", "libebt_redirect.so"),
SonameAtom("x86_64", "libebt_standard.so"),
SonameAtom("x86_64", "libebt_stp.so"),
SonameAtom("x86_64", "libebt_ulog.so"),
SonameAtom("x86_64", "libebt_vlan.so"),
SonameAtom("x86_64", "libebtable_broute.so"),
SonameAtom("x86_64", "libebtable_filter.so"),
SonameAtom("x86_64", "libebtable_nat.so")],
# Ignore the Rust libraries in their own libdir.
"dev-libs/rustlib": [SonameAtom("arm_64", "librustc_data_structures.so"),
SonameAtom("arm_64", "librustc_errors.so"),
SonameAtom("arm_64", "libserialize.so"),
SonameAtom("arm_64", "libstd.so"),
SonameAtom("arm_64", "libsyntax.so"),
SonameAtom("arm_64", "libsyntax_pos.so"),
SonameAtom("arm_64", "libterm.so"),
SonameAtom("x86_64", "librustc_data_structures.so"),
SonameAtom("x86_64", "librustc_errors.so"),
SonameAtom("x86_64", "libserialize.so"),
SonameAtom("x86_64", "libstd.so"),
SonameAtom("x86_64", "libsyntax.so"),
SonameAtom("x86_64", "libsyntax_pos.so"),
SonameAtom("x86_64", "libterm.so")],
"sys-kernel/coreos-modules": [SonameAtom("x86_64", "libc.so.6"),
SonameAtom("x86_64", "libcrypto.so.1.0.0")],
}
USR_LINKS = ("/bin/", "/sbin/", "/lib/", "/lib32/", "/lib64/")
IGNORE_SHEBANG = (
b"*/python[0-9].[0-9]/cgi.py",
b"*/usr/lib64/modules/*/source/scripts/*",
b"*/usr/share/nova-agent/*/etc/gentoo/nova-agent",
b"*/tmp/*",
b"*/Documentation/*",
b"*/doc/*",
)
IGNORE_SYMLINK = (
# symlinks to sdk chroot
b"/build/*",
b"/var/tmp/portage/*",
b"/etc/portage/*",
# symlinks to /run
b"/usr/share/baselayout/motd",
b"/etc/issue",
b"/etc/motd",
# Other
b"/etc/lsb-release", # set later in the build process
b"/usr/share/coreos", # set later in the build process
b"/etc/coreos" # set later in the build process
)
def provided_sonames():
for cpv in VARDB.cpv_all():
raw = VARDB.aux_get(cpv, ["PROVIDES"])[0]
for atom in parse_soname_deps(raw):
yield atom
# soname.provided in PORTAGE_CONFIGROOT
for atom in VARDB.settings.soname_provided:
yield atom
def ignore_sonames(cpv):
for key in dep.match_to_list(cpv, IGNORE_MISSING.iterkeys()):
for atom in IGNORE_MISSING[key]:
yield atom
def missing_sonames():
provided = frozenset(provided_sonames())
for cpv in VARDB.cpv_all():
raw = VARDB.aux_get(cpv, ["REQUIRES"])[0]
requires = frozenset(parse_soname_deps(raw))
ignore = frozenset(ignore_sonames(cpv))
missing = requires - provided - ignore
if missing:
yield (cpv, missing)
def usr_conflicts():
for cpv in VARDB.cpv_all():
raw = VARDB.aux_get(cpv, ["CONTENTS"])[0]
usr = set()
root = set()
# format is:
# obj /path goo 123
# dir /path/foo
# sym /this -> that 123
# and so on
for line in raw.split("\n"):
if line[:4] != "obj " and line[:4] != "sym ":
continue
# yeah, hard to read, trying to make it fast...
i = line.find("/", 5)
topdir = line[4:i+1]
if topdir == "/usr/":
j = line.find("/", 9)
nextdir = line[8:j+1]
if nextdir in USR_LINKS:
end = line.find(" ", 8)
usr.add(line[8:end])
elif topdir in USR_LINKS:
end = line.find(" ", 4)
root.add(line[4:end])
conflicts = frozenset(root).intersection(usr)
if conflicts:
yield (cpv, conflicts)
def check_libs():
ok = True
for cpv, sonames in missing_sonames():
error("%s is missing libraries:", cpv)
for soname in sonames:
error("\t%s", soname)
ok = False
return ok
def check_usr():
ok = True
for cpv, conflicts in usr_conflicts():
error("%s has paths that conflict with /usr", cpv)
for path in conflicts:
error("\t%s", path)
ok = False
return ok
def is_exe(path):
# just check other, assuming root or group only commands are not scripts.
perms = stat.S_IROTH | stat.S_IXOTH
mode = os.lstat(path).st_mode
return stat.S_ISREG(mode) and (mode & perms) == perms
def check_shebang():
ok = True
cache = {}
root = os.environ.get("ROOT", b"/")
for parent, _, files in os.walk(root):
for path in [os.path.join(parent, f) for f in files]:
if any(fnmatch.fnmatchcase(path, i) for i in IGNORE_SHEBANG):
continue
if not is_exe(path):
continue
with open(path, "r") as fd:
line = fd.readline(80)
if not line.startswith(b"#!"):
continue
args = line[2:].rstrip().split(None, 2)
cmd = args.pop(0)
if cmd in ('/usr/bin/env', '/bin/env') and args:
prog = args.pop(0)
if prog.startswith('-') and args:
prog = args.pop(0)
cmd = '(env)/%s' % prog
if cmd not in cache:
if cmd.startswith('(env)'):
cache[cmd] = False
for bindir in (root+'/usr/bin', root+'/usr/sbin'):
if os.path.exists(os.path.join(bindir, prog)):
cache[cmd] = True
break
else:
cache[cmd] = os.path.exists(root+cmd)
if not cache[cmd]:
relpath = path[len(root):]
error("%s: %s does not exist", relpath, cmd)
ok = False
return ok
class chrooted():
"""
chrooted provides a context so that it can be used via with.
For example:
with chrooted("/some/rootfs"):
do_operations_in_rootfs()
do_operations_not_in_rootfs()
"""
def __init__(self, path):
self.path = path
def __enter__(self):
self.restore_fd = os.open(b"/", os.O_RDONLY)
self.working_dir = os.getcwd()
os.chroot(self.path)
def __exit__(self, type, value, traceback):
os.fchdir(self.restore_fd)
os.chroot(b".")
os.chdir(self.working_dir)
os.close(self.restore_fd)
def check_symlink():
if os.getuid() != 0:
error("symlink check must be run as root (chroot)")
return False
ok = True
root = os.environ.get("ROOT", b"/")
with chrooted(root):
for parent, dirs, files in os.walk(b"/"):
for path in [os.path.join(parent, p) for p in files + dirs]:
if any(fnmatch.fnmatchcase(path, i) for i in IGNORE_SYMLINK):
continue
if os.path.islink(path) and not os.path.exists(path):
ok = False
error("broken link: %s", path)
return ok
def error(fmt, *args):
sys.stderr.write(output.red(fmt % args))
sys.stderr.write("\n")
def main():
ok = True
check_funcs = {
"libs": check_libs,
"usr": check_usr,
"shebang": check_shebang,
"symlink": check_symlink,
}
if not sys.stderr.isatty():
output.nocolor()
checks = sys.argv[1:]
if not checks:
checks = check_funcs.keys()
for check in checks:
func = check_funcs.get(check)
if func:
ok = func() and ok
else:
error("Unknown test name '%s'", check)
error("Valid tests: %s", " ".join(check_funcs))
ok = False
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())