* [gentoo-portage-dev] [PATCH] dispatch-conf: handle file/directory collisions (bug 256376)
@ 2015-05-10 10:00 Zac Medico
2015-05-11 0:24 ` Brian Dolbec
0 siblings, 1 reply; 2+ messages in thread
From: Zac Medico @ 2015-05-10 10:00 UTC (permalink / raw
To: gentoo-portage-dev; +Cc: Zac Medico
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)
--
2.3.5
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [gentoo-portage-dev] [PATCH] dispatch-conf: handle file/directory collisions (bug 256376)
2015-05-10 10:00 [gentoo-portage-dev] [PATCH] dispatch-conf: handle file/directory collisions (bug 256376) Zac Medico
@ 2015-05-11 0:24 ` Brian Dolbec
0 siblings, 0 replies; 2+ messages in thread
From: Brian Dolbec @ 2015-05-11 0:24 UTC (permalink / raw
To: gentoo-portage-dev
On Sun, 10 May 2015 03:00:56 -0700
Zac Medico <zmedico@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>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2015-05-11 0:24 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-05-10 10:00 [gentoo-portage-dev] [PATCH] dispatch-conf: handle file/directory collisions (bug 256376) Zac Medico
2015-05-11 0:24 ` Brian Dolbec
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox