From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from lists.gentoo.org (pigeon.gentoo.org [208.92.234.80]) by finch.gentoo.org (Postfix) with ESMTP id C0F6C138CD0 for ; Mon, 11 May 2015 00:47:36 +0000 (UTC) Received: from pigeon.gentoo.org (localhost [127.0.0.1]) by pigeon.gentoo.org (Postfix) with SMTP id 386B3E0849; Mon, 11 May 2015 00:47:34 +0000 (UTC) Received: from smtp.gentoo.org (smtp.gentoo.org [140.211.166.183]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by pigeon.gentoo.org (Postfix) with ESMTPS id B4F65E0849 for ; Mon, 11 May 2015 00:47:33 +0000 (UTC) Received: from oystercatcher.gentoo.org (oystercatcher.gentoo.org [148.251.78.52]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by smtp.gentoo.org (Postfix) with ESMTPS id 4EE5D340C39 for ; Mon, 11 May 2015 00:47:32 +0000 (UTC) Received: from localhost.localdomain (localhost [127.0.0.1]) by oystercatcher.gentoo.org (Postfix) with ESMTP id 3D2249D1 for ; Mon, 11 May 2015 00:47:30 +0000 (UTC) From: "Zac Medico" To: gentoo-commits@lists.gentoo.org Content-Transfer-Encoding: 8bit Content-type: text/plain; charset=UTF-8 Reply-To: gentoo-dev@lists.gentoo.org, "Zac Medico" Message-ID: <1431304818.757beccdd058eb9ce9884a7c5e766e24c881ec8d.zmedico@gentoo> Subject: [gentoo-commits] proj/portage:master commit in: pym/portage/ X-VCS-Repository: proj/portage X-VCS-Files: pym/portage/dispatch_conf.py X-VCS-Directories: pym/portage/ X-VCS-Committer: zmedico X-VCS-Committer-Name: Zac Medico X-VCS-Revision: 757beccdd058eb9ce9884a7c5e766e24c881ec8d X-VCS-Branch: master Date: Mon, 11 May 2015 00:47:30 +0000 (UTC) Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-Id: Gentoo Linux mail X-BeenThere: gentoo-commits@lists.gentoo.org X-Archives-Salt: f9419ac4-1126-4cb9-ad8d-81ad2f3f5d8e X-Archives-Hash: 617ea64b89f289b680375844b4a35de9 commit: 757beccdd058eb9ce9884a7c5e766e24c881ec8d Author: Zac Medico gentoo org> AuthorDate: Sun May 10 09:39:05 2015 +0000 Commit: Zac Medico gentoo org> CommitDate: Mon May 11 00:40:18 2015 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=757beccd dispatch-conf: handle file/directory collisions (bug 256376) Whenever a file/directory collision would have previously caused a problem, the colliding file or directory will now be renamed. X-Gentoo-Bug: 256376 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=256376 Acked-by: Brian Dolbec gentoo.org> pym/portage/dispatch_conf.py | 97 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 14 deletions(-) diff --git a/pym/portage/dispatch_conf.py b/pym/portage/dispatch_conf.py index 98939fd..ed9a64a 100644 --- a/pym/portage/dispatch_conf.py +++ b/pym/portage/dispatch_conf.py @@ -8,6 +8,7 @@ from __future__ import print_function, unicode_literals +import errno import io import functools import stat @@ -20,6 +21,7 @@ from portage import _encodings, os, shutil from portage.env.loaders import KeyValuePairFileLoader from portage.localization import _ from portage.util import shlex_split, varexpand +from portage.util.path import iter_parents RCS_BRANCH = '1.1.1' RCS_LOCK = 'rcs -ko -M -l' @@ -28,6 +30,7 @@ RCS_GET = 'co' RCS_MERGE = "rcsmerge -p -r" + RCS_BRANCH + " '%s' > '%s'" DIFF3_MERGE = "diff3 -mE '%s' '%s' '%s' > '%s'" +_ARCHIVE_ROTATE_MAX = 9 def diffstatusoutput(cmd, file1, file2): """ @@ -244,6 +247,77 @@ def rcs_archive(archive, curconf, newconf, mrgconf): return ret +def _file_archive_rotate(archive): + """ + Rename archive to archive + '.1', and perform similar rotation + for files up to archive + '.9'. + + @param archive: file path to archive + @type archive: str + """ + + max_suf = 0 + try: + for max_suf, max_st, max_path in ( + (suf, os.lstat(path), path) for suf, path in ( + (suf, "%s.%s" % (archive, suf)) for suf in range( + 1, _ARCHIVE_ROTATE_MAX + 1))): + pass + except OSError as e: + if e.errno not in (errno.ENOENT, errno.ESTALE): + raise + # There's already an unused suffix. + else: + # Free the max suffix in order to avoid possible problems + # when we rename another file or directory to the same + # location (see bug 256376). + if stat.S_ISDIR(max_st.st_mode): + # Removing a directory might destroy something important, + # so rename it instead. + head, tail = os.path.split(archive) + placeholder = tempfile.NamedTemporaryFile( + prefix="%s." % tail, + dir=head) + placeholder.close() + os.rename(max_path, placeholder.name) + else: + os.unlink(max_path) + + # The max suffix is now unused. + max_suf -= 1 + + for suf in range(max_suf + 1, 1, -1): + os.rename("%s.%s" % (archive, suf - 1), "%s.%s" % (archive, suf)) + + os.rename(archive, "%s.1" % (archive,)) + +def _file_archive_ensure_dir(parent_dir): + """ + Ensure that the parent directory for an archive exists. + If a file exists where a directory is needed, then rename + it (see bug 256376). + + @param parent_dir: path of parent directory + @type parent_dir: str + """ + + for parent in iter_parents(parent_dir): + # Use lstat because a symlink to a directory might point + # to a directory outside of the config archive, making + # it an unsuitable parent. + try: + parent_st = os.lstat(parent) + except OSError: + pass + else: + if not stat.S_ISDIR(parent_st.st_mode): + _file_archive_rotate(parent) + break + + try: + os.makedirs(parent_dir) + except OSError: + pass def file_archive(archive, curconf, newconf, mrgconf): """Archive existing config to the archive-dir, bumping old versions @@ -253,24 +327,13 @@ def file_archive(archive, curconf, newconf, mrgconf): if newconf was specified, archive it as a .dist.new version (which gets moved to the .dist version at the end of the processing).""" - try: - os.makedirs(os.path.dirname(archive)) - except OSError: - pass + _file_archive_ensure_dir(os.path.dirname(archive)) # Archive the current config file if it isn't already saved 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.lexists(archive + '.' + str(suf)): - suf += 1 - - while suf > 1: - os.rename(archive + '.' + str(suf-1), archive + '.' + str(suf)) - suf -= 1 - - os.rename(archive, archive + '.1') + _file_archive_rotate(archive) try: curconf_st = os.lstat(curconf) @@ -294,6 +357,9 @@ def file_archive(archive, curconf, newconf, mrgconf): stat.S_ISLNK(mystat.st_mode)): # Save off new config file in the archive dir with .dist.new suffix newconf_archive = archive + '.dist.new' + if os.path.isdir(newconf_archive + ) and not os.path.islink(newconf_archive): + _file_archive_rotate(newconf_archive) _archive_copy(mystat, newconf, newconf_archive) ret = 0 @@ -325,4 +391,7 @@ 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.lexists(archive + '.dist.new'): - os.rename(archive + '.dist.new', archive + '.dist') + dest = "%s.dist" % archive + if os.path.isdir(dest) and not os.path.islink(dest): + _file_archive_rotate(dest) + os.rename(archive + '.dist.new', dest)