duplicity-team team mailing list archive
-
duplicity-team team
-
Mailing list archive
-
Message #02196
[Merge] lp:~mterry/duplicity/encode-exceptions into lp:duplicity
Michael Terry has proposed merging lp:~mterry/duplicity/encode-exceptions into lp:duplicity.
Requested reviews:
duplicity-team (duplicity-team)
Related bugs:
Bug #1311176 in duplicity (Ubuntu): "crash deja-dup backup ascii' codec can't decode byte 0xc3"
https://bugs.launchpad.net/ubuntu/+source/duplicity/+bug/1311176
Bug #1313966 in duplicity (Ubuntu): "duplicity cannot back up files from Russian file directory"
https://bugs.launchpad.net/ubuntu/+source/duplicity/+bug/1313966
For more details, see:
https://code.launchpad.net/~mterry/duplicity/encode-exceptions/+merge/217699
I've noticed several bugs being filed lately for UnicodeDecodeErrors during exception handling. Because exceptions often contain file paths, they have the same problem with Python 2.x's implicit decoding using the 'ascii' encoding that we've experienced before.
So I added a new util.uexc() method that uses the util.ufn() method to convert an exception to a unicode string and used it around the place.
--
https://code.launchpad.net/~mterry/duplicity/encode-exceptions/+merge/217699
Your team duplicity-team is requested to review the proposed merge of lp:~mterry/duplicity/encode-exceptions into lp:duplicity.
=== modified file 'bin/duplicity'
--- bin/duplicity 2014-04-22 15:01:38 +0000
+++ bin/duplicity 2014-04-29 23:53:13 +0000
@@ -1042,7 +1042,7 @@
try:
util.ignore_missing(os.unlink, del_name)
except Exception as e:
- log.Warn(_("Unable to delete %s: %s") % (util.ufn(del_name), str(e)))
+ log.Warn(_("Unable to delete %s: %s") % (util.ufn(del_name), util.uexc(e)))
def copy_to_local(fn):
"""
@@ -1531,7 +1531,7 @@
# default. But do with sufficient verbosity.
log.Info(_("User error detail: %s")
% (u''.join(traceback.format_exception(*sys.exc_info()))))
- log.FatalError(u"%s: %s" % (e.__class__.__name__, unicode(e)),
+ log.FatalError(u"%s: %s" % (e.__class__.__name__, util.uexc(e)),
log.ErrorCode.user_error,
e.__class__.__name__)
@@ -1541,14 +1541,14 @@
# default. But do with sufficient verbosity.
log.Info(_("Backend error detail: %s")
% (u''.join(traceback.format_exception(*sys.exc_info()))))
- log.FatalError(u"%s: %s" % (e.__class__.__name__, unicode(e)),
+ log.FatalError(u"%s: %s" % (e.__class__.__name__, util.uexc(e)),
log.ErrorCode.user_error,
e.__class__.__name__)
except Exception as e:
util.release_lockfile()
if "Forced assertion for testing" in str(e):
- log.FatalError(u"%s: %s" % (e.__class__.__name__, unicode(e)),
+ log.FatalError(u"%s: %s" % (e.__class__.__name__, util.uexc(e)),
log.ErrorCode.exception,
e.__class__.__name__)
else:
=== modified file 'duplicity/backend.py'
--- duplicity/backend.py 2014-04-28 02:49:39 +0000
+++ duplicity/backend.py 2014-04-29 23:53:13 +0000
@@ -383,10 +383,10 @@
extra = ' '.join([operation] + [make_filename(x) for x in args if x])
log.FatalError(_("Giving up after %s attempts. %s: %s")
% (n, e.__class__.__name__,
- str(e)), code=code, extra=extra)
+ util.uexc(e)), code=code, extra=extra)
else:
log.Warn(_("Attempt %s failed. %s: %s")
- % (n, e.__class__.__name__, str(e)))
+ % (n, e.__class__.__name__, util.uexc(e)))
if not at_end:
if isinstance(e, TemporaryLoadException):
time.sleep(90) # wait longer before trying again
@@ -495,7 +495,7 @@
def __do_put(self, source_path, remote_filename):
if hasattr(self.backend, '_put'):
- log.Info(_("Writing %s") % remote_filename)
+ log.Info(_("Writing %s") % util.ufn(remote_filename))
self.backend._put(source_path, remote_filename)
else:
raise NotImplementedError()
=== modified file 'duplicity/backends/_cf_cloudfiles.py'
--- duplicity/backends/_cf_cloudfiles.py 2014-04-28 02:49:39 +0000
+++ duplicity/backends/_cf_cloudfiles.py 2014-04-29 23:53:13 +0000
@@ -22,6 +22,7 @@
import duplicity.backend
from duplicity import log
+from duplicity import util
from duplicity.errors import BackendException
class CloudFilesBackend(duplicity.backend.Backend):
@@ -61,7 +62,7 @@
conn = Connection(**conn_kwargs)
except Exception as e:
log.FatalError("Connection failed, please check your credentials: %s %s"
- % (e.__class__.__name__, str(e)),
+ % (e.__class__.__name__, util.uexc(e)),
log.ErrorCode.connection_failed)
self.container = conn.create_container(container)
=== modified file 'duplicity/backends/_cf_pyrax.py'
--- duplicity/backends/_cf_pyrax.py 2014-04-28 02:49:39 +0000
+++ duplicity/backends/_cf_pyrax.py 2014-04-29 23:53:13 +0000
@@ -22,6 +22,7 @@
import duplicity.backend
from duplicity import log
+from duplicity import util
from duplicity.errors import BackendException
@@ -61,7 +62,7 @@
pyrax.set_credentials(**conn_kwargs)
except Exception as e:
log.FatalError("Connection failed, please check your credentials: %s %s"
- % (e.__class__.__name__, str(e)),
+ % (e.__class__.__name__, util.uexc(e)),
log.ErrorCode.connection_failed)
self.client_exc = pyrax.exceptions.ClientException
=== modified file 'duplicity/backends/dpbxbackend.py'
--- duplicity/backends/dpbxbackend.py 2014-04-28 02:49:39 +0000
+++ duplicity/backends/dpbxbackend.py 2014-04-29 23:53:13 +0000
@@ -35,6 +35,7 @@
import duplicity.backend
from duplicity import log
+from duplicity import util
from duplicity.errors import BackendException
@@ -81,7 +82,7 @@
log_exception(e)
raise BackendException('dpbx type error "%s"' % (e,))
except rest.ErrorResponse as e:
- msg = e.user_error_msg or str(e)
+ msg = e.user_error_msg or util.uexc(e)
log.Error('dpbx error: %s' % (msg,), log.ErrorCode.backend_command_error)
raise e
except Exception as e:
@@ -157,7 +158,7 @@
try: # to login to the box
self.sess.link()
except rest.ErrorResponse as e:
- log.FatalError('dpbx Error: %s\n' % str(e), log.ErrorCode.dpbx_nologin)
+ log.FatalError('dpbx Error: %s\n' % util.uexc(e), log.ErrorCode.dpbx_nologin)
if not self.sess.is_linked(): # stil not logged in
log.FatalError("dpbx Cannot login: check your credentials",log.ErrorCode.dpbx_nologin)
=== modified file 'duplicity/backends/giobackend.py'
--- duplicity/backends/giobackend.py 2014-04-28 02:49:39 +0000
+++ duplicity/backends/giobackend.py 2014-04-29 23:53:13 +0000
@@ -25,6 +25,7 @@
import duplicity.backend
from duplicity import log
+from duplicity import util
def ensure_dbus():
# GIO requires a dbus session bus which can start the gvfs daemons
@@ -103,7 +104,7 @@
# check for NOT_SUPPORTED because some schemas (e.g. file://) validly don't
if e.code != Gio.IOErrorEnum.ALREADY_MOUNTED and e.code != Gio.IOErrorEnum.NOT_SUPPORTED:
log.FatalError(_("Connection failed, please check your password: %s")
- % str(e), log.ErrorCode.connection_failed)
+ % util.uexc(e), log.ErrorCode.connection_failed)
loop.quit()
def __copy_progress(self, *args, **kwargs):
=== modified file 'duplicity/backends/swiftbackend.py'
--- duplicity/backends/swiftbackend.py 2014-04-28 02:49:39 +0000
+++ duplicity/backends/swiftbackend.py 2014-04-29 23:53:13 +0000
@@ -22,6 +22,7 @@
import duplicity.backend
from duplicity import log
+from duplicity import util
from duplicity.errors import BackendException
@@ -76,7 +77,7 @@
self.conn.put_container(self.container)
except Exception as e:
log.FatalError("Connection failed: %s %s"
- % (e.__class__.__name__, str(e)),
+ % (e.__class__.__name__, util.uexc(e)),
log.ErrorCode.connection_failed)
def _error_code(self, operation, e):
=== modified file 'duplicity/backends/webdavbackend.py'
--- duplicity/backends/webdavbackend.py 2014-04-28 02:49:39 +0000
+++ duplicity/backends/webdavbackend.py 2014-04-29 23:53:13 +0000
@@ -32,6 +32,7 @@
import duplicity.backend
from duplicity import globals
from duplicity import log
+from duplicity import util
from duplicity.errors import BackendException, FatalBackendException
class CustomMethodRequest(urllib2.Request):
@@ -97,7 +98,7 @@
return httplib.HTTPSConnection.request(self, *args, **kwargs)
except ssl.SSLError as e:
# encapsulate ssl errors
- raise BackendException("SSL failed: %s" % str(e),log.ErrorCode.backend_error)
+ raise BackendException("SSL failed: %s" % util.uexc(e),log.ErrorCode.backend_error)
class WebDAVBackend(duplicity.backend.Backend):
=== modified file 'duplicity/patchdir.py'
--- duplicity/patchdir.py 2014-04-25 23:53:46 +0000
+++ duplicity/patchdir.py 2014-04-29 23:53:13 +0000
@@ -508,7 +508,7 @@
except Exception as e:
filename = normalized[-1].get_ropath().get_relative_path()
log.Warn(_("Error '%s' patching %s") %
- (str(e), filename),
+ (util.uexc(e), util.ufn(filename)),
log.WarningCode.cannot_process,
util.escape(filename))
=== modified file 'duplicity/util.py'
--- duplicity/util.py 2014-04-25 23:53:46 +0000
+++ duplicity/util.py 2014-04-29 23:53:13 +0000
@@ -48,11 +48,10 @@
lines = traceback.format_tb(tb, limit)
lines.extend(traceback.format_exception_only(type, value))
- str = "Traceback (innermost last):\n"
- str = str + "%-20s %s" % (string.join(lines[:-1], ""),
- lines[-1])
+ msg = "Traceback (innermost last):\n"
+ msg = msg + "%-20s %s" % (string.join(lines[:-1], ""), lines[-1])
- return str
+ return uexc(msg)
def escape(string):
"Convert a (bytes) filename to a format suitable for logging (quoted utf8)"
@@ -71,6 +70,13 @@
else:
return u'.'
+def uexc(e):
+ # Exceptions in duplicity often have path names in them, which if they are
+ # non-ascii will cause a UnicodeDecodeError when implicitly decoding to
+ # unicode. So we decode manually, using the filesystem encoding.
+ # 99.99% of the time, this will be a fine encoding to use.
+ return ufn(str(e))
+
def maybe_ignore_errors(fn):
"""
Execute fn. If the global configuration setting ignore_errors is
@@ -85,7 +91,7 @@
except Exception as e:
if globals.ignore_errors:
log.Warn(_("IGNORED_ERROR: Warning: ignoring error as requested: %s: %s")
- % (e.__class__.__name__, str(e)))
+ % (e.__class__.__name__, uexc(e)))
return None
else:
raise
Follow ups