summaryrefslogtreecommitdiffstats
path: root/bin/emaint
diff options
context:
space:
mode:
authorBrian Dolbec <dolsen@gentoo.org>2012-07-22 17:50:39 -0700
committerZac Medico <zmedico@gentoo.org>2012-07-22 17:50:39 -0700
commit9e19ac0a16a57f3dded8124e650fb6bf6f3d00be (patch)
tree721568bac79a9c02abc556ca8981c4373dac264c /bin/emaint
parent8f9ba227869775cf7f35037283e88e4cee047703 (diff)
downloadportage-9e19ac0a16a57f3dded8124e650fb6bf6f3d00be.tar.gz
portage-9e19ac0a16a57f3dded8124e650fb6bf6f3d00be.tar.bz2
portage-9e19ac0a16a57f3dded8124e650fb6bf6f3d00be.zip
emaint: split into separate modules
Diffstat (limited to 'bin/emaint')
-rwxr-xr-xbin/emaint659
1 files changed, 28 insertions, 631 deletions
diff --git a/bin/emaint b/bin/emaint
index 21604511b..bee46c40d 100755
--- a/bin/emaint
+++ b/bin/emaint
@@ -2,16 +2,29 @@
# Copyright 2005-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
+"""'The emaint program provides an interface to system health
+ checks and maintenance.
+"""
+
from __future__ import print_function
-import errno
-import re
-import signal
-import stat
import sys
-import textwrap
-import time
-from optparse import OptionParser, OptionValueError
+import errno
+# This block ensures that ^C interrupts are handled quietly.
+try:
+ import signal
+
+ def exithandler(signum,frame):
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
+ signal.signal(signal.SIGTERM, signal.SIG_IGN)
+ sys.exit(1)
+
+ signal.signal(signal.SIGINT, exithandler)
+ signal.signal(signal.SIGTERM, exithandler)
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+
+except KeyboardInterrupt:
+ sys.exit(1)
try:
import portage
@@ -20,629 +33,13 @@ except ImportError:
sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
import portage
-from portage import os
-from portage.util import writemsg
-
-if sys.hexversion >= 0x3000000:
- long = int
-
-class WorldHandler(object):
-
- short_desc = "Fix problems in the world file"
-
- def name():
- return "world"
- name = staticmethod(name)
-
- def __init__(self):
- self.invalid = []
- self.not_installed = []
- self.okay = []
- from portage._sets import load_default_config
- setconfig = load_default_config(portage.settings,
- portage.db[portage.settings['EROOT']])
- self._sets = setconfig.getSets()
-
- def _check_world(self, onProgress):
- eroot = portage.settings['EROOT']
- self.world_file = os.path.join(eroot, portage.const.WORLD_FILE)
- self.found = os.access(self.world_file, os.R_OK)
- vardb = portage.db[eroot]["vartree"].dbapi
-
- from portage._sets import SETPREFIX
- sets = self._sets
- world_atoms = list(sets["selected"])
- maxval = len(world_atoms)
- if onProgress:
- onProgress(maxval, 0)
- for i, atom in enumerate(world_atoms):
- if not isinstance(atom, portage.dep.Atom):
- if atom.startswith(SETPREFIX):
- s = atom[len(SETPREFIX):]
- if s in sets:
- self.okay.append(atom)
- else:
- self.not_installed.append(atom)
- else:
- self.invalid.append(atom)
- if onProgress:
- onProgress(maxval, i+1)
- continue
- okay = True
- if not vardb.match(atom):
- self.not_installed.append(atom)
- okay = False
- if okay:
- self.okay.append(atom)
- if onProgress:
- onProgress(maxval, i+1)
-
- def check(self, onProgress=None):
- self._check_world(onProgress)
- errors = []
- if self.found:
- errors += ["'%s' is not a valid atom" % x for x in self.invalid]
- errors += ["'%s' is not installed" % x for x in self.not_installed]
- else:
- errors.append(self.world_file + " could not be opened for reading")
- return errors
-
- def fix(self, onProgress=None):
- world_set = self._sets["selected"]
- world_set.lock()
- try:
- world_set.load() # maybe it's changed on disk
- before = set(world_set)
- self._check_world(onProgress)
- after = set(self.okay)
- errors = []
- if before != after:
- try:
- world_set.replace(self.okay)
- except portage.exception.PortageException:
- errors.append("%s could not be opened for writing" % \
- self.world_file)
- return errors
- finally:
- world_set.unlock()
-
-class BinhostHandler(object):
-
- short_desc = "Generate a metadata index for binary packages"
-
- def name():
- return "binhost"
- name = staticmethod(name)
-
- def __init__(self):
- eroot = portage.settings['EROOT']
- self._bintree = portage.db[eroot]["bintree"]
- self._bintree.populate()
- self._pkgindex_file = self._bintree._pkgindex_file
- self._pkgindex = self._bintree._load_pkgindex()
-
- def _need_update(self, cpv, data):
-
- if "MD5" not in data:
- return True
-
- size = data.get("SIZE")
- if size is None:
- return True
-
- mtime = data.get("MTIME")
- if mtime is None:
- return True
-
- pkg_path = self._bintree.getname(cpv)
- try:
- s = os.lstat(pkg_path)
- except OSError as e:
- if e.errno not in (errno.ENOENT, errno.ESTALE):
- raise
- # We can't update the index for this one because
- # it disappeared.
- return False
-
- try:
- if long(mtime) != s[stat.ST_MTIME]:
- return True
- if long(size) != long(s.st_size):
- return True
- except ValueError:
- return True
-
- return False
-
- def check(self, onProgress=None):
- missing = []
- cpv_all = self._bintree.dbapi.cpv_all()
- cpv_all.sort()
- maxval = len(cpv_all)
- if onProgress:
- onProgress(maxval, 0)
- pkgindex = self._pkgindex
- missing = []
- metadata = {}
- for d in pkgindex.packages:
- metadata[d["CPV"]] = d
- for i, cpv in enumerate(cpv_all):
- d = metadata.get(cpv)
- if not d or self._need_update(cpv, d):
- missing.append(cpv)
- if onProgress:
- onProgress(maxval, i+1)
- errors = ["'%s' is not in Packages" % cpv for cpv in missing]
- stale = set(metadata).difference(cpv_all)
- for cpv in stale:
- errors.append("'%s' is not in the repository" % cpv)
- return errors
-
- def fix(self, onProgress=None):
- bintree = self._bintree
- cpv_all = self._bintree.dbapi.cpv_all()
- cpv_all.sort()
- missing = []
- maxval = 0
- if onProgress:
- onProgress(maxval, 0)
- pkgindex = self._pkgindex
- missing = []
- metadata = {}
- for d in pkgindex.packages:
- metadata[d["CPV"]] = d
-
- for i, cpv in enumerate(cpv_all):
- d = metadata.get(cpv)
- if not d or self._need_update(cpv, d):
- missing.append(cpv)
-
- stale = set(metadata).difference(cpv_all)
- if missing or stale:
- from portage import locks
- pkgindex_lock = locks.lockfile(
- self._pkgindex_file, wantnewlockfile=1)
- try:
- # Repopulate with lock held.
- bintree._populate()
- cpv_all = self._bintree.dbapi.cpv_all()
- cpv_all.sort()
-
- pkgindex = bintree._load_pkgindex()
- self._pkgindex = pkgindex
-
- metadata = {}
- for d in pkgindex.packages:
- metadata[d["CPV"]] = d
-
- # Recount missing packages, with lock held.
- del missing[:]
- for i, cpv in enumerate(cpv_all):
- d = metadata.get(cpv)
- if not d or self._need_update(cpv, d):
- missing.append(cpv)
-
- maxval = len(missing)
- for i, cpv in enumerate(missing):
- try:
- metadata[cpv] = bintree._pkgindex_entry(cpv)
- except portage.exception.InvalidDependString:
- writemsg("!!! Invalid binary package: '%s'\n" % \
- bintree.getname(cpv), noiselevel=-1)
-
- if onProgress:
- onProgress(maxval, i+1)
-
- for cpv in set(metadata).difference(
- self._bintree.dbapi.cpv_all()):
- del metadata[cpv]
-
- # We've updated the pkgindex, so set it to
- # repopulate when necessary.
- bintree.populated = False
-
- del pkgindex.packages[:]
- pkgindex.packages.extend(metadata.values())
- from portage.util import atomic_ofstream
- f = atomic_ofstream(self._pkgindex_file)
- try:
- self._pkgindex.write(f)
- finally:
- f.close()
- finally:
- locks.unlockfile(pkgindex_lock)
-
- if onProgress:
- if maxval == 0:
- maxval = 1
- onProgress(maxval, maxval)
- return None
-
-class MoveHandler(object):
-
- def __init__(self, tree, porttree):
- self._tree = tree
- self._portdb = porttree.dbapi
- self._update_keys = ["DEPEND", "RDEPEND", "PDEPEND", "PROVIDE"]
- self._master_repo = \
- self._portdb.getRepositoryName(self._portdb.porttree_root)
-
- def _grab_global_updates(self):
- from portage.update import grab_updates, parse_updates
- retupdates = {}
- errors = []
-
- for repo_name in self._portdb.getRepositories():
- repo = self._portdb.getRepositoryPath(repo_name)
- updpath = os.path.join(repo, "profiles", "updates")
- if not os.path.isdir(updpath):
- continue
-
- try:
- rawupdates = grab_updates(updpath)
- except portage.exception.DirectoryNotFound:
- rawupdates = []
- upd_commands = []
- for mykey, mystat, mycontent in rawupdates:
- commands, errors = parse_updates(mycontent)
- upd_commands.extend(commands)
- errors.extend(errors)
- retupdates[repo_name] = upd_commands
-
- if self._master_repo in retupdates:
- retupdates['DEFAULT'] = retupdates[self._master_repo]
-
- return retupdates, errors
+from portage.emaint.main import emaint_main
- def check(self, onProgress=None):
- allupdates, errors = self._grab_global_updates()
- # Matching packages and moving them is relatively fast, so the
- # progress bar is updated in indeterminate mode.
- match = self._tree.dbapi.match
- aux_get = self._tree.dbapi.aux_get
- if onProgress:
- onProgress(0, 0)
- for repo, updates in allupdates.items():
- if repo == 'DEFAULT':
- continue
- if not updates:
- continue
-
- def repo_match(repository):
- return repository == repo or \
- (repo == self._master_repo and \
- repository not in allupdates)
-
- for i, update_cmd in enumerate(updates):
- if update_cmd[0] == "move":
- origcp, newcp = update_cmd[1:]
- for cpv in match(origcp):
- if repo_match(aux_get(cpv, ["repository"])[0]):
- errors.append("'%s' moved to '%s'" % (cpv, newcp))
- elif update_cmd[0] == "slotmove":
- pkg, origslot, newslot = update_cmd[1:]
- for cpv in match(pkg):
- slot, prepo = aux_get(cpv, ["SLOT", "repository"])
- if slot == origslot and repo_match(prepo):
- errors.append("'%s' slot moved from '%s' to '%s'" % \
- (cpv, origslot, newslot))
- if onProgress:
- onProgress(0, 0)
-
- # Searching for updates in all the metadata is relatively slow, so this
- # is where the progress bar comes out of indeterminate mode.
- cpv_all = self._tree.dbapi.cpv_all()
- cpv_all.sort()
- maxval = len(cpv_all)
- aux_update = self._tree.dbapi.aux_update
- meta_keys = self._update_keys + ['repository', 'EAPI']
- if onProgress:
- onProgress(maxval, 0)
- for i, cpv in enumerate(cpv_all):
- metadata = dict(zip(meta_keys, aux_get(cpv, meta_keys)))
- eapi = metadata.pop('EAPI')
- repository = metadata.pop('repository')
- try:
- updates = allupdates[repository]
- except KeyError:
- try:
- updates = allupdates['DEFAULT']
- except KeyError:
- continue
- if not updates:
- continue
- metadata_updates = \
- portage.update_dbentries(updates, metadata, eapi=eapi)
- if metadata_updates:
- errors.append("'%s' has outdated metadata" % cpv)
- if onProgress:
- onProgress(maxval, i+1)
- return errors
-
- def fix(self, onProgress=None):
- allupdates, errors = self._grab_global_updates()
- # Matching packages and moving them is relatively fast, so the
- # progress bar is updated in indeterminate mode.
- move = self._tree.dbapi.move_ent
- slotmove = self._tree.dbapi.move_slot_ent
- if onProgress:
- onProgress(0, 0)
- for repo, updates in allupdates.items():
- if repo == 'DEFAULT':
- continue
- if not updates:
- continue
-
- def repo_match(repository):
- return repository == repo or \
- (repo == self._master_repo and \
- repository not in allupdates)
-
- for i, update_cmd in enumerate(updates):
- if update_cmd[0] == "move":
- move(update_cmd, repo_match=repo_match)
- elif update_cmd[0] == "slotmove":
- slotmove(update_cmd, repo_match=repo_match)
- if onProgress:
- onProgress(0, 0)
-
- # Searching for updates in all the metadata is relatively slow, so this
- # is where the progress bar comes out of indeterminate mode.
- self._tree.dbapi.update_ents(allupdates, onProgress=onProgress)
- return errors
-
-class MoveInstalled(MoveHandler):
-
- short_desc = "Perform package move updates for installed packages"
-
- def name():
- return "moveinst"
- name = staticmethod(name)
- def __init__(self):
- eroot = portage.settings['EROOT']
- MoveHandler.__init__(self, portage.db[eroot]["vartree"], portage.db[eroot]["porttree"])
-
-class MoveBinary(MoveHandler):
-
- short_desc = "Perform package move updates for binary packages"
-
- def name():
- return "movebin"
- name = staticmethod(name)
- def __init__(self):
- eroot = portage.settings['EROOT']
- MoveHandler.__init__(self, portage.db[eroot]["bintree"], portage.db[eroot]['porttree'])
-
-class VdbKeyHandler(object):
- def name():
- return "vdbkeys"
- name = staticmethod(name)
-
- def __init__(self):
- self.list = portage.db[portage.settings["EROOT"]]["vartree"].dbapi.cpv_all()
- self.missing = []
- self.keys = ["HOMEPAGE", "SRC_URI", "KEYWORDS", "DESCRIPTION"]
-
- for p in self.list:
- mydir = os.path.join(portage.settings["EROOT"], 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(portage.settings["EROOT"], 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) as e:
- errors.append("Could not write %s, reason was: %s" % (mydir+k, e))
-
- return errors
-
-class ProgressHandler(object):
- def __init__(self):
- self.curval = 0
- self.maxval = 0
- self.last_update = 0
- self.min_display_latency = 0.2
-
- def onProgress(self, maxval, curval):
- self.maxval = maxval
- self.curval = curval
- cur_time = time.time()
- if cur_time - self.last_update >= self.min_display_latency:
- self.last_update = cur_time
- self.display()
-
- def display(self):
- raise NotImplementedError(self)
-
-class CleanResume(object):
-
- short_desc = "Discard emerge --resume merge lists"
-
- def name():
- return "cleanresume"
- name = staticmethod(name)
-
- def check(self, onProgress=None):
- messages = []
- mtimedb = portage.mtimedb
- resume_keys = ("resume", "resume_backup")
- maxval = len(resume_keys)
- if onProgress:
- onProgress(maxval, 0)
- for i, k in enumerate(resume_keys):
- try:
- d = mtimedb.get(k)
- if d is None:
- continue
- if not isinstance(d, dict):
- messages.append("unrecognized resume list: '%s'" % k)
- continue
- mergelist = d.get("mergelist")
- if mergelist is None or not hasattr(mergelist, "__len__"):
- messages.append("unrecognized resume list: '%s'" % k)
- continue
- messages.append("resume list '%s' contains %d packages" % \
- (k, len(mergelist)))
- finally:
- if onProgress:
- onProgress(maxval, i+1)
- return messages
-
- def fix(self, onProgress=None):
- delete_count = 0
- mtimedb = portage.mtimedb
- resume_keys = ("resume", "resume_backup")
- maxval = len(resume_keys)
- if onProgress:
- onProgress(maxval, 0)
- for i, k in enumerate(resume_keys):
- try:
- if mtimedb.pop(k, None) is not None:
- delete_count += 1
- finally:
- if onProgress:
- onProgress(maxval, i+1)
- if delete_count:
- mtimedb.commit()
-
-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(0o22)
-
- # TODO: Create a system that allows external modules to be added without
- # the need for hard coding.
- modules = {
- "world" : WorldHandler,
- "binhost":BinhostHandler,
- "moveinst":MoveInstalled,
- "movebin":MoveBinary,
- "cleanresume":CleanResume
- }
-
- module_names = list(modules)
- 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] COMMAND"
-
- desc = "The emaint program provides an interface to system health " + \
- "checks and maintenance. See the emaint(1) man page " + \
- "for additional information about the following commands:"
-
- usage += "\n\n"
- for line in textwrap.wrap(desc, 65):
- usage += "%s\n" % line
- usage += "\n"
- usage += " %s" % "all".ljust(15) + \
- "Perform all supported commands\n"
- for m in module_names[1:]:
- usage += " %s%s\n" % (m.ljust(15), modules[m].short_desc)
-
- 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":
- func = "check"
- else:
- func = "fix"
-
- isatty = os.environ.get('TERM') != 'dumb' and sys.stdout.isatty()
- for task in tasks:
- inst = task()
- onProgress = None
- if isatty:
- progressBar = portage.output.TermProgressBar(title="Emaint", max_desc_length=26)
- progressHandler = ProgressHandler()
- onProgress = progressHandler.onProgress
- def display():
- progressBar.set(progressHandler.curval, progressHandler.maxval)
- progressHandler.display = display
- def sigwinch_handler(signum, frame):
- lines, progressBar.term_columns = \
- portage.output.get_term_size()
- signal.signal(signal.SIGWINCH, sigwinch_handler)
- progressBar.label(func + " " + inst.name())
- result = getattr(inst, func)(onProgress=onProgress)
- if isatty:
- # make sure the final progress is displayed
- progressHandler.display()
- print()
- signal.signal(signal.SIGWINCH, signal.SIG_DFL)
- if result:
- print()
- print("\n".join(result))
- print("\n")
-
- print("Finished")
-
-if __name__ == "__main__":
+try:
emaint_main(sys.argv[1:])
+except IOError as e:
+ if e.errno == errno.EACCES:
+ print("\nemaint: Need superuser access")
+ sys.exit(1)
+ else:
+ raise