From 944387335c99c4e5fdc89f98a8ffc4602afb612c Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Wed, 10 Aug 2011 18:13:38 +0200 Subject: [PATCH] Add code, that actually runs scriptlets. --- pakfire/actions.py | 147 ++++++++++++++++++++++++++++++++++++--- pakfire/constants.py | 63 ++++++++++++++++- pakfire/errors.py | 3 + pakfire/packages/file.py | 2 +- po/pakfire.pot | 40 ++++++++--- 5 files changed, 233 insertions(+), 22 deletions(-) diff --git a/pakfire/actions.py b/pakfire/actions.py index e0bf67890..1518ae980 100644 --- a/pakfire/actions.py +++ b/pakfire/actions.py @@ -20,8 +20,11 @@ ############################################################################### import logging +import os +import chroot import packages +import util from constants import * from i18n import _ @@ -37,12 +40,18 @@ class Action(object): if binary_package: self.pkg = binary_package + self.init() + def __cmp__(self, other): return cmp(self.pkg, other.pkg) def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.pkg.friendly_name) + def init(self): + # A function to run additional initialization. + pass + @property def needs_download(self): return self.type in ("install", "reinstall", "upgrade", "downgrade",) \ @@ -68,33 +77,143 @@ class Action(object): class ActionScript(Action): type = "script" + def init(self): + # Load the scriplet. + self.scriptlet = self.pkg.scriptlet + + @property + def interpreter(self): + """ + Get the interpreter of this scriptlet. + """ + # XXX check, how to handle elf files here. + + # If nothing was found, we return the default interpreter. + interpreter = SCRIPTLET_INTERPRETER + + for line in self.scriptlet.splitlines(): + if line.startswith("#!/"): + interpreter = line[2:] + interpreter = interpreter.split()[0] + break + + return interpreter + + @property + def args(self): + raise NotImplementedError + def run(self): - #print "Pretending to run script: %s" % self.__class__.__name__ - pass + # Exit immediately, if the scriptlet is empty. + if not self.scriptlet: + return + + # Actually run the scriplet. + logging.debug("Running scriptlet %s" % self) + + # Check if the interpreter does exist and is executable. + interpreter = "%s/%s" % (self.pakfire.path, self.interpreter) + if not os.path.exists(interpreter): + raise ActionError, _("Cannot run scriptlet because no interpreter is available: %s" \ + % self.interpreter) + + if not os.access(interpreter, os.X_OK): + raise ActionError, _("Cannot run scriptlet because the interpreter is not executable: %s" \ + % self.interpreter) + + # Create a name for the temporary script file. + script_file_chroot = os.path.join("/", LOCAL_TMP_PATH, + "scriptlet_%s" % util.random_string(10)) + script_file = os.path.join(self.pakfire.path, script_file_chroot[1:]) + assert script_file.startswith("%s/" % self.pakfire.path) + + # Create script directory, if it does not exist. + script_dir = os.path.dirname(script_file) + if not os.path.exists(script_dir): + os.makedirs(script_dir) + + # Write the scriptlet to a file that we can execute it. + try: + f = open(script_file, "wb") + f.write(self.scriptlet) + f.close() + + # The file is only accessable by root. + os.chmod(script_file, 700) + except: + # Remove the file if an error occurs. + try: + os.unlink(script_file) + except OSError: + pass + + # XXX catch errors and return a beautiful message to the user + raise + + command = [script_file_chroot,] + self.args + + # If we are running in /, we do not need to chroot there. + chroot_dir = None + if not self.pakfire.path == "/": + chroot_dir = self.pakfire.path + + try: + ret = chroot.do(command, cwd="/tmp", + chrootPath=chroot_path, + personality=self.pakfire.distro.personality, + shell=False, + timeout=SCRIPTLET_TIMEOUT, + logger=logging.getLogger()) + + except Error, e: + raise ActionError, _("The scriptlet returned an error:\n%s" % e) + + except commandTimeoutExpired: + raise ActionError, _("The scriptlet ran more than %s seconds and was killed." \ + % SCRIPTLET_TIMEOUT) + + finally: + # Remove the script file. + try: + os.unlink(script_file) + except OSError: + logging.debug("Could not remove scriptlet file: %s" % script_file) class ActionScriptPreIn(ActionScript): - pass + @property + def args(self): + return ["prein",] class ActionScriptPostIn(ActionScript): - pass + @property + def args(self): + return ["postin",] class ActionScriptPreUn(ActionScript): - pass + @property + def args(self): + return ["preun",] class ActionScriptPostUn(ActionScript): - pass + @property + def args(self): + return ["postun",] class ActionScriptPreUp(ActionScript): - pass + @property + def args(self): + return ["preup",] class ActionScriptPostUp(ActionScript): - pass + @property + def args(self): + return ["postup",] class ActionScriptPostTrans(ActionScript): @@ -102,15 +221,21 @@ class ActionScriptPostTrans(ActionScript): class ActionScriptPostTransIn(ActionScriptPostTrans): - pass + @property + def args(self): + return ["posttransin",] class ActionScriptPostTransUn(ActionScriptPostTrans): - pass + @property + def args(self): + return ["posttransun",] class ActionScriptPostTransUp(ActionScriptPostTrans): - pass + @property + def args(self): + return ["posttransup",] class ActionInstall(Action): diff --git a/pakfire/constants.py b/pakfire/constants.py index 37229f8bb..02cdc5dd7 100644 --- a/pakfire/constants.py +++ b/pakfire/constants.py @@ -35,7 +35,7 @@ CCACHE_CACHE_DIR = os.path.join(CACHE_DIR, "ccache") REPO_CACHE_DIR = os.path.join(CACHE_DIR, "repos") LOCAL_BUILD_REPO_PATH = "/var/lib/pakfire/local" -LOCAL_TMP_PATH = "/var/tmp/pakfire" +LOCAL_TMP_PATH = "var/tmp/pakfire" PACKAGES_DB_DIR = "var/lib/pakfire" PACKAGES_DB = os.path.join(PACKAGES_DB_DIR, "packages.db") @@ -125,3 +125,64 @@ PKG_PAYLOAD_HASH1="%(payload_hash1)s" # XXX make this configurable in pakfire.conf PAKFIRE_MULTIINSTALL = ["kernel",] + +SCRIPTLET_INTERPRETER = "/bin/sh" +SCRIPTLET_TIMEOUT = 60 * 15 + +SCRIPTLET_TEMPLATE = """\ +#!/bin/sh + +function control_prein() { +%(control_prein)s +} + +function control_postin() { +%(control_postin)s +} + +function control_preun() { +%(control_preun)s +} + +function control_postun() { +%(control_postun)s +} + +function control_preup() { +%(control_preup)s +} + +function control_postup() { +%(control_postup)s +} + +function control_postransin() { +%(control_posttransin)s +} + +function control_posttransun() { +%(control_posttransun)s +} + +function control_posttransup() { +%(control_posttransup)s +} + +# Get right action from commandline. +action=${1} +shift + +case "${action}" in + prein|postin|preun|postun|preup|postup|posttransin|posttransun|posttransup) + control_${action} $@ + ;; + + *) + echo "Unknown action: ${action}" >&2 + exit 2 + ;; +esac + +# Always exit with an okay status. +exit 0 +""" diff --git a/pakfire/errors.py b/pakfire/errors.py index 9bc8d68d8..c5574477d 100644 --- a/pakfire/errors.py +++ b/pakfire/errors.py @@ -21,6 +21,9 @@ from i18n import _ +class commandTimeoutExpired(Exception): + pass # XXX cannot be as is + class Error(Exception): exit_code = 1 diff --git a/pakfire/packages/file.py b/pakfire/packages/file.py index 7c19c9fb1..3926f40bd 100644 --- a/pakfire/packages/file.py +++ b/pakfire/packages/file.py @@ -137,7 +137,7 @@ class FilePackage(Package): return True def open_archive(self): - return tarfile.open(self.filename) + return tarfile.open(self.filename, format=tarfile.PAX_FORMAT) def extract(self, message=None, prefix=None): logging.debug("Extracting package %s" % self.friendly_name) diff --git a/po/pakfire.pot b/po/pakfire.pot index 4705bbd0c..61bb9b88a 100644 --- a/po/pakfire.pot +++ b/po/pakfire.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-07 13:23+0200\n" +"POT-Creation-Date: 2011-08-10 18:10+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,24 +17,46 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: ../pakfire/actions.py:123 ../pakfire/actions.py:180 +#: ../pakfire/actions.py:117 +#, python-format +msgid "Cannot run scriptlet because no interpreter is available: %s" +msgstr "" + +#: ../pakfire/actions.py:121 +#, python-format +msgid "Cannot run scriptlet because the interpreter is not executable: %s" +msgstr "" + +#: ../pakfire/actions.py:169 +#, python-format +msgid "" +"The scriptlet returned an error:\n" +"%s" +msgstr "" + +#: ../pakfire/actions.py:172 +#, python-format +msgid "The scriptlet ran more than %s seconds and was killed." +msgstr "" + +#: ../pakfire/actions.py:248 ../pakfire/actions.py:305 msgid "Installing" msgstr "" -#: ../pakfire/actions.py:133 +#: ../pakfire/actions.py:258 msgid "Updating" msgstr "" -#: ../pakfire/actions.py:147 +#: ../pakfire/actions.py:272 msgid "Removing" msgstr "" #. Cleaning up leftover files and stuff. -#: ../pakfire/actions.py:165 +#: ../pakfire/actions.py:290 msgid "Cleanup" msgstr "" -#: ../pakfire/actions.py:190 +#: ../pakfire/actions.py:315 msgid "Downgrading" msgstr "" @@ -288,15 +310,15 @@ msgstr "" msgid "Path to input packages." msgstr "" -#: ../pakfire/errors.py:27 +#: ../pakfire/errors.py:30 msgid "An unhandled error occured." msgstr "" -#: ../pakfire/errors.py:48 +#: ../pakfire/errors.py:51 msgid "One or more dependencies could not been resolved." msgstr "" -#: ../pakfire/errors.py:63 +#: ../pakfire/errors.py:66 msgid "" "The requested action cannot be done on offline mode.\n" "Please connect your system to the network, remove --offline from the command " -- 2.39.5