Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH] dispatch-conf: handle file/directory collisions (bug 256376)
Date: Sun, 10 May 2015 10:01:18
Message-Id: 1431252056-24778-1-git-send-email-zmedico@gentoo.org
1 Whenever a file/directory collision would have previously caused a
2 problem, the colliding file or directory will now be renamed.
3
4 X-Gentoo-Bug: 256376
5 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=256376
6 ---
7 pym/portage/dispatch_conf.py | 97 +++++++++++++++++++++++++++++++++++++-------
8 1 file changed, 83 insertions(+), 14 deletions(-)
9
10 diff --git a/pym/portage/dispatch_conf.py b/pym/portage/dispatch_conf.py
11 index 98939fd..ed9a64a 100644
12 --- a/pym/portage/dispatch_conf.py
13 +++ b/pym/portage/dispatch_conf.py
14 @@ -8,6 +8,7 @@
15
16 from __future__ import print_function, unicode_literals
17
18 +import errno
19 import io
20 import functools
21 import stat
22 @@ -20,6 +21,7 @@ from portage import _encodings, os, shutil
23 from portage.env.loaders import KeyValuePairFileLoader
24 from portage.localization import _
25 from portage.util import shlex_split, varexpand
26 +from portage.util.path import iter_parents
27
28 RCS_BRANCH = '1.1.1'
29 RCS_LOCK = 'rcs -ko -M -l'
30 @@ -28,6 +30,7 @@ RCS_GET = 'co'
31 RCS_MERGE = "rcsmerge -p -r" + RCS_BRANCH + " '%s' > '%s'"
32
33 DIFF3_MERGE = "diff3 -mE '%s' '%s' '%s' > '%s'"
34 +_ARCHIVE_ROTATE_MAX = 9
35
36 def diffstatusoutput(cmd, file1, file2):
37 """
38 @@ -244,6 +247,77 @@ def rcs_archive(archive, curconf, newconf, mrgconf):
39
40 return ret
41
42 +def _file_archive_rotate(archive):
43 + """
44 + Rename archive to archive + '.1', and perform similar rotation
45 + for files up to archive + '.9'.
46 +
47 + @param archive: file path to archive
48 + @type archive: str
49 + """
50 +
51 + max_suf = 0
52 + try:
53 + for max_suf, max_st, max_path in (
54 + (suf, os.lstat(path), path) for suf, path in (
55 + (suf, "%s.%s" % (archive, suf)) for suf in range(
56 + 1, _ARCHIVE_ROTATE_MAX + 1))):
57 + pass
58 + except OSError as e:
59 + if e.errno not in (errno.ENOENT, errno.ESTALE):
60 + raise
61 + # There's already an unused suffix.
62 + else:
63 + # Free the max suffix in order to avoid possible problems
64 + # when we rename another file or directory to the same
65 + # location (see bug 256376).
66 + if stat.S_ISDIR(max_st.st_mode):
67 + # Removing a directory might destroy something important,
68 + # so rename it instead.
69 + head, tail = os.path.split(archive)
70 + placeholder = tempfile.NamedTemporaryFile(
71 + prefix="%s." % tail,
72 + dir=head)
73 + placeholder.close()
74 + os.rename(max_path, placeholder.name)
75 + else:
76 + os.unlink(max_path)
77 +
78 + # The max suffix is now unused.
79 + max_suf -= 1
80 +
81 + for suf in range(max_suf + 1, 1, -1):
82 + os.rename("%s.%s" % (archive, suf - 1), "%s.%s" % (archive, suf))
83 +
84 + os.rename(archive, "%s.1" % (archive,))
85 +
86 +def _file_archive_ensure_dir(parent_dir):
87 + """
88 + Ensure that the parent directory for an archive exists.
89 + If a file exists where a directory is needed, then rename
90 + it (see bug 256376).
91 +
92 + @param parent_dir: path of parent directory
93 + @type parent_dir: str
94 + """
95 +
96 + for parent in iter_parents(parent_dir):
97 + # Use lstat because a symlink to a directory might point
98 + # to a directory outside of the config archive, making
99 + # it an unsuitable parent.
100 + try:
101 + parent_st = os.lstat(parent)
102 + except OSError:
103 + pass
104 + else:
105 + if not stat.S_ISDIR(parent_st.st_mode):
106 + _file_archive_rotate(parent)
107 + break
108 +
109 + try:
110 + os.makedirs(parent_dir)
111 + except OSError:
112 + pass
113
114 def file_archive(archive, curconf, newconf, mrgconf):
115 """Archive existing config to the archive-dir, bumping old versions
116 @@ -253,24 +327,13 @@ def file_archive(archive, curconf, newconf, mrgconf):
117 if newconf was specified, archive it as a .dist.new version (which
118 gets moved to the .dist version at the end of the processing)."""
119
120 - try:
121 - os.makedirs(os.path.dirname(archive))
122 - except OSError:
123 - pass
124 + _file_archive_ensure_dir(os.path.dirname(archive))
125
126 # Archive the current config file if it isn't already saved
127 if (os.path.lexists(archive) and
128 len(diffstatusoutput_mixed(
129 "diff -aq '%s' '%s'", curconf, archive)[1]) != 0):
130 - suf = 1
131 - while suf < 9 and os.path.lexists(archive + '.' + str(suf)):
132 - suf += 1
133 -
134 - while suf > 1:
135 - os.rename(archive + '.' + str(suf-1), archive + '.' + str(suf))
136 - suf -= 1
137 -
138 - os.rename(archive, archive + '.1')
139 + _file_archive_rotate(archive)
140
141 try:
142 curconf_st = os.lstat(curconf)
143 @@ -294,6 +357,9 @@ def file_archive(archive, curconf, newconf, mrgconf):
144 stat.S_ISLNK(mystat.st_mode)):
145 # Save off new config file in the archive dir with .dist.new suffix
146 newconf_archive = archive + '.dist.new'
147 + if os.path.isdir(newconf_archive
148 + ) and not os.path.islink(newconf_archive):
149 + _file_archive_rotate(newconf_archive)
150 _archive_copy(mystat, newconf, newconf_archive)
151
152 ret = 0
153 @@ -325,4 +391,7 @@ def rcs_archive_post_process(archive):
154 def file_archive_post_process(archive):
155 """Rename the archive file with the .dist.new suffix to a .dist suffix"""
156 if os.path.lexists(archive + '.dist.new'):
157 - os.rename(archive + '.dist.new', archive + '.dist')
158 + dest = "%s.dist" % archive
159 + if os.path.isdir(dest) and not os.path.islink(dest):
160 + _file_archive_rotate(dest)
161 + os.rename(archive + '.dist.new', dest)
162 --
163 2.3.5

Replies