4 from __future__ import print_function
11 from optparse import OptionParser, OptionValueError
16 from os import path as osp
17 sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
20 from portage import os
22 class WorldHandler(object):
24 short_desc = "Fix problems in the world file"
28 name = staticmethod(name)
32 self.not_installed = []
33 self.invalid_category = []
35 from portage.sets import load_default_config
36 setconfig = load_default_config(portage.settings,
37 portage.db[portage.settings["ROOT"]])
38 self._sets = setconfig.getSets()
40 def _check_world(self, onProgress):
41 categories = set(portage.settings.categories)
42 myroot = portage.settings["ROOT"]
43 self.world_file = os.path.join(myroot, portage.const.WORLD_FILE)
44 self.found = os.access(self.world_file, os.R_OK)
45 vardb = portage.db[myroot]["vartree"].dbapi
47 from portage.sets import SETPREFIX
49 world_atoms = list(sets["world"])
50 maxval = len(world_atoms)
53 for i, atom in enumerate(world_atoms):
54 if not portage.isvalidatom(atom):
55 if atom.startswith(SETPREFIX):
56 s = atom[len(SETPREFIX):]
58 self.okay.append(atom)
60 self.not_installed.append(atom)
62 self.invalid.append(atom)
64 onProgress(maxval, i+1)
66 cp = portage.dep_getkey(atom)
68 if not vardb.match(atom):
69 self.not_installed.append(atom)
71 if portage.catsplit(cp)[0] not in categories:
72 self.invalid_category.append(atom)
75 self.okay.append(atom)
77 onProgress(maxval, i+1)
79 def check(self, onProgress=None):
80 self._check_world(onProgress)
83 errors += map(lambda x: "'%s' is not a valid atom" % x, self.invalid)
84 errors += map(lambda x: "'%s' is not installed" % x, self.not_installed)
85 errors += map(lambda x: "'%s' has a category that is not listed in /etc/portage/categories" % x, self.invalid_category)
87 errors.append(self.world_file + " could not be opened for reading")
90 def fix(self, onProgress=None):
91 world_set = self._sets["world"]
94 world_set.load() # maybe it's changed on disk
95 before = set(world_set)
96 self._check_world(onProgress)
97 after = set(self.okay)
101 world_set.replace(self.okay)
102 except portage.exception.PortageException:
103 errors.append("%s could not be opened for writing" % \
109 class BinhostHandler(object):
111 short_desc = "Generate a metadata index for binary packages"
115 name = staticmethod(name)
118 myroot = portage.settings["ROOT"]
119 self._bintree = portage.db[myroot]["bintree"]
120 self._bintree.populate()
121 self._pkgindex_file = self._bintree._pkgindex_file
122 self._pkgindex = self._bintree._load_pkgindex()
124 def check(self, onProgress=None):
126 cpv_all = self._bintree.dbapi.cpv_all()
128 maxval = len(cpv_all)
130 onProgress(maxval, 0)
131 pkgindex = self._pkgindex
134 for d in pkgindex.packages:
135 metadata[d["CPV"]] = d
136 for i, cpv in enumerate(cpv_all):
137 d = metadata.get(cpv)
138 if not d or "MD5" not in d:
141 onProgress(maxval, i+1)
142 errors = ["'%s' is not in Packages" % cpv for cpv in missing]
143 stale = set(metadata).difference(cpv_all)
145 errors.append("'%s' is not in the repository" % cpv)
148 def fix(self, onProgress=None):
149 bintree = self._bintree
150 cpv_all = self._bintree.dbapi.cpv_all()
155 onProgress(maxval, 0)
156 pkgindex = self._pkgindex
159 for d in pkgindex.packages:
160 metadata[d["CPV"]] = d
162 for i, cpv in enumerate(cpv_all):
163 d = metadata.get(cpv)
164 if not d or "MD5" not in d:
167 stale = set(metadata).difference(cpv_all)
169 from portage import locks
170 pkgindex_lock = locks.lockfile(
171 self._pkgindex_file, wantnewlockfile=1)
173 # Repopulate with lock held.
175 cpv_all = self._bintree.dbapi.cpv_all()
178 pkgindex = bintree._load_pkgindex()
179 self._pkgindex = pkgindex
182 for d in pkgindex.packages:
183 metadata[d["CPV"]] = d
185 # Recount missing packages, with lock held.
187 for i, cpv in enumerate(cpv_all):
188 d = metadata.get(cpv)
189 if not d or "MD5" not in d:
192 maxval = len(missing)
193 for i, cpv in enumerate(missing):
195 metadata[cpv] = bintree._pkgindex_entry(cpv)
196 except portage.exception.InvalidDependString:
197 writemsg("!!! Invalid binary package: '%s'\n" % \
198 bintree.getname(cpv), noiselevel=-1)
201 onProgress(maxval, i+1)
203 for cpv in set(metadata).difference(
204 self._bintree.dbapi.cpv_all()):
207 # We've updated the pkgindex, so set it to
208 # repopulate when necessary.
209 bintree.populated = False
211 del pkgindex.packages[:]
212 pkgindex.packages.extend(metadata.values())
213 from portage.util import atomic_ofstream
214 f = atomic_ofstream(self._pkgindex_file)
216 self._pkgindex.write(f)
220 locks.unlockfile(pkgindex_lock)
225 onProgress(maxval, maxval)
228 class MoveHandler(object):
230 def __init__(self, tree):
232 self._portdir = tree.settings["PORTDIR"]
233 self._update_keys = ["DEPEND", "RDEPEND", "PDEPEND", "PROVIDE"]
235 def _grab_global_updates(self, portdir):
236 from portage.update import grab_updates, parse_updates
237 updpath = os.path.join(portdir, "profiles", "updates")
239 rawupdates = grab_updates(updpath)
240 except portage.exception.DirectoryNotFound:
244 for mykey, mystat, mycontent in rawupdates:
245 commands, errors = parse_updates(mycontent)
246 upd_commands.extend(commands)
247 errors.extend(errors)
248 return upd_commands, errors
250 def check(self, onProgress=None):
251 updates, errors = self._grab_global_updates(self._portdir)
252 # Matching packages and moving them is relatively fast, so the
253 # progress bar is updated in indeterminate mode.
254 match = self._tree.dbapi.match
255 aux_get = self._tree.dbapi.aux_get
258 for i, update_cmd in enumerate(updates):
259 if update_cmd[0] == "move":
260 origcp, newcp = update_cmd[1:]
261 for cpv in match(origcp):
262 errors.append("'%s' moved to '%s'" % (cpv, newcp))
263 elif update_cmd[0] == "slotmove":
264 pkg, origslot, newslot = update_cmd[1:]
265 for cpv in match(pkg):
266 slot = aux_get(cpv, ["SLOT"])[0]
268 errors.append("'%s' slot moved from '%s' to '%s'" % \
269 (cpv, origslot, newslot))
273 # Searching for updates in all the metadata is relatively slow, so this
274 # is where the progress bar comes out of indeterminate mode.
275 cpv_all = self._tree.dbapi.cpv_all()
277 maxval = len(cpv_all)
278 aux_update = self._tree.dbapi.aux_update
279 update_keys = self._update_keys
280 from portage.update import update_dbentries
282 onProgress(maxval, 0)
283 for i, cpv in enumerate(cpv_all):
284 metadata = dict(zip(update_keys, aux_get(cpv, update_keys)))
285 metadata_updates = update_dbentries(updates, metadata)
287 errors.append("'%s' has outdated metadata" % cpv)
289 onProgress(maxval, i+1)
292 def fix(self, onProgress=None):
293 updates, errors = self._grab_global_updates(self._portdir)
294 # Matching packages and moving them is relatively fast, so the
295 # progress bar is updated in indeterminate mode.
296 move = self._tree.dbapi.move_ent
297 slotmove = self._tree.dbapi.move_slot_ent
300 for i, update_cmd in enumerate(updates):
301 if update_cmd[0] == "move":
303 elif update_cmd[0] == "slotmove":
308 # Searching for updates in all the metadata is relatively slow, so this
309 # is where the progress bar comes out of indeterminate mode.
310 self._tree.dbapi.update_ents(updates, onProgress=onProgress)
313 class MoveInstalled(MoveHandler):
315 short_desc = "Perform package move updates for installed packages"
319 name = staticmethod(name)
321 myroot = portage.settings["ROOT"]
322 MoveHandler.__init__(self, portage.db[myroot]["vartree"])
324 class MoveBinary(MoveHandler):
326 short_desc = "Perform package move updates for binary packages"
330 name = staticmethod(name)
332 myroot = portage.settings["ROOT"]
333 MoveHandler.__init__(self, portage.db[myroot]["bintree"])
335 class VdbKeyHandler(object):
338 name = staticmethod(name)
341 self.list = portage.db["/"]["vartree"].dbapi.cpv_all()
343 self.keys = ["HOMEPAGE", "SRC_URI", "KEYWORDS", "DESCRIPTION"]
346 mydir = os.path.join(os.sep, portage.settings["ROOT"], portage.const.VDB_PATH, p)+os.sep
349 if os.path.exists(mydir+k):
353 self.missing.append(p)
356 return ["%s has missing keys" % x for x in self.missing]
362 for p in self.missing:
363 mydir = os.path.join(os.sep, portage.settings["ROOT"], portage.const.VDB_PATH, p)+os.sep
364 if not os.access(mydir+"environment.bz2", os.R_OK):
365 errors.append("Can't access %s" % (mydir+"environment.bz2"))
366 elif not os.access(mydir, os.W_OK):
367 errors.append("Can't create files in %s" % mydir)
369 env = os.popen("bzip2 -dcq "+mydir+"environment.bz2", "r")
370 envlines = env.read().split("\n")
373 s = [l for l in envlines if l.startswith(k+"=")]
375 errors.append("multiple matches for %s found in %senvironment.bz2" % (k, mydir))
379 s = s[0].split("=",1)[1]
380 s = s.lstrip("$").strip("\'\"")
381 s = re.sub("(\\\\[nrt])+", " ", s)
382 s = " ".join(s.split()).strip()
385 keyfile = open(mydir+os.sep+k, "w")
386 keyfile.write(s+"\n")
388 except (IOError, OSError) as e:
389 errors.append("Could not write %s, reason was: %s" % (mydir+k, e))
393 class ProgressHandler(object):
398 self.min_display_latency = 0.2
400 def onProgress(self, maxval, curval):
403 cur_time = time.time()
404 if cur_time - self.last_update >= self.min_display_latency:
405 self.last_update = cur_time
409 raise NotImplementedError(self)
411 class CleanResume(object):
413 short_desc = "Discard emerge --resume merge lists"
417 name = staticmethod(name)
419 def check(self, onProgress=None):
421 mtimedb = portage.mtimedb
422 resume_keys = ("resume", "resume_backup")
423 maxval = len(resume_keys)
425 onProgress(maxval, 0)
426 for i, k in enumerate(resume_keys):
431 if not isinstance(d, dict):
432 messages.append("unrecognized resume list: '%s'" % k)
434 mergelist = d.get("mergelist")
435 if mergelist is None or not hasattr(mergelist, "__len__"):
436 messages.append("unrecognized resume list: '%s'" % k)
438 messages.append("resume list '%s' contains %d packages" % \
442 onProgress(maxval, i+1)
445 def fix(self, onProgress=None):
447 mtimedb = portage.mtimedb
448 resume_keys = ("resume", "resume_backup")
449 maxval = len(resume_keys)
451 onProgress(maxval, 0)
452 for i, k in enumerate(resume_keys):
454 if mtimedb.pop(k, None) is not None:
458 onProgress(maxval, i+1)
462 def emaint_main(myargv):
464 # Similar to emerge, emaint needs a default umask so that created
465 # files (such as the world file) have sane permissions.
468 # TODO: Create a system that allows external modules to be added without
469 # the need for hard coding.
471 "world" : WorldHandler,
472 "binhost":BinhostHandler,
473 "moveinst":MoveInstalled,
474 "movebin":MoveBinary,
475 "cleanresume":CleanResume
478 module_names = list(modules.keys())
480 module_names.insert(0, "all")
482 def exclusive(option, *args, **kw):
483 var = kw.get("var", None)
485 raise ValueError("var not specified to exclusive()")
486 if getattr(parser, var, ""):
487 raise OptionValueError("%s and %s are exclusive options" % (getattr(parser, var), option))
488 setattr(parser, var, str(option))
491 usage = "usage: emaint [options] COMMAND"
493 desc = "The emaint program provides an interface to system health " + \
494 "checks and maintenance. See the emaint(1) man page " + \
495 "for additional information about the following commands:"
498 for line in textwrap.wrap(desc, 65):
499 usage += "%s\n" % line
501 usage += " %s" % "all".ljust(15) + \
502 "Perform all supported commands\n"
503 for m in module_names[1:]:
504 usage += " %s%s\n" % (m.ljust(15), modules[m].short_desc)
506 parser = OptionParser(usage=usage, version=portage.VERSION)
507 parser.add_option("-c", "--check", help="check for problems",
508 action="callback", callback=exclusive, callback_kwargs={"var":"action"})
509 parser.add_option("-f", "--fix", help="attempt to fix problems",
510 action="callback", callback=exclusive, callback_kwargs={"var":"action"})
514 (options, args) = parser.parse_args(args=myargv)
516 parser.error("Incorrect number of arguments")
517 if args[0] not in module_names:
518 parser.error("%s target is not a known target" % args[0])
521 action = parser.action
523 print("Defaulting to --check")
524 action = "-c/--check"
527 tasks = modules.values()
529 tasks = [modules[args[0]]]
532 if action == "-c/--check":
533 status = "Checking %s for problems"
536 status = "Attempting to fix %s"
539 isatty = sys.stdout.isatty()
541 print(status % task.name())
545 progressBar = portage.output.TermProgressBar()
546 progressHandler = ProgressHandler()
547 onProgress = progressHandler.onProgress
549 progressBar.set(progressHandler.curval, progressHandler.maxval)
550 progressHandler.display = display
551 def sigwinch_handler(signum, frame):
552 lines, progressBar.term_columns = \
553 portage.output.get_term_size()
554 signal.signal(signal.SIGWINCH, sigwinch_handler)
555 result = getattr(inst, func)(onProgress=onProgress)
557 # make sure the final progress is displayed
558 progressHandler.display()
560 signal.signal(signal.SIGWINCH, signal.SIG_DFL)
563 print("\n".join(result))
568 if __name__ == "__main__":
569 emaint_main(sys.argv[1:])