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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
# Copyright 1999-2011 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
import errno
import sys
from portage.util import writemsg
from portage.data import secpass
import portage
from portage import os
try:
import cPickle as pickle
except ImportError:
import pickle
if sys.hexversion >= 0x3000000:
basestring = str
long = int
class BlockerCache(portage.cache.mappings.MutableMapping):
"""This caches blockers of installed packages so that dep_check does not
have to be done for every single installed package on every invocation of
emerge. The cache is invalidated whenever it is detected that something
has changed that might alter the results of dep_check() calls:
1) the set of installed packages (including COUNTER) has changed
"""
# Number of uncached packages to trigger cache update, since
# it's wasteful to update it for every vdb change.
_cache_threshold = 5
class BlockerData(object):
__slots__ = ("__weakref__", "atoms", "counter")
def __init__(self, counter, atoms):
self.counter = counter
self.atoms = atoms
def __init__(self, myroot, vardb):
""" myroot is ignored in favour of EROOT """
self._vardb = vardb
self._cache_filename = os.path.join(vardb.settings['EROOT'],
portage.CACHE_PATH, "vdb_blockers.pickle")
self._cache_version = "1"
self._cache_data = None
self._modified = set()
self._load()
def _load(self):
try:
f = open(self._cache_filename, mode='rb')
mypickle = pickle.Unpickler(f)
try:
mypickle.find_global = None
except AttributeError:
# TODO: If py3k, override Unpickler.find_class().
pass
self._cache_data = mypickle.load()
f.close()
del f
except (AttributeError, EOFError, EnvironmentError, ValueError, pickle.UnpicklingError) as e:
if isinstance(e, EnvironmentError) and \
getattr(e, 'errno', None) in (errno.ENOENT, errno.EACCES):
pass
else:
writemsg("!!! Error loading '%s': %s\n" % \
(self._cache_filename, str(e)), noiselevel=-1)
del e
cache_valid = self._cache_data and \
isinstance(self._cache_data, dict) and \
self._cache_data.get("version") == self._cache_version and \
isinstance(self._cache_data.get("blockers"), dict)
if cache_valid:
# Validate all the atoms and counters so that
# corruption is detected as soon as possible.
invalid_items = set()
for k, v in self._cache_data["blockers"].items():
if not isinstance(k, basestring):
invalid_items.add(k)
continue
try:
if portage.catpkgsplit(k) is None:
invalid_items.add(k)
continue
except portage.exception.InvalidData:
invalid_items.add(k)
continue
if not isinstance(v, tuple) or \
len(v) != 2:
invalid_items.add(k)
continue
counter, atoms = v
if not isinstance(counter, (int, long)):
invalid_items.add(k)
continue
if not isinstance(atoms, (list, tuple)):
invalid_items.add(k)
continue
invalid_atom = False
for atom in atoms:
if not isinstance(atom, basestring):
invalid_atom = True
break
if atom[:1] != "!" or \
not portage.isvalidatom(
atom, allow_blockers=True):
invalid_atom = True
break
if invalid_atom:
invalid_items.add(k)
continue
for k in invalid_items:
del self._cache_data["blockers"][k]
if not self._cache_data["blockers"]:
cache_valid = False
if not cache_valid:
self._cache_data = {"version":self._cache_version}
self._cache_data["blockers"] = {}
self._modified.clear()
def flush(self):
"""If the current user has permission and the internal blocker cache
been updated, save it to disk and mark it unmodified. This is called
by emerge after it has proccessed blockers for all installed packages.
Currently, the cache is only written if the user has superuser
privileges (since that's required to obtain a lock), but all users
have read access and benefit from faster blocker lookups (as long as
the entire cache is still valid). The cache is stored as a pickled
dict object with the following format:
{
version : "1",
"blockers" : {cpv1:(counter,(atom1, atom2...)), cpv2...},
}
"""
if len(self._modified) >= self._cache_threshold and \
secpass >= 2:
try:
f = portage.util.atomic_ofstream(self._cache_filename, mode='wb')
pickle.dump(self._cache_data, f, protocol=2)
f.close()
portage.util.apply_secpass_permissions(
self._cache_filename, gid=portage.portage_gid, mode=0o644)
except (IOError, OSError):
pass
self._modified.clear()
def __setitem__(self, cpv, blocker_data):
"""
Update the cache and mark it as modified for a future call to
self.flush().
@param cpv: Package for which to cache blockers.
@type cpv: String
@param blocker_data: An object with counter and atoms attributes.
@type blocker_data: BlockerData
"""
self._cache_data["blockers"][cpv] = \
(blocker_data.counter, tuple(str(x) for x in blocker_data.atoms))
self._modified.add(cpv)
def __iter__(self):
if self._cache_data is None:
# triggered by python-trace
return iter([])
return iter(self._cache_data["blockers"])
def __len__(self):
"""This needs to be implemented in order to avoid
infinite recursion in some cases."""
return len(self._cache_data["blockers"])
def __delitem__(self, cpv):
del self._cache_data["blockers"][cpv]
def __getitem__(self, cpv):
"""
@rtype: BlockerData
@returns: An object with counter and atoms attributes.
"""
return self.BlockerData(*self._cache_data["blockers"][cpv])
|