Gentoo Archives: gentoo-portage-dev

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