aboutsummaryrefslogtreecommitdiff
blob: e5b14c02324f3520ce00e4e0e44b24c7956d06c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#-*- 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,
}