Merge lp:~mterry/duplicity/encode-exceptions into lp:duplicity/0.6

Proposed by Michael Terry
Status: Merged
Merged at revision: 982
Proposed branch: lp:~mterry/duplicity/encode-exceptions
Merge into: lp:duplicity/0.6
Diff against target: 254 lines (+32/-20)
10 files modified
bin/duplicity (+4/-4)
duplicity/backend.py (+3/-3)
duplicity/backends/_cf_cloudfiles.py (+2/-1)
duplicity/backends/_cf_pyrax.py (+2/-1)
duplicity/backends/dpbxbackend.py (+3/-2)
duplicity/backends/giobackend.py (+2/-1)
duplicity/backends/swiftbackend.py (+2/-1)
duplicity/backends/webdavbackend.py (+2/-1)
duplicity/patchdir.py (+1/-1)
duplicity/util.py (+11/-5)
To merge this branch: bzr merge lp:~mterry/duplicity/encode-exceptions
Reviewer Review Type Date Requested Status
duplicity-team Pending
Review via email: mp+217699@code.launchpad.net

Description of the change

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.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/duplicity'
2--- bin/duplicity 2014-04-22 15:01:38 +0000
3+++ bin/duplicity 2014-04-29 23:53:13 +0000
4@@ -1042,7 +1042,7 @@
5 try:
6 util.ignore_missing(os.unlink, del_name)
7 except Exception as e:
8- log.Warn(_("Unable to delete %s: %s") % (util.ufn(del_name), str(e)))
9+ log.Warn(_("Unable to delete %s: %s") % (util.ufn(del_name), util.uexc(e)))
10
11 def copy_to_local(fn):
12 """
13@@ -1531,7 +1531,7 @@
14 # default. But do with sufficient verbosity.
15 log.Info(_("User error detail: %s")
16 % (u''.join(traceback.format_exception(*sys.exc_info()))))
17- log.FatalError(u"%s: %s" % (e.__class__.__name__, unicode(e)),
18+ log.FatalError(u"%s: %s" % (e.__class__.__name__, util.uexc(e)),
19 log.ErrorCode.user_error,
20 e.__class__.__name__)
21
22@@ -1541,14 +1541,14 @@
23 # default. But do with sufficient verbosity.
24 log.Info(_("Backend error detail: %s")
25 % (u''.join(traceback.format_exception(*sys.exc_info()))))
26- log.FatalError(u"%s: %s" % (e.__class__.__name__, unicode(e)),
27+ log.FatalError(u"%s: %s" % (e.__class__.__name__, util.uexc(e)),
28 log.ErrorCode.user_error,
29 e.__class__.__name__)
30
31 except Exception as e:
32 util.release_lockfile()
33 if "Forced assertion for testing" in str(e):
34- log.FatalError(u"%s: %s" % (e.__class__.__name__, unicode(e)),
35+ log.FatalError(u"%s: %s" % (e.__class__.__name__, util.uexc(e)),
36 log.ErrorCode.exception,
37 e.__class__.__name__)
38 else:
39
40=== modified file 'duplicity/backend.py'
41--- duplicity/backend.py 2014-04-28 02:49:39 +0000
42+++ duplicity/backend.py 2014-04-29 23:53:13 +0000
43@@ -383,10 +383,10 @@
44 extra = ' '.join([operation] + [make_filename(x) for x in args if x])
45 log.FatalError(_("Giving up after %s attempts. %s: %s")
46 % (n, e.__class__.__name__,
47- str(e)), code=code, extra=extra)
48+ util.uexc(e)), code=code, extra=extra)
49 else:
50 log.Warn(_("Attempt %s failed. %s: %s")
51- % (n, e.__class__.__name__, str(e)))
52+ % (n, e.__class__.__name__, util.uexc(e)))
53 if not at_end:
54 if isinstance(e, TemporaryLoadException):
55 time.sleep(90) # wait longer before trying again
56@@ -495,7 +495,7 @@
57
58 def __do_put(self, source_path, remote_filename):
59 if hasattr(self.backend, '_put'):
60- log.Info(_("Writing %s") % remote_filename)
61+ log.Info(_("Writing %s") % util.ufn(remote_filename))
62 self.backend._put(source_path, remote_filename)
63 else:
64 raise NotImplementedError()
65
66=== modified file 'duplicity/backends/_cf_cloudfiles.py'
67--- duplicity/backends/_cf_cloudfiles.py 2014-04-28 02:49:39 +0000
68+++ duplicity/backends/_cf_cloudfiles.py 2014-04-29 23:53:13 +0000
69@@ -22,6 +22,7 @@
70
71 import duplicity.backend
72 from duplicity import log
73+from duplicity import util
74 from duplicity.errors import BackendException
75
76 class CloudFilesBackend(duplicity.backend.Backend):
77@@ -61,7 +62,7 @@
78 conn = Connection(**conn_kwargs)
79 except Exception as e:
80 log.FatalError("Connection failed, please check your credentials: %s %s"
81- % (e.__class__.__name__, str(e)),
82+ % (e.__class__.__name__, util.uexc(e)),
83 log.ErrorCode.connection_failed)
84 self.container = conn.create_container(container)
85
86
87=== modified file 'duplicity/backends/_cf_pyrax.py'
88--- duplicity/backends/_cf_pyrax.py 2014-04-28 02:49:39 +0000
89+++ duplicity/backends/_cf_pyrax.py 2014-04-29 23:53:13 +0000
90@@ -22,6 +22,7 @@
91
92 import duplicity.backend
93 from duplicity import log
94+from duplicity import util
95 from duplicity.errors import BackendException
96
97
98@@ -61,7 +62,7 @@
99 pyrax.set_credentials(**conn_kwargs)
100 except Exception as e:
101 log.FatalError("Connection failed, please check your credentials: %s %s"
102- % (e.__class__.__name__, str(e)),
103+ % (e.__class__.__name__, util.uexc(e)),
104 log.ErrorCode.connection_failed)
105
106 self.client_exc = pyrax.exceptions.ClientException
107
108=== modified file 'duplicity/backends/dpbxbackend.py'
109--- duplicity/backends/dpbxbackend.py 2014-04-28 02:49:39 +0000
110+++ duplicity/backends/dpbxbackend.py 2014-04-29 23:53:13 +0000
111@@ -35,6 +35,7 @@
112
113 import duplicity.backend
114 from duplicity import log
115+from duplicity import util
116 from duplicity.errors import BackendException
117
118
119@@ -81,7 +82,7 @@
120 log_exception(e)
121 raise BackendException('dpbx type error "%s"' % (e,))
122 except rest.ErrorResponse as e:
123- msg = e.user_error_msg or str(e)
124+ msg = e.user_error_msg or util.uexc(e)
125 log.Error('dpbx error: %s' % (msg,), log.ErrorCode.backend_command_error)
126 raise e
127 except Exception as e:
128@@ -157,7 +158,7 @@
129 try: # to login to the box
130 self.sess.link()
131 except rest.ErrorResponse as e:
132- log.FatalError('dpbx Error: %s\n' % str(e), log.ErrorCode.dpbx_nologin)
133+ log.FatalError('dpbx Error: %s\n' % util.uexc(e), log.ErrorCode.dpbx_nologin)
134 if not self.sess.is_linked(): # stil not logged in
135 log.FatalError("dpbx Cannot login: check your credentials",log.ErrorCode.dpbx_nologin)
136
137
138=== modified file 'duplicity/backends/giobackend.py'
139--- duplicity/backends/giobackend.py 2014-04-28 02:49:39 +0000
140+++ duplicity/backends/giobackend.py 2014-04-29 23:53:13 +0000
141@@ -25,6 +25,7 @@
142
143 import duplicity.backend
144 from duplicity import log
145+from duplicity import util
146
147 def ensure_dbus():
148 # GIO requires a dbus session bus which can start the gvfs daemons
149@@ -103,7 +104,7 @@
150 # check for NOT_SUPPORTED because some schemas (e.g. file://) validly don't
151 if e.code != Gio.IOErrorEnum.ALREADY_MOUNTED and e.code != Gio.IOErrorEnum.NOT_SUPPORTED:
152 log.FatalError(_("Connection failed, please check your password: %s")
153- % str(e), log.ErrorCode.connection_failed)
154+ % util.uexc(e), log.ErrorCode.connection_failed)
155 loop.quit()
156
157 def __copy_progress(self, *args, **kwargs):
158
159=== modified file 'duplicity/backends/swiftbackend.py'
160--- duplicity/backends/swiftbackend.py 2014-04-28 02:49:39 +0000
161+++ duplicity/backends/swiftbackend.py 2014-04-29 23:53:13 +0000
162@@ -22,6 +22,7 @@
163
164 import duplicity.backend
165 from duplicity import log
166+from duplicity import util
167 from duplicity.errors import BackendException
168
169
170@@ -76,7 +77,7 @@
171 self.conn.put_container(self.container)
172 except Exception as e:
173 log.FatalError("Connection failed: %s %s"
174- % (e.__class__.__name__, str(e)),
175+ % (e.__class__.__name__, util.uexc(e)),
176 log.ErrorCode.connection_failed)
177
178 def _error_code(self, operation, e):
179
180=== modified file 'duplicity/backends/webdavbackend.py'
181--- duplicity/backends/webdavbackend.py 2014-04-28 02:49:39 +0000
182+++ duplicity/backends/webdavbackend.py 2014-04-29 23:53:13 +0000
183@@ -32,6 +32,7 @@
184 import duplicity.backend
185 from duplicity import globals
186 from duplicity import log
187+from duplicity import util
188 from duplicity.errors import BackendException, FatalBackendException
189
190 class CustomMethodRequest(urllib2.Request):
191@@ -97,7 +98,7 @@
192 return httplib.HTTPSConnection.request(self, *args, **kwargs)
193 except ssl.SSLError as e:
194 # encapsulate ssl errors
195- raise BackendException("SSL failed: %s" % str(e),log.ErrorCode.backend_error)
196+ raise BackendException("SSL failed: %s" % util.uexc(e),log.ErrorCode.backend_error)
197
198
199 class WebDAVBackend(duplicity.backend.Backend):
200
201=== modified file 'duplicity/patchdir.py'
202--- duplicity/patchdir.py 2014-04-25 23:53:46 +0000
203+++ duplicity/patchdir.py 2014-04-29 23:53:13 +0000
204@@ -508,7 +508,7 @@
205 except Exception as e:
206 filename = normalized[-1].get_ropath().get_relative_path()
207 log.Warn(_("Error '%s' patching %s") %
208- (str(e), filename),
209+ (util.uexc(e), util.ufn(filename)),
210 log.WarningCode.cannot_process,
211 util.escape(filename))
212
213
214=== modified file 'duplicity/util.py'
215--- duplicity/util.py 2014-04-25 23:53:46 +0000
216+++ duplicity/util.py 2014-04-29 23:53:13 +0000
217@@ -48,11 +48,10 @@
218 lines = traceback.format_tb(tb, limit)
219 lines.extend(traceback.format_exception_only(type, value))
220
221- str = "Traceback (innermost last):\n"
222- str = str + "%-20s %s" % (string.join(lines[:-1], ""),
223- lines[-1])
224+ msg = "Traceback (innermost last):\n"
225+ msg = msg + "%-20s %s" % (string.join(lines[:-1], ""), lines[-1])
226
227- return str
228+ return uexc(msg)
229
230 def escape(string):
231 "Convert a (bytes) filename to a format suitable for logging (quoted utf8)"
232@@ -71,6 +70,13 @@
233 else:
234 return u'.'
235
236+def uexc(e):
237+ # Exceptions in duplicity often have path names in them, which if they are
238+ # non-ascii will cause a UnicodeDecodeError when implicitly decoding to
239+ # unicode. So we decode manually, using the filesystem encoding.
240+ # 99.99% of the time, this will be a fine encoding to use.
241+ return ufn(str(e))
242+
243 def maybe_ignore_errors(fn):
244 """
245 Execute fn. If the global configuration setting ignore_errors is
246@@ -85,7 +91,7 @@
247 except Exception as e:
248 if globals.ignore_errors:
249 log.Warn(_("IGNORED_ERROR: Warning: ignoring error as requested: %s: %s")
250- % (e.__class__.__name__, str(e)))
251+ % (e.__class__.__name__, uexc(e)))
252 return None
253 else:
254 raise

Subscribers

People subscribed via source and target branches

to all changes: