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] dispatch-conf: symlink support for bug #485598
Date: Fri, 31 Oct 2014 13:34:41
Message-Id: 1414762472-13482-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 adds a diff_mixed_wrapper class which is used to simplify
13 diff_mixed usage.
14
15 bin/dispatch-conf | 39 ++++++-----
16 pym/portage/dispatch_conf.py | 150 +++++++++++++++++++++++++++++++++++++------
17 2 files changed, 151 insertions(+), 38 deletions(-)
18
19 diff --git a/bin/dispatch-conf b/bin/dispatch-conf
20 index 6d2ae94..412dcdc 100755
21 --- a/bin/dispatch-conf
22 +++ b/bin/dispatch-conf
23 @@ -15,15 +15,15 @@ from __future__ import print_function
24
25 from stat import ST_GID, ST_MODE, ST_UID
26 from random import random
27 -import atexit, re, shutil, stat, sys
28 +import atexit, io, re, functools, shutil, sys
29 from os import path as osp
30 if osp.isfile(osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), ".portage_not_installed")):
31 sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
32 import portage
33 portage._internal_caller = True
34 from portage import os
35 -from portage import _unicode_decode
36 -from portage.dispatch_conf import diffstatusoutput
37 +from portage import _encodings, _unicode_decode
38 +from portage.dispatch_conf import diffstatusoutput, diff_mixed_wrapper
39 from portage.process import find_binary, spawn
40
41 FIND_EXTANT_CONFIGS = "find '%s' %s -name '._cfg????_%s' ! -name '.*~' ! -iname '.*.bak' -print"
42 @@ -72,6 +72,8 @@ def cmd_var_is_valid(cmd):
43
44 return find_binary(cmd[0]) is not None
45
46 +diff = diff_mixed_wrapper(diffstatusoutput, DIFF_CONTENTS)
47 +
48 class dispatch:
49 options = {}
50
51 @@ -89,8 +91,6 @@ class dispatch:
52 or not os.path.exists(self.options["log-file"]):
53 open(self.options["log-file"], 'w').close() # Truncate it
54 os.chmod(self.options["log-file"], 0o600)
55 - else:
56 - self.options["log-file"] = "/dev/null"
57
58 pager = self.options.get("pager")
59 if pager is None or not cmd_var_is_valid(pager):
60 @@ -148,9 +148,6 @@ class dispatch:
61 portage.util.shlex_split(
62 portage.settings.get('CONFIG_PROTECT_MASK', '')))
63
64 - def diff(file1, file2):
65 - return diffstatusoutput(DIFF_CONTENTS, file1, file2)
66 -
67 #
68 # Remove new configs identical to current
69 # and
70 @@ -166,7 +163,7 @@ class dispatch:
71 mrgfail = portage.dispatch_conf.rcs_archive(archive, conf['current'], conf['new'], mrgconf)
72 else:
73 mrgfail = portage.dispatch_conf.file_archive(archive, conf['current'], conf['new'], mrgconf)
74 - if os.path.exists(archive + '.dist'):
75 + if os.path.lexists(archive + '.dist'):
76 unmodified = len(diff(conf['current'], archive + '.dist')[1]) == 0
77 else:
78 unmodified = 0
79 @@ -181,7 +178,7 @@ class dispatch:
80
81 if newconf == mrgconf and \
82 self.options.get('ignore-previously-merged') != 'yes' and \
83 - os.path.exists(archive+'.dist') and \
84 + os.path.lexists(archive+'.dist') and \
85 len(diff(archive+'.dist', conf['new'])[1]) == 0:
86 # The current update is identical to the archived .dist
87 # version that has previously been merged.
88 @@ -254,6 +251,13 @@ class dispatch:
89
90 valid_input = "qhtnmlezu"
91
92 + def diff_pager(file1, file2):
93 + cmd = self.options['diff'] % (file1, file2)
94 + cmd += pager
95 + spawn_shell(cmd)
96 +
97 + diff_pager = diff_mixed_wrapper(diff_pager)
98 +
99 for conf in confs:
100 count = count + 1
101
102 @@ -266,14 +270,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_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_pager(conf['current'], newconf)
116
117 print()
118 print('>> (%i of %i) -- %s' % (count, len(confs), conf ['current']))
119 @@ -357,7 +357,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..cda45d5 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,66 @@ 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 + try:
172 + st = os.lstat(diff_files[i])
173 + except OSError:
174 + st = None
175 + if st is not None and stat.S_ISREG(st.st_mode):
176 + continue
177 +
178 + if tempdir is None:
179 + tempdir = tempfile.mkdtemp()
180 + diff_files[i] = os.path.join(tempdir, "%d" % i)
181 + if st is None:
182 + content = "/dev/null\n"
183 + elif stat.S_ISLNK(st.st_mode):
184 + link_dest = os.readlink(files[i])
185 + content = "SYM: %s -> %s\n" % \
186 + (file1, link_dest)
187 + elif stat.S_ISDIR(st.st_mode):
188 + content = "DIR: %s\n" % (file1,)
189 + elif stat.S_ISFIFO(st.st_mode):
190 + content = "FIF: %s\n" % (file1,)
191 + else:
192 + content = "DEV: %s\n" % (file1,)
193 + with io.open(diff_files[i], mode='w',
194 + encoding=_encodings['stdio']) as f:
195 + f.write(content)
196 +
197 + return func(diff_files[0], diff_files[1])
198 +
199 + finally:
200 + if tempdir is not None:
201 + shutil.rmtree(tempdir)
202 +
203 +class diff_mixed_wrapper(object):
204 +
205 + def __init__(self, f, *args):
206 + self._func = f
207 + self._args = args
208 +
209 + def __call__(self, *args):
210 + return diff_mixed(
211 + functools.partial(self._func, *(self._args + args[:-2])),
212 + *args[-2:])
213 +
214 +diffstatusoutput_mixed = diff_mixed_wrapper(diffstatusoutput)
215 +
216 def read_config(mandatory_opts):
217 eprefix = portage.settings["EPREFIX"]
218 if portage._not_installed:
219 @@ -103,35 +164,57 @@ def rcs_archive(archive, curconf, newconf, mrgconf):
220 except OSError:
221 pass
222
223 - if os.path.isfile(curconf):
224 + try:
225 + curconf_st = os.lstat(curconf)
226 + except OSError:
227 + curconf_st = None
228 +
229 + if curconf_st is not None and \
230 + (stat.S_ISREG(curconf_st.st_mode) or
231 + stat.S_ISLNK(curconf_st.st_mode)):
232 try:
233 - shutil.copy2(curconf, archive)
234 + if stat.S_ISLNK(curconf_st.st_mode):
235 + os.symlink(os.readlink(curconf), archive)
236 + else:
237 + shutil.copy2(curconf, archive)
238 except(IOError, os.error) as why:
239 print(_('dispatch-conf: Error copying %(curconf)s to %(archive)s: %(reason)s; fatal') % \
240 {"curconf": curconf, "archive": archive, "reason": str(why)}, file=sys.stderr)
241
242 - if os.path.exists(archive + ',v'):
243 + if os.path.lexists(archive + ',v'):
244 os.system(RCS_LOCK + ' ' + archive)
245 os.system(RCS_PUT + ' ' + archive)
246
247 ret = 0
248 - if newconf != '':
249 + mystat = None
250 + if newconf:
251 + try:
252 + mystat = os.lstat(newconf)
253 + except OSError:
254 + pass
255 +
256 + if mystat is not None and \
257 + (stat.S_ISREG(mystat.st_mode) or
258 + stat.S_ISLNK(mystat.st_mode)):
259 os.system(RCS_GET + ' -r' + RCS_BRANCH + ' ' + archive)
260 - has_branch = os.path.exists(archive)
261 + has_branch = os.path.lexists(archive)
262 if has_branch:
263 os.rename(archive, archive + '.dist')
264
265 try:
266 - shutil.copy2(newconf, archive)
267 + if stat.S_ISLNK(mystat.st_mode):
268 + os.symlink(os.readlink(newconf), archive)
269 + else:
270 + shutil.copy2(newconf, archive)
271 except(IOError, os.error) as why:
272 print(_('dispatch-conf: Error copying %(newconf)s to %(archive)s: %(reason)s; fatal') % \
273 {"newconf": newconf, "archive": archive, "reason": str(why)}, file=sys.stderr)
274
275 if has_branch:
276 - if mrgconf != '':
277 + if mrgconf and os.path.isfile(archive) and \
278 + os.path.isfile(mrgconf):
279 # This puts the results of the merge into mrgconf.
280 ret = os.system(RCS_MERGE % (archive, mrgconf))
281 - mystat = os.lstat(newconf)
282 os.chmod(mrgconf, mystat.st_mode)
283 os.chown(mrgconf, mystat.st_uid, mystat.st_gid)
284 os.rename(archive, archive + '.dist.new')
285 @@ -153,10 +236,11 @@ def file_archive(archive, curconf, newconf, mrgconf):
286 pass
287
288 # Archive the current config file if it isn't already saved
289 - if (os.path.exists(archive) and
290 - len(diffstatusoutput("diff -aq '%s' '%s'", curconf, archive)[1]) != 0):
291 + if (os.path.lexists(archive) and
292 + len(diffstatusoutput_mixed(
293 + "diff -aq '%s' '%s'", curconf, archive)[1]) != 0):
294 suf = 1
295 - while suf < 9 and os.path.exists(archive + '.' + str(suf)):
296 + while suf < 9 and os.path.lexists(archive + '.' + str(suf)):
297 suf += 1
298
299 while suf > 1:
300 @@ -165,26 +249,50 @@ def file_archive(archive, curconf, newconf, mrgconf):
301
302 os.rename(archive, archive + '.1')
303
304 - if os.path.isfile(curconf):
305 + try:
306 + curconf_st = os.lstat(curconf)
307 + except OSError:
308 + curconf_st = None
309 +
310 + if curconf_st is not None and \
311 + (stat.S_ISREG(curconf_st.st_mode) or
312 + stat.S_ISLNK(curconf_st.st_mode)):
313 try:
314 - shutil.copy2(curconf, archive)
315 + if stat.S_ISLNK(curconf_st.st_mode):
316 + os.symlink(os.readlink(curconf), archive)
317 + else:
318 + shutil.copy2(curconf, archive)
319 except(IOError, os.error) as why:
320 print(_('dispatch-conf: Error copying %(curconf)s to %(archive)s: %(reason)s; fatal') % \
321 {"curconf": curconf, "archive": archive, "reason": str(why)}, file=sys.stderr)
322
323 - if newconf != '':
324 + mystat = None
325 + if newconf:
326 + try:
327 + mystat = os.lstat(newconf)
328 + except OSError:
329 + pass
330 +
331 + if mystat is not None and \
332 + (stat.S_ISREG(mystat.st_mode) or
333 + stat.S_ISLNK(mystat.st_mode)):
334 # Save off new config file in the archive dir with .dist.new suffix
335 + newconf_archive = archive + '.dist.new'
336 try:
337 - shutil.copy2(newconf, archive + '.dist.new')
338 + if stat.S_ISLNK(mystat.st_mode):
339 + os.symlink(os.readlink(newconf), newconf_archive)
340 + else:
341 + shutil.copy2(newconf, newconf_archive)
342 except(IOError, os.error) as why:
343 print(_('dispatch-conf: Error copying %(newconf)s to %(archive)s: %(reason)s; fatal') % \
344 {"newconf": newconf, "archive": archive + '.dist.new', "reason": str(why)}, file=sys.stderr)
345
346 ret = 0
347 - if mrgconf != '' and os.path.exists(archive + '.dist'):
348 + if mrgconf and os.path.isfile(curconf) and \
349 + os.path.isfile(newconf) and \
350 + os.path.isfile(archive + '.dist'):
351 # This puts the results of the merge into mrgconf.
352 ret = os.system(DIFF3_MERGE % (curconf, archive + '.dist', newconf, mrgconf))
353 - mystat = os.lstat(newconf)
354 os.chmod(mrgconf, mystat.st_mode)
355 os.chown(mrgconf, mystat.st_uid, mystat.st_gid)
356
357 @@ -195,7 +303,7 @@ def rcs_archive_post_process(archive):
358 """Check in the archive file with the .dist.new suffix on the branch
359 and remove the one with the .dist suffix."""
360 os.rename(archive + '.dist.new', archive)
361 - if os.path.exists(archive + '.dist'):
362 + if os.path.lexists(archive + '.dist'):
363 # Commit the last-distributed version onto the branch.
364 os.system(RCS_LOCK + RCS_BRANCH + ' ' + archive)
365 os.system(RCS_PUT + ' -r' + RCS_BRANCH + ' ' + archive)
366 @@ -207,5 +315,5 @@ def rcs_archive_post_process(archive):
367
368 def file_archive_post_process(archive):
369 """Rename the archive file with the .dist.new suffix to a .dist suffix"""
370 - if os.path.exists(archive + '.dist.new'):
371 + if os.path.lexists(archive + '.dist.new'):
372 os.rename(archive + '.dist.new', archive + '.dist')
373 --
374 2.0.4