Yedidyah Bar David has uploaded a new change for review. Change subject: packaging: setup: Add keepcontenttransaction ......................................................................
packaging: setup: Add keepcontenttransaction Similar to filetransaction, but does not write content itself - instead, expects content to be written elsewhere between prepare and commit/abort. Change-Id: I91bc120325f98f490567054132f117efa42fab94 Signed-off-by: Yedidyah Bar David <[email protected]> --- A packaging/setup/ovirt_engine_setup/keepcontenttransaction.py 1 file changed, 297 insertions(+), 0 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/37/41837/1 diff --git a/packaging/setup/ovirt_engine_setup/keepcontenttransaction.py b/packaging/setup/ovirt_engine_setup/keepcontenttransaction.py new file mode 100644 index 0000000..c0cab97 --- /dev/null +++ b/packaging/setup/ovirt_engine_setup/keepcontenttransaction.py @@ -0,0 +1,297 @@ +# +# otopi -- plugable installer +# Copyright (C) 2015 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + + +"""Keep-content transaction element.""" + + +import datetime +import gettext +import grp +import os +import pwd +import shutil +import subprocess + + +from . import transaction +from . import util + + +def _(m): + return gettext.dgettext(message=m, domain='otopi') + + [email protected] +class KeepContentTransaction(transaction.TransactionElement): + """Keep-content transaction element.""" + # This is similar in some way to otopi's filetransaction, + # but is used for cases where the content is actually modified + # externally. + + @staticmethod + def _defaultAtomicMove(source, destination, binary=False): + atomic = False + + # perform atomic move if on same device + # (not mount -o bind) + # if destination does not exist, check directory. + # if destination exists, check file. + if not os.path.exists(destination): + if ( + os.stat(source).st_dev == os.stat( + os.path.dirname(destination) + ).st_dev + ): + atomic = True + elif os.stat(destination).st_dev == os.stat(source).st_dev: + atomic = True + + if atomic: + os.rename(source, destination) + else: + # pray! + content = '' + with open(source, 'r' + ('b' if binary else '')) as f: + content = f.read() + with open(destination, 'w' + ('b' if binary else '')) as f: + f.write(content) + os.unlink(source) + + def _createDirRecursive(self, d): + ret = None + if d and d != '/': + ret = self._createDirRecursive(os.path.dirname(d)) + if not os.path.exists(d): + ret = d if ret is None else ret + os.mkdir(d) + os.chmod(d, self._dmode) + os.chown( + d, + self._downer, + self._dgroup + ) + return ret + + _atomicMove = _defaultAtomicMove + + @property + def name(self): + return self._name + + @classmethod + def registerAtomicMove(clz, function): + clz._atomicMove = function + + @classmethod + def getAtomicMove(clz, function): + return clz._atomicMove + + def __init__( + self, + name, + binary=False, + mode=0o644, + dmode=0o755, + owner=None, + group=None, + downer=None, + dgroup=None, + enforcePermissions=False, + modifiedList=None, + ): + """Constructor. + + Backup current file. + Create the new file as temporary name at same directory. + Copy or assign new file attributes. + When commit move temporary file to target file. + + Keyword arguments: + name -- name of file. + binary -- treat content as binary. + mode -- mode of file. + dmode -- directory mode if directory is to be created. + owner -- owner (name) + group -- group (name) + downer -- directory owner (name) if directory is to be created. + dgroup -- directory group (name) if directory is to be created. + enforcePermissions -- if True permissions are enforced also + if previous file was exists. + modifiedList -- a list to add file name if was changed. + + """ + super(KeepContentTransaction, self).__init__() + self._name = name + self._binary = binary + self._orig_content = None + + self._mode = mode + self._dmode = dmode + self._owner = -1 + self._group = -1 + self._downer = -1 + self._dgroup = -1 + self._enforcePermissions = enforcePermissions + self._modifiedList = modifiedList + if owner is not None: + self._owner, self._group = pwd.getpwnam(owner)[2:4] + if group is not None: + self._group = grp.getgrnam(group)[2] + if downer is not None: + self._downer, self._group = pwd.getpwnam(downer)[2:4] + if dgroup is not None: + self._dgroup = grp.getgrnam(dgroup)[2] + self._backup = None + self._originalFileWasMissing = not os.path.exists(self._name) + self._prepared = False + self._createdDirectory = None + + def __str__(self): + return _("File transaction for '{file}'").format( + file=self._name + ) + + def prepare(self): + if self._originalFileWasMissing: + self.logger.debug("file '%s' missing" % self._name) + mydir = os.path.dirname(self._name) + if not os.path.exists(mydir): + self._createdDirectory = self._createDirRecursive(mydir) + else: + self.logger.debug("file '%s' exists" % self._name) + with open(self._name, 'r' + ('b' if self._binary else '')) as f: + self._orig_content = f.read() + # check we can open file for write + with open(self._name, 'a'): + pass + + currentStat = os.stat(self._name) + if not self._enforcePermissions: + self._mode = currentStat.st_mode + self._owner = currentStat.st_uid + self._group = currentStat.st_gid + + # + # backup the file + # + self._backup = "%s.%s" % ( + self._name, + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ) + self.logger.debug( + "backup '%s'->'%s'" % ( + self._name, + self._backup + ) + ) + shutil.copyfile(self._name, self._backup) + shutil.copystat(self._name, self._backup) + os.chown( + self._backup, + currentStat.st_uid, + currentStat.st_gid + ) + + self._prepared = True + + def abort(self): + try: + if self._originalFileWasMissing: + if os.path.exists(self._name): + os.unlink(self._name) + elif ( + self._backup is not None and + os.path.exists(self._backup) + ): + if os.path.exists(self._name): + # TODO: + # Do we want this? abort should be quick/"atomic", + # perhaps always just rename. + with open( + self._name, + 'r' + ('b' if self._binary else '') + ) as f: + if f.read() == self._orig_content: + self.logger.debug( + "file '%s' did not change" % self._name + ) + else: + type(self)._atomicMove( + source=self._backup, + destination=self._name, + binary=self._binary, + ) + except OSError: + self.logger.debug('Exception during abort', exc_info=True) + pass + + def commit(self): + if self._prepared: + if self._modifiedList is not None: + self._modifiedList.append(self._name) + + RESTORECON = '/sbin/restorecon' + if os.path.exists(RESTORECON): + what = ( + self._name if self._createdDirectory is None + else self._createdDirectory + ) + try: + self.logger.debug( + 'Executing restorecon for %s', + what + ) + p = subprocess.Popen( + (RESTORECON, '-r', what), + executable=RESTORECON, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, + ) + stdout, stderr = p.communicate() + self.logger.debug( + 'restorecon result rc=%s, stdout=%s, stderr=%s', + p.returncode, + stdout, + stderr, + ) + if p.returncode != 0: + self.logger.warning( + _( + "Failed to restore SELinux attributes " + "for '{file}'" + ).format( + file=what, + ) + ) + except Exception: + self.logger.warning( + _( + "Failed to restore SELinux attributes " + "for '{file}'" + ).format( + file=what, + ) + ) + self.logger.debug('Exception', exc_info=True) + raise + + +# vim: expandtab tabstop=4 shiftwidth=4 -- To view, visit https://gerrit.ovirt.org/41837 To unsubscribe, visit https://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I91bc120325f98f490567054132f117efa42fab94 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Yedidyah Bar David <[email protected]> Gerrit-Reviewer: Jenkins CI Gerrit-Reviewer: [email protected] _______________________________________________ Engine-patches mailing list [email protected] http://lists.ovirt.org/mailman/listinfo/engine-patches
