1 |
commit: 4bb08136f073024c5d31dceb1618b6f4e7246369 |
2 |
Author: Zac Medico <zmedico <AT> gentoo <DOT> org> |
3 |
AuthorDate: Thu Jul 28 11:29:25 2011 +0000 |
4 |
Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> |
5 |
CommitDate: Thu Jul 28 11:29:25 2011 +0000 |
6 |
URL: http://git.overlays.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=4bb08136 |
7 |
|
8 |
emerge: protect symlinks to directories sometimes |
9 |
|
10 |
Before, it was possible to unmerge a symlink to a directory, such that |
11 |
files installed via the path of the symlink could become inaccessible |
12 |
via that path (and also making it impossible to unmerge them via that |
13 |
path). |
14 |
|
15 |
Now, the symlink will only be unmerged if the directory that it points |
16 |
to only contains regular files which are all being unmerged. In any |
17 |
other case, the symlink will be preserved and an eerror log message |
18 |
will record the event. This will give the user an opportunity to take |
19 |
further action if they deem it necessary, and such symlink preservation |
20 |
will not be silent as it was reported in bug #326685, comment #3. |
21 |
|
22 |
--- |
23 |
pym/portage/dbapi/vartree.py | 90 +++++++++++++++++++++++++++++++++++++++++- |
24 |
1 files changed, 88 insertions(+), 2 deletions(-) |
25 |
|
26 |
diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py |
27 |
index 47f5eb2..6bb68d3 100644 |
28 |
--- a/pym/portage/dbapi/vartree.py |
29 |
+++ b/pym/portage/dbapi/vartree.py |
30 |
@@ -70,6 +70,7 @@ import shutil |
31 |
import stat |
32 |
import sys |
33 |
import tempfile |
34 |
+import textwrap |
35 |
import time |
36 |
import warnings |
37 |
|
38 |
@@ -1908,6 +1909,7 @@ class dblink(object): |
39 |
|
40 |
cfgfiledict = grabdict(self.vartree.dbapi._conf_mem_file) |
41 |
stale_confmem = [] |
42 |
+ protected_symlinks = [] |
43 |
|
44 |
unmerge_orphans = "unmerge-orphans" in self.settings.features |
45 |
calc_prelink = "prelink-checksums" in self.settings.features |
46 |
@@ -2027,9 +2029,34 @@ class dblink(object): |
47 |
if dblnk.isowner(relative_path): |
48 |
is_owned = True |
49 |
break |
50 |
- if is_owned: |
51 |
+ |
52 |
+ if is_owned and \ |
53 |
+ (islink and statobj and stat.S_ISDIR(statobj.st_mode)): |
54 |
# A new instance of this package claims the file, so |
55 |
- # don't unmerge it. |
56 |
+ # don't unmerge it. If the file is symlink to a |
57 |
+ # directory and the unmerging package installed it as |
58 |
+ # a symlink, but the new owner has it listed as a |
59 |
+ # directory, then we'll produce a warning since the |
60 |
+ # symlink is a sort of orphan in this case (see |
61 |
+ # bug #326685). |
62 |
+ symlink_orphan = False |
63 |
+ for dblnk in others_in_slot: |
64 |
+ parent_contents_key = \ |
65 |
+ dblnk._match_contents(relative_path) |
66 |
+ if not parent_contents_key: |
67 |
+ continue |
68 |
+ if not parent_contents_key.startswith( |
69 |
+ real_root): |
70 |
+ continue |
71 |
+ if dblnk.getcontents()[ |
72 |
+ parent_contents_key][0] == "dir": |
73 |
+ symlink_orphan = True |
74 |
+ break |
75 |
+ |
76 |
+ if symlink_orphan: |
77 |
+ protected_symlinks.append(relative_path) |
78 |
+ |
79 |
+ if is_owned: |
80 |
show_unmerge("---", unmerge_desc["replaced"], file_type, obj) |
81 |
continue |
82 |
elif relative_path in cfgfiledict: |
83 |
@@ -2076,6 +2103,52 @@ class dblink(object): |
84 |
if not islink: |
85 |
show_unmerge("---", unmerge_desc["!sym"], file_type, obj) |
86 |
continue |
87 |
+ |
88 |
+ # If this symlink points to a direcory then we don't want |
89 |
+ # to unmerge it if there are any other packages that |
90 |
+ # installed files into the directory via this symlink |
91 |
+ # (see bug #326685). |
92 |
+ # TODO: Resolving a symlink to a directory will require |
93 |
+ # simulation if $ROOT != / and the link is not relative. |
94 |
+ if islink and statobj and stat.S_ISDIR(statobj.st_mode) \ |
95 |
+ and obj.startswith(real_root): |
96 |
+ |
97 |
+ relative_path = obj[real_root_len:] |
98 |
+ try: |
99 |
+ target_dir_contents = os.listdir(obj) |
100 |
+ except OSError: |
101 |
+ pass |
102 |
+ else: |
103 |
+ if target_dir_contents: |
104 |
+ # If all the children are regular files owned |
105 |
+ # by this package, then the symlink should be |
106 |
+ # safe to unmerge. |
107 |
+ all_owned = True |
108 |
+ for child in target_dir_contents: |
109 |
+ child = os.path.join(relative_path, child) |
110 |
+ if not self.isowner(child): |
111 |
+ all_owned = False |
112 |
+ break |
113 |
+ try: |
114 |
+ child_lstat = os.lstat(os.path.join( |
115 |
+ real_root, child.lstrip(os.sep))) |
116 |
+ except OSError: |
117 |
+ continue |
118 |
+ |
119 |
+ if not stat.S_ISREG(child_lstat.st_mode): |
120 |
+ # Nested symlinks or directories make |
121 |
+ # the issue very complex, so just |
122 |
+ # preserve the symlink in order to be |
123 |
+ # on the safe side. |
124 |
+ all_owned = False |
125 |
+ break |
126 |
+ |
127 |
+ if not all_owned: |
128 |
+ protected_symlinks.append(relative_path) |
129 |
+ show_unmerge("---", unmerge_desc["!empty"], |
130 |
+ file_type, obj) |
131 |
+ continue |
132 |
+ |
133 |
# Go ahead and unlink symlinks to directories here when |
134 |
# they're actually recorded as symlinks in the contents. |
135 |
# Normally, symlinks such as /lib -> lib64 are not recorded |
136 |
@@ -2152,6 +2225,19 @@ class dblink(object): |
137 |
show_unmerge("---", unmerge_desc["!empty"], "dir", obj) |
138 |
del e |
139 |
|
140 |
+ if protected_symlinks: |
141 |
+ msg = "One or more symlinks to directories have been " + \ |
142 |
+ "preserved in order to ensure that files installed " + \ |
143 |
+ "via these symlinks remain accessible:" |
144 |
+ lines = textwrap.wrap(msg, 72) |
145 |
+ lines.append("") |
146 |
+ protected_symlinks.reverse() |
147 |
+ for f in protected_symlinks: |
148 |
+ lines.append("\t%s" % (os.path.join(real_root, |
149 |
+ f.lstrip(os.sep)))) |
150 |
+ lines.append("") |
151 |
+ self._elog("eerror", "postrm", lines) |
152 |
+ |
153 |
# Remove stale entries from config memory. |
154 |
if stale_confmem: |
155 |
for filename in stale_confmem: |