1 |
Rework the INSTALL_MASK handler to filter installed files while merging |
2 |
instead of removing them from the installation image. |
3 |
|
4 |
The INSTALL_MASK handling is now moved to Python side of code, with bash |
5 |
being only used to evaluate the value of INSTALL_MASK (with respect to |
6 |
bashrc). |
7 |
|
8 |
The evaluated value of INSTALL_MASK is used to test for collisions, |
9 |
and afterwards to skip masked files from being installed. It is also |
10 |
used in uninstall code to properly remove newly-masked files that were |
11 |
installed previously. |
12 |
--- |
13 |
bin/misc-functions.sh | 13 ++-- |
14 |
bin/phase-functions.sh | 3 +- |
15 |
pym/portage/dbapi/vartree.py | 138 +++++++++++++++++++++++++++++++------------ |
16 |
3 files changed, 108 insertions(+), 46 deletions(-) |
17 |
|
18 |
diff --git a/bin/misc-functions.sh b/bin/misc-functions.sh |
19 |
index 7643af7b5..5e11eadb4 100755 |
20 |
--- a/bin/misc-functions.sh |
21 |
+++ b/bin/misc-functions.sh |
22 |
@@ -383,12 +383,13 @@ preinst_mask() { |
23 |
fi |
24 |
done |
25 |
|
26 |
- install_mask "${ED}" "${INSTALL_MASK}" |
27 |
- |
28 |
- # remove share dir if unnessesary |
29 |
- if has nodoc $FEATURES || has noman $FEATURES || has noinfo $FEATURES; then |
30 |
- rmdir "${ED%/}/usr/share" &> /dev/null |
31 |
- fi |
32 |
+ # Store the final value of INSTALL_MASK in build-info |
33 |
+ local x |
34 |
+ set -f |
35 |
+ local IFS=$' \t\n\r' |
36 |
+ x=$(echo ${INSTALL_MASK}) |
37 |
+ [[ -n $x ]] && echo "$x" > "${PORTAGE_BUILDDIR}"/build-info/INSTALL_MASK |
38 |
+ set +f |
39 |
} |
40 |
|
41 |
preinst_sfperms() { |
42 |
diff --git a/bin/phase-functions.sh b/bin/phase-functions.sh |
43 |
index 3aae3ef56..7f7f6fa11 100644 |
44 |
--- a/bin/phase-functions.sh |
45 |
+++ b/bin/phase-functions.sh |
46 |
@@ -666,7 +666,8 @@ __dyn_install() { |
47 |
ASFLAGS CBUILD CC CFLAGS CHOST CTARGET CXX \ |
48 |
CXXFLAGS EXTRA_ECONF EXTRA_EINSTALL EXTRA_MAKE \ |
49 |
LDFLAGS LIBCFLAGS LIBCXXFLAGS QA_CONFIGURE_OPTIONS \ |
50 |
- QA_DESKTOP_FILE QA_PREBUILT PROVIDES_EXCLUDE REQUIRES_EXCLUDE ; do |
51 |
+ QA_DESKTOP_FILE QA_PREBUILT PROVIDES_EXCLUDE REQUIRES_EXCLUDE \ |
52 |
+ PKG_INSTALL_MASK ; do |
53 |
|
54 |
x=$(echo -n ${!f}) |
55 |
[[ -n $x ]] && echo "$x" > $f |
56 |
diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py |
57 |
index bed76d80f..12137a0a4 100644 |
58 |
--- a/pym/portage/dbapi/vartree.py |
59 |
+++ b/pym/portage/dbapi/vartree.py |
60 |
@@ -1876,6 +1876,7 @@ class dblink(object): |
61 |
for pos, e in errors: |
62 |
writemsg(_("!!! line %d: %s\n") % (pos, e), noiselevel=-1) |
63 |
self.contentscache = pkgfiles |
64 |
+ |
65 |
return pkgfiles |
66 |
|
67 |
def _prune_plib_registry(self, unmerge=False, |
68 |
@@ -1953,7 +1954,7 @@ class dblink(object): |
69 |
@_slot_locked |
70 |
def unmerge(self, pkgfiles=None, trimworld=None, cleanup=True, |
71 |
ldpath_mtimes=None, others_in_slot=None, needed=None, |
72 |
- preserve_paths=None): |
73 |
+ preserve_paths=None, install_mask=[]): |
74 |
""" |
75 |
Calls prerm |
76 |
Unmerges a given package (CPV) |
77 |
@@ -1978,6 +1979,10 @@ class dblink(object): |
78 |
LinkageMap, since they are not registered in the |
79 |
PreservedLibsRegistry yet. |
80 |
@type preserve_paths: set |
81 |
+ @param install_mask: List of INSTALL_MASK values for the install |
82 |
+ enforcing cleanup. This is needed to let unmerge() clean old |
83 |
+ files that now are filtered via INSTALL_MASK. |
84 |
+ @type install_mask: list |
85 |
@rtype: Integer |
86 |
@return: |
87 |
1. os.EX_OK if everything went well. |
88 |
@@ -2121,7 +2126,7 @@ class dblink(object): |
89 |
|
90 |
self.vartree.dbapi._fs_lock() |
91 |
try: |
92 |
- self._unmerge_pkgfiles(pkgfiles, others_in_slot) |
93 |
+ self._unmerge_pkgfiles(pkgfiles, others_in_slot, install_mask) |
94 |
finally: |
95 |
self.vartree.dbapi._fs_unlock() |
96 |
self._clear_contents_cache() |
97 |
@@ -2267,7 +2272,7 @@ class dblink(object): |
98 |
self._display_merge("%s %s %s %s\n" % \ |
99 |
(zing, desc.ljust(8), file_type, file_name)) |
100 |
|
101 |
- def _unmerge_pkgfiles(self, pkgfiles, others_in_slot): |
102 |
+ def _unmerge_pkgfiles(self, pkgfiles, others_in_slot, install_mask): |
103 |
""" |
104 |
|
105 |
Unmerges the contents of a package from the liveFS |
106 |
@@ -2277,6 +2282,8 @@ class dblink(object): |
107 |
@type pkgfiles: Dictionary { filename: [ 'type', '?', 'md5sum' ] } |
108 |
@param others_in_slot: all dblink instances in this slot, excluding self |
109 |
@type others_in_slot: list |
110 |
+ @param install_mask: List of values in INSTALL_MASK. |
111 |
+ @type install_maks: list |
112 |
@rtype: None |
113 |
""" |
114 |
|
115 |
@@ -2491,7 +2498,10 @@ class dblink(object): |
116 |
(statobj.st_dev, statobj.st_ino), |
117 |
[]).append(relative_path) |
118 |
|
119 |
- if is_owned: |
120 |
+ # if the file was replaced by new version, keep it |
121 |
+ # unless it is also INSTALL_MASK-ed in the new version |
122 |
+ # (then it was not really installed and we want to clean it) |
123 |
+ if is_owned and not self._is_install_masked(relative_path[1:], install_mask): |
124 |
show_unmerge("---", unmerge_desc["replaced"], file_type, obj) |
125 |
continue |
126 |
elif relative_path in cfgfiledict: |
127 |
@@ -3689,6 +3699,24 @@ class dblink(object): |
128 |
def _emerge_log(self, msg): |
129 |
emergelog(False, msg) |
130 |
|
131 |
+ def _is_install_masked(self, relative_path, install_mask): |
132 |
+ ret = False |
133 |
+ for pattern in install_mask: |
134 |
+ # absolute path pattern |
135 |
+ if pattern.startswith('/'): |
136 |
+ # match either exact path or one of parent dirs |
137 |
+ # the latter is done via matching pattern/* |
138 |
+ if (fnmatch.fnmatch(relative_path, pattern[1:]) |
139 |
+ or fnmatch.fnmatch(relative_path, pattern[1:] + '/*')): |
140 |
+ ret = True |
141 |
+ break |
142 |
+ # filename |
143 |
+ else: |
144 |
+ if fnmatch.fnmatch(os.path.basename(relative_path), pattern): |
145 |
+ ret = True |
146 |
+ break |
147 |
+ return ret |
148 |
+ |
149 |
def treewalk(self, srcroot, destroot, inforoot, myebuild, cleanup=0, |
150 |
mydbapi=None, prev_mtimes=None, counter=None): |
151 |
""" |
152 |
@@ -3841,7 +3869,7 @@ class dblink(object): |
153 |
max_dblnk = dblnk |
154 |
self._installed_instance = max_dblnk |
155 |
|
156 |
- # Apply INSTALL_MASK before collision-protect, since it may |
157 |
+ # Update INSTALL_MASK before collision-protect, since it may |
158 |
# be useful to avoid collisions in some scenarios. |
159 |
# We cannot detect if this is needed or not here as INSTALL_MASK can be |
160 |
# modified by bashrc files. |
161 |
@@ -3851,6 +3879,17 @@ class dblink(object): |
162 |
phase.start() |
163 |
phase.wait() |
164 |
|
165 |
+ try: |
166 |
+ print('!!!\nRead INSTALL_MASK from %s\n!!!' % os.path.join(inforoot, "INSTALL_MASK")) |
167 |
+ with io.open(_unicode_encode( |
168 |
+ os.path.join(inforoot, "INSTALL_MASK"), |
169 |
+ encoding=_encodings['fs'], errors='strict'), |
170 |
+ mode='r', encoding=_encodings['repo.content'], |
171 |
+ errors='replace') as f: |
172 |
+ install_mask = f.read().split() |
173 |
+ except EnvironmentError as e: |
174 |
+ install_mask = self.settings.get('INSTALL_MASK', '').split() |
175 |
+ |
176 |
# We check for unicode encoding issues after src_install. However, |
177 |
# the check must be repeated here for binary packages (it's |
178 |
# inexpensive since we call os.walk() here anyway). |
179 |
@@ -3922,6 +3961,10 @@ class dblink(object): |
180 |
|
181 |
relative_path = fpath[srcroot_len:] |
182 |
|
183 |
+ # filter on INSTALL_MASK |
184 |
+ if self._is_install_masked(relative_path, install_mask): |
185 |
+ continue |
186 |
+ |
187 |
if line_ending_re.search(relative_path) is not None: |
188 |
paths_with_newlines.append(relative_path) |
189 |
|
190 |
@@ -4270,7 +4313,8 @@ class dblink(object): |
191 |
else: |
192 |
cfgfiledict["IGNORE"]=0 |
193 |
|
194 |
- rval = self._merge_contents(srcroot, destroot, cfgfiledict) |
195 |
+ rval = self._merge_contents(srcroot, destroot, cfgfiledict, |
196 |
+ install_mask) |
197 |
if rval != os.EX_OK: |
198 |
return rval |
199 |
finally: |
200 |
@@ -4351,7 +4395,7 @@ class dblink(object): |
201 |
dblnk.settings.backup_changes("REPLACED_BY_VERSION") |
202 |
unmerge_rval = dblnk.unmerge(ldpath_mtimes=prev_mtimes, |
203 |
others_in_slot=others_in_slot, needed=needed, |
204 |
- preserve_paths=preserve_paths) |
205 |
+ preserve_paths=preserve_paths, install_mask=install_mask) |
206 |
dblnk.settings.pop("REPLACED_BY_VERSION", None) |
207 |
|
208 |
if unmerge_rval == os.EX_OK: |
209 |
@@ -4523,7 +4567,7 @@ class dblink(object): |
210 |
|
211 |
return backup_p |
212 |
|
213 |
- def _merge_contents(self, srcroot, destroot, cfgfiledict): |
214 |
+ def _merge_contents(self, srcroot, destroot, cfgfiledict, install_mask): |
215 |
|
216 |
cfgfiledict_orig = cfgfiledict.copy() |
217 |
|
218 |
@@ -4550,7 +4594,8 @@ class dblink(object): |
219 |
# we do a first merge; this will recurse through all files in our srcroot but also build up a |
220 |
# "second hand" of symlinks to merge later |
221 |
if self.mergeme(srcroot, destroot, outfile, secondhand, |
222 |
- self.settings["EPREFIX"].lstrip(os.sep), cfgfiledict, mymtime): |
223 |
+ self.settings["EPREFIX"].lstrip(os.sep), cfgfiledict, |
224 |
+ mymtime, install_mask): |
225 |
return 1 |
226 |
|
227 |
# now, it's time for dealing our second hand; we'll loop until we can't merge anymore. The rest are |
228 |
@@ -4562,7 +4607,7 @@ class dblink(object): |
229 |
|
230 |
thirdhand = [] |
231 |
if self.mergeme(srcroot, destroot, outfile, thirdhand, |
232 |
- secondhand, cfgfiledict, mymtime): |
233 |
+ secondhand, cfgfiledict, mymtime, install_mask): |
234 |
return 1 |
235 |
|
236 |
#swap hands |
237 |
@@ -4576,7 +4621,7 @@ class dblink(object): |
238 |
if len(secondhand): |
239 |
# force merge of remaining symlinks (broken or circular; oh well) |
240 |
if self.mergeme(srcroot, destroot, outfile, None, |
241 |
- secondhand, cfgfiledict, mymtime): |
242 |
+ secondhand, cfgfiledict, mymtime, install_mask): |
243 |
return 1 |
244 |
|
245 |
#restore umask |
246 |
@@ -4597,7 +4642,8 @@ class dblink(object): |
247 |
|
248 |
return os.EX_OK |
249 |
|
250 |
- def mergeme(self, srcroot, destroot, outfile, secondhand, stufftomerge, cfgfiledict, thismtime): |
251 |
+ def mergeme(self, srcroot, destroot, outfile, secondhand, stufftomerge, |
252 |
+ cfgfiledict, thismtime, install_mask): |
253 |
""" |
254 |
|
255 |
This function handles actual merging of the package contents to the livefs. |
256 |
@@ -4651,6 +4697,7 @@ class dblink(object): |
257 |
while mergelist: |
258 |
|
259 |
relative_path = mergelist.pop() |
260 |
+ instmasked = self._is_install_masked(relative_path, install_mask) |
261 |
mysrc = join(srcroot, relative_path) |
262 |
mydest = join(destroot, relative_path) |
263 |
# myrealdest is mydest without the $ROOT prefix (makes a difference if ROOT!="/") |
264 |
@@ -4737,7 +4784,7 @@ class dblink(object): |
265 |
destmd5 = None |
266 |
|
267 |
moveme = True |
268 |
- if protected: |
269 |
+ if protected and not instmasked: |
270 |
mydest, protected, moveme = self._protect(cfgfiledict, |
271 |
protect_if_modified, mymd5, myto, mydest, |
272 |
myrealdest, mydmode, destmd5, mydest_link) |
273 |
@@ -4765,7 +4812,7 @@ class dblink(object): |
274 |
# we can simply test for existence of this file to see if the target has been merged yet |
275 |
myrealto = normalize_path(os.path.join(destroot, myabsto)) |
276 |
if mydmode is not None and stat.S_ISDIR(mydmode): |
277 |
- if not protected: |
278 |
+ if not protected and not instmasked: |
279 |
# we can't merge a symlink over a directory |
280 |
newdest = self._new_backup_path(mydest) |
281 |
msg = [] |
282 |
@@ -4785,26 +4832,32 @@ class dblink(object): |
283 |
# it later. |
284 |
secondhand.append(mysrc[len(srcroot):]) |
285 |
continue |
286 |
- # unlinking no longer necessary; "movefile" will overwrite symlinks atomically and correctly |
287 |
- if moveme: |
288 |
- zing = ">>>" |
289 |
- mymtime = movefile(mysrc, mydest, newmtime=thismtime, |
290 |
- sstat=mystat, mysettings=self.settings, |
291 |
- encoding=_encodings['merge']) |
292 |
|
293 |
- try: |
294 |
- self._merged_path(mydest, os.lstat(mydest)) |
295 |
- except OSError: |
296 |
- pass |
297 |
+ if instmasked: |
298 |
+ zing = "###" |
299 |
+ # pass mymtime through from initial stat |
300 |
+ else: |
301 |
+ # unlinking no longer necessary; "movefile" will overwrite symlinks atomically and correctly |
302 |
+ if moveme: |
303 |
+ zing = ">>>" |
304 |
+ mymtime = movefile(mysrc, mydest, newmtime=thismtime, |
305 |
+ sstat=mystat, mysettings=self.settings, |
306 |
+ encoding=_encodings['merge']) |
307 |
+ |
308 |
+ try: |
309 |
+ self._merged_path(mydest, os.lstat(mydest)) |
310 |
+ except OSError: |
311 |
+ pass |
312 |
|
313 |
if mymtime != None: |
314 |
- # Use lexists, since if the target happens to be a broken |
315 |
- # symlink then that should trigger an independent warning. |
316 |
- if not (os.path.lexists(myrealto) or |
317 |
- os.path.lexists(join(srcroot, myabsto))): |
318 |
- self._eqawarn('preinst', |
319 |
- [_("QA Notice: Symbolic link /%s points to /%s which does not exist.") |
320 |
- % (relative_path, myabsto)]) |
321 |
+ if not instmasked: |
322 |
+ # Use lexists, since if the target happens to be a broken |
323 |
+ # symlink then that should trigger an independent warning. |
324 |
+ if not (os.path.lexists(myrealto) or |
325 |
+ os.path.lexists(join(srcroot, myabsto))): |
326 |
+ self._eqawarn('preinst', |
327 |
+ [_("QA Notice: Symbolic link /%s points to /%s which does not exist.") |
328 |
+ % (relative_path, myabsto)]) |
329 |
|
330 |
showMessage("%s %s -> %s\n" % (zing, mydest, myto)) |
331 |
if sys.hexversion >= 0x3030000: |
332 |
@@ -4819,7 +4872,9 @@ class dblink(object): |
333 |
return 1 |
334 |
elif stat.S_ISDIR(mymode): |
335 |
# we are merging a directory |
336 |
- if mydmode != None: |
337 |
+ if instmasked: |
338 |
+ showMessage("### %s/\n" % mydest) |
339 |
+ elif mydmode != None: |
340 |
# destination exists |
341 |
|
342 |
if bsd_chflags: |
343 |
@@ -4906,10 +4961,11 @@ class dblink(object): |
344 |
os.chown(mydest, mystat[4], mystat[5]) |
345 |
showMessage(">>> %s/\n" % mydest) |
346 |
|
347 |
- try: |
348 |
- self._merged_path(mydest, os.lstat(mydest)) |
349 |
- except OSError: |
350 |
- pass |
351 |
+ if not instmasked: |
352 |
+ try: |
353 |
+ self._merged_path(mydest, os.lstat(mydest)) |
354 |
+ except OSError: |
355 |
+ pass |
356 |
|
357 |
outfile.write("dir "+myrealdest+"\n") |
358 |
# recurse and merge this directory |
359 |
@@ -4918,7 +4974,7 @@ class dblink(object): |
360 |
|
361 |
elif stat.S_ISREG(mymode): |
362 |
# we are merging a regular file |
363 |
- if not protected and \ |
364 |
+ if not protected and not instmasked and \ |
365 |
mydmode is not None and stat.S_ISDIR(mydmode): |
366 |
# install of destination is blocked by an existing directory with the same name |
367 |
newdest = self._new_backup_path(mydest) |
368 |
@@ -4932,9 +4988,11 @@ class dblink(object): |
369 |
self._eerror("preinst", msg) |
370 |
mydest = newdest |
371 |
|
372 |
+ if instmasked: |
373 |
+ zing = "###" |
374 |
# whether config protection or not, we merge the new file the |
375 |
# same way. Unless moveme=0 (blocking directory) |
376 |
- if moveme: |
377 |
+ elif moveme: |
378 |
# Create hardlinks only for source files that already exist |
379 |
# as hardlinks (having identical st_dev and st_ino). |
380 |
hardlink_key = (mystat.st_dev, mystat.st_ino) |
381 |
@@ -4967,7 +5025,9 @@ class dblink(object): |
382 |
else: |
383 |
# we are merging a fifo or device node |
384 |
zing = "!!!" |
385 |
- if mydmode is None: |
386 |
+ if instmasked: |
387 |
+ zing = "###" |
388 |
+ elif mydmode is None: |
389 |
# destination doesn't exist |
390 |
if movefile(mysrc, mydest, newmtime=thismtime, |
391 |
sstat=mystat, mysettings=self.settings, |
392 |
-- |
393 |
2.16.2 |