blob: e5b14c02324f3520ce00e4e0e44b24c7956d06c2 [file] [log] [blame]
#-*- coding:utf-8 -*-
# Copyright 2014-2015 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 os
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/self/mountinfo 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()
invalids = []
try:
with io.open("/proc/self/mountinfo", mode='r',
encoding=_encodings['content'], errors='replace') as f:
for line in f:
# we're interested in dir and both attr fileds which always
# start with either 'ro' or 'rw'
# example line:
# 14 1 8:3 / / rw,noatime - ext3 /dev/root rw,errors=continue,commit=5,barrier=1,data=writeback
# _dir ^ ^ attr1 ^ attr2
# there can be a variable number of fields
# to the left of the ' - ', after the attr's, so split it there
mount = line.split(' - ', 1)
try:
_dir, attr1 = mount[0].split()[4:6]
except ValueError:
# If it raises ValueError we can simply ignore the line.
invalids.append(line)
continue
# check for situation with invalid entries for /home and /root in /proc/self/mountinfo
# root path is missing sometimes on WSL
# for example: 16 1 0:16 / /root rw,noatime - lxfs rw
if len(mount) > 1:
try:
attr2 = mount[1].split()[2]
except IndexError:
try:
attr2 = mount[1].split()[1]
except IndexError:
invalids.append(line)
continue
else:
invalids.append(line)
continue
if attr1.startswith('ro') or attr2.startswith('ro'):
ro_filesystems.add(_dir)
# If /proc/self/mountinfo can't be read, assume that there are no RO
# filesystems and return.
except EnvironmentError:
writemsg_level(_("!!! /proc/self/mountinfo cannot be read"),
level=logging.WARNING, noiselevel=-1)
return []
for line in invalids:
writemsg_level(_("!!! /proc/self/mountinfo contains unrecognized line: %s\n")
% line.rstrip(), level=logging.WARNING, noiselevel=-1)
ro_devs = {}
for x in ro_filesystems:
try:
ro_devs[os.stat(x).st_dev] = x
except OSError:
pass
ro_filesystems.clear()
for x in set(dir_list):
try:
dev = os.stat(x).st_dev
except OSError:
pass
else:
try:
ro_filesystems.add(ro_devs[dev])
except KeyError:
pass
return ro_filesystems
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,
}