#!/usr/bin/env python # # This script reads the dependency information under /var/db/pkg # and verifies their consistency. Any mismatches are printed. # Also, several other things are checked, for example that every # package must have a unique "origin". # # Copyright (C) 2007-2011 Oliver Fromme # All rights reserved. Standard 2-clause BSD license and disclaimer apply, # please refer to the file /usr/share/examples/etc/bsd-style-copyright on # FreeBSD or ask the author for a copy. # import os from sys import argv, stderr, exit from os.path import join, basename, isfile, isdir from errno import ENOENT from subprocess import check_output, CalledProcessError me = basename(argv[0]) default_pkg_dbdir = "/var/db/pkg" default_portsdir = "/usr/ports" usage = """ Usage: %(me)s [-q] PKG_DBDIR and PORTSDIR are picked up from the environment or /etc/make.conf (the latter has priority). If not set, the usual defaults are used. Options: -q (quiet) Don't list dependencies, only report problems. """.replace('\t', '') % globals() def warn (msg): print >> stderr, "%s" % msg def err (msg): warn (msg) exit (1) if len(argv) == 2 and argv[1] == '-q': quiet = True else: quiet = False if len(argv) != 1: print >> stderr, usage exit (1) ############################################ ### ### ### Initialize pkg_dbdir and portsdir. ### ### ### ############################################ def get_make_var (name, default): try: value = check_output(["make", "-f", "/dev/null", "-V", name]).strip() except (OSError, CalledProcessError), detail: if hasattr(detail, "errno") and detail.errno == ENOENT: warn ("WARNING: Cannot execute make(1): " + detail.strerror) else: warn ("WARNING: make(1) failed.") warn ("WARNING: Assuming %s = %s" % (name, default)) value = "" return value or default pkg_dbdir = get_make_var("PKG_DBDIR", default_pkg_dbdir) portsdir = get_make_var("PORTSDIR", default_portsdir) check_origins = isdir(portsdir) if not check_origins: warn ("WARNING: %s does not exist, origins will not be checked." % portsdir) ############################################# ### ### ### Get the list of installed packages. ### ### ### ############################################# try: pkglist = os.listdir(pkg_dbdir) except OSError, detail: err ("Can't read directory %s: %s" % (pkg_dbdir, detail.strerror)) if not pkglist: err ("No packages registered.") pkglist.sort() ################################################ ### ### ### Read +CONTENTS and +REQUIRED_BY files. ### ### ### ################################################ reqby = {} depend = {} name = {} origin = {} origin_use = {} for i in pkglist: fname = join(pkg_dbdir, i, "+CONTENTS") if isfile(fname): contents = [line for line in file(fname)] depend[i] = sorted([ line.split()[1] for line in contents if line.startswith("@pkgdep ") ]) name[i] = [ line.split()[1] for line in contents if line.startswith("@name ") ] if len(name[i]) == 1: name[i] = name[i][0] origin[i] = [ line.split(':')[1].strip() for line in contents if line.startswith("@comment ORIGIN:") ] for j in origin[i]: if j in origin_use: origin_use[j].append(i) else: origin_use[j] = [i] if len(origin[i]) == 1: origin[i] = origin[i][0] else: depend[i] = [] origin[i] = [] fname = join(pkg_dbdir, i, "+REQUIRED_BY") if isfile(fname): reqby[i] = sorted([ line.strip() for line in file(fname) if line[0:1] not in "@#" ]) else: reqby[i] = [] if not quiet: print "%-32s depends on %2d pkgs, required by %2d pkgs" \ % (i, len(depend[i]), len(reqby[i])) ################################# ### ### ### Analyze what we've got. ### ### ### ################################# for i in origin_use: if len(origin_use[i]) > 1: warn ("INCONSISTENCY: Origin %s is used by multiple packages:" % i) for j in origin_use[i]: warn (" - %s" % j) for i in pkglist: if i not in name: if not isdir(join(pkg_dbdir, i)): warn ("WARNING: Bogus file %s in %s" % (i, pkg_dbdir)) elif not isfile(join(pkg_dbdir, i, "+CONTENTS")): warn ("WARNING: No +CONTENTS in %s" % join(pkg_dbdir, i)) else: err ("INTERNAL ERROR: %s/+CONTENTS exists, but name[] not set" % i) else: if not name[i]: print "BROKEN: %s has no @name in +CONTENTS" % i elif type(name[i]) == list and len(name[i]) > 1: print "BROKEN: %s has multiple @name in +CONTENTS" % i elif name[i] != i: print "INCONSISTENCY: %s has @name %s" % (i, repr(name[i])) if not origin[i]: print "BROKEN: %s has no ORIGIN in +CONTENTS" % i elif type(origin[i]) == list and len(origin[i]) > 1: print "BROKEN: %s has multiple ORIGIN in +CONTENTS" % i elif check_origins and not isdir(join(portsdir, origin[i])): print "WARNING: origin %s of %s does not exist" % (origin[i], i) seen = {} for j in depend[i]: if j not in pkglist: print "MISSING: %s (dependency of %s)" % (j, i) elif i not in reqby[j]: print "MISMATCH: %s depends on %s but no required-by record" % (i, j) if j in seen: print "DUPLICATE: %s (dependency of %s)" % (j ,i) else: seen[j] = True seen = {} for j in reqby[i]: if j not in pkglist: print "MISSING: %s (listed in required-by of %s)" % (j, i) elif i not in depend[j]: print "MISMATCH: %s required by %s but no dependency recorded" % (i, j) if j in seen: print 'DUPLICATE: %s (in required-by of %s)' % (j ,i) else: seen[j] = True #--