4 from __future__ import print_function
13 from optparse import OptionParser, OptionValueError
18 from os import path as osp
19 sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
22 from portage import os
23 from portage.util import writemsg
25 if sys.hexversion >= 0x3000000:
28 class WorldHandler(object):
30 short_desc = "Fix problems in the world file"
34 name = staticmethod(name)
38 self.not_installed = []
39 self.invalid_category = []
41 from portage._sets import load_default_config
42 setconfig = load_default_config(portage.settings,
43 portage.db[portage.settings['EROOT']])
44 self._sets = setconfig.getSets()
46 def _check_world(self, onProgress):
47 categories = set(portage.settings.categories)
48 eroot = portage.settings['EROOT']
49 self.world_file = os.path.join(eroot, portage.const.WORLD_FILE)
50 self.found = os.access(self.world_file, os.R_OK)
51 vardb = portage.db[eroot]["vartree"].dbapi
53 from portage._sets import SETPREFIX
55 world_atoms = list(sets["selected"])
56 maxval = len(world_atoms)
59 for i, atom in enumerate(world_atoms):
60 if not isinstance(atom, portage.dep.Atom):
61 if atom.startswith(SETPREFIX):
62 s = atom[len(SETPREFIX):]
64 self.okay.append(atom)
66 self.not_installed.append(atom)
68 self.invalid.append(atom)
70 onProgress(maxval, i+1)
73 if not vardb.match(atom):
74 self.not_installed.append(atom)
76 if portage.catsplit(atom.cp)[0] not in categories:
77 self.invalid_category.append(atom)
80 self.okay.append(atom)
82 onProgress(maxval, i+1)
84 def check(self, onProgress=None):
85 self._check_world(onProgress)
88 errors += ["'%s' is not a valid atom" % x for x in self.invalid]
89 errors += ["'%s' is not installed" % x for x in self.not_installed]
90 errors += ["'%s' has a category that is not listed in /etc/portage/categories" % x for x in self.invalid_category]
92 errors.append(self.world_file + " could not be opened for reading")
95 def fix(self, onProgress=None):
96 world_set = self._sets["selected"]
99 world_set.load() # maybe it's changed on disk
100 before = set(world_set)
101 self._check_world(onProgress)
102 after = set(self.okay)
106 world_set.replace(self.okay)
107 except portage.exception.PortageException:
108 errors.append("%s could not be opened for writing" % \
114 class BinhostHandler(object):
116 short_desc = "Generate a metadata index for binary packages"
120 name = staticmethod(name)
123 eroot = portage.settings['EROOT']
124 self._bintree = portage.db[eroot]["bintree"]
125 self._bintree.populate()
126 self._pkgindex_file = self._bintree._pkgindex_file
127 self._pkgindex = self._bintree._load_pkgindex()
129 def _need_update(self, cpv, data):
131 if "MD5" not in data:
134 size = data.get("SIZE")
138 mtime = data.get("MTIME")
142 pkg_path = self._bintree.getname(cpv)
144 s = os.lstat(pkg_path)
146 if e.errno not in (errno.ENOENT, errno.ESTALE):
148 # We can't update the index for this one because
153 if long(mtime) != s[stat.ST_MTIME]:
155 if long(size) != long(s.st_size):
162 def check(self, onProgress=None):
164 cpv_all = self._bintree.dbapi.cpv_all()
166 maxval = len(cpv_all)
168 onProgress(maxval, 0)
169 pkgindex = self._pkgindex
172 for d in pkgindex.packages:
173 metadata[d["CPV"]] = d
174 for i, cpv in enumerate(cpv_all):
175 d = metadata.get(cpv)
176 if not d or self._need_update(cpv, d):
179 onProgress(maxval, i+1)
180 errors = ["'%s' is not in Packages" % cpv for cpv in missing]
181 stale = set(metadata).difference(cpv_all)
183 errors.append("'%s' is not in the repository" % cpv)
186 def fix(self, onProgress=None):
187 bintree = self._bintree
188 cpv_all = self._bintree.dbapi.cpv_all()
193 onProgress(maxval, 0)
194 pkgindex = self._pkgindex
197 for d in pkgindex.packages:
198 metadata[d["CPV"]] = d
200 for i, cpv in enumerate(cpv_all):
201 d = metadata.get(cpv)
202 if not d or self._need_update(cpv, d):
205 stale = set(metadata).difference(cpv_all)
207 from portage import locks
208 pkgindex_lock = locks.lockfile(
209 self._pkgindex_file, wantnewlockfile=1)
211 # Repopulate with lock held.
213 cpv_all = self._bintree.dbapi.cpv_all()
216 pkgindex = bintree._load_pkgindex()
217 self._pkgindex = pkgindex
220 for d in pkgindex.packages:
221 metadata[d["CPV"]] = d
223 # Recount missing packages, with lock held.
225 for i, cpv in enumerate(cpv_all):
226 d = metadata.get(cpv)
227 if not d or self._need_update(cpv, d):
230 maxval = len(missing)
231 for i, cpv in enumerate(missing):
233 metadata[cpv] = bintree._pkgindex_entry(cpv)
234 except portage.exception.InvalidDependString:
235 writemsg("!!! Invalid binary package: '%s'\n" % \
236 bintree.getname(cpv), noiselevel=-1)
239 onProgress(maxval, i+1)
241 for cpv in set(metadata).difference(
242 self._bintree.dbapi.cpv_all()):
245 # We've updated the pkgindex, so set it to
246 # repopulate when necessary.
247 bintree.populated = False
249 del pkgindex.packages[:]
250 pkgindex.packages.extend(metadata.values())
251 from portage.util import atomic_ofstream
252 f = atomic_ofstream(self._pkgindex_file)
254 self._pkgindex.write(f)
258 locks.unlockfile(pkgindex_lock)
263 onProgress(maxval, maxval)
266 class MoveHandler(object):
268 def __init__(self, tree, porttree):
270 self._portdb = porttree.dbapi
271 self._update_keys = ["DEPEND", "RDEPEND", "PDEPEND", "PROVIDE"]
272 self._master_repo = \
273 self._portdb.getRepositoryName(self._portdb.porttree_root)
275 def _grab_global_updates(self):
276 from portage.update import grab_updates, parse_updates
280 for repo_name in self._portdb.getRepositories():
281 repo = self._portdb.getRepositoryPath(repo_name)
282 updpath = os.path.join(repo, "profiles", "updates")
283 if not os.path.isdir(updpath):
287 rawupdates = grab_updates(updpath)
288 except portage.exception.DirectoryNotFound:
291 for mykey, mystat, mycontent in rawupdates:
292 commands, errors = parse_updates(mycontent)
293 upd_commands.extend(commands)
294 errors.extend(errors)
295 retupdates[repo_name] = upd_commands
297 if self._master_repo in retupdates:
298 retupdates['DEFAULT'] = retupdates[self._master_repo]
300 return retupdates, errors
302 def check(self, onProgress=None):
303 allupdates, errors = self._grab_global_updates()
304 # Matching packages and moving them is relatively fast, so the
305 # progress bar is updated in indeterminate mode.
306 match = self._tree.dbapi.match
307 aux_get = self._tree.dbapi.aux_get
310 for repo, updates in allupdates.items():
311 if repo == 'DEFAULT':
316 def repo_match(repository):
317 return repository == repo or \
318 (repo == self._master_repo and \
319 repository not in allupdates)
321 for i, update_cmd in enumerate(updates):
322 if update_cmd[0] == "move":
323 origcp, newcp = update_cmd[1:]
324 for cpv in match(origcp):
325 if repo_match(aux_get(cpv, ["repository"])[0]):
326 errors.append("'%s' moved to '%s'" % (cpv, newcp))
327 elif update_cmd[0] == "slotmove":
328 pkg, origslot, newslot = update_cmd[1:]
329 for cpv in match(pkg):
330 slot, prepo = aux_get(cpv, ["SLOT", "repository"])
331 if slot == origslot and repo_match(prepo):
332 errors.append("'%s' slot moved from '%s' to '%s'" % \
333 (cpv, origslot, newslot))
337 # Searching for updates in all the metadata is relatively slow, so this
338 # is where the progress bar comes out of indeterminate mode.
339 cpv_all = self._tree.dbapi.cpv_all()
341 maxval = len(cpv_all)
342 aux_update = self._tree.dbapi.aux_update
343 meta_keys = self._update_keys + ['repository']
344 from portage.update import update_dbentries
346 onProgress(maxval, 0)
347 for i, cpv in enumerate(cpv_all):
348 metadata = dict(zip(meta_keys, aux_get(cpv, meta_keys)))
349 repository = metadata.pop('repository')
351 updates = allupdates[repository]
354 updates = allupdates['DEFAULT']
359 metadata_updates = update_dbentries(updates, metadata)
361 errors.append("'%s' has outdated metadata" % cpv)
363 onProgress(maxval, i+1)
366 def fix(self, onProgress=None):
367 allupdates, errors = self._grab_global_updates()
368 # Matching packages and moving them is relatively fast, so the
369 # progress bar is updated in indeterminate mode.
370 move = self._tree.dbapi.move_ent
371 slotmove = self._tree.dbapi.move_slot_ent
374 for repo, updates in allupdates.items():
375 if repo == 'DEFAULT':
380 def repo_match(repository):
381 return repository == repo or \
382 (repo == self._master_repo and \
383 repository not in allupdates)
385 for i, update_cmd in enumerate(updates):
386 if update_cmd[0] == "move":
387 move(update_cmd, repo_match=repo_match)
388 elif update_cmd[0] == "slotmove":
389 slotmove(update_cmd, repo_match=repo_match)
393 # Searching for updates in all the metadata is relatively slow, so this
394 # is where the progress bar comes out of indeterminate mode.
395 self._tree.dbapi.update_ents(allupdates, onProgress=onProgress)
398 class MoveInstalled(MoveHandler):
400 short_desc = "Perform package move updates for installed packages"
404 name = staticmethod(name)
406 eroot = portage.settings['EROOT']
407 MoveHandler.__init__(self, portage.db[eroot]["vartree"], portage.db[eroot]["porttree"])
409 class MoveBinary(MoveHandler):
411 short_desc = "Perform package move updates for binary packages"
415 name = staticmethod(name)
417 eroot = portage.settings['EROOT']
418 MoveHandler.__init__(self, portage.db[eroot]["bintree"], portage.db[eroot]['porttree'])
420 class VdbKeyHandler(object):
423 name = staticmethod(name)
426 self.list = portage.db[portage.settings["EROOT"]]["vartree"].dbapi.cpv_all()
428 self.keys = ["HOMEPAGE", "SRC_URI", "KEYWORDS", "DESCRIPTION"]
431 mydir = os.path.join(portage.settings["EROOT"], portage.const.VDB_PATH, p)+os.sep
434 if os.path.exists(mydir+k):
438 self.missing.append(p)
441 return ["%s has missing keys" % x for x in self.missing]
447 for p in self.missing:
448 mydir = os.path.join(portage.settings["EROOT"], portage.const.VDB_PATH, p)+os.sep
449 if not os.access(mydir+"environment.bz2", os.R_OK):
450 errors.append("Can't access %s" % (mydir+"environment.bz2"))
451 elif not os.access(mydir, os.W_OK):
452 errors.append("Can't create files in %s" % mydir)
454 env = os.popen("bzip2 -dcq "+mydir+"environment.bz2", "r")
455 envlines = env.read().split("\n")
458 s = [l for l in envlines if l.startswith(k+"=")]
460 errors.append("multiple matches for %s found in %senvironment.bz2" % (k, mydir))
464 s = s[0].split("=",1)[1]
465 s = s.lstrip("$").strip("\'\"")
466 s = re.sub("(\\\\[nrt])+", " ", s)
467 s = " ".join(s.split()).strip()
470 keyfile = open(mydir+os.sep+k, "w")
471 keyfile.write(s+"\n")
473 except (IOError, OSError) as e:
474 errors.append("Could not write %s, reason was: %s" % (mydir+k, e))
478 class ProgressHandler(object):
483 self.min_display_latency = 0.2
485 def onProgress(self, maxval, curval):
488 cur_time = time.time()
489 if cur_time - self.last_update >= self.min_display_latency:
490 self.last_update = cur_time
494 raise NotImplementedError(self)
496 class CleanResume(object):
498 short_desc = "Discard emerge --resume merge lists"
502 name = staticmethod(name)
504 def check(self, onProgress=None):
506 mtimedb = portage.mtimedb
507 resume_keys = ("resume", "resume_backup")
508 maxval = len(resume_keys)
510 onProgress(maxval, 0)
511 for i, k in enumerate(resume_keys):
516 if not isinstance(d, dict):
517 messages.append("unrecognized resume list: '%s'" % k)
519 mergelist = d.get("mergelist")
520 if mergelist is None or not hasattr(mergelist, "__len__"):
521 messages.append("unrecognized resume list: '%s'" % k)
523 messages.append("resume list '%s' contains %d packages" % \
527 onProgress(maxval, i+1)
530 def fix(self, onProgress=None):
532 mtimedb = portage.mtimedb
533 resume_keys = ("resume", "resume_backup")
534 maxval = len(resume_keys)
536 onProgress(maxval, 0)
537 for i, k in enumerate(resume_keys):
539 if mtimedb.pop(k, None) is not None:
543 onProgress(maxval, i+1)
547 def emaint_main(myargv):
549 # Similar to emerge, emaint needs a default umask so that created
550 # files (such as the world file) have sane permissions.
553 # TODO: Create a system that allows external modules to be added without
554 # the need for hard coding.
556 "world" : WorldHandler,
557 "binhost":BinhostHandler,
558 "moveinst":MoveInstalled,
559 "movebin":MoveBinary,
560 "cleanresume":CleanResume
563 module_names = list(modules)
565 module_names.insert(0, "all")
567 def exclusive(option, *args, **kw):
568 var = kw.get("var", None)
570 raise ValueError("var not specified to exclusive()")
571 if getattr(parser, var, ""):
572 raise OptionValueError("%s and %s are exclusive options" % (getattr(parser, var), option))
573 setattr(parser, var, str(option))
576 usage = "usage: emaint [options] COMMAND"
578 desc = "The emaint program provides an interface to system health " + \
579 "checks and maintenance. See the emaint(1) man page " + \
580 "for additional information about the following commands:"
583 for line in textwrap.wrap(desc, 65):
584 usage += "%s\n" % line
586 usage += " %s" % "all".ljust(15) + \
587 "Perform all supported commands\n"
588 for m in module_names[1:]:
589 usage += " %s%s\n" % (m.ljust(15), modules[m].short_desc)
591 parser = OptionParser(usage=usage, version=portage.VERSION)
592 parser.add_option("-c", "--check", help="check for problems",
593 action="callback", callback=exclusive, callback_kwargs={"var":"action"})
594 parser.add_option("-f", "--fix", help="attempt to fix problems",
595 action="callback", callback=exclusive, callback_kwargs={"var":"action"})
599 (options, args) = parser.parse_args(args=myargv)
601 parser.error("Incorrect number of arguments")
602 if args[0] not in module_names:
603 parser.error("%s target is not a known target" % args[0])
606 action = parser.action
608 print("Defaulting to --check")
609 action = "-c/--check"
612 tasks = modules.values()
614 tasks = [modules[args[0]]]
617 if action == "-c/--check":
618 status = "Checking %s for problems"
621 status = "Attempting to fix %s"
624 isatty = os.environ.get('TERM') != 'dumb' and sys.stdout.isatty()
626 print(status % task.name())
630 progressBar = portage.output.TermProgressBar()
631 progressHandler = ProgressHandler()
632 onProgress = progressHandler.onProgress
634 progressBar.set(progressHandler.curval, progressHandler.maxval)
635 progressHandler.display = display
636 def sigwinch_handler(signum, frame):
637 lines, progressBar.term_columns = \
638 portage.output.get_term_size()
639 signal.signal(signal.SIGWINCH, sigwinch_handler)
640 result = getattr(inst, func)(onProgress=onProgress)
642 # make sure the final progress is displayed
643 progressHandler.display()
645 signal.signal(signal.SIGWINCH, signal.SIG_DFL)
648 print("\n".join(result))
653 if __name__ == "__main__":
654 emaint_main(sys.argv[1:])