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