blob: ea8eef4ee3164ec62503aa6ce66c333b152e1de0 [file] [log] [blame]
#!/usr/bin/python -O
import sys, os
from optparse import OptionParser, OptionValueError
if not hasattr(__builtins__, "set"):
from sets import Set as set
import re
try:
import portage
except ImportError:
from os import path as osp
sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
import portage
import portage_const, portage_exception
class WorldHandler(object):
def name():
return "world"
name = staticmethod(name)
def __init__(self):
self.invalid = []
self.not_installed = []
self.invalid_category = []
self.okay = []
self.world_file = os.path.join("/", portage_const.WORLD_FILE)
self.found = os.access(self.world_file, os.R_OK)
categories = set(portage.settings.categories)
myroot = portage.settings["ROOT"]
vardb = portage.db[myroot]["vartree"].dbapi
for atom in open(self.world_file).read().split():
if not portage.isvalidatom(atom):
self.invalid.append(atom)
continue
cp = portage.dep_getkey(atom)
okay = True
if not vardb.match(atom):
self.not_installed.append(atom)
okay = False
if portage.catsplit(cp)[0] not in categories:
self.invalid_category.append(atom)
okay = False
if okay:
self.okay.append(atom)
def check(self):
errors = []
if self.found:
errors += map(lambda x: "'%s' is not a valid atom" % x, self.invalid)
errors += map(lambda x: "'%s' is not installed" % x, self.not_installed)
errors += map(lambda x: "'%s' has a category that is not listed in /etc/portage/categories" % x, self.invalid_category)
else:
errors.append(self.world_file + " could not be opened for reading")
return errors
def fix(self):
errors = []
try:
portage.write_atomic(self.world_file,
"\n".join(sorted(self.okay)) + "\n")
except portage_exception.PortageException:
errors.append(self.world_file + " could not be opened for writing")
return errors
class VdbKeyHandler(object):
def name():
return "vdbkeys"
name = staticmethod(name)
def __init__(self):
self.list = portage.db["/"]["vartree"].dbapi.cpv_all()
self.missing = []
self.keys = ["HOMEPAGE", "SRC_URI", "KEYWORDS", "DESCRIPTION"]
for p in self.list:
mydir = os.path.join(os.sep, portage.settings["ROOT"], portage_const.VDB_PATH, p)+os.sep
ismissing = True
for k in self.keys:
if os.path.exists(mydir+k):
ismissing = False
break
if ismissing:
self.missing.append(p)
def check(self):
return ["%s has missing keys" % x for x in self.missing]
def fix(self):
errors = []
for p in self.missing:
mydir = os.path.join(os.sep, portage.settings["ROOT"], portage_const.VDB_PATH, p)+os.sep
if not os.access(mydir+"environment.bz2", os.R_OK):
errors.append("Can't access %s" % (mydir+"environment.bz2"))
elif not os.access(mydir, os.W_OK):
errors.append("Can't create files in %s" % mydir)
else:
env = os.popen("bzip2 -dcq "+mydir+"environment.bz2", "r")
envlines = env.read().split("\n")
env.close()
for k in self.keys:
s = [l for l in envlines if l.startswith(k+"=")]
if len(s) > 1:
errors.append("multiple matches for %s found in %senvironment.bz2" % (k, mydir))
elif len(s) == 0:
s = ""
else:
s = s[0].split("=",1)[1]
s = s.lstrip("$").strip("\'\"")
s = re.sub("(\\\\[nrt])+", " ", s)
s = " ".join(s.split()).strip()
if s != "":
try:
keyfile = open(mydir+os.sep+k, "w")
keyfile.write(s+"\n")
keyfile.close()
except (IOError, OSError), e:
errors.append("Could not write %s, reason was: %s" % (mydir+k, e))
return errors
def emaint_main(myargv):
# Similar to emerge, emaint needs a default umask so that created
# files (such as the world file) have sane permissions.
os.umask(022)
# TODO: Create a system that allows external modules to be added without
# the need for hard coding.
modules = {"world" : WorldHandler}
module_names = modules.keys()
module_names.sort()
module_names.insert(0, "all")
def exclusive(option, *args, **kw):
var = kw.get("var", None)
if var is None:
raise ValueError("var not specified to exclusive()")
if getattr(parser, var, ""):
raise OptionValueError("%s and %s are exclusive options" % (getattr(parser, var), option))
setattr(parser, var, str(option))
usage = "usage: emaint [options] " + " | ".join(module_names)
usage+= "\n\nCurrently emaint can only check and fix problems with one's world\n"
usage+= "file. Future versions will integrate other portage check-and-fix\n"
usage+= "tools and provide a single interface to system health checks."
parser = OptionParser(usage=usage, version=portage.VERSION)
parser.add_option("-c", "--check", help="check for problems",
action="callback", callback=exclusive, callback_kwargs={"var":"action"})
parser.add_option("-f", "--fix", help="attempt to fix problems",
action="callback", callback=exclusive, callback_kwargs={"var":"action"})
parser.action = None
(options, args) = parser.parse_args(args=myargv)
if len(args) != 1:
parser.error("Incorrect number of arguments")
if args[0] not in module_names:
parser.error("%s target is not a known target" % args[0])
if parser.action:
action = parser.action
else:
print "Defaulting to --check"
action = "-c/--check"
if args[0] == "all":
tasks = modules.values()
else:
tasks = [modules[args[0]]]
if action == "-c/--check":
status = "Checking %s for problems"
func = "check"
else:
status = "Attempting to fix %s"
func = "fix"
for task in tasks:
print status % task.name()
inst = task()
result = getattr(inst, func)()
if result:
print
print "\n".join(result)
print "\n"
print "Finished"
if __name__ == "__main__":
emaint_main(sys.argv[1:])