Gentoo Archives: gentoo-portage-dev

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