From: Zac Medico <zmedico@gentoo.org>
To: gentoo-portage-dev@lists.gentoo.org
Cc: Zac Medico <zmedico@gentoo.org>
Subject: [gentoo-portage-dev] [PATCH] dispatch-conf: symlink support for bug #485598
Date: Fri, 31 Oct 2014 06:34:32 -0700 [thread overview]
Message-ID: <1414762472-13482-1-git-send-email-zmedico@gentoo.org> (raw)
In-Reply-To: <1414321936-22851-1-git-send-email-zmedico@gentoo.org>
This includes numerous logic adjustments that are needed to support
protected symlinks. The new diff_mixed function is used for diffs
between arbitrary file types. For example, a diff between two symlinks
looks like this:
-SYM: /foo/bar -> baz
+SYM: /foo/bar -> blah
X-Gentoo-Bug: 485598
X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=485598
---
This updated patch adds a diff_mixed_wrapper class which is used to simplify
diff_mixed usage.
bin/dispatch-conf | 39 ++++++-----
pym/portage/dispatch_conf.py | 150 +++++++++++++++++++++++++++++++++++++------
2 files changed, 151 insertions(+), 38 deletions(-)
diff --git a/bin/dispatch-conf b/bin/dispatch-conf
index 6d2ae94..412dcdc 100755
--- a/bin/dispatch-conf
+++ b/bin/dispatch-conf
@@ -15,15 +15,15 @@ from __future__ import print_function
from stat import ST_GID, ST_MODE, ST_UID
from random import random
-import atexit, re, shutil, stat, sys
+import atexit, io, re, functools, shutil, sys
from os import path as osp
if osp.isfile(osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), ".portage_not_installed")):
sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
import portage
portage._internal_caller = True
from portage import os
-from portage import _unicode_decode
-from portage.dispatch_conf import diffstatusoutput
+from portage import _encodings, _unicode_decode
+from portage.dispatch_conf import diffstatusoutput, diff_mixed_wrapper
from portage.process import find_binary, spawn
FIND_EXTANT_CONFIGS = "find '%s' %s -name '._cfg????_%s' ! -name '.*~' ! -iname '.*.bak' -print"
@@ -72,6 +72,8 @@ def cmd_var_is_valid(cmd):
return find_binary(cmd[0]) is not None
+diff = diff_mixed_wrapper(diffstatusoutput, DIFF_CONTENTS)
+
class dispatch:
options = {}
@@ -89,8 +91,6 @@ class dispatch:
or not os.path.exists(self.options["log-file"]):
open(self.options["log-file"], 'w').close() # Truncate it
os.chmod(self.options["log-file"], 0o600)
- else:
- self.options["log-file"] = "/dev/null"
pager = self.options.get("pager")
if pager is None or not cmd_var_is_valid(pager):
@@ -148,9 +148,6 @@ class dispatch:
portage.util.shlex_split(
portage.settings.get('CONFIG_PROTECT_MASK', '')))
- def diff(file1, file2):
- return diffstatusoutput(DIFF_CONTENTS, file1, file2)
-
#
# Remove new configs identical to current
# and
@@ -166,7 +163,7 @@ class dispatch:
mrgfail = portage.dispatch_conf.rcs_archive(archive, conf['current'], conf['new'], mrgconf)
else:
mrgfail = portage.dispatch_conf.file_archive(archive, conf['current'], conf['new'], mrgconf)
- if os.path.exists(archive + '.dist'):
+ if os.path.lexists(archive + '.dist'):
unmodified = len(diff(conf['current'], archive + '.dist')[1]) == 0
else:
unmodified = 0
@@ -181,7 +178,7 @@ class dispatch:
if newconf == mrgconf and \
self.options.get('ignore-previously-merged') != 'yes' and \
- os.path.exists(archive+'.dist') and \
+ os.path.lexists(archive+'.dist') and \
len(diff(archive+'.dist', conf['new'])[1]) == 0:
# The current update is identical to the archived .dist
# version that has previously been merged.
@@ -254,6 +251,13 @@ class dispatch:
valid_input = "qhtnmlezu"
+ def diff_pager(file1, file2):
+ cmd = self.options['diff'] % (file1, file2)
+ cmd += pager
+ spawn_shell(cmd)
+
+ diff_pager = diff_mixed_wrapper(diff_pager)
+
for conf in confs:
count = count + 1
@@ -266,14 +270,10 @@ class dispatch:
while 1:
clear_screen()
if show_new_diff:
- cmd = self.options['diff'] % (conf['new'], mrgconf)
- cmd += pager
- spawn_shell(cmd)
+ diff_pager(conf['new'], mrgconf)
show_new_diff = 0
else:
- cmd = self.options['diff'] % (conf['current'], newconf)
- cmd += pager
- spawn_shell(cmd)
+ diff_pager(conf['current'], newconf)
print()
print('>> (%i of %i) -- %s' % (count, len(confs), conf ['current']))
@@ -357,7 +357,12 @@ class dispatch:
def replace (self, newconf, curconf):
"""Replace current config with the new/merged version. Also logs
the diff of what changed into the configured log file."""
- os.system((DIFF_CONTENTS % (curconf, newconf)) + '>>' + self.options["log-file"])
+ if "log-file" in self.options:
+ status, output = diff(curconf, newconf)
+ with io.open(self.options["log-file"], mode="a",
+ encoding=_encodings["stdio"]) as f:
+ f.write(output + "\n")
+
try:
os.rename(newconf, curconf)
except (IOError, os.error) as why:
diff --git a/pym/portage/dispatch_conf.py b/pym/portage/dispatch_conf.py
index 113d965..cda45d5 100644
--- a/pym/portage/dispatch_conf.py
+++ b/pym/portage/dispatch_conf.py
@@ -6,11 +6,12 @@
# Library by Wayne Davison <gentoo@blorf.net>, derived from code
# written by Jeremy Wohl (http://igmus.org)
-from __future__ import print_function
+from __future__ import print_function, unicode_literals
-import os, shutil, subprocess, sys
+import functools, io, os, shutil, stat, subprocess, sys, tempfile
import portage
+from portage import _encodings
from portage.env.loaders import KeyValuePairFileLoader
from portage.localization import _
from portage.util import shlex_split, varexpand
@@ -50,6 +51,66 @@ def diffstatusoutput(cmd, file1, file2):
output = output[:-1]
return (proc.wait(), output)
+def diff_mixed(func, file1, file2):
+ tempdir = None
+ try:
+ if os.path.islink(file1) and \
+ not os.path.islink(file2) and \
+ os.path.isfile(file1) and \
+ os.path.isfile(file2):
+ # If a regular file replaces a symlink to a regular
+ # file, then show the diff between the regular files
+ # (bug #330221).
+ diff_files = (file2, file2)
+ else:
+ files = [file1, file2]
+ diff_files = [file1, file2]
+ for i in range(len(diff_files)):
+ try:
+ st = os.lstat(diff_files[i])
+ except OSError:
+ st = None
+ if st is not None and stat.S_ISREG(st.st_mode):
+ continue
+
+ if tempdir is None:
+ tempdir = tempfile.mkdtemp()
+ diff_files[i] = os.path.join(tempdir, "%d" % i)
+ if st is None:
+ content = "/dev/null\n"
+ elif stat.S_ISLNK(st.st_mode):
+ link_dest = os.readlink(files[i])
+ content = "SYM: %s -> %s\n" % \
+ (file1, link_dest)
+ elif stat.S_ISDIR(st.st_mode):
+ content = "DIR: %s\n" % (file1,)
+ elif stat.S_ISFIFO(st.st_mode):
+ content = "FIF: %s\n" % (file1,)
+ else:
+ content = "DEV: %s\n" % (file1,)
+ with io.open(diff_files[i], mode='w',
+ encoding=_encodings['stdio']) as f:
+ f.write(content)
+
+ return func(diff_files[0], diff_files[1])
+
+ finally:
+ if tempdir is not None:
+ shutil.rmtree(tempdir)
+
+class diff_mixed_wrapper(object):
+
+ def __init__(self, f, *args):
+ self._func = f
+ self._args = args
+
+ def __call__(self, *args):
+ return diff_mixed(
+ functools.partial(self._func, *(self._args + args[:-2])),
+ *args[-2:])
+
+diffstatusoutput_mixed = diff_mixed_wrapper(diffstatusoutput)
+
def read_config(mandatory_opts):
eprefix = portage.settings["EPREFIX"]
if portage._not_installed:
@@ -103,35 +164,57 @@ def rcs_archive(archive, curconf, newconf, mrgconf):
except OSError:
pass
- if os.path.isfile(curconf):
+ try:
+ curconf_st = os.lstat(curconf)
+ except OSError:
+ curconf_st = None
+
+ if curconf_st is not None and \
+ (stat.S_ISREG(curconf_st.st_mode) or
+ stat.S_ISLNK(curconf_st.st_mode)):
try:
- shutil.copy2(curconf, archive)
+ if stat.S_ISLNK(curconf_st.st_mode):
+ os.symlink(os.readlink(curconf), archive)
+ else:
+ shutil.copy2(curconf, archive)
except(IOError, os.error) as why:
print(_('dispatch-conf: Error copying %(curconf)s to %(archive)s: %(reason)s; fatal') % \
{"curconf": curconf, "archive": archive, "reason": str(why)}, file=sys.stderr)
- if os.path.exists(archive + ',v'):
+ if os.path.lexists(archive + ',v'):
os.system(RCS_LOCK + ' ' + archive)
os.system(RCS_PUT + ' ' + archive)
ret = 0
- if newconf != '':
+ mystat = None
+ if newconf:
+ try:
+ mystat = os.lstat(newconf)
+ except OSError:
+ pass
+
+ if mystat is not None and \
+ (stat.S_ISREG(mystat.st_mode) or
+ stat.S_ISLNK(mystat.st_mode)):
os.system(RCS_GET + ' -r' + RCS_BRANCH + ' ' + archive)
- has_branch = os.path.exists(archive)
+ has_branch = os.path.lexists(archive)
if has_branch:
os.rename(archive, archive + '.dist')
try:
- shutil.copy2(newconf, archive)
+ if stat.S_ISLNK(mystat.st_mode):
+ os.symlink(os.readlink(newconf), archive)
+ else:
+ shutil.copy2(newconf, archive)
except(IOError, os.error) as why:
print(_('dispatch-conf: Error copying %(newconf)s to %(archive)s: %(reason)s; fatal') % \
{"newconf": newconf, "archive": archive, "reason": str(why)}, file=sys.stderr)
if has_branch:
- if mrgconf != '':
+ if mrgconf and os.path.isfile(archive) and \
+ os.path.isfile(mrgconf):
# This puts the results of the merge into mrgconf.
ret = os.system(RCS_MERGE % (archive, mrgconf))
- mystat = os.lstat(newconf)
os.chmod(mrgconf, mystat.st_mode)
os.chown(mrgconf, mystat.st_uid, mystat.st_gid)
os.rename(archive, archive + '.dist.new')
@@ -153,10 +236,11 @@ def file_archive(archive, curconf, newconf, mrgconf):
pass
# Archive the current config file if it isn't already saved
- if (os.path.exists(archive) and
- len(diffstatusoutput("diff -aq '%s' '%s'", curconf, archive)[1]) != 0):
+ if (os.path.lexists(archive) and
+ len(diffstatusoutput_mixed(
+ "diff -aq '%s' '%s'", curconf, archive)[1]) != 0):
suf = 1
- while suf < 9 and os.path.exists(archive + '.' + str(suf)):
+ while suf < 9 and os.path.lexists(archive + '.' + str(suf)):
suf += 1
while suf > 1:
@@ -165,26 +249,50 @@ def file_archive(archive, curconf, newconf, mrgconf):
os.rename(archive, archive + '.1')
- if os.path.isfile(curconf):
+ try:
+ curconf_st = os.lstat(curconf)
+ except OSError:
+ curconf_st = None
+
+ if curconf_st is not None and \
+ (stat.S_ISREG(curconf_st.st_mode) or
+ stat.S_ISLNK(curconf_st.st_mode)):
try:
- shutil.copy2(curconf, archive)
+ if stat.S_ISLNK(curconf_st.st_mode):
+ os.symlink(os.readlink(curconf), archive)
+ else:
+ shutil.copy2(curconf, archive)
except(IOError, os.error) as why:
print(_('dispatch-conf: Error copying %(curconf)s to %(archive)s: %(reason)s; fatal') % \
{"curconf": curconf, "archive": archive, "reason": str(why)}, file=sys.stderr)
- if newconf != '':
+ mystat = None
+ if newconf:
+ try:
+ mystat = os.lstat(newconf)
+ except OSError:
+ pass
+
+ if mystat is not None and \
+ (stat.S_ISREG(mystat.st_mode) or
+ stat.S_ISLNK(mystat.st_mode)):
# Save off new config file in the archive dir with .dist.new suffix
+ newconf_archive = archive + '.dist.new'
try:
- shutil.copy2(newconf, archive + '.dist.new')
+ if stat.S_ISLNK(mystat.st_mode):
+ os.symlink(os.readlink(newconf), newconf_archive)
+ else:
+ shutil.copy2(newconf, newconf_archive)
except(IOError, os.error) as why:
print(_('dispatch-conf: Error copying %(newconf)s to %(archive)s: %(reason)s; fatal') % \
{"newconf": newconf, "archive": archive + '.dist.new', "reason": str(why)}, file=sys.stderr)
ret = 0
- if mrgconf != '' and os.path.exists(archive + '.dist'):
+ if mrgconf and os.path.isfile(curconf) and \
+ os.path.isfile(newconf) and \
+ os.path.isfile(archive + '.dist'):
# This puts the results of the merge into mrgconf.
ret = os.system(DIFF3_MERGE % (curconf, archive + '.dist', newconf, mrgconf))
- mystat = os.lstat(newconf)
os.chmod(mrgconf, mystat.st_mode)
os.chown(mrgconf, mystat.st_uid, mystat.st_gid)
@@ -195,7 +303,7 @@ def rcs_archive_post_process(archive):
"""Check in the archive file with the .dist.new suffix on the branch
and remove the one with the .dist suffix."""
os.rename(archive + '.dist.new', archive)
- if os.path.exists(archive + '.dist'):
+ if os.path.lexists(archive + '.dist'):
# Commit the last-distributed version onto the branch.
os.system(RCS_LOCK + RCS_BRANCH + ' ' + archive)
os.system(RCS_PUT + ' -r' + RCS_BRANCH + ' ' + archive)
@@ -207,5 +315,5 @@ def rcs_archive_post_process(archive):
def file_archive_post_process(archive):
"""Rename the archive file with the .dist.new suffix to a .dist suffix"""
- if os.path.exists(archive + '.dist.new'):
+ if os.path.lexists(archive + '.dist.new'):
os.rename(archive + '.dist.new', archive + '.dist')
--
2.0.4
prev parent reply other threads:[~2014-10-31 13:34 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-10-26 11:12 [gentoo-portage-dev] [PATCH 1/3] etc-update: symlink support for bug #485598 zmedico
2014-10-26 11:12 ` [gentoo-portage-dev] [PATCH 2/3] dispatch-conf: " zmedico
2014-10-26 11:12 ` [gentoo-portage-dev] [PATCH 3/3] CONFIG_PROTECT: protect symlinks, " zmedico
2014-10-27 8:08 ` Alexander Berntsen
2014-10-27 9:07 ` Zac Medico
2014-10-27 20:35 ` Zac Medico
2014-11-03 3:54 ` Brian Dolbec
2014-10-27 22:57 ` [gentoo-portage-dev] [PATCH 1/3] etc-update: symlink support for " Zac Medico
2014-10-27 23:04 ` [gentoo-portage-dev] [PATCH 2/3] dispatch-conf: " Zac Medico
2014-10-31 13:34 ` Zac Medico [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1414762472-13482-1-git-send-email-zmedico@gentoo.org \
--to=zmedico@gentoo.org \
--cc=gentoo-portage-dev@lists.gentoo.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox