mirror of
https://github.com/flatcar/scripts.git
synced 2025-08-06 20:47:00 +02:00
341 lines
12 KiB
Python
Executable File
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())
|