]>
Commit | Line | Data |
---|---|---|
1e80d5d7 | 1 | #!/usr/bin/python |
b792d887 MT |
2 | ############################################################################### |
3 | # # | |
4 | # Pakfire - The IPFire package management system # | |
5 | # Copyright (C) 2011 Pakfire development team # | |
6 | # # | |
7 | # This program is free software: you can redistribute it and/or modify # | |
8 | # it under the terms of the GNU General Public License as published by # | |
9 | # the Free Software Foundation, either version 3 of the License, or # | |
10 | # (at your option) any later version. # | |
11 | # # | |
12 | # This program is distributed in the hope that it will be useful, # | |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
15 | # GNU General Public License for more details. # | |
16 | # # | |
17 | # You should have received a copy of the GNU General Public License # | |
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
19 | # # | |
20 | ############################################################################### | |
1e80d5d7 | 21 | |
94438733 | 22 | import os |
536bec81 | 23 | import sys |
1e80d5d7 MT |
24 | |
25 | import packages | |
64ffcd37 | 26 | import shell |
94438733 | 27 | import util |
1e80d5d7 | 28 | |
8b6bc023 MT |
29 | import logging |
30 | log = logging.getLogger("pakfire") | |
31 | ||
1e80d5d7 MT |
32 | from constants import * |
33 | from i18n import _ | |
34 | ||
35 | class Action(object): | |
d37a555d | 36 | def __init__(self, pakfire, pkg_solv, pkg_bin=None): |
1e80d5d7 | 37 | self.pakfire = pakfire |
1e80d5d7 | 38 | |
d37a555d MT |
39 | self.pkg_solv = pkg_solv |
40 | if pkg_bin: | |
41 | self.pkg_bin = pkg_bin | |
1e80d5d7 | 42 | |
94438733 MT |
43 | self.init() |
44 | ||
aa9f2645 MT |
45 | def __cmp__(self, other): |
46 | return cmp(self.pkg, other.pkg) | |
47 | ||
1e80d5d7 MT |
48 | def __repr__(self): |
49 | return "<%s %s>" % (self.__class__.__name__, self.pkg.friendly_name) | |
50 | ||
94438733 MT |
51 | def init(self): |
52 | # A function to run additional initialization. | |
53 | pass | |
54 | ||
862bea4d MT |
55 | def check(self, filelist): |
56 | # This is just a dummy test that does nothing at all. | |
57 | return filelist | |
58 | ||
81bb7698 | 59 | def verify(self): |
d37a555d | 60 | assert self.pkg, "No package! %s" % self.pkg |
0f8d6745 MT |
61 | assert self.pkg.repo, "Package has no repository? %s" % self.pkg |
62 | ||
63 | # Local packages need no verification. | |
64 | if self.pkg.repo.local: | |
65 | return | |
66 | ||
81bb7698 MT |
67 | # Check if there are any signatures at all. |
68 | if not self.pkg.signatures: | |
69 | raise SignatureError, _("%s has got no signatures") % self.pkg.friendly_name | |
70 | ||
71 | # Run the verification process and save the result. | |
72 | sigs = self.pkg.verify() | |
73 | ||
74 | if not sigs: | |
75 | raise SignatureError, _("%s has got no valid signatures") % self.pkg.friendly_name | |
76 | ||
1e80d5d7 | 77 | @property |
d37a555d MT |
78 | def pkg(self): |
79 | """ | |
80 | Return the best version of the package we can use. | |
81 | """ | |
82 | return self.pkg_bin or self.pkg_solv | |
1e80d5d7 | 83 | |
d37a555d MT |
84 | def get_binary_package(self): |
85 | """ | |
86 | Tries to find the binary version of the package in the local cache. | |
87 | """ | |
88 | return self.pkg_solv.get_from_cache() | |
89 | ||
90 | def _get_pkg_bin(self): | |
91 | if not hasattr(self, "_pkg_bin"): | |
92 | self._pkg_bin = self.get_binary_package() | |
1e80d5d7 | 93 | |
d37a555d MT |
94 | return self._pkg_bin |
95 | ||
96 | def _set_pkg_bin(self, pkg): | |
97 | if pkg and not self.pkg_solv.uuid == pkg.uuid: | |
98 | raise RuntimeError, "Not the same package: %s != %s" % (self.pkg_solv, pkg) | |
99 | ||
100 | self._pkg_bin = pkg | |
101 | ||
102 | pkg_bin = property(_get_pkg_bin, _set_pkg_bin) | |
1e80d5d7 MT |
103 | |
104 | def run(self): | |
105 | raise NotImplementedError | |
106 | ||
107 | @property | |
108 | def local(self): | |
109 | """ | |
110 | Reference to local repository. | |
111 | """ | |
112 | return self.pakfire.repos.local | |
113 | ||
c5b58985 MT |
114 | def get_logger_name(self): |
115 | return "pakfire.action.%s" % self.pkg.friendly_name | |
116 | ||
117 | def get_logger(self): | |
118 | logger_name = self.get_logger_name() | |
119 | ||
120 | logger = logging.getLogger(logger_name) | |
121 | logger.setLevel(logging.INFO) | |
122 | ||
123 | # Propagate everything to upstream logger. | |
124 | logger.propagate = True | |
125 | ||
126 | return logger | |
127 | ||
64ffcd37 | 128 | def execute(self, command, **kwargs): |
d335c40e MT |
129 | # If we are running in /, we do not need to chroot there. |
130 | chroot_path = None | |
131 | if not self.pakfire.path == "/": | |
132 | chroot_path = self.pakfire.path | |
133 | ||
134 | # Find suitable cwd. | |
135 | cwd = "/" | |
136 | for i in ("tmp", "root"): | |
254f5e56 MT |
137 | if chroot_path: |
138 | _cwd = os.path.join(chroot_path, i) | |
139 | else: | |
140 | _cwd = i | |
141 | ||
d335c40e MT |
142 | if os.path.exists(_cwd): |
143 | cwd = _cwd | |
144 | break | |
145 | ||
146 | args = { | |
147 | "cwd" : cwd, | |
1dd38b7b | 148 | "env" : MINIMAL_ENVIRONMENT, |
c5b58985 | 149 | "logger" : self.get_logger(), |
d335c40e MT |
150 | "personality" : self.pakfire.distro.personality, |
151 | "shell" : False, | |
152 | "timeout" : SCRIPTLET_TIMEOUT, | |
153 | } | |
154 | ||
155 | # Overwrite by args that were passed. | |
156 | args.update(kwargs) | |
157 | ||
158 | # You can never overwrite chrootPath. | |
64ffcd37 | 159 | args["chroot_path"] = chroot_path |
d335c40e | 160 | |
64ffcd37 MT |
161 | # Execute command. |
162 | shellenv = shell.ShellExecuteEnvironment(command, **args) | |
163 | shellenv.execute() | |
d335c40e | 164 | |
1e80d5d7 MT |
165 | |
166 | class ActionScript(Action): | |
d767668b | 167 | type = "script" |
c07a3ca7 | 168 | script_action = None |
d767668b | 169 | |
94438733 | 170 | def init(self): |
bd629777 MT |
171 | self._scriptlet = None |
172 | ||
c5b58985 MT |
173 | def get_logger_name(self): |
174 | logger_name = Action.get_logger_name(self) | |
175 | ||
176 | return "%s.%s" % (logger_name, self.script_action or "unknown") | |
177 | ||
bd629777 MT |
178 | @property |
179 | def scriptlet(self): | |
180 | """ | |
181 | Load the scriplet. | |
182 | """ | |
183 | if self._scriptlet is None: | |
184 | self._scriptlet = self.pkg.get_scriptlet(self.script_action) | |
185 | ||
186 | return self._scriptlet | |
94438733 | 187 | |
536bec81 MT |
188 | def get_lang(self): |
189 | if not self.scriptlet: | |
190 | return | |
191 | ||
192 | interp = None | |
193 | ||
194 | for line in self.scriptlet.splitlines(): | |
195 | if line.startswith("#!/"): | |
196 | interp = "exec" | |
197 | break | |
198 | ||
199 | elif line.startswith("#<lang: "): | |
200 | interp = line[8:].replace(">", "") | |
201 | break | |
202 | ||
203 | return interp | |
204 | ||
94438733 MT |
205 | @property |
206 | def interpreter(self): | |
207 | """ | |
208 | Get the interpreter of this scriptlet. | |
209 | """ | |
c07a3ca7 | 210 | return util.scriptlet_interpreter(self.scriptlet) |
94438733 MT |
211 | |
212 | @property | |
213 | def args(self): | |
c07a3ca7 | 214 | return [] |
94438733 | 215 | |
1e80d5d7 | 216 | def run(self): |
94438733 MT |
217 | # Exit immediately, if the scriptlet is empty. |
218 | if not self.scriptlet: | |
219 | return | |
220 | ||
221 | # Actually run the scriplet. | |
8b6bc023 | 222 | log.debug("Running scriptlet %s" % self) |
94438733 | 223 | |
536bec81 MT |
224 | # Check of what kind the scriptlet is and run the |
225 | # corresponding handler. | |
226 | lang = self.get_lang() | |
227 | ||
228 | if lang == "exec": | |
229 | self.run_exec() | |
230 | ||
231 | elif lang == "python": | |
232 | self.run_python() | |
233 | ||
234 | else: | |
235 | raise ActionError, _("Could not handle scriptlet of unknown type. Skipping.") | |
236 | ||
237 | def run_exec(self): | |
c5b58985 | 238 | log.debug(_("Executing scriptlet...")) |
536bec81 | 239 | |
94438733 | 240 | # Check if the interpreter does exist and is executable. |
c07a3ca7 MT |
241 | if self.interpreter: |
242 | interpreter = "%s/%s" % (self.pakfire.path, self.interpreter) | |
243 | if not os.path.exists(interpreter): | |
244 | raise ActionError, _("Cannot run scriptlet because no interpreter is available: %s" \ | |
245 | % self.interpreter) | |
94438733 | 246 | |
c07a3ca7 MT |
247 | if not os.access(interpreter, os.X_OK): |
248 | raise ActionError, _("Cannot run scriptlet because the interpreter is not executable: %s" \ | |
249 | % self.interpreter) | |
94438733 MT |
250 | |
251 | # Create a name for the temporary script file. | |
252 | script_file_chroot = os.path.join("/", LOCAL_TMP_PATH, | |
253 | "scriptlet_%s" % util.random_string(10)) | |
254 | script_file = os.path.join(self.pakfire.path, script_file_chroot[1:]) | |
254f5e56 | 255 | assert script_file.startswith(self.pakfire.path) |
94438733 MT |
256 | |
257 | # Create script directory, if it does not exist. | |
258 | script_dir = os.path.dirname(script_file) | |
259 | if not os.path.exists(script_dir): | |
260 | os.makedirs(script_dir) | |
261 | ||
262 | # Write the scriptlet to a file that we can execute it. | |
263 | try: | |
264 | f = open(script_file, "wb") | |
265 | f.write(self.scriptlet) | |
266 | f.close() | |
267 | ||
268 | # The file is only accessable by root. | |
269 | os.chmod(script_file, 700) | |
270 | except: | |
271 | # Remove the file if an error occurs. | |
272 | try: | |
273 | os.unlink(script_file) | |
274 | except OSError: | |
275 | pass | |
276 | ||
277 | # XXX catch errors and return a beautiful message to the user | |
278 | raise | |
279 | ||
c07a3ca7 MT |
280 | # Generate the script command. |
281 | command = [script_file_chroot] + self.args | |
94438733 | 282 | |
94438733 | 283 | try: |
64ffcd37 | 284 | self.execute(command) |
94438733 | 285 | |
64ffcd37 | 286 | except ShellEnvironmentError, e: |
94438733 MT |
287 | raise ActionError, _("The scriptlet returned an error:\n%s" % e) |
288 | ||
289 | except commandTimeoutExpired: | |
290 | raise ActionError, _("The scriptlet ran more than %s seconds and was killed." \ | |
291 | % SCRIPTLET_TIMEOUT) | |
292 | ||
1361a82b MT |
293 | except Exception, e: |
294 | raise ActionError, _("The scriptlet returned with an unhandled error:\n%s" % e) | |
295 | ||
94438733 MT |
296 | finally: |
297 | # Remove the script file. | |
298 | try: | |
299 | os.unlink(script_file) | |
300 | except OSError: | |
8b6bc023 | 301 | log.debug("Could not remove scriptlet file: %s" % script_file) |
1e80d5d7 | 302 | |
536bec81 MT |
303 | def run_python(self): |
304 | # This functions creates a fork with then chroots into the | |
305 | # pakfire root if necessary and then compiles the given scriptlet | |
306 | # code and runs it. | |
307 | ||
308 | log.debug(_("Executing python scriptlet...")) | |
309 | ||
310 | # Create fork. | |
311 | pid = os.fork() | |
312 | ||
313 | if not pid: | |
314 | # child code | |
315 | ||
316 | # The child chroots into the pakfire path. | |
317 | if not self.pakfire.path == "/": | |
318 | os.chroot(self.pakfire.path) | |
319 | ||
320 | # Create a clean global environment, where only | |
321 | # builtin functions are available and the os and sys modules. | |
322 | _globals = { | |
323 | "os" : os, | |
324 | "sys" : sys, | |
325 | } | |
326 | ||
327 | # Compile the scriptlet and execute it. | |
328 | try: | |
329 | obj = compile(self.scriptlet, "<string>", "exec") | |
330 | eval(obj, _globals, {}) | |
331 | ||
332 | except Exception, e: | |
333 | print _("Exception occured: %s") % e | |
334 | os._exit(1) | |
335 | ||
336 | # End the child process without cleaning up. | |
337 | os._exit(0) | |
338 | ||
339 | else: | |
340 | # parent code | |
341 | ||
342 | # Wait until the child process has finished. | |
343 | os.waitpid(pid, 0) | |
344 | ||
1e80d5d7 MT |
345 | |
346 | class ActionScriptPreIn(ActionScript): | |
c07a3ca7 | 347 | script_action = "prein" |
1e80d5d7 MT |
348 | |
349 | ||
350 | class ActionScriptPostIn(ActionScript): | |
c07a3ca7 | 351 | script_action = "postin" |
1e80d5d7 MT |
352 | |
353 | ||
354 | class ActionScriptPreUn(ActionScript): | |
c07a3ca7 | 355 | script_action = "preun" |
1e80d5d7 MT |
356 | |
357 | ||
358 | class ActionScriptPostUn(ActionScript): | |
c07a3ca7 | 359 | script_action = "postun" |
1e80d5d7 MT |
360 | |
361 | ||
d767668b | 362 | class ActionScriptPreUp(ActionScript): |
c07a3ca7 | 363 | script_action = "preup" |
d767668b MT |
364 | |
365 | ||
366 | class ActionScriptPostUp(ActionScript): | |
c07a3ca7 | 367 | script_action = "postup" |
d767668b MT |
368 | |
369 | ||
f943de15 MT |
370 | class ActionScriptPreTrans(ActionScript): |
371 | pass | |
372 | ||
373 | ||
374 | class ActionScriptPreTransIn(ActionScriptPreTrans): | |
375 | script_action = "pretransin" | |
376 | ||
377 | ||
378 | class ActionScriptPreTransUn(ActionScriptPreTrans): | |
379 | script_action = "pretransun" | |
380 | ||
381 | ||
382 | class ActionScriptPreTransUp(ActionScriptPreTrans): | |
383 | script_action = "pretransup" | |
384 | ||
385 | ||
d767668b MT |
386 | class ActionScriptPostTrans(ActionScript): |
387 | pass | |
388 | ||
389 | ||
390 | class ActionScriptPostTransIn(ActionScriptPostTrans): | |
c07a3ca7 | 391 | script_action = "posttransin" |
d767668b MT |
392 | |
393 | ||
394 | class ActionScriptPostTransUn(ActionScriptPostTrans): | |
c07a3ca7 | 395 | script_action = "posttransun" |
d767668b MT |
396 | |
397 | ||
398 | class ActionScriptPostTransUp(ActionScriptPostTrans): | |
c07a3ca7 | 399 | script_action = "posttransup" |
d767668b MT |
400 | |
401 | ||
1e80d5d7 MT |
402 | class ActionInstall(Action): |
403 | type = "install" | |
404 | ||
862bea4d | 405 | def check(self, check): |
8b6bc023 | 406 | log.debug(_("Running transaction test for %s") % self.pkg.friendly_name) |
862bea4d MT |
407 | |
408 | # Check if this package can be installed. | |
409 | check.install(self.pkg) | |
410 | ||
1e80d5d7 | 411 | def run(self): |
e871a081 MT |
412 | # Add package to the database. |
413 | self.local.add_package(self.pkg) | |
414 | ||
d37a555d MT |
415 | if isinstance(self, ActionReinstall): |
416 | msg = _("Reinstalling") | |
417 | elif isinstance(self, ActionUpdate): | |
418 | msg = _("Updating") | |
419 | elif isinstance(self, ActionDowngrade): | |
420 | msg = _("Downgrading") | |
421 | else: | |
422 | msg = _("Installing") | |
423 | ||
424 | self.pkg.extract(msg, prefix=self.pakfire.path) | |
1e80d5d7 | 425 | |
d335c40e MT |
426 | # Check if shared objects were extracted. If this is the case, we need |
427 | # to run ldconfig. | |
428 | ldconfig_needed = False | |
429 | for file in self.pkg.filelist: | |
430 | if ".so." in file.name: | |
431 | ldconfig_needed = True | |
432 | break | |
433 | ||
434 | if "etc/ld.so.conf" in file.name: | |
435 | ldconfig_needed = True | |
436 | break | |
437 | ||
438 | if ldconfig_needed: | |
439 | # Check if ldconfig is present. | |
440 | ldconfig = os.path.join(self.pakfire.path, LDCONFIG[1:]) | |
441 | ||
442 | if os.path.exists(ldconfig) and os.access(ldconfig, os.X_OK): | |
64ffcd37 | 443 | self.execute(LDCONFIG) |
d335c40e MT |
444 | |
445 | else: | |
8b6bc023 | 446 | log.debug("ldconfig is not present or not executable.") |
d335c40e | 447 | |
1e80d5d7 | 448 | |
d37a555d | 449 | class ActionUpdate(ActionInstall): |
1e80d5d7 MT |
450 | type = "upgrade" |
451 | ||
862bea4d | 452 | def check(self, check): |
8b6bc023 | 453 | log.debug(_("Running transaction test for %s") % self.pkg.friendly_name) |
862bea4d MT |
454 | |
455 | # Check if this package can be updated. | |
456 | check.update(self.pkg) | |
457 | ||
1e80d5d7 | 458 | |
6ee3d6b9 | 459 | class ActionRemove(Action): |
1e80d5d7 MT |
460 | type = "erase" |
461 | ||
862bea4d | 462 | def check(self, check): |
8b6bc023 | 463 | log.debug(_("Running transaction test for %s") % self.pkg.friendly_name) |
862bea4d MT |
464 | |
465 | # Check if this package can be removed. | |
466 | check.remove(self.pkg) | |
467 | ||
6ee3d6b9 | 468 | def run(self): |
d37a555d MT |
469 | if isinstance(self, ActionCleanup): |
470 | msg = _("Cleanup") | |
471 | else: | |
472 | msg = _("Removing") | |
473 | ||
474 | self.pkg.cleanup(msg, prefix=self.pakfire.path) | |
1e80d5d7 | 475 | |
e871a081 | 476 | # Remove package from the database. |
d37a555d | 477 | self.local.rem_package(self.pkg) |
e871a081 MT |
478 | |
479 | ||
d37a555d | 480 | class ActionCleanup(ActionRemove): |
e871a081 MT |
481 | type = "ignore" |
482 | ||
862bea4d | 483 | def check(self, check): |
8b6bc023 | 484 | log.debug(_("Running transaction test for %s") % self.pkg.friendly_name) |
862bea4d MT |
485 | |
486 | # Check if this package can be removed. | |
487 | check.cleanup(self.pkg) | |
488 | ||
1e80d5d7 | 489 | |
d37a555d | 490 | class ActionReinstall(ActionInstall): |
1e80d5d7 MT |
491 | type = "reinstall" |
492 | ||
1e80d5d7 | 493 | |
d37a555d | 494 | class ActionDowngrade(ActionInstall): |
1e80d5d7 | 495 | type = "downgrade" |