Subject: [PATCH] Added 'rpm-dbpath' option; enhanced chroot operations This patch adds an 'rpm-dbpath' option which overrides the default '/var/lib/rpm' location. To ensure proper 'rpm-root' operations, all related operations will be executed after a 'chroot(2)'. So, attacks/nasty surprises can be avoided when e.g. '/var' is a symlink to '/my-var'. Unfortunatly, this patch does not honor an existing %_dbpath macro because it seems to be impossible to expand macros with the rpm python bindings. --- smart/backends/rpm/base.py | 53 ++++++++++++++++++++++------------ smart/channels/rpm_sys.py | 11 +++++-- smart/chrootguard.py | 69 ++++++++++++++++++++++++++++++++++++++++++++ smart/plugins/detectsys.py | 3 +- 4 files changed, 114 insertions(+), 22 deletions(-) create mode 100644 smart/chrootguard.py 37c7bbe1852498d43ddfacd901bb3bc6c4fec24e diff --git a/smart/backends/rpm/base.py b/smart/backends/rpm/base.py --- a/smart/backends/rpm/base.py +++ b/smart/backends/rpm/base.py @@ -27,6 +27,7 @@ import zlib from rpmver import checkdep, vercmp, splitarch, splitrelease from smart.util.strtools import isGlob from smart.cache import * +from smart.chrootguard import ChrootGuard from smart import * import fnmatch import string @@ -45,27 +46,43 @@ __all__ = ["RPMPackage", "RPMProvides", def getTS(new=False): if not hasattr(getTS, "ts"): - getTS.root = sysconf.get("rpm-root", "/") + getTS.root = sysconf.get("rpm-root", "/") + getTS.dbpath = sysconf.get("rpm-dbpath", None) + if getTS.dbpath: + rpm.addMacro("_dbpath", getTS.dbpath) + else: + # HACK: what is the proper way to extend '%_dbpath' in + # python-rpm? + getTS.dbpath = "/var/lib/rpm" getTS.ts = rpm.ts(getTS.root) if not sysconf.get("rpm-check-signatures", False): getTS.ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES) - dbdir = os.path.join(getTS.root, "var/lib/rpm") - if not os.path.isfile(os.path.join(dbdir, "Packages")): - try: - os.makedirs(dbdir) - getTS.ts.initDB() - except (rpm.error, OSError): - raise Error, _("Couldn't initizalize rpm database at %s") \ - % getTS.root - else: - iface.warning(_("Initialized new rpm database at %s") - % getTS.root) - tmpdir = os.path.join(getTS.root, "var/tmp") - if not os.path.isdir(tmpdir): - try: - os.makedirs(tmpdir) - except OSError: - pass + dbdir = getTS.dbpath + chroot = ChrootGuard(getTS.root) + + try: + tmpdir = "/var/tmp" + if not os.path.isdir(tmpdir): + try: + os.makedirs(tmpdir) + except OSError: + pass + + if not os.path.isfile(os.path.join(dbdir, "Packages")): + try: + os.makedirs(dbdir) + chroot.unchroot() + getTS.ts.initDB() + except (rpm.error, OSError): + raise Error, _("Couldn't initizalize rpm database at %s") \ + % getTS.root + else: + iface.warning(_("Initialized new rpm database at %s") + % getTS.root) + finally: + chroot.unchroot() + del chroot + if new: ts = rpm.ts(getTS.root) if not sysconf.get("rpm-check-signatures", False): diff --git a/smart/channels/rpm_sys.py b/smart/channels/rpm_sys.py --- a/smart/channels/rpm_sys.py +++ b/smart/channels/rpm_sys.py @@ -22,6 +22,7 @@ from smart.backends.rpm.header import RPMDBLoader from smart.backends.rpm.base import getTS from smart.channel import PackageChannel +from smart.chrootguard import ChrootGuard from smart import * import os @@ -32,9 +33,13 @@ class RPMSysChannel(PackageChannel): def fetch(self, fetcher, progress): getTS() # Make sure the db exists. - path = os.path.join(sysconf.get("rpm-root", "/"), - "var/lib/rpm/Packages") - digest = os.path.getmtime(path) + chroot = ChrootGuard(getTS.root) + try: + digest = os.path.getmtime(getTS.dbpath) + finally: + chroot.unchroot() + del chroot + if digest == self._digest: return True self.removeLoaders() diff --git a/smart/chrootguard.py b/smart/chrootguard.py new file mode 100644 --- /dev/null +++ b/smart/chrootguard.py @@ -0,0 +1,69 @@ +# Copyright (C) 2005 Enrico Scholz +# +# This file is part of Smart Package Manager. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import os, os.path +import fcntl + +class ChrootGuard: + class __FDHelper: + def __init__(self, fd): + self.__fd = fd + + def __del__(self): + if self.__fd!=-1: + os.close(self.__fd) + + def setCloseOnExec(self): + fcntl.fcntl(self.__fd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) + + def chdir(self): + os.fchdir(self.__fd) + + def __init__(self, chrootdir): + if os.path.normpath(chrootdir)=="/": + self.__curdir = None + self.__curroot = None + self.__chrootdir = None + self.__is_chrooted = False + else: + self.__curdir = self.__openDir(".") + self.__curroot = self.__openDir("/") + os.chroot(chrootdir) + self.__chrootdir = self.__openDir("/") + self.__chrootdir.chdir() + self.__is_chrooted = True + + def __del__(self): + self.unchroot() + + def __openDir(self, dir): + rc = ChrootGuard.__FDHelper(os.open(dir, os.O_RDONLY|os.O_DIRECTORY)) + rc.setCloseOnExec() + return rc + + def chroot(self): + if not self.__is_chrooted and self.__chrootdir!=None: + self.__chrootdir.chdir() + os.chroot(".") + self.__is_chrooted = True + + def unchroot(self): + if self.__is_chrooted: + self.__curroot.chdir() + os.chroot(".") + self.__curdir.chdir() + self.__is_chrooted = False diff --git a/smart/plugins/detectsys.py b/smart/plugins/detectsys.py --- a/smart/plugins/detectsys.py +++ b/smart/plugins/detectsys.py @@ -23,7 +23,8 @@ from smart import * import os def detectRPMSystem(): - dir = os.path.join(sysconf.get("rpm-root", "/"), "var/lib/rpm") + dir = os.path.join(sysconf.get("rpm-root", "/"), + sysconf.get("rpm-dbpath", "/var/lib/rpm").lstrip("/")) if os.path.isdir(dir): for alias in sysconf.keys("channels"): if sysconf.get(("channels", alias, "type")) == "rpm-sys":