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

Reply via email to