1 |
Load and update keys early to avoid delaying failures post rsync. Any |
2 |
failure will prevent verification from happening, and presumably most of |
3 |
the users will prefer fixing it and trying to sync again. For that case, |
4 |
it is better to perform the task before actual rsync to avoid |
5 |
unnecessarily rsyncing twice. |
6 |
--- |
7 |
pym/portage/sync/modules/rsync/rsync.py | 416 +++++++++++++++++--------------- |
8 |
1 file changed, 215 insertions(+), 201 deletions(-) |
9 |
|
10 |
diff --git a/pym/portage/sync/modules/rsync/rsync.py b/pym/portage/sync/modules/rsync/rsync.py |
11 |
index b1faf1ec9..b7758c2c3 100644 |
12 |
--- a/pym/portage/sync/modules/rsync/rsync.py |
13 |
+++ b/pym/portage/sync/modules/rsync/rsync.py |
14 |
@@ -110,211 +110,222 @@ class RsyncSync(NewBase): |
15 |
level=logging.WARNING, noiselevel=-1) |
16 |
self.verify_jobs = None |
17 |
|
18 |
- # Real local timestamp file. |
19 |
- self.servertimestampfile = os.path.join( |
20 |
- self.repo.location, "metadata", "timestamp.chk") |
21 |
- |
22 |
- content = portage.util.grabfile(self.servertimestampfile) |
23 |
- timestamp = 0 |
24 |
- if content: |
25 |
- try: |
26 |
- timestamp = time.mktime(time.strptime(content[0], |
27 |
- TIMESTAMP_FORMAT)) |
28 |
- except (OverflowError, ValueError): |
29 |
- pass |
30 |
- del content |
31 |
- |
32 |
- try: |
33 |
- self.rsync_initial_timeout = \ |
34 |
- int(self.settings.get("PORTAGE_RSYNC_INITIAL_TIMEOUT", "15")) |
35 |
- except ValueError: |
36 |
- self.rsync_initial_timeout = 15 |
37 |
+ openpgp_env = None |
38 |
+ if self.verify_metamanifest and gemato is not None: |
39 |
+ # Use isolated environment if key is specified, |
40 |
+ # system environment otherwise |
41 |
+ if self.repo.openpgp_key_path is not None: |
42 |
+ openpgp_env = gemato.openpgp.OpenPGPEnvironment() |
43 |
+ else: |
44 |
+ openpgp_env = gemato.openpgp.OpenPGPSystemEnvironment() |
45 |
|
46 |
try: |
47 |
- maxretries=int(self.settings["PORTAGE_RSYNC_RETRIES"]) |
48 |
- except SystemExit as e: |
49 |
- raise # Needed else can't exit |
50 |
- except: |
51 |
- maxretries = -1 #default number of retries |
52 |
- |
53 |
- if syncuri.startswith("file://"): |
54 |
- self.proto = "file" |
55 |
- dosyncuri = syncuri[7:] |
56 |
- unchanged, is_synced, exitcode, updatecache_flg = self._do_rsync( |
57 |
- dosyncuri, timestamp, opts) |
58 |
- self._process_exitcode(exitcode, dosyncuri, out, 1) |
59 |
- return (exitcode, updatecache_flg) |
60 |
+ # Load and update the keyring early. If it fails, then verification |
61 |
+ # will not be performed and the user will have to fix it and try again, |
62 |
+ # so we may as well bail out before actual rsync happens. |
63 |
+ if openpgp_env is not None and self.repo.openpgp_key_path is not None: |
64 |
+ try: |
65 |
+ out.einfo('Using keys from %s' % (self.repo.openpgp_key_path,)) |
66 |
+ with io.open(self.repo.openpgp_key_path, 'rb') as f: |
67 |
+ openpgp_env.import_key(f) |
68 |
+ out.ebegin('Refreshing keys from keyserver') |
69 |
+ openpgp_env.refresh_keys() |
70 |
+ out.eend(0) |
71 |
+ except Exception as e: |
72 |
+ writemsg_level("!!! Manifest verification impossible due to keyring problem:\n%s\n" |
73 |
+ % (e,), |
74 |
+ level=logging.ERROR, noiselevel=-1) |
75 |
+ return (1, False) |
76 |
|
77 |
- retries=0 |
78 |
- try: |
79 |
- self.proto, user_name, hostname, port = re.split( |
80 |
- r"(rsync|ssh)://([^:/]+@)?(\[[:\da-fA-F]*\]|[^:/]*)(:[0-9]+)?", |
81 |
- syncuri, maxsplit=4)[1:5] |
82 |
- except ValueError: |
83 |
- writemsg_level("!!! sync-uri is invalid: %s\n" % syncuri, |
84 |
- noiselevel=-1, level=logging.ERROR) |
85 |
- return (1, False) |
86 |
+ # Real local timestamp file. |
87 |
+ self.servertimestampfile = os.path.join( |
88 |
+ self.repo.location, "metadata", "timestamp.chk") |
89 |
|
90 |
- self.ssh_opts = self.settings.get("PORTAGE_SSH_OPTS") |
91 |
+ content = portage.util.grabfile(self.servertimestampfile) |
92 |
+ timestamp = 0 |
93 |
+ if content: |
94 |
+ try: |
95 |
+ timestamp = time.mktime(time.strptime(content[0], |
96 |
+ TIMESTAMP_FORMAT)) |
97 |
+ except (OverflowError, ValueError): |
98 |
+ pass |
99 |
+ del content |
100 |
|
101 |
- if port is None: |
102 |
- port="" |
103 |
- if user_name is None: |
104 |
- user_name="" |
105 |
- if re.match(r"^\[[:\da-fA-F]*\]$", hostname) is None: |
106 |
- getaddrinfo_host = hostname |
107 |
- else: |
108 |
- # getaddrinfo needs the brackets stripped |
109 |
- getaddrinfo_host = hostname[1:-1] |
110 |
- updatecache_flg = False |
111 |
- all_rsync_opts = set(self.rsync_opts) |
112 |
- all_rsync_opts.update(self.extra_rsync_opts) |
113 |
+ try: |
114 |
+ self.rsync_initial_timeout = \ |
115 |
+ int(self.settings.get("PORTAGE_RSYNC_INITIAL_TIMEOUT", "15")) |
116 |
+ except ValueError: |
117 |
+ self.rsync_initial_timeout = 15 |
118 |
|
119 |
- family = socket.AF_UNSPEC |
120 |
- if "-4" in all_rsync_opts or "--ipv4" in all_rsync_opts: |
121 |
- family = socket.AF_INET |
122 |
- elif socket.has_ipv6 and \ |
123 |
- ("-6" in all_rsync_opts or "--ipv6" in all_rsync_opts): |
124 |
- family = socket.AF_INET6 |
125 |
+ try: |
126 |
+ maxretries=int(self.settings["PORTAGE_RSYNC_RETRIES"]) |
127 |
+ except SystemExit as e: |
128 |
+ raise # Needed else can't exit |
129 |
+ except: |
130 |
+ maxretries = -1 #default number of retries |
131 |
+ |
132 |
+ if syncuri.startswith("file://"): |
133 |
+ self.proto = "file" |
134 |
+ dosyncuri = syncuri[7:] |
135 |
+ unchanged, is_synced, exitcode, updatecache_flg = self._do_rsync( |
136 |
+ dosyncuri, timestamp, opts) |
137 |
+ self._process_exitcode(exitcode, dosyncuri, out, 1) |
138 |
+ return (exitcode, updatecache_flg) |
139 |
+ |
140 |
+ retries=0 |
141 |
+ try: |
142 |
+ self.proto, user_name, hostname, port = re.split( |
143 |
+ r"(rsync|ssh)://([^:/]+@)?(\[[:\da-fA-F]*\]|[^:/]*)(:[0-9]+)?", |
144 |
+ syncuri, maxsplit=4)[1:5] |
145 |
+ except ValueError: |
146 |
+ writemsg_level("!!! sync-uri is invalid: %s\n" % syncuri, |
147 |
+ noiselevel=-1, level=logging.ERROR) |
148 |
+ return (1, False) |
149 |
|
150 |
- addrinfos = None |
151 |
- uris = [] |
152 |
+ self.ssh_opts = self.settings.get("PORTAGE_SSH_OPTS") |
153 |
|
154 |
- try: |
155 |
- addrinfos = getaddrinfo_validate( |
156 |
- socket.getaddrinfo(getaddrinfo_host, None, |
157 |
- family, socket.SOCK_STREAM)) |
158 |
- except socket.error as e: |
159 |
- writemsg_level( |
160 |
- "!!! getaddrinfo failed for '%s': %s\n" |
161 |
- % (_unicode_decode(hostname), _unicode(e)), |
162 |
- noiselevel=-1, level=logging.ERROR) |
163 |
- |
164 |
- if addrinfos: |
165 |
- |
166 |
- AF_INET = socket.AF_INET |
167 |
- AF_INET6 = None |
168 |
- if socket.has_ipv6: |
169 |
- AF_INET6 = socket.AF_INET6 |
170 |
- |
171 |
- ips_v4 = [] |
172 |
- ips_v6 = [] |
173 |
- |
174 |
- for addrinfo in addrinfos: |
175 |
- if addrinfo[0] == AF_INET: |
176 |
- ips_v4.append("%s" % addrinfo[4][0]) |
177 |
- elif AF_INET6 is not None and addrinfo[0] == AF_INET6: |
178 |
- # IPv6 addresses need to be enclosed in square brackets |
179 |
- ips_v6.append("[%s]" % addrinfo[4][0]) |
180 |
- |
181 |
- random.shuffle(ips_v4) |
182 |
- random.shuffle(ips_v6) |
183 |
- |
184 |
- # Give priority to the address family that |
185 |
- # getaddrinfo() returned first. |
186 |
- if AF_INET6 is not None and addrinfos and \ |
187 |
- addrinfos[0][0] == AF_INET6: |
188 |
- ips = ips_v6 + ips_v4 |
189 |
- else: |
190 |
- ips = ips_v4 + ips_v6 |
191 |
- |
192 |
- for ip in ips: |
193 |
- uris.append(syncuri.replace( |
194 |
- "//" + user_name + hostname + port + "/", |
195 |
- "//" + user_name + ip + port + "/", 1)) |
196 |
- |
197 |
- if not uris: |
198 |
- # With some configurations we need to use the plain hostname |
199 |
- # rather than try to resolve the ip addresses (bug #340817). |
200 |
- uris.append(syncuri) |
201 |
- |
202 |
- # reverse, for use with pop() |
203 |
- uris.reverse() |
204 |
- uris_orig = uris[:] |
205 |
- |
206 |
- effective_maxretries = maxretries |
207 |
- if effective_maxretries < 0: |
208 |
- effective_maxretries = len(uris) - 1 |
209 |
- |
210 |
- local_state_unchanged = True |
211 |
- while (1): |
212 |
- if uris: |
213 |
- dosyncuri = uris.pop() |
214 |
- elif maxretries < 0 or retries > maxretries: |
215 |
- writemsg("!!! Exhausted addresses for %s\n" |
216 |
- % _unicode_decode(hostname), noiselevel=-1) |
217 |
- return (1, False) |
218 |
- else: |
219 |
- uris.extend(uris_orig) |
220 |
- dosyncuri = uris.pop() |
221 |
- |
222 |
- if (retries==0): |
223 |
- if "--ask" in opts: |
224 |
- uq = UserQuery(opts) |
225 |
- if uq.query("Do you want to sync your Portage tree " + \ |
226 |
- "with the mirror at\n" + blue(dosyncuri) + bold("?"), |
227 |
- enter_invalid) == "No": |
228 |
- print() |
229 |
- print("Quitting.") |
230 |
- print() |
231 |
- sys.exit(128 + signal.SIGINT) |
232 |
- self.logger(self.xterm_titles, |
233 |
- ">>> Starting rsync with " + dosyncuri) |
234 |
- if "--quiet" not in opts: |
235 |
- print(">>> Starting rsync with "+dosyncuri+"...") |
236 |
+ if port is None: |
237 |
+ port="" |
238 |
+ if user_name is None: |
239 |
+ user_name="" |
240 |
+ if re.match(r"^\[[:\da-fA-F]*\]$", hostname) is None: |
241 |
+ getaddrinfo_host = hostname |
242 |
else: |
243 |
- self.logger(self.xterm_titles, |
244 |
- ">>> Starting retry %d of %d with %s" % \ |
245 |
- (retries, effective_maxretries, dosyncuri)) |
246 |
- writemsg_stdout( |
247 |
- "\n\n>>> Starting retry %d of %d with %s\n" % \ |
248 |
- (retries, effective_maxretries, dosyncuri), noiselevel=-1) |
249 |
- |
250 |
- if dosyncuri.startswith('ssh://'): |
251 |
- dosyncuri = dosyncuri[6:].replace('/', ':/', 1) |
252 |
- |
253 |
- unchanged, is_synced, exitcode, updatecache_flg = self._do_rsync( |
254 |
- dosyncuri, timestamp, opts) |
255 |
- if not unchanged: |
256 |
- local_state_unchanged = False |
257 |
- if is_synced: |
258 |
- break |
259 |
- |
260 |
- retries=retries+1 |
261 |
- |
262 |
- if maxretries < 0 or retries <= maxretries: |
263 |
- print(">>> Retrying...") |
264 |
- else: |
265 |
- # over retries |
266 |
- # exit loop |
267 |
- exitcode = EXCEEDED_MAX_RETRIES |
268 |
- break |
269 |
- self._process_exitcode(exitcode, dosyncuri, out, maxretries) |
270 |
- |
271 |
- # if synced successfully, verify now |
272 |
- if exitcode == 0 and self.verify_metamanifest: |
273 |
- if gemato is None: |
274 |
- writemsg_level("!!! Unable to verify: gemato not found\n", |
275 |
- level=logging.ERROR, noiselevel=-1) |
276 |
- exitcode = 127 |
277 |
- else: |
278 |
- # Use isolated environment if key is specified, |
279 |
- # system environment otherwise |
280 |
- if self.repo.openpgp_key_path is not None: |
281 |
- openpgp_env_cls = gemato.openpgp.OpenPGPEnvironment |
282 |
- else: |
283 |
- openpgp_env_cls = gemato.openpgp.OpenPGPSystemEnvironment |
284 |
- |
285 |
- try: |
286 |
- with openpgp_env_cls() as openpgp_env: |
287 |
- if self.repo.openpgp_key_path is not None: |
288 |
- out.einfo('Using keys from %s' % (self.repo.openpgp_key_path,)) |
289 |
- with io.open(self.repo.openpgp_key_path, 'rb') as f: |
290 |
- openpgp_env.import_key(f) |
291 |
- out.ebegin('Refreshing keys from keyserver') |
292 |
- openpgp_env.refresh_keys() |
293 |
- out.eend(0) |
294 |
+ # getaddrinfo needs the brackets stripped |
295 |
+ getaddrinfo_host = hostname[1:-1] |
296 |
+ updatecache_flg = False |
297 |
+ all_rsync_opts = set(self.rsync_opts) |
298 |
+ all_rsync_opts.update(self.extra_rsync_opts) |
299 |
+ |
300 |
+ family = socket.AF_UNSPEC |
301 |
+ if "-4" in all_rsync_opts or "--ipv4" in all_rsync_opts: |
302 |
+ family = socket.AF_INET |
303 |
+ elif socket.has_ipv6 and \ |
304 |
+ ("-6" in all_rsync_opts or "--ipv6" in all_rsync_opts): |
305 |
+ family = socket.AF_INET6 |
306 |
+ |
307 |
+ addrinfos = None |
308 |
+ uris = [] |
309 |
|
310 |
+ try: |
311 |
+ addrinfos = getaddrinfo_validate( |
312 |
+ socket.getaddrinfo(getaddrinfo_host, None, |
313 |
+ family, socket.SOCK_STREAM)) |
314 |
+ except socket.error as e: |
315 |
+ writemsg_level( |
316 |
+ "!!! getaddrinfo failed for '%s': %s\n" |
317 |
+ % (_unicode_decode(hostname), _unicode(e)), |
318 |
+ noiselevel=-1, level=logging.ERROR) |
319 |
+ |
320 |
+ if addrinfos: |
321 |
+ |
322 |
+ AF_INET = socket.AF_INET |
323 |
+ AF_INET6 = None |
324 |
+ if socket.has_ipv6: |
325 |
+ AF_INET6 = socket.AF_INET6 |
326 |
+ |
327 |
+ ips_v4 = [] |
328 |
+ ips_v6 = [] |
329 |
+ |
330 |
+ for addrinfo in addrinfos: |
331 |
+ if addrinfo[0] == AF_INET: |
332 |
+ ips_v4.append("%s" % addrinfo[4][0]) |
333 |
+ elif AF_INET6 is not None and addrinfo[0] == AF_INET6: |
334 |
+ # IPv6 addresses need to be enclosed in square brackets |
335 |
+ ips_v6.append("[%s]" % addrinfo[4][0]) |
336 |
+ |
337 |
+ random.shuffle(ips_v4) |
338 |
+ random.shuffle(ips_v6) |
339 |
+ |
340 |
+ # Give priority to the address family that |
341 |
+ # getaddrinfo() returned first. |
342 |
+ if AF_INET6 is not None and addrinfos and \ |
343 |
+ addrinfos[0][0] == AF_INET6: |
344 |
+ ips = ips_v6 + ips_v4 |
345 |
+ else: |
346 |
+ ips = ips_v4 + ips_v6 |
347 |
+ |
348 |
+ for ip in ips: |
349 |
+ uris.append(syncuri.replace( |
350 |
+ "//" + user_name + hostname + port + "/", |
351 |
+ "//" + user_name + ip + port + "/", 1)) |
352 |
+ |
353 |
+ if not uris: |
354 |
+ # With some configurations we need to use the plain hostname |
355 |
+ # rather than try to resolve the ip addresses (bug #340817). |
356 |
+ uris.append(syncuri) |
357 |
+ |
358 |
+ # reverse, for use with pop() |
359 |
+ uris.reverse() |
360 |
+ uris_orig = uris[:] |
361 |
+ |
362 |
+ effective_maxretries = maxretries |
363 |
+ if effective_maxretries < 0: |
364 |
+ effective_maxretries = len(uris) - 1 |
365 |
+ |
366 |
+ local_state_unchanged = True |
367 |
+ while (1): |
368 |
+ if uris: |
369 |
+ dosyncuri = uris.pop() |
370 |
+ elif maxretries < 0 or retries > maxretries: |
371 |
+ writemsg("!!! Exhausted addresses for %s\n" |
372 |
+ % _unicode_decode(hostname), noiselevel=-1) |
373 |
+ return (1, False) |
374 |
+ else: |
375 |
+ uris.extend(uris_orig) |
376 |
+ dosyncuri = uris.pop() |
377 |
+ |
378 |
+ if (retries==0): |
379 |
+ if "--ask" in opts: |
380 |
+ uq = UserQuery(opts) |
381 |
+ if uq.query("Do you want to sync your Portage tree " + \ |
382 |
+ "with the mirror at\n" + blue(dosyncuri) + bold("?"), |
383 |
+ enter_invalid) == "No": |
384 |
+ print() |
385 |
+ print("Quitting.") |
386 |
+ print() |
387 |
+ sys.exit(128 + signal.SIGINT) |
388 |
+ self.logger(self.xterm_titles, |
389 |
+ ">>> Starting rsync with " + dosyncuri) |
390 |
+ if "--quiet" not in opts: |
391 |
+ print(">>> Starting rsync with "+dosyncuri+"...") |
392 |
+ else: |
393 |
+ self.logger(self.xterm_titles, |
394 |
+ ">>> Starting retry %d of %d with %s" % \ |
395 |
+ (retries, effective_maxretries, dosyncuri)) |
396 |
+ writemsg_stdout( |
397 |
+ "\n\n>>> Starting retry %d of %d with %s\n" % \ |
398 |
+ (retries, effective_maxretries, dosyncuri), noiselevel=-1) |
399 |
+ |
400 |
+ if dosyncuri.startswith('ssh://'): |
401 |
+ dosyncuri = dosyncuri[6:].replace('/', ':/', 1) |
402 |
+ |
403 |
+ unchanged, is_synced, exitcode, updatecache_flg = self._do_rsync( |
404 |
+ dosyncuri, timestamp, opts) |
405 |
+ if not unchanged: |
406 |
+ local_state_unchanged = False |
407 |
+ if is_synced: |
408 |
+ break |
409 |
+ |
410 |
+ retries=retries+1 |
411 |
+ |
412 |
+ if maxretries < 0 or retries <= maxretries: |
413 |
+ print(">>> Retrying...") |
414 |
+ else: |
415 |
+ # over retries |
416 |
+ # exit loop |
417 |
+ exitcode = EXCEEDED_MAX_RETRIES |
418 |
+ break |
419 |
+ self._process_exitcode(exitcode, dosyncuri, out, maxretries) |
420 |
+ |
421 |
+ # if synced successfully, verify now |
422 |
+ if exitcode == 0 and self.verify_metamanifest: |
423 |
+ if gemato is None: |
424 |
+ writemsg_level("!!! Unable to verify: gemato not found\n", |
425 |
+ level=logging.ERROR, noiselevel=-1) |
426 |
+ exitcode = 127 |
427 |
+ else: |
428 |
+ try: |
429 |
# we always verify the Manifest signature, in case |
430 |
# we had to deal with key revocation case |
431 |
m = gemato.recursiveloader.ManifestRecursiveLoader( |
432 |
@@ -344,13 +355,16 @@ class RsyncSync(NewBase): |
433 |
out.ebegin('Verifying %s' % (self.repo.location,)) |
434 |
m.assert_directory_verifies() |
435 |
out.eend(0) |
436 |
- except Exception as e: |
437 |
- writemsg_level("!!! Manifest verification failed:\n%s\n" |
438 |
- % (e,), |
439 |
- level=logging.ERROR, noiselevel=-1) |
440 |
- exitcode = 1 |
441 |
+ except Exception as e: |
442 |
+ writemsg_level("!!! Manifest verification failed:\n%s\n" |
443 |
+ % (e,), |
444 |
+ level=logging.ERROR, noiselevel=-1) |
445 |
+ exitcode = 1 |
446 |
|
447 |
- return (exitcode, updatecache_flg) |
448 |
+ return (exitcode, updatecache_flg) |
449 |
+ finally: |
450 |
+ if openpgp_env is not None: |
451 |
+ openpgp_env.close() |
452 |
|
453 |
|
454 |
def _process_exitcode(self, exitcode, syncuri, out, maxretries): |
455 |
-- |
456 |
2.16.1 |