0e9ba853d8f770d037935886065b84e45eda92c3
[portage.git] / bin / emaint
1 #!/usr/bin/python -O
2
3 import sys, os
4 from optparse import OptionParser, OptionValueError
5 if not hasattr(__builtins__, "set"):
6         from sets import Set as set
7 import re
8 try:
9         import portage
10 except ImportError:
11         from os import path as osp
12         sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
13         import portage
14
15 import portage.const, portage.exception
16 class WorldHandler(object):
17
18         def name():
19                 return "world"
20         name = staticmethod(name)
21
22         def __init__(self):
23                 self.invalid = []
24                 self.not_installed = []
25                 self.invalid_category = []
26                 self.okay = []
27                 self.world_file = os.path.join("/", portage.const.WORLD_FILE)
28                 self.found = os.access(self.world_file, os.R_OK)
29
30         def _check_world(self, onProgress):
31                 categories = set(portage.settings.categories)
32                 myroot = portage.settings["ROOT"]
33                 vardb = portage.db[myroot]["vartree"].dbapi
34
35                 world_atoms = open(self.world_file).read().split()
36                 maxval = len(world_atoms)
37                 if onProgress:
38                         onProgress(maxval, 0)
39                 for i, atom in enumerate(world_atoms):
40                         if not portage.isvalidatom(atom):
41                                 self.invalid.append(atom)
42                                 if onProgress:
43                                         onProgress(maxval, i+1)
44                                 continue
45                         okay = True
46                         if not vardb.match(atom):
47                                 self.not_installed.append(atom)
48                                 okay = False
49                         if portage.catsplit(atom)[0] not in categories:
50                                 self.invalid_category.append(atom)
51                                 okay = False
52                         if okay:
53                                 self.okay.append(atom)
54                         if onProgress:
55                                 onProgress(maxval, i+1)
56
57         def check(self, onProgress=None):
58                 self._check_world(onProgress)
59                 errors = []
60                 if self.found:
61                         errors += map(lambda x: "'%s' is not a valid atom" % x, self.invalid)
62                         errors += map(lambda x: "'%s' is not installed" % x, self.not_installed)
63                         errors += map(lambda x: "'%s' has a category that is not listed in /etc/portage/categories" % x, self.invalid_category)
64                 else:
65                         errors.append(self.world_file + " could not be opened for reading")
66                 return errors
67
68         def fix(self, onProgress=None):
69                 self._check_world(onProgress)
70                 errors = []
71                 try:
72                         portage.write_atomic(self.world_file, "\n".join(self.okay))
73                 except portage.exception.PortageException:
74                         errors.append(self.world_file + " could not be opened for writing")
75                 return errors
76
77 class VdbKeyHandler(object):
78         def name():
79                 return "vdbkeys"
80         name = staticmethod(name)
81
82         def __init__(self):
83                 self.list = portage.db["/"]["vartree"].dbapi.cpv_all()
84                 self.missing = []
85                 self.keys = ["HOMEPAGE", "SRC_URI", "KEYWORDS", "DESCRIPTION"]
86                 
87                 for p in self.list:
88                         mydir = os.path.join(os.sep, portage.settings["ROOT"], portage.const.VDB_PATH, p)+os.sep
89                         ismissing = True
90                         for k in self.keys:
91                                 if os.path.exists(mydir+k):
92                                         ismissing = False
93                                         break
94                         if ismissing:
95                                 self.missing.append(p)
96                 
97         def check(self):
98                 return ["%s has missing keys" % x for x in self.missing]
99         
100         def fix(self):
101         
102                 errors = []
103         
104                 for p in self.missing:
105                         mydir = os.path.join(os.sep, portage.settings["ROOT"], portage.const.VDB_PATH, p)+os.sep
106                         if not os.access(mydir+"environment.bz2", os.R_OK):
107                                 errors.append("Can't access %s" % (mydir+"environment.bz2"))
108                         elif not os.access(mydir, os.W_OK):
109                                 errors.append("Can't create files in %s" % mydir)
110                         else:
111                                 env = os.popen("bzip2 -dcq "+mydir+"environment.bz2", "r")
112                                 envlines = env.read().split("\n")
113                                 env.close()
114                                 for k in self.keys:
115                                         s = [l for l in envlines if l.startswith(k+"=")]
116                                         if len(s) > 1:
117                                                 errors.append("multiple matches for %s found in %senvironment.bz2" % (k, mydir))
118                                         elif len(s) == 0:
119                                                 s = ""
120                                         else:
121                                                 s = s[0].split("=",1)[1]
122                                                 s = s.lstrip("$").strip("\'\"")
123                                                 s = re.sub("(\\\\[nrt])+", " ", s)
124                                                 s = " ".join(s.split()).strip()
125                                                 if s != "":
126                                                         try:
127                                                                 keyfile = open(mydir+os.sep+k, "w")
128                                                                 keyfile.write(s+"\n")
129                                                                 keyfile.close()
130                                                         except (IOError, OSError), e:
131                                                                 errors.append("Could not write %s, reason was: %s" % (mydir+k, e))
132                 
133                 return errors
134
135 def emaint_main(myargv):
136
137         # TODO: Create a system that allows external modules to be added without
138         #       the need for hard coding.
139         modules = {"world" : WorldHandler}
140
141         module_names = modules.keys()
142         module_names.sort()
143         module_names.insert(0, "all")
144
145         def exclusive(option, *args, **kw):
146                 var = kw.get("var", None)
147                 if var is None:
148                         raise ValueError("var not specified to exclusive()")
149                 if getattr(parser, var, ""):
150                         raise OptionValueError("%s and %s are exclusive options" % (getattr(parser, var), option))
151                 setattr(parser, var, str(option))
152
153
154         usage = "usage: emaint [options] " + " | ".join(module_names)
155
156         usage+= "\n\nCurrently emaint can only check and fix problems with one's world\n"
157         usage+= "file.  Future versions will integrate other portage check-and-fix\n"
158         usage+= "tools and provide a single interface to system health checks."
159
160
161         parser = OptionParser(usage=usage)
162         parser.add_option("-c", "--check", help="check for problems",
163                 action="callback", callback=exclusive, callback_kwargs={"var":"action"})
164         parser.add_option("-f", "--fix", help="attempt to fix problems",
165                 action="callback", callback=exclusive, callback_kwargs={"var":"action"})
166         parser.action = None
167
168
169         (options, args) = parser.parse_args(args=myargv)
170         if len(args) != 1:
171                 parser.error("Incorrect number of arguments")
172         if args[0] not in module_names:
173                 parser.error("%s target is not a known target" % args[0])
174
175         if parser.action:
176                 action = parser.action
177         else:
178                 print "Defaulting to --check"
179                 action = "-c/--check"
180
181         if args[0] == "all":
182                 tasks = modules.values()
183         else:
184                 tasks = [modules[args[0]]]
185
186
187         if action == "-c/--check":
188                 status = "Checking %s for problems"
189                 func = "check"
190         else:
191                 status = "Attempting to fix %s"
192                 func = "fix"
193
194
195         for task in tasks:
196                 print status % task.name()
197                 inst = task()
198                 result = getattr(inst, func)()
199                 if result:
200                         print
201                         print "\n".join(result)
202                         print "\n"
203
204         print "Finished"
205
206 if __name__ == "__main__":
207         emaint_main(sys.argv[1:])