public inbox for gentoo-portage-dev@lists.gentoo.org
 help / color / mirror / Atom feed
* [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