v2: Reformat, add a function to return an appropriate read-only checker
for the operating system, so that this can be extended to other OSes.

v3: minor formatting tweaks from bernalex, including use os.path.join()
instead of string concatenation

v4: Update copyright header, change the code in rochecker to open with
io.open in order to not break on non-ascii characters as reported by
Arfrever. Change get_ro_checker to use a dict as suggested by vapier.

v5: Fix comment format as requested by vapier.

v6: Change linux_ro_checker to use set.intersection instead of the
longer check-and-append system as suggested by vapier.
---
 pym/portage/dbapi/vartree.py  | 34 +++++++++++++++++-
 pym/portage/util/rochecker.py | 80 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 113 insertions(+), 1 deletion(-)
 create mode 100644 pym/portage/util/rochecker.py

diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py
index ed62323..cf6781b 100644
--- a/pym/portage/dbapi/vartree.py
+++ b/pym/portage/dbapi/vartree.py
@@ -1,4 +1,4 @@
-# Copyright 1998-2013 Gentoo Foundation
+# Copyright 1998-2014 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
 from __future__ import unicode_literals
@@ -32,6 +32,7 @@ portage.proxy.lazyimport.lazyimport(globals(),
        'portage.util.env_update:env_update',
        'portage.util.listdir:dircache,listdir',
        'portage.util.movefile:movefile',
+       'portage.util.rochecker:get_ro_checker',
        'portage.util._dyn_libs.PreservedLibsRegistry:PreservedLibsRegistry',
        'portage.util._dyn_libs.LinkageMapELF:LinkageMapELF@LinkageMap',
        'portage.util._async.SchedulerInterface:SchedulerInterface',
@@ -3508,6 +3509,8 @@ class dblink(object):
                
                This function does the following:
                
+               calls get_ro_checker to retrieve a function for checking 
whether Portage
+               will write to a read-only filesystem, then runs it against the 
directory list
                calls self._preserve_libs if FEATURES=preserve-libs
                calls self._collision_protect if FEATURES=collision-protect
                calls doebuild(mydo=pkg_preinst)
@@ -3685,6 +3688,7 @@ class dblink(object):
                        eagain_error = False
 
                        myfilelist = []
+                       mydirlist = []
                        mylinklist = []
                        paths_with_newlines = []
                        def onerror(e):
@@ -3717,6 +3721,9 @@ class dblink(object):
                                        
unicode_errors.append(new_parent[ed_len:])
                                        break
 
+                               relative_path = parent[srcroot_len:]
+                               mydirlist.append(os.path.join("/", 
relative_path))
+
                                for fname in files:
                                        try:
                                                fname = _unicode_decode(fname,
@@ -3829,6 +3836,31 @@ class dblink(object):
                        for other in others_in_slot])
                prepare_build_dirs(settings=self.settings, cleanup=cleanup)
 
+               # Check for read-only filesystems.
+               ro_checker = get_ro_checker()
+               rofilesystems = ro_checker(mydirlist)
+
+               if rofilesystems:
+                       msg = _("One or more files installed to this package 
are "
+                               "set to be installed to read-only filesystems. "
+                               "Please mount the following filesystems as 
read-write "
+                               "and retry.")
+                       msg = textwrap.wrap(msg, 70)
+                       msg.append("")
+                       for f in rofilesystems:
+                               msg.append("\t%s" % os.path.join(destroot,
+                                       f.lstrip(os.path.sep)))
+                       msg.append("")
+                       self._elog("eerror", "preinst", msg)
+
+                       msg = _("Package '%s' NOT merged due to read-only file 
systems.") % \
+                               self.settings.mycpv
+                       msg += _(" If necessary, refer to your elog "
+                               "messages for the whole content of the above 
message.")
+                       msg = textwrap.wrap(msg, 70)
+                       eerror(msg)
+                       return 1
+
                # check for package collisions
                blockers = self._blockers
                if blockers is None:
diff --git a/pym/portage/util/rochecker.py b/pym/portage/util/rochecker.py
new file mode 100644
index 0000000..c13bdfc
--- /dev/null
+++ b/pym/portage/util/rochecker.py
@@ -0,0 +1,80 @@
+#-*- coding:utf-8 -*-
+# Copyright 2014 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+"""
+Methods to check whether Portage is going to write to read-only filesystems.
+Since the methods are not portable across different OSes, each OS needs its
+own method. To expand RO checking for different OSes, add a method which
+accepts a list of directories and returns a list of mounts which need to be
+remounted RW, then add "elif ostype == (the ostype value for your OS)" to
+get_ro_checker().
+"""
+from __future__ import unicode_literals
+
+import io
+import logging
+import re
+
+from portage import _encodings
+from portage.util import writemsg_level
+from portage.localization import _
+from portage.data import ostype
+
+
+def get_ro_checker():
+       """
+       Uses the system type to find an appropriate method for testing whether 
Portage
+       is going to write to any read-only filesystems.
+
+       @return:
+       1. A method for testing for RO filesystems appropriate to the current 
system.
+       """
+       return _CHECKERS.get(ostype, empty_ro_checker)
+
+
+def linux_ro_checker(dir_list):
+       """
+       Use /proc/mounts to check that no directories installed by the ebuild 
are set
+       to be installed to a read-only filesystem.
+
+       @param dir_list: A list of directories installed by the ebuild.
+       @type dir_list: List
+       @return:
+       1. A list of filesystems which are both set to be written to and are 
mounted
+       read-only, may be empty.
+       """
+       ro_filesystems = set()
+       ro_filesystems_written = set()
+
+       try:
+               with io.open("/proc/mounts", mode='r', 
encoding=_encodings['content'],
+                       errors='replace') as f:
+                       roregex = re.compile(r'(\A|,)ro(\Z|,)')
+                       for line in f:
+                               if roregex.search(line.split(" ")[3].strip()) 
is not None:
+                                       romount = line.split(" ")[1].strip()
+                                       ro_filesystems.add(romount)
+
+       # If /proc/mounts can't be read, assume that there are no RO
+       # filesystems and return.
+       except EnvironmentError:
+               writemsg_level(_("!!! /proc/mounts cannot be read"),
+                       level=logging.WARNING, noiselevel=-1)
+               return []
+
+       return set.intersection(ro_filesystems, set(dir_list))
+
+
+def empty_ro_checker(dir_list):
+       """
+       Always returns [], this is the fallback function if the system does not 
have
+       an ro_checker method defined.
+       """
+       return []
+
+
+# _CHECKERS is a map from ostype output to the appropriate function to return
+# in get_ro_checker.
+_CHECKERS = {
+       "Linux": linux_ro_checker,
+}
-- 
1.8.5.3


Reply via email to