Gentoo Archives: gentoo-portage-dev

From: 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: Sun, 26 Oct 2014 11:12:34
Message-Id: 1414321936-22851-2-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 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