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 |