Gentoo Archives: gentoo-portage-dev

From: Alexandru Elisei <alexandru.elisei@×××××.com>
To: gentoo-portage-dev@l.g.o
Subject: [gentoo-portage-dev] Re: [PATCH V2] emaint: exit with non-zero status code when module fails (bug 567478)
Date: Wed, 18 Jan 2017 16:12:44
Message-Id: CAB-4s4=9762opr-X-0oC7fA3QrmECaBsb=nAwC2GJNn-UXXeSA@mail.gmail.com
In Reply to: [gentoo-portage-dev] [PATCH] emaint: exit with non-zero status code when module fails (bug 567478) by Alexandru Elisei
1 Module functions currently return a message to emaint after invocation.
2 Emaint prints this message then exits normally (with a success return
3 code) even if the module encountered an error. This patch aims to
4 change this by having each module public function return a tuple of
5 (returncode, message), where returncode is boolean True if the function
6 was successful or False otherwise. Emaint will inspect the return codes
7 and exit unsuccessfully if necessary.
8 ---
9 #
10 # I've modified the patch as per Brian's suggestions:
11 # - the modules return True on success and False on failure.
12 # - TaskHandler.run_tasks() returns a list of all the module return
13 # codes, then emaint_main() exits.
14 # - the portage/pym/portage/tests/emerge/test_simple.py change wasn't
15 # present in the first version of the patch because of a subtle bug that
16 # I introduced: rval was None when the variable PORT_LOGDIR wasn't set,
17 # it was different from os.EX_OK so TaskHandler.run_tasks() was exiting
18 # with sys.exit(None), which is zero - success.
19 #
20 pym/portage/emaint/main.py | 12 +++++++++---
21 pym/portage/emaint/modules/binhost/binhost.py | 6 ++++--
22 pym/portage/emaint/modules/config/config.py | 6 ++++--
23 pym/portage/emaint/modules/logs/logs.py | 9 +++++----
24 pym/portage/emaint/modules/merges/merges.py | 18 +++++++++++-------
25 pym/portage/emaint/modules/move/move.py | 9 +++++++--
26 pym/portage/emaint/modules/resume/resume.py | 4 +++-
27 pym/portage/emaint/modules/sync/sync.py | 20 +++++++++++++-------
28 pym/portage/emaint/modules/world/world.py | 8 ++++++--
29 pym/portage/tests/emerge/test_simple.py | 1 +
30 10 files changed, 63 insertions(+), 30 deletions(-)
31
32 diff --git a/pym/portage/emaint/main.py b/pym/portage/emaint/main.py
33 index 65e3545..f448d6b 100644
34 --- a/pym/portage/emaint/main.py
35 +++ b/pym/portage/emaint/main.py
36 @@ -115,6 +115,7 @@ class TaskHandler(object):
37 """Runs the module tasks"""
38 if tasks is None or func is None:
39 return
40 + returncodes = []
41 for task in tasks:
42 inst = task()
43 show_progress = self.show_progress_bar and self.isatty
44 @@ -135,14 +136,17 @@ class TaskHandler(object):
45 # them for other tasks if there is more to do.
46 'options': options.copy()
47 }
48 - result = getattr(inst, func)(**kwargs)
49 + returncode, msgs = getattr(inst, func)(**kwargs)
50 + returncodes.append(returncode)
51 if show_progress:
52 # make sure the final progress is displayed
53 self.progress_bar.display()
54 print()
55 self.progress_bar.stop()
56 if self.callback:
57 - self.callback(result)
58 + self.callback(msgs)
59 +
60 + return returncodes
61
62
63 def print_results(results):
64 @@ -237,4 +241,6 @@ def emaint_main(myargv):
65 task_opts = options.__dict__
66 task_opts['return-messages'] = True
67 taskmaster = TaskHandler(callback=print_results, module_output=sys.stdout)
68 - taskmaster.run_tasks(tasks, func, status, options=task_opts)
69 + returncodes = taskmaster.run_tasks(tasks, func, status, options=task_opts)
70 +
71 + sys.exit(False in returncodes)
72 diff --git a/pym/portage/emaint/modules/binhost/binhost.py
73 b/pym/portage/emaint/modules/binhost/binhost.py
74 index cf1213e..527b02f 100644
75 --- a/pym/portage/emaint/modules/binhost/binhost.py
76 +++ b/pym/portage/emaint/modules/binhost/binhost.py
77 @@ -86,7 +86,9 @@ class BinhostHandler(object):
78 stale = set(metadata).difference(cpv_all)
79 for cpv in stale:
80 errors.append("'%s' is not in the repository" % cpv)
81 - return errors
82 + if errors:
83 + return (False, errors)
84 + return (True, None)
85
86 def fix(self, **kwargs):
87 onProgress = kwargs.get('onProgress', None)
88 @@ -177,4 +179,4 @@ class BinhostHandler(object):
89 if maxval == 0:
90 maxval = 1
91 onProgress(maxval, maxval)
92 - return None
93 + return (True, None)
94 diff --git a/pym/portage/emaint/modules/config/config.py
95 b/pym/portage/emaint/modules/config/config.py
96 index dad024b..a05a3c2 100644
97 --- a/pym/portage/emaint/modules/config/config.py
98 +++ b/pym/portage/emaint/modules/config/config.py
99 @@ -36,7 +36,8 @@ class CleanConfig(object):
100 if onProgress:
101 onProgress(maxval, i+1)
102 i += 1
103 - return self._format_output(messages)
104 + msgs = self._format_output(messages)
105 + return (True, msgs)
106
107 def fix(self, **kwargs):
108 onProgress = kwargs.get('onProgress', None)
109 @@ -65,7 +66,8 @@ class CleanConfig(object):
110 i += 1
111 if modified:
112 writedict(configs, self.target)
113 - return self._format_output(messages, True)
114 + msgs = self._format_output(messages, True)
115 + return (True, msgs)
116
117 def _format_output(self, messages=[], cleaned=False):
118 output = []
119 diff --git a/pym/portage/emaint/modules/logs/logs.py
120 b/pym/portage/emaint/modules/logs/logs.py
121 index fe65cf5..028084a 100644
122 --- a/pym/portage/emaint/modules/logs/logs.py
123 +++ b/pym/portage/emaint/modules/logs/logs.py
124 @@ -40,7 +40,6 @@ class CleanLogs(object):
125 'NUM': int: number of days
126 'pretend': boolean
127 """
128 - messages = []
129 num_of_days = None
130 pretend = False
131 if kwargs:
132 @@ -70,10 +69,12 @@ class CleanLogs(object):
133 clean_cmd.remove("-delete")
134
135 if not clean_cmd:
136 - return []
137 + return (True, None)
138 rval = self._clean_logs(clean_cmd, settings)
139 - messages += self._convert_errors(rval)
140 - return messages
141 + errors = self._convert_errors(rval)
142 + if errors:
143 + return (False, errors)
144 + return (True, None)
145
146
147 @staticmethod
148 diff --git a/pym/portage/emaint/modules/merges/merges.py
149 b/pym/portage/emaint/modules/merges/merges.py
150 index 8f677c2..416a725 100644
151 --- a/pym/portage/emaint/modules/merges/merges.py
152 +++ b/pym/portage/emaint/modules/merges/merges.py
153 @@ -239,7 +239,9 @@ class MergesHandler(object):
154 for pkg, mtime in failed_pkgs.items():
155 mtime_str = time.ctime(int(mtime))
156 errors.append("'%s' failed to merge on '%s'" % (pkg, mtime_str))
157 - return errors
158 + if errors:
159 + return (False, errors)
160 + return (True, None)
161
162
163 def fix(self, **kwargs):
164 @@ -247,13 +249,13 @@ class MergesHandler(object):
165 module_output = kwargs.get('module_output', None)
166 failed_pkgs = self._failed_pkgs()
167 if not failed_pkgs:
168 - return ['No failed merges found.']
169 + return (True, ['No failed merges found.'])
170
171 pkg_invalid_entries = set()
172 pkg_atoms = set()
173 self._get_pkg_atoms(failed_pkgs, pkg_atoms, pkg_invalid_entries)
174 if pkg_invalid_entries:
175 - return pkg_invalid_entries
176 + return (False, pkg_invalid_entries)
177
178 try:
179 self._tracking_file.save(failed_pkgs)
180 @@ -261,7 +263,7 @@ class MergesHandler(object):
181 errors = ['Unable to save failed merges to tracking file: %s\n'
182 % str(ex)]
183 errors.append(', '.join(sorted(failed_pkgs)))
184 - return errors
185 + return (False, errors)
186 self._remove_failed_dirs(failed_pkgs)
187 results = self._emerge_pkg_atoms(module_output, pkg_atoms)
188 # list any new failed merges
189 @@ -276,12 +278,14 @@ class MergesHandler(object):
190 if not vardb.cpv_exists(pkg_name):
191 still_failed_pkgs[pkg] = mtime
192 self._tracking_file.save(still_failed_pkgs)
193 - return results
194 + if still_failed_pkgs:
195 + return (False, results)
196 + return (True, results)
197
198
199 def purge(self, **kwargs):
200 """Attempt to remove previously saved tracking file."""
201 if not self._tracking_file.exists():
202 - return ['Tracking file not found.']
203 + return (True, ['Tracking file not found.'])
204 self._tracking_file.purge()
205 - return ['Removed tracking file.']
206 + return (True, ['Removed tracking file.'])
207 diff --git a/pym/portage/emaint/modules/move/move.py
208 b/pym/portage/emaint/modules/move/move.py
209 index 41ca167..1c2636d 100644
210 --- a/pym/portage/emaint/modules/move/move.py
211 +++ b/pym/portage/emaint/modules/move/move.py
212 @@ -123,7 +123,10 @@ class MoveHandler(object):
213 errors.append("'%s' has outdated metadata" % cpv)
214 if onProgress:
215 onProgress(maxval, i+1)
216 - return errors
217 +
218 + if errors:
219 + return (False, errors)
220 + return (True, None)
221
222 def fix(self, **kwargs):
223 onProgress = kwargs.get('onProgress', None)
224 @@ -156,7 +159,9 @@ class MoveHandler(object):
225 # Searching for updates in all the metadata is relatively slow, so this
226 # is where the progress bar comes out of indeterminate mode.
227 self._tree.dbapi.update_ents(allupdates, onProgress=onProgress)
228 - return errors
229 + if errors:
230 + return (False, errors)
231 + return (True, None)
232
233 class MoveInstalled(MoveHandler):
234
235 diff --git a/pym/portage/emaint/modules/resume/resume.py
236 b/pym/portage/emaint/modules/resume/resume.py
237 index 1bada52..1d14275 100644
238 --- a/pym/portage/emaint/modules/resume/resume.py
239 +++ b/pym/portage/emaint/modules/resume/resume.py
240 @@ -2,6 +2,7 @@
241 # Distributed under the terms of the GNU General Public License v2
242
243 import portage
244 +import os
245
246
247 class CleanResume(object):
248 @@ -37,7 +38,7 @@ class CleanResume(object):
249 finally:
250 if onProgress:
251 onProgress(maxval, i+1)
252 - return messages
253 + return (True, messages)
254
255 def fix(self, **kwargs):
256 onProgress = kwargs.get('onProgress', None)
257 @@ -56,3 +57,4 @@ class CleanResume(object):
258 onProgress(maxval, i+1)
259 if delete_count:
260 mtimedb.commit()
261 + return (True, None)
262 diff --git a/pym/portage/emaint/modules/sync/sync.py
263 b/pym/portage/emaint/modules/sync/sync.py
264 index 15d63e2..d867699 100644
265 --- a/pym/portage/emaint/modules/sync/sync.py
266 +++ b/pym/portage/emaint/modules/sync/sync.py
267 @@ -127,8 +127,8 @@ class SyncRepos(object):
268 % (bold(", ".join(repos))) + "\n ...returning"
269 ]
270 if return_messages:
271 - return msgs
272 - return
273 + return (False, msgs)
274 + return (False, None)
275 return self._sync(selected, return_messages,
276 emaint_opts=options)
277
278 @@ -211,8 +211,8 @@ class SyncRepos(object):
279 msgs.append("Emaint sync, nothing to sync... returning")
280 if return_messages:
281 msgs.extend(self.rmessage([('None', os.EX_OK)], 'sync'))
282 - return msgs
283 - return
284 + return (True, msgs)
285 + return (True, None)
286 # Portage needs to ensure a sane umask for the files it creates.
287 os.umask(0o22)
288
289 @@ -232,9 +232,14 @@ class SyncRepos(object):
290 sync_scheduler.wait()
291 retvals = sync_scheduler.retvals
292 msgs.extend(sync_scheduler.msgs)
293 + returncode = True
294
295 if retvals:
296 msgs.extend(self.rmessage(retvals, 'sync'))
297 + for repo, returncode in retvals:
298 + if returncode != os.EX_OK:
299 + returncode = False
300 + break
301 else:
302 msgs.extend(self.rmessage([('None', os.EX_OK)], 'sync'))
303
304 @@ -244,6 +249,8 @@ class SyncRepos(object):
305 rcode = sync_manager.perform_post_sync_hook('')
306 if rcode:
307 msgs.extend(self.rmessage([('None', rcode)], 'post-sync'))
308 + if rcode != os.EX_OK:
309 + returncode = False
310
311 # Reload the whole config.
312 portage._sync_mode = False
313 @@ -254,8 +261,8 @@ class SyncRepos(object):
314 self.emerge_config.opts)
315
316 if return_messages:
317 - return msgs
318 - return
319 + return (returncode, msgs)
320 + return (returncode, None)
321
322
323 def _do_pkg_moves(self):
324 @@ -355,7 +362,6 @@ class SyncScheduler(AsyncScheduler):
325 # that hooks will be called in a backward-compatible manner
326 # even if all sync tasks have failed.
327 hooks_enabled = True
328 - returncode = task.returncode
329 if task.returncode == os.EX_OK:
330 returncode, message, updatecache_flg, hooks_enabled = task.result
331 if message:
332 diff --git a/pym/portage/emaint/modules/world/world.py
333 b/pym/portage/emaint/modules/world/world.py
334 index 2c9dbff..ebc3adc 100644
335 --- a/pym/portage/emaint/modules/world/world.py
336 +++ b/pym/portage/emaint/modules/world/world.py
337 @@ -65,7 +65,9 @@ class WorldHandler(object):
338 errors += ["'%s' is not installed" % x for x in self.not_installed]
339 else:
340 errors.append(self.world_file + " could not be opened for reading")
341 - return errors
342 + if errors:
343 + return (False, errors)
344 + return (True, None)
345
346 def fix(self, **kwargs):
347 onProgress = kwargs.get('onProgress', None)
348 @@ -83,7 +85,9 @@ class WorldHandler(object):
349 except portage.exception.PortageException:
350 errors.append("%s could not be opened for writing" % \
351 self.world_file)
352 - return errors
353 + if errors:
354 + return (False, errors)
355 + return (True, None)
356 finally:
357 world_set.unlock()
358
359 diff --git a/pym/portage/tests/emerge/test_simple.py
360 b/pym/portage/tests/emerge/test_simple.py
361 index b1a2af5..5930f6c 100644
362 --- a/pym/portage/tests/emerge/test_simple.py
363 +++ b/pym/portage/tests/emerge/test_simple.py
364 @@ -382,6 +382,7 @@ pkg_preinst() {
365 "PORTAGE_PYTHON" : portage_python,
366 "PORTAGE_REPOSITORIES" : settings.repositories.config_string(),
367 "PORTAGE_TMPDIR" : portage_tmpdir,
368 + "PORT_LOGDIR" : portage_tmpdir,
369 "PYTHONDONTWRITEBYTECODE" : os.environ.get("PYTHONDONTWRITEBYTECODE", ""),
370 "PYTHONPATH" : pythonpath,
371 "__PORTAGE_TEST_PATH_OVERRIDE" : fake_bin,
372 --
373 2.10.2
374
375 On 1/17/17, Alexandru Elisei <alexandru.elisei@×××××.com> wrote:
376 > Currently module functions return a message to emaint after invocation.
377 > Emaint prints this message then exits normally (with a success return
378 > code) even if the module encountered an error. This patch aims to
379 > change this by having each module public function return a tuple of
380 > (returncode, message). Emaint will inspect the return code and will
381 > exit unsuccessfully if necessary.
382 > ---
383 > pym/portage/emaint/main.py | 11 +++++++++--
384 > pym/portage/emaint/modules/binhost/binhost.py | 6 ++++--
385 > pym/portage/emaint/modules/config/config.py | 8 ++++++--
386 > pym/portage/emaint/modules/logs/logs.py | 9 +++++----
387 > pym/portage/emaint/modules/merges/merges.py | 18 +++++++++++-------
388 > pym/portage/emaint/modules/move/move.py | 9 +++++++--
389 > pym/portage/emaint/modules/resume/resume.py | 4 +++-
390 > pym/portage/emaint/modules/sync/sync.py | 19 +++++++++++++------
391 > pym/portage/emaint/modules/world/world.py | 8 ++++++--
392 > 9 files changed, 64 insertions(+), 28 deletions(-)
393 >
394 > diff --git a/pym/portage/emaint/main.py b/pym/portage/emaint/main.py
395 > index 65e3545..ef4383a 100644
396 > --- a/pym/portage/emaint/main.py
397 > +++ b/pym/portage/emaint/main.py
398 > @@ -115,6 +115,7 @@ class TaskHandler(object):
399 > """Runs the module tasks"""
400 > if tasks is None or func is None:
401 > return
402 > + returncode = os.EX_OK
403 > for task in tasks:
404 > inst = task()
405 > show_progress = self.show_progress_bar and self.isatty
406 > @@ -135,14 +136,20 @@ class TaskHandler(object):
407 > # them for other tasks if there is more to do.
408 > 'options': options.copy()
409 > }
410 > - result = getattr(inst, func)(**kwargs)
411 > + rcode, msgs = getattr(inst, func)(**kwargs)
412 > if show_progress:
413 > # make sure the final progress is displayed
414 > self.progress_bar.display()
415 > print()
416 > self.progress_bar.stop()
417 > if self.callback:
418 > - self.callback(result)
419 > + self.callback(msgs)
420 > + # Keep the last error code when using the 'all' command.
421 > + if rcode != os.EX_OK:
422 > + returncode = rcode
423 > +
424 > + if returncode != os.EX_OK:
425 > + sys.exit(returncode)
426 >
427 >
428 > def print_results(results):
429 > diff --git a/pym/portage/emaint/modules/binhost/binhost.py
430 > b/pym/portage/emaint/modules/binhost/binhost.py
431 > index cf1213e..8cf3da6 100644
432 > --- a/pym/portage/emaint/modules/binhost/binhost.py
433 > +++ b/pym/portage/emaint/modules/binhost/binhost.py
434 > @@ -86,7 +86,9 @@ class BinhostHandler(object):
435 > stale = set(metadata).difference(cpv_all)
436 > for cpv in stale:
437 > errors.append("'%s' is not in the repository" % cpv)
438 > - return errors
439 > + if errors:
440 > + return (1, errors)
441 > + return (os.EX_OK, None)
442 >
443 > def fix(self, **kwargs):
444 > onProgress = kwargs.get('onProgress', None)
445 > @@ -177,4 +179,4 @@ class BinhostHandler(object):
446 > if maxval == 0:
447 > maxval = 1
448 > onProgress(maxval, maxval)
449 > - return None
450 > + return (os.EX_OK, None)
451 > diff --git a/pym/portage/emaint/modules/config/config.py
452 > b/pym/portage/emaint/modules/config/config.py
453 > index dad024b..a4a58d9 100644
454 > --- a/pym/portage/emaint/modules/config/config.py
455 > +++ b/pym/portage/emaint/modules/config/config.py
456 > @@ -36,7 +36,10 @@ class CleanConfig(object):
457 > if onProgress:
458 > onProgress(maxval, i+1)
459 > i += 1
460 > - return self._format_output(messages)
461 > + msgs = self._format_output(messages)
462 > + if msgs:
463 > + return (1, msgs)
464 > + return (os.EX_OK, None)
465 >
466 > def fix(self, **kwargs):
467 > onProgress = kwargs.get('onProgress', None)
468 > @@ -65,7 +68,8 @@ class CleanConfig(object):
469 > i += 1
470 > if modified:
471 > writedict(configs, self.target)
472 > - return self._format_output(messages, True)
473 > + msgs = self._format_output(messages, True)
474 > + return (os.EX_OK, msgs)
475 >
476 > def _format_output(self, messages=[], cleaned=False):
477 > output = []
478 > diff --git a/pym/portage/emaint/modules/logs/logs.py
479 > b/pym/portage/emaint/modules/logs/logs.py
480 > index fe65cf5..8c638d7 100644
481 > --- a/pym/portage/emaint/modules/logs/logs.py
482 > +++ b/pym/portage/emaint/modules/logs/logs.py
483 > @@ -40,7 +40,6 @@ class CleanLogs(object):
484 > 'NUM': int: number of days
485 > 'pretend': boolean
486 > """
487 > - messages = []
488 > num_of_days = None
489 > pretend = False
490 > if kwargs:
491 > @@ -70,10 +69,12 @@ class CleanLogs(object):
492 > clean_cmd.remove("-delete")
493 >
494 > if not clean_cmd:
495 > - return []
496 > + return (os.EX_OK, None)
497 > rval = self._clean_logs(clean_cmd, settings)
498 > - messages += self._convert_errors(rval)
499 > - return messages
500 > + errors = self._convert_errors(rval)
501 > + if errors:
502 > + return (rval, errors)
503 > + return (os.EX_OK, None)
504 >
505 >
506 > @staticmethod
507 > diff --git a/pym/portage/emaint/modules/merges/merges.py
508 > b/pym/portage/emaint/modules/merges/merges.py
509 > index 8f677c2..9a87d87 100644
510 > --- a/pym/portage/emaint/modules/merges/merges.py
511 > +++ b/pym/portage/emaint/modules/merges/merges.py
512 > @@ -239,7 +239,9 @@ class MergesHandler(object):
513 > for pkg, mtime in failed_pkgs.items():
514 > mtime_str = time.ctime(int(mtime))
515 > errors.append("'%s' failed to merge on '%s'" % (pkg, mtime_str))
516 > - return errors
517 > + if errors:
518 > + return (1, errors)
519 > + return (os.EX_OK, None)
520 >
521 >
522 > def fix(self, **kwargs):
523 > @@ -247,13 +249,13 @@ class MergesHandler(object):
524 > module_output = kwargs.get('module_output', None)
525 > failed_pkgs = self._failed_pkgs()
526 > if not failed_pkgs:
527 > - return ['No failed merges found.']
528 > + return (os.EX_OK, ['No failed merges found.'])
529 >
530 > pkg_invalid_entries = set()
531 > pkg_atoms = set()
532 > self._get_pkg_atoms(failed_pkgs, pkg_atoms, pkg_invalid_entries)
533 > if pkg_invalid_entries:
534 > - return pkg_invalid_entries
535 > + return (1, pkg_invalid_entries)
536 >
537 > try:
538 > self._tracking_file.save(failed_pkgs)
539 > @@ -261,7 +263,7 @@ class MergesHandler(object):
540 > errors = ['Unable to save failed merges to tracking file: %s\n'
541 > % str(ex)]
542 > errors.append(', '.join(sorted(failed_pkgs)))
543 > - return errors
544 > + return (1, errors)
545 > self._remove_failed_dirs(failed_pkgs)
546 > results = self._emerge_pkg_atoms(module_output, pkg_atoms)
547 > # list any new failed merges
548 > @@ -276,12 +278,14 @@ class MergesHandler(object):
549 > if not vardb.cpv_exists(pkg_name):
550 > still_failed_pkgs[pkg] = mtime
551 > self._tracking_file.save(still_failed_pkgs)
552 > - return results
553 > + if still_failed_pkgs:
554 > + return (1, results)
555 > + return (os.EX_OK, results)
556 >
557 >
558 > def purge(self, **kwargs):
559 > """Attempt to remove previously saved tracking file."""
560 > if not self._tracking_file.exists():
561 > - return ['Tracking file not found.']
562 > + return (os.EX_OK, ['Tracking file not found.'])
563 > self._tracking_file.purge()
564 > - return ['Removed tracking file.']
565 > + return (os.EX_OK, ['Removed tracking file.'])
566 > diff --git a/pym/portage/emaint/modules/move/move.py
567 > b/pym/portage/emaint/modules/move/move.py
568 > index 41ca167..1a566c3 100644
569 > --- a/pym/portage/emaint/modules/move/move.py
570 > +++ b/pym/portage/emaint/modules/move/move.py
571 > @@ -123,7 +123,10 @@ class MoveHandler(object):
572 > errors.append("'%s' has outdated metadata" % cpv)
573 > if onProgress:
574 > onProgress(maxval, i+1)
575 > - return errors
576 > +
577 > + if errors:
578 > + return (1, errors)
579 > + return (os.EX_OK, None)
580 >
581 > def fix(self, **kwargs):
582 > onProgress = kwargs.get('onProgress', None)
583 > @@ -156,7 +159,9 @@ class MoveHandler(object):
584 > # Searching for updates in all the metadata is relatively slow, so this
585 > # is where the progress bar comes out of indeterminate mode.
586 > self._tree.dbapi.update_ents(allupdates, onProgress=onProgress)
587 > - return errors
588 > + if errors:
589 > + return(1, errors)
590 > + return (os.EX_OK, None)
591 >
592 > class MoveInstalled(MoveHandler):
593 >
594 > diff --git a/pym/portage/emaint/modules/resume/resume.py
595 > b/pym/portage/emaint/modules/resume/resume.py
596 > index 1bada52..648e8c1 100644
597 > --- a/pym/portage/emaint/modules/resume/resume.py
598 > +++ b/pym/portage/emaint/modules/resume/resume.py
599 > @@ -2,6 +2,7 @@
600 > # Distributed under the terms of the GNU General Public License v2
601 >
602 > import portage
603 > +import os
604 >
605 >
606 > class CleanResume(object):
607 > @@ -37,7 +38,7 @@ class CleanResume(object):
608 > finally:
609 > if onProgress:
610 > onProgress(maxval, i+1)
611 > - return messages
612 > + return (os.EX_OK, messages)
613 >
614 > def fix(self, **kwargs):
615 > onProgress = kwargs.get('onProgress', None)
616 > @@ -56,3 +57,4 @@ class CleanResume(object):
617 > onProgress(maxval, i+1)
618 > if delete_count:
619 > mtimedb.commit()
620 > + return (os.EX_OK, None)
621 > diff --git a/pym/portage/emaint/modules/sync/sync.py
622 > b/pym/portage/emaint/modules/sync/sync.py
623 > index 15d63e2..03a684b 100644
624 > --- a/pym/portage/emaint/modules/sync/sync.py
625 > +++ b/pym/portage/emaint/modules/sync/sync.py
626 > @@ -127,8 +127,8 @@ class SyncRepos(object):
627 > % (bold(", ".join(repos))) + "\n ...returning"
628 > ]
629 > if return_messages:
630 > - return msgs
631 > - return
632 > + return (1, msgs)
633 > + return (1, None)
634 > return self._sync(selected, return_messages,
635 > emaint_opts=options)
636 >
637 > @@ -211,8 +211,8 @@ class SyncRepos(object):
638 > msgs.append("Emaint sync, nothing to sync... returning")
639 > if return_messages:
640 > msgs.extend(self.rmessage([('None', os.EX_OK)], 'sync'))
641 > - return msgs
642 > - return
643 > + return (os.EX_OK, msgs)
644 > + return (os.EX_OK, None)
645 > # Portage needs to ensure a sane umask for the files it creates.
646 > os.umask(0o22)
647 >
648 > @@ -232,6 +232,7 @@ class SyncRepos(object):
649 > sync_scheduler.wait()
650 > retvals = sync_scheduler.retvals
651 > msgs.extend(sync_scheduler.msgs)
652 > + returncode = sync_scheduler.returncode
653 >
654 > if retvals:
655 > msgs.extend(self.rmessage(retvals, 'sync'))
656 > @@ -244,6 +245,8 @@ class SyncRepos(object):
657 > rcode = sync_manager.perform_post_sync_hook('')
658 > if rcode:
659 > msgs.extend(self.rmessage([('None', rcode)], 'post-sync'))
660 > + if rcode != os.EX_OK:
661 > + returncode = rcode
662 >
663 > # Reload the whole config.
664 > portage._sync_mode = False
665 > @@ -254,8 +257,8 @@ class SyncRepos(object):
666 > self.emerge_config.opts)
667 >
668 > if return_messages:
669 > - return msgs
670 > - return
671 > + return (returncode, msgs)
672 > + return (returncode, None)
673 >
674 >
675 > def _do_pkg_moves(self):
676 > @@ -323,6 +326,7 @@ class SyncScheduler(AsyncScheduler):
677 > self._init_graph()
678 > self.retvals = []
679 > self.msgs = []
680 > + self.returncode = os.EX_OK
681 >
682 > def _init_graph(self):
683 > '''
684 > @@ -360,6 +364,9 @@ class SyncScheduler(AsyncScheduler):
685 > returncode, message, updatecache_flg, hooks_enabled = task.result
686 > if message:
687 > self.msgs.append(message)
688 > + else:
689 > + # Keep the last unsuccessful sync return code.
690 > + self.returncode = returncode
691 > repo = task.kwargs['repo'].name
692 > self._running_repos.remove(repo)
693 > self.retvals.append((repo, returncode))
694 > diff --git a/pym/portage/emaint/modules/world/world.py
695 > b/pym/portage/emaint/modules/world/world.py
696 > index 2c9dbff..d8a4e69 100644
697 > --- a/pym/portage/emaint/modules/world/world.py
698 > +++ b/pym/portage/emaint/modules/world/world.py
699 > @@ -65,7 +65,9 @@ class WorldHandler(object):
700 > errors += ["'%s' is not installed" % x for x in self.not_installed]
701 > else:
702 > errors.append(self.world_file + " could not be opened for reading")
703 > - return errors
704 > + if errors:
705 > + return (1, errors)
706 > + return (os.EX_OK, None)
707 >
708 > def fix(self, **kwargs):
709 > onProgress = kwargs.get('onProgress', None)
710 > @@ -83,7 +85,9 @@ class WorldHandler(object):
711 > except portage.exception.PortageException:
712 > errors.append("%s could not be opened for writing" % \
713 > self.world_file)
714 > - return errors
715 > + if errors:
716 > + return (1, errors)
717 > + return (os.EX_OK, None)
718 > finally:
719 > world_set.unlock()
720 >
721 > --
722 > 2.10.2
723 >