On Sun, 10 May 2015 03:00:56 -0700 Zac Medico <zmed...@gentoo.org> wrote:
> 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 > --- > 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) looks good -- Brian Dolbec <dolsen>