1 |
From: Zac Medico <zmedico@g.o> |
2 |
|
3 |
This includes numerous logic adjustments that are needed to support |
4 |
protected symlinks. The new diff_mixed function is used for diffs |
5 |
between arbitrary file types. For example, a diff between two symlinks |
6 |
looks like this: |
7 |
|
8 |
-SYM: /foo/bar -> baz |
9 |
+SYM: /foo/bar -> blah |
10 |
|
11 |
X-Gentoo-Bug: 485598 |
12 |
X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=485598 |
13 |
--- |
14 |
bin/dispatch-conf | 40 +++++++------ |
15 |
pym/portage/dispatch_conf.py | 137 ++++++++++++++++++++++++++++++++++++------- |
16 |
2 files changed, 139 insertions(+), 38 deletions(-) |
17 |
|
18 |
diff --git a/bin/dispatch-conf b/bin/dispatch-conf |
19 |
index 6d2ae94..80dafd6 100755 |
20 |
--- a/bin/dispatch-conf |
21 |
+++ b/bin/dispatch-conf |
22 |
@@ -15,15 +15,15 @@ from __future__ import print_function |
23 |
|
24 |
from stat import ST_GID, ST_MODE, ST_UID |
25 |
from random import random |
26 |
-import atexit, re, shutil, stat, sys |
27 |
+import atexit, io, re, functools, shutil, sys |
28 |
from os import path as osp |
29 |
if osp.isfile(osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), ".portage_not_installed")): |
30 |
sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")) |
31 |
import portage |
32 |
portage._internal_caller = True |
33 |
from portage import os |
34 |
-from portage import _unicode_decode |
35 |
-from portage.dispatch_conf import diffstatusoutput |
36 |
+from portage import _encodings, _unicode_decode |
37 |
+from portage.dispatch_conf import diffstatusoutput, diff_mixed |
38 |
from portage.process import find_binary, spawn |
39 |
|
40 |
FIND_EXTANT_CONFIGS = "find '%s' %s -name '._cfg????_%s' ! -name '.*~' ! -iname '.*.bak' -print" |
41 |
@@ -72,6 +72,11 @@ def cmd_var_is_valid(cmd): |
42 |
|
43 |
return find_binary(cmd[0]) is not None |
44 |
|
45 |
+def diff(file1, file2): |
46 |
+ return diff_mixed( |
47 |
+ functools.partial(diffstatusoutput, DIFF_CONTENTS), |
48 |
+ file1, file2) |
49 |
+ |
50 |
class dispatch: |
51 |
options = {} |
52 |
|
53 |
@@ -89,8 +94,6 @@ class dispatch: |
54 |
or not os.path.exists(self.options["log-file"]): |
55 |
open(self.options["log-file"], 'w').close() # Truncate it |
56 |
os.chmod(self.options["log-file"], 0o600) |
57 |
- else: |
58 |
- self.options["log-file"] = "/dev/null" |
59 |
|
60 |
pager = self.options.get("pager") |
61 |
if pager is None or not cmd_var_is_valid(pager): |
62 |
@@ -148,9 +151,6 @@ class dispatch: |
63 |
portage.util.shlex_split( |
64 |
portage.settings.get('CONFIG_PROTECT_MASK', ''))) |
65 |
|
66 |
- def diff(file1, file2): |
67 |
- return diffstatusoutput(DIFF_CONTENTS, file1, file2) |
68 |
- |
69 |
# |
70 |
# Remove new configs identical to current |
71 |
# and |
72 |
@@ -166,7 +166,7 @@ class dispatch: |
73 |
mrgfail = portage.dispatch_conf.rcs_archive(archive, conf['current'], conf['new'], mrgconf) |
74 |
else: |
75 |
mrgfail = portage.dispatch_conf.file_archive(archive, conf['current'], conf['new'], mrgconf) |
76 |
- if os.path.exists(archive + '.dist'): |
77 |
+ if os.path.lexists(archive + '.dist'): |
78 |
unmodified = len(diff(conf['current'], archive + '.dist')[1]) == 0 |
79 |
else: |
80 |
unmodified = 0 |
81 |
@@ -181,7 +181,7 @@ class dispatch: |
82 |
|
83 |
if newconf == mrgconf and \ |
84 |
self.options.get('ignore-previously-merged') != 'yes' and \ |
85 |
- os.path.exists(archive+'.dist') and \ |
86 |
+ os.path.lexists(archive+'.dist') and \ |
87 |
len(diff(archive+'.dist', conf['new'])[1]) == 0: |
88 |
# The current update is identical to the archived .dist |
89 |
# version that has previously been merged. |
90 |
@@ -254,6 +254,11 @@ class dispatch: |
91 |
|
92 |
valid_input = "qhtnmlezu" |
93 |
|
94 |
+ def diff_pager(file1, file2): |
95 |
+ cmd = self.options['diff'] % (file1, file2) |
96 |
+ cmd += pager |
97 |
+ spawn_shell(cmd) |
98 |
+ |
99 |
for conf in confs: |
100 |
count = count + 1 |
101 |
|
102 |
@@ -266,14 +271,10 @@ class dispatch: |
103 |
while 1: |
104 |
clear_screen() |
105 |
if show_new_diff: |
106 |
- cmd = self.options['diff'] % (conf['new'], mrgconf) |
107 |
- cmd += pager |
108 |
- spawn_shell(cmd) |
109 |
+ diff_mixed(diff_pager, conf['new'], mrgconf) |
110 |
show_new_diff = 0 |
111 |
else: |
112 |
- cmd = self.options['diff'] % (conf['current'], newconf) |
113 |
- cmd += pager |
114 |
- spawn_shell(cmd) |
115 |
+ diff_mixed(diff_pager, conf['current'], newconf) |
116 |
|
117 |
print() |
118 |
print('>> (%i of %i) -- %s' % (count, len(confs), conf ['current'])) |
119 |
@@ -357,7 +358,12 @@ class dispatch: |
120 |
def replace (self, newconf, curconf): |
121 |
"""Replace current config with the new/merged version. Also logs |
122 |
the diff of what changed into the configured log file.""" |
123 |
- os.system((DIFF_CONTENTS % (curconf, newconf)) + '>>' + self.options["log-file"]) |
124 |
+ if "log-file" in self.options: |
125 |
+ status, output = diff(curconf, newconf) |
126 |
+ with io.open(self.options["log-file"], mode="a", |
127 |
+ encoding=_encodings["stdio"]) as f: |
128 |
+ f.write(output + "\n") |
129 |
+ |
130 |
try: |
131 |
os.rename(newconf, curconf) |
132 |
except (IOError, os.error) as why: |
133 |
diff --git a/pym/portage/dispatch_conf.py b/pym/portage/dispatch_conf.py |
134 |
index 113d965..7fb99d0 100644 |
135 |
--- a/pym/portage/dispatch_conf.py |
136 |
+++ b/pym/portage/dispatch_conf.py |
137 |
@@ -6,11 +6,12 @@ |
138 |
# Library by Wayne Davison <gentoo@×××××.net>, derived from code |
139 |
# written by Jeremy Wohl (http://igmus.org) |
140 |
|
141 |
-from __future__ import print_function |
142 |
+from __future__ import print_function, unicode_literals |
143 |
|
144 |
-import os, shutil, subprocess, sys |
145 |
+import functools, io, os, shutil, stat, subprocess, sys, tempfile |
146 |
|
147 |
import portage |
148 |
+from portage import _encodings |
149 |
from portage.env.loaders import KeyValuePairFileLoader |
150 |
from portage.localization import _ |
151 |
from portage.util import shlex_split, varexpand |
152 |
@@ -50,6 +51,53 @@ def diffstatusoutput(cmd, file1, file2): |
153 |
output = output[:-1] |
154 |
return (proc.wait(), output) |
155 |
|
156 |
+def diff_mixed(func, file1, file2): |
157 |
+ tempdir = None |
158 |
+ try: |
159 |
+ if os.path.islink(file1) and \ |
160 |
+ not os.path.islink(file2) and \ |
161 |
+ os.path.isfile(file1) and \ |
162 |
+ os.path.isfile(file2): |
163 |
+ # If a regular file replaces a symlink to a regular |
164 |
+ # file, then show the diff between the regular files |
165 |
+ # (bug #330221). |
166 |
+ diff_files = (file2, file2) |
167 |
+ else: |
168 |
+ files = [file1, file2] |
169 |
+ diff_files = [file1, file2] |
170 |
+ for i in range(len(diff_files)): |
171 |
+ st = os.lstat(diff_files[i]) |
172 |
+ if stat.S_ISREG(st.st_mode): |
173 |
+ continue |
174 |
+ |
175 |
+ if tempdir is None: |
176 |
+ tempdir = tempfile.mkdtemp() |
177 |
+ diff_files[i] = os.path.join(tempdir, "%d" % i) |
178 |
+ if stat.S_ISLNK(st.st_mode): |
179 |
+ link_dest = os.readlink(files[i]) |
180 |
+ content = "SYM: %s -> %s\n" % \ |
181 |
+ (file1, link_dest) |
182 |
+ elif stat.S_ISDIR(st.st_mode): |
183 |
+ content = "DIR: %s\n" % (file1,) |
184 |
+ elif stat.S_ISFIFO(st.st_mode): |
185 |
+ content = "FIF: %s\n" % (file1,) |
186 |
+ else: |
187 |
+ content = "DEV: %s\n" % (file1,) |
188 |
+ with io.open(diff_files[i], mode='w', |
189 |
+ encoding=_encodings['stdio']) as f: |
190 |
+ f.write(content) |
191 |
+ |
192 |
+ return func(diff_files[0], diff_files[1]) |
193 |
+ |
194 |
+ finally: |
195 |
+ if tempdir is not None: |
196 |
+ shutil.rmtree(tempdir) |
197 |
+ |
198 |
+def diffstatusoutput_symlink(cmd, file1, file2): |
199 |
+ return diff_mixed( |
200 |
+ functools.partial(diffstatusoutput, cmd), |
201 |
+ file1, file2) |
202 |
+ |
203 |
def read_config(mandatory_opts): |
204 |
eprefix = portage.settings["EPREFIX"] |
205 |
if portage._not_installed: |
206 |
@@ -103,35 +151,57 @@ def rcs_archive(archive, curconf, newconf, mrgconf): |
207 |
except OSError: |
208 |
pass |
209 |
|
210 |
- if os.path.isfile(curconf): |
211 |
+ try: |
212 |
+ curconf_st = os.lstat(curconf) |
213 |
+ except OSError: |
214 |
+ curconf_st = None |
215 |
+ |
216 |
+ if curconf_st is not None and \ |
217 |
+ (stat.S_ISREG(curconf_st.st_mode) or |
218 |
+ stat.S_ISLNK(curconf_st.st_mode)): |
219 |
try: |
220 |
- shutil.copy2(curconf, archive) |
221 |
+ if stat.S_ISLNK(curconf_st.st_mode): |
222 |
+ os.symlink(os.readlink(curconf), archive) |
223 |
+ else: |
224 |
+ shutil.copy2(curconf, archive) |
225 |
except(IOError, os.error) as why: |
226 |
print(_('dispatch-conf: Error copying %(curconf)s to %(archive)s: %(reason)s; fatal') % \ |
227 |
{"curconf": curconf, "archive": archive, "reason": str(why)}, file=sys.stderr) |
228 |
|
229 |
- if os.path.exists(archive + ',v'): |
230 |
+ if os.path.lexists(archive + ',v'): |
231 |
os.system(RCS_LOCK + ' ' + archive) |
232 |
os.system(RCS_PUT + ' ' + archive) |
233 |
|
234 |
ret = 0 |
235 |
- if newconf != '': |
236 |
+ mystat = None |
237 |
+ if newconf: |
238 |
+ try: |
239 |
+ mystat = os.lstat(newconf) |
240 |
+ except OSError: |
241 |
+ pass |
242 |
+ |
243 |
+ if mystat is not None and \ |
244 |
+ (stat.S_ISREG(mystat.st_mode) or |
245 |
+ stat.S_ISLNK(mystat.st_mode)): |
246 |
os.system(RCS_GET + ' -r' + RCS_BRANCH + ' ' + archive) |
247 |
- has_branch = os.path.exists(archive) |
248 |
+ has_branch = os.path.lexists(archive) |
249 |
if has_branch: |
250 |
os.rename(archive, archive + '.dist') |
251 |
|
252 |
try: |
253 |
- shutil.copy2(newconf, archive) |
254 |
+ if stat.S_ISLNK(mystat.st_mode): |
255 |
+ os.symlink(os.readlink(newconf), archive) |
256 |
+ else: |
257 |
+ shutil.copy2(newconf, archive) |
258 |
except(IOError, os.error) as why: |
259 |
print(_('dispatch-conf: Error copying %(newconf)s to %(archive)s: %(reason)s; fatal') % \ |
260 |
{"newconf": newconf, "archive": archive, "reason": str(why)}, file=sys.stderr) |
261 |
|
262 |
if has_branch: |
263 |
- if mrgconf != '': |
264 |
+ if mrgconf and os.path.isfile(archive) and \ |
265 |
+ os.path.isfile(mrgconf): |
266 |
# This puts the results of the merge into mrgconf. |
267 |
ret = os.system(RCS_MERGE % (archive, mrgconf)) |
268 |
- mystat = os.lstat(newconf) |
269 |
os.chmod(mrgconf, mystat.st_mode) |
270 |
os.chown(mrgconf, mystat.st_uid, mystat.st_gid) |
271 |
os.rename(archive, archive + '.dist.new') |
272 |
@@ -153,10 +223,11 @@ def file_archive(archive, curconf, newconf, mrgconf): |
273 |
pass |
274 |
|
275 |
# Archive the current config file if it isn't already saved |
276 |
- if (os.path.exists(archive) and |
277 |
- len(diffstatusoutput("diff -aq '%s' '%s'", curconf, archive)[1]) != 0): |
278 |
+ if (os.path.lexists(archive) and |
279 |
+ len(diffstatusoutput_symlink( |
280 |
+ "diff -aq '%s' '%s'", curconf, archive)[1]) != 0): |
281 |
suf = 1 |
282 |
- while suf < 9 and os.path.exists(archive + '.' + str(suf)): |
283 |
+ while suf < 9 and os.path.lexists(archive + '.' + str(suf)): |
284 |
suf += 1 |
285 |
|
286 |
while suf > 1: |
287 |
@@ -165,26 +236,50 @@ def file_archive(archive, curconf, newconf, mrgconf): |
288 |
|
289 |
os.rename(archive, archive + '.1') |
290 |
|
291 |
- if os.path.isfile(curconf): |
292 |
+ try: |
293 |
+ curconf_st = os.lstat(curconf) |
294 |
+ except OSError: |
295 |
+ curconf_st = None |
296 |
+ |
297 |
+ if curconf_st is not None and \ |
298 |
+ (stat.S_ISREG(curconf_st.st_mode) or |
299 |
+ stat.S_ISLNK(curconf_st.st_mode)): |
300 |
try: |
301 |
- shutil.copy2(curconf, archive) |
302 |
+ if stat.S_ISLNK(curconf_st.st_mode): |
303 |
+ os.symlink(os.readlink(curconf), archive) |
304 |
+ else: |
305 |
+ shutil.copy2(curconf, archive) |
306 |
except(IOError, os.error) as why: |
307 |
print(_('dispatch-conf: Error copying %(curconf)s to %(archive)s: %(reason)s; fatal') % \ |
308 |
{"curconf": curconf, "archive": archive, "reason": str(why)}, file=sys.stderr) |
309 |
|
310 |
- if newconf != '': |
311 |
+ mystat = None |
312 |
+ if newconf: |
313 |
+ try: |
314 |
+ mystat = os.lstat(newconf) |
315 |
+ except OSError: |
316 |
+ pass |
317 |
+ |
318 |
+ if mystat is not None and \ |
319 |
+ (stat.S_ISREG(mystat.st_mode) or |
320 |
+ stat.S_ISLNK(mystat.st_mode)): |
321 |
# Save off new config file in the archive dir with .dist.new suffix |
322 |
+ newconf_archive = archive + '.dist.new' |
323 |
try: |
324 |
- shutil.copy2(newconf, archive + '.dist.new') |
325 |
+ if stat.S_ISLNK(mystat.st_mode): |
326 |
+ os.symlink(os.readlink(newconf), newconf_archive) |
327 |
+ else: |
328 |
+ shutil.copy2(newconf, newconf_archive) |
329 |
except(IOError, os.error) as why: |
330 |
print(_('dispatch-conf: Error copying %(newconf)s to %(archive)s: %(reason)s; fatal') % \ |
331 |
{"newconf": newconf, "archive": archive + '.dist.new', "reason": str(why)}, file=sys.stderr) |
332 |
|
333 |
ret = 0 |
334 |
- if mrgconf != '' and os.path.exists(archive + '.dist'): |
335 |
+ if mrgconf and os.path.isfile(curconf) and \ |
336 |
+ os.path.isfile(newconf) and \ |
337 |
+ os.path.isfile(archive + '.dist'): |
338 |
# This puts the results of the merge into mrgconf. |
339 |
ret = os.system(DIFF3_MERGE % (curconf, archive + '.dist', newconf, mrgconf)) |
340 |
- mystat = os.lstat(newconf) |
341 |
os.chmod(mrgconf, mystat.st_mode) |
342 |
os.chown(mrgconf, mystat.st_uid, mystat.st_gid) |
343 |
|
344 |
@@ -195,7 +290,7 @@ def rcs_archive_post_process(archive): |
345 |
"""Check in the archive file with the .dist.new suffix on the branch |
346 |
and remove the one with the .dist suffix.""" |
347 |
os.rename(archive + '.dist.new', archive) |
348 |
- if os.path.exists(archive + '.dist'): |
349 |
+ if os.path.lexists(archive + '.dist'): |
350 |
# Commit the last-distributed version onto the branch. |
351 |
os.system(RCS_LOCK + RCS_BRANCH + ' ' + archive) |
352 |
os.system(RCS_PUT + ' -r' + RCS_BRANCH + ' ' + archive) |
353 |
@@ -207,5 +302,5 @@ def rcs_archive_post_process(archive): |
354 |
|
355 |
def file_archive_post_process(archive): |
356 |
"""Rename the archive file with the .dist.new suffix to a .dist suffix""" |
357 |
- if os.path.exists(archive + '.dist.new'): |
358 |
+ if os.path.lexists(archive + '.dist.new'): |
359 |
os.rename(archive + '.dist.new', archive + '.dist') |
360 |
-- |
361 |
2.0.4 |