Ticket #393: 393status23.dpatch

File 393status23.dpatch, 650.4 KB (added by kevan, at 2010-07-31T00:10:47Z)
Line 
1Thu Jun 24 16:46:37 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
2  * Misc. changes to support the work I'm doing
3 
4      - Add a notion of file version number to interfaces.py
5      - Alter mutable file node interfaces to have a notion of version,
6        though this may be changed later.
7      - Alter mutable/filenode.py to conform to these changes.
8      - Add a salt hasher to util/hashutil.py
9
10Thu Jun 24 16:48:33 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
11  * nodemaker.py: create MDMF files when asked to
12
13Thu Jun 24 16:49:05 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
14  * storage/server.py: minor code cleanup
15
16Thu Jun 24 16:49:24 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
17  * test/test_mutable.py: alter some tests that were failing due to MDMF; minor code cleanup.
18
19Fri Jun 25 17:35:20 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
20  * test/test_mutable.py: change the definition of corrupt() to work with MDMF as well as SDMF files, change users of corrupt to use the new definition
21
22Sat Jun 26 16:41:18 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
23  * Alter the ServermapUpdater to find MDMF files
24 
25  The servermapupdater should find MDMF files on a grid in the same way
26  that it finds SDMF files. This patch makes it do that.
27
28Sat Jun 26 16:42:04 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
29  * Make a segmented mutable uploader
30 
31  The mutable file uploader should be able to publish files with one
32  segment and files with multiple segments. This patch makes it do that.
33  This is still incomplete, and rather ugly -- I need to flesh out error
34  handling, I need to write tests, and I need to remove some of the uglier
35  kludges in the process before I can call this done.
36
37Sat Jun 26 16:43:14 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
38  * Write a segmented mutable downloader
39 
40  The segmented mutable downloader can deal with MDMF files (files with
41  one or more segments in MDMF format) and SDMF files (files with one
42  segment in SDMF format). It is backwards compatible with the old
43  file format.
44 
45  This patch also contains tests for the segmented mutable downloader.
46
47Mon Jun 28 15:50:48 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
48  * mutable/checker.py: check MDMF files
49 
50  This patch adapts the mutable file checker and verifier to check and
51  verify MDMF files. It does this by using the new segmented downloader,
52  which is trained to perform verification operations on request. This
53  removes some code duplication.
54
55Mon Jun 28 15:52:01 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
56  * mutable/retrieve.py: learn how to verify mutable files
57
58Wed Jun 30 11:33:05 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
59  * interfaces.py: add IMutableSlotWriter
60
61Thu Jul  1 16:28:06 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
62  * test/test_mutable.py: temporarily disable two tests that are now irrelevant
63
64Fri Jul  2 15:55:31 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
65  * Add MDMF reader and writer, and SDMF writer
66 
67  The MDMF/SDMF reader MDMF writer, and SDMF writer are similar to the
68  object proxies that exist for immutable files. They abstract away
69  details of connection, state, and caching from their callers (in this
70  case, the download, servermap updater, and uploader), and expose methods
71  to get and set information on the remote server.
72 
73  MDMFSlotReadProxy reads a mutable file from the server, doing the right
74  thing (in most cases) regardless of whether the file is MDMF or SDMF. It
75  allows callers to tell it how to batch and flush reads.
76 
77  MDMFSlotWriteProxy writes an MDMF mutable file to a server.
78 
79  SDMFSlotWriteProxy writes an SDMF mutable file to a server.
80 
81  This patch also includes tests for MDMFSlotReadProxy,
82  SDMFSlotWriteProxy, and MDMFSlotWriteProxy.
83
84Fri Jul  2 15:55:54 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
85  * mutable/publish.py: cleanup + simplification
86
87Fri Jul  2 15:57:10 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
88  * test/test_mutable.py: remove tests that are no longer relevant
89
90Tue Jul  6 14:52:17 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
91  * interfaces.py: create IMutableUploadable
92
93Tue Jul  6 14:52:57 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
94  * mutable/publish.py: add MutableDataHandle and MutableFileHandle
95
96Tue Jul  6 14:55:41 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
97  * mutable/publish.py: reorganize in preparation of file-like uploadables
98
99Tue Jul  6 14:56:49 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
100  * test/test_mutable.py: write tests for MutableFileHandle and MutableDataHandle
101
102Wed Jul  7 17:00:31 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
103  * Alter tests to work with the new APIs
104
105Wed Jul  7 17:07:32 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
106  * Alter mutable files to use file-like objects for publishing instead of strings.
107
108Thu Jul  8 12:35:22 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
109  * test/test_sftp.py: alter a setup routine to work with new mutable file APIs.
110
111Thu Jul  8 12:36:00 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
112  * mutable/publish.py: make MutableFileHandle seek to the beginning of its file handle before reading.
113
114Fri Jul  9 16:29:12 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
115  * Refactor download interfaces to be more uniform, per #993
116
117Fri Jul 16 18:44:46 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
118  * frontends/sftpd.py: alter a mutable file overwrite to work with the new API
119
120Fri Jul 16 18:45:16 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
121  * mutable/filenode.py: implement most of IVersion, per #993
122
123Fri Jul 16 18:45:49 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
124  * mutable/publish.py: enable segmented uploading for big files, change constants and wording, change MutableDataHandle to MutableData
125
126Fri Jul 16 18:50:49 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
127  * immutable/filenode.py: fix broken implementation of #993 interfaces
128
129Fri Jul 16 18:51:23 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
130  * mutable/retrieve.py: alter Retrieve so that it can download parts of mutable files
131
132Fri Jul 16 18:52:10 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
133  * change MutableDataHandle to MutableData in code.
134
135Fri Jul 16 18:52:30 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
136  * tests: fix tests that were broken by #993
137
138Fri Jul 16 18:54:02 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
139  * test/test_immutable.py: add tests for #993-related modifications
140
141Fri Jul 16 18:54:26 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
142  * web/filenode.py: alter download code to use the new #993 interface.
143
144Fri Jul 16 18:55:01 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
145  * test/common.py: remove FileTooLargeErrors that tested for an SDMF limitation that no longer exists
146
147Tue Jul 20 14:31:09 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
148  * nodemaker.py: resolve a conflict introduced in one of the 1.7.1 patches
149
150Tue Jul 27 15:46:51 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
151  * frontends/sftpd.py: fix conflicts with trunk
152
153Tue Jul 27 15:47:03 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
154  * interfaces.py: Create an IWritable interface
155
156Tue Jul 27 15:47:25 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
157  * mutable/layout.py: Alter MDMFSlotWriteProxy to perform all write operations in one actual write operation
158
159Tue Jul 27 15:48:17 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
160  * test/test_mutable.py: test that write operations occur all at once
161
162Tue Jul 27 15:48:53 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
163  * test/test_storage.py: modify proxy tests to work with the new writing semantics
164
165Tue Jul 27 15:50:01 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
166  * mutable/publish.py: alter mutable publisher to work with new writing semantics
167
168Wed Jul 28 16:24:34 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
169  * test/test_mutable.py: Add tests for new servermap behavior
170
171Fri Jul 30 16:40:29 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
172  * mutable/filenode.py: add an update method.
173
174Fri Jul 30 16:40:56 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
175  * mutable/publish.py: learn how to update as well as publish files.
176
177Fri Jul 30 16:41:41 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
178  * mutable/retrieve.py: expose decoding and decrypting methods to callers
179
180Fri Jul 30 16:42:29 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
181  * mutable/servermap.py: lay some groundwork for IWritable
182
183New patches:
184
185[Misc. changes to support the work I'm doing
186Kevan Carstensen <kevan@isnotajoke.com>**20100624234637
187 Ignore-this: fdd18fa8cc05f4b4b15ff53ee24a1819
188 
189     - Add a notion of file version number to interfaces.py
190     - Alter mutable file node interfaces to have a notion of version,
191       though this may be changed later.
192     - Alter mutable/filenode.py to conform to these changes.
193     - Add a salt hasher to util/hashutil.py
194] {
195hunk ./src/allmydata/interfaces.py 7
196      ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
197 
198 HASH_SIZE=32
199+SALT_SIZE=16
200+
201+SDMF_VERSION=0
202+MDMF_VERSION=1
203 
204 Hash = StringConstraint(maxLength=HASH_SIZE,
205                         minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
206hunk ./src/allmydata/interfaces.py 811
207         writer-visible data using this writekey.
208         """
209 
210+    def set_version(version):
211+        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
212+        we upload in SDMF for reasons of compatibility. If you want to
213+        change this, set_version will let you do that.
214+
215+        To say that this file should be uploaded in SDMF, pass in a 0. To
216+        say that the file should be uploaded as MDMF, pass in a 1.
217+        """
218+
219+    def get_version():
220+        """Returns the mutable file protocol version."""
221+
222 class NotEnoughSharesError(Exception):
223     """Download was unable to get enough shares"""
224 
225hunk ./src/allmydata/mutable/filenode.py 8
226 from twisted.internet import defer, reactor
227 from foolscap.api import eventually
228 from allmydata.interfaces import IMutableFileNode, \
229-     ICheckable, ICheckResults, NotEnoughSharesError
230+     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
231 from allmydata.util import hashutil, log
232 from allmydata.util.assertutil import precondition
233 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
234hunk ./src/allmydata/mutable/filenode.py 67
235         self._sharemap = {} # known shares, shnum-to-[nodeids]
236         self._cache = ResponseCache()
237         self._most_recent_size = None
238+        # filled in after __init__ if we're being created for the first time;
239+        # filled in by the servermap updater before publishing, otherwise.
240+        # set to this default value in case neither of those things happen,
241+        # or in case the servermap can't find any shares to tell us what
242+        # to publish as.
243+        # TODO: Set this back to None, and find out why the tests fail
244+        #       with it set to None.
245+        self._protocol_version = SDMF_VERSION
246 
247         # all users of this MutableFileNode go through the serializer. This
248         # takes advantage of the fact that Deferreds discard the callbacks
249hunk ./src/allmydata/mutable/filenode.py 472
250     def _did_upload(self, res, size):
251         self._most_recent_size = size
252         return res
253+
254+
255+    def set_version(self, version):
256+        # I can be set in two ways:
257+        #  1. When the node is created.
258+        #  2. (for an existing share) when the Servermap is updated
259+        #     before I am read.
260+        assert version in (MDMF_VERSION, SDMF_VERSION)
261+        self._protocol_version = version
262+
263+
264+    def get_version(self):
265+        return self._protocol_version
266hunk ./src/allmydata/util/hashutil.py 90
267 MUTABLE_READKEY_TAG = "allmydata_mutable_writekey_to_readkey_v1"
268 MUTABLE_DATAKEY_TAG = "allmydata_mutable_readkey_to_datakey_v1"
269 MUTABLE_STORAGEINDEX_TAG = "allmydata_mutable_readkey_to_storage_index_v1"
270+MUTABLE_SALT_TAG = "allmydata_mutable_segment_salt_v1"
271 
272 # dirnodes
273 DIRNODE_CHILD_WRITECAP_TAG = "allmydata_mutable_writekey_and_salt_to_dirnode_child_capkey_v1"
274hunk ./src/allmydata/util/hashutil.py 134
275 def plaintext_segment_hasher():
276     return tagged_hasher(PLAINTEXT_SEGMENT_TAG)
277 
278+def mutable_salt_hash(data):
279+    return tagged_hash(MUTABLE_SALT_TAG, data)
280+def mutable_salt_hasher():
281+    return tagged_hasher(MUTABLE_SALT_TAG)
282+
283 KEYLEN = 16
284 IVLEN = 16
285 
286}
287[nodemaker.py: create MDMF files when asked to
288Kevan Carstensen <kevan@isnotajoke.com>**20100624234833
289 Ignore-this: 26c16aaca9ddab7a7ce37a4530bc970
290] {
291hunk ./src/allmydata/nodemaker.py 3
292 import weakref
293 from zope.interface import implements
294-from allmydata.interfaces import INodeMaker
295+from allmydata.util.assertutil import precondition
296+from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError, \
297+                                 SDMF_VERSION, MDMF_VERSION
298 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
299 from allmydata.immutable.upload import Data
300 from allmydata.mutable.filenode import MutableFileNode
301hunk ./src/allmydata/nodemaker.py 88
302             return self._create_dirnode(filenode)
303         return None
304 
305-    def create_mutable_file(self, contents=None, keysize=None):
306+    def create_mutable_file(self, contents=None, keysize=None,
307+                            version=SDMF_VERSION):
308         n = MutableFileNode(self.storage_broker, self.secret_holder,
309                             self.default_encoding_parameters, self.history)
310hunk ./src/allmydata/nodemaker.py 92
311+        n.set_version(version)
312         d = self.key_generator.generate(keysize)
313         d.addCallback(n.create_with_keys, contents)
314         d.addCallback(lambda res: n)
315hunk ./src/allmydata/nodemaker.py 98
316         return d
317 
318-    def create_new_mutable_directory(self, initial_children={}):
319+    def create_new_mutable_directory(self, initial_children={},
320+                                     version=SDMF_VERSION):
321+        # initial_children must have metadata (i.e. {} instead of None)
322+        for (name, (node, metadata)) in initial_children.iteritems():
323+            precondition(isinstance(metadata, dict),
324+                         "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
325+            node.raise_error()
326         d = self.create_mutable_file(lambda n:
327                                      pack_children(initial_children, n.get_writekey()))
328         d.addCallback(self._create_dirnode)
329merger 0.0 (
330hunk ./src/allmydata/nodemaker.py 106
331-                                     pack_children(n, initial_children))
332+                                     pack_children(initial_children, n.get_writekey()))
333hunk ./src/allmydata/nodemaker.py 106
334-                                     pack_children(n, initial_children))
335+                                     pack_children(n, initial_children),
336+                                     version)
337)
338}
339[storage/server.py: minor code cleanup
340Kevan Carstensen <kevan@isnotajoke.com>**20100624234905
341 Ignore-this: 2358c531c39e48d3c8e56b62b5768228
342] {
343hunk ./src/allmydata/storage/server.py 569
344                                          self)
345         return share
346 
347-    def remote_slot_readv(self, storage_index, shares, readv):
348+    def remote_slot_readv(self, storage_index, shares, readvs):
349         start = time.time()
350         self.count("readv")
351         si_s = si_b2a(storage_index)
352hunk ./src/allmydata/storage/server.py 590
353             if sharenum in shares or not shares:
354                 filename = os.path.join(bucketdir, sharenum_s)
355                 msf = MutableShareFile(filename, self)
356-                datavs[sharenum] = msf.readv(readv)
357+                datavs[sharenum] = msf.readv(readvs)
358         log.msg("returning shares %s" % (datavs.keys(),),
359                 facility="tahoe.storage", level=log.NOISY, parent=lp)
360         self.add_latency("readv", time.time() - start)
361}
362[test/test_mutable.py: alter some tests that were failing due to MDMF; minor code cleanup.
363Kevan Carstensen <kevan@isnotajoke.com>**20100624234924
364 Ignore-this: afb86ec1fbdbfe1a5ef6f46f350273c0
365] {
366hunk ./src/allmydata/test/test_mutable.py 151
367             chr(ord(original[byte_offset]) ^ 0x01) +
368             original[byte_offset+1:])
369 
370+def add_two(original, byte_offset):
371+    # It isn't enough to simply flip the bit for the version number,
372+    # because 1 is a valid version number. So we add two instead.
373+    return (original[:byte_offset] +
374+            chr(ord(original[byte_offset]) ^ 0x02) +
375+            original[byte_offset+1:])
376+
377 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
378     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
379     # list of shnums to corrupt.
380hunk ./src/allmydata/test/test_mutable.py 187
381                 real_offset = offset1
382             real_offset = int(real_offset) + offset2 + offset_offset
383             assert isinstance(real_offset, int), offset
384-            shares[shnum] = flip_bit(data, real_offset)
385+            if offset1 == 0: # verbyte
386+                f = add_two
387+            else:
388+                f = flip_bit
389+            shares[shnum] = f(data, real_offset)
390     return res
391 
392 def make_storagebroker(s=None, num_peers=10):
393hunk ./src/allmydata/test/test_mutable.py 423
394         d.addCallback(_created)
395         return d
396 
397+
398     def test_modify_backoffer(self):
399         def _modifier(old_contents, servermap, first_time):
400             return old_contents + "line2"
401hunk ./src/allmydata/test/test_mutable.py 658
402         d.addCallback(_created)
403         return d
404 
405+
406     def _copy_shares(self, ignored, index):
407         shares = self._storage._peers
408         # we need a deep copy
409}
410[test/test_mutable.py: change the definition of corrupt() to work with MDMF as well as SDMF files, change users of corrupt to use the new definition
411Kevan Carstensen <kevan@isnotajoke.com>**20100626003520
412 Ignore-this: 836e59e2fde0535f6b4bea3468dc8244
413] {
414hunk ./src/allmydata/test/test_mutable.py 168
415                 and shnum not in shnums_to_corrupt):
416                 continue
417             data = shares[shnum]
418-            (version,
419-             seqnum,
420-             root_hash,
421-             IV,
422-             k, N, segsize, datalen,
423-             o) = unpack_header(data)
424-            if isinstance(offset, tuple):
425-                offset1, offset2 = offset
426-            else:
427-                offset1 = offset
428-                offset2 = 0
429-            if offset1 == "pubkey":
430-                real_offset = 107
431-            elif offset1 in o:
432-                real_offset = o[offset1]
433-            else:
434-                real_offset = offset1
435-            real_offset = int(real_offset) + offset2 + offset_offset
436-            assert isinstance(real_offset, int), offset
437-            if offset1 == 0: # verbyte
438-                f = add_two
439-            else:
440-                f = flip_bit
441-            shares[shnum] = f(data, real_offset)
442-    return res
443+            # We're feeding the reader all of the share data, so it
444+            # won't need to use the rref that we didn't provide, nor the
445+            # storage index that we didn't provide. We do this because
446+            # the reader will work for both MDMF and SDMF.
447+            reader = MDMFSlotReadProxy(None, None, shnum, data)
448+            # We need to get the offsets for the next part.
449+            d = reader.get_verinfo()
450+            def _do_corruption(verinfo, data, shnum):
451+                (seqnum,
452+                 root_hash,
453+                 IV,
454+                 segsize,
455+                 datalen,
456+                 k, n, prefix, o) = verinfo
457+                if isinstance(offset, tuple):
458+                    offset1, offset2 = offset
459+                else:
460+                    offset1 = offset
461+                    offset2 = 0
462+                if offset1 == "pubkey":
463+                    real_offset = 107
464+                elif offset1 in o:
465+                    real_offset = o[offset1]
466+                else:
467+                    real_offset = offset1
468+                real_offset = int(real_offset) + offset2 + offset_offset
469+                assert isinstance(real_offset, int), offset
470+                if offset1 == 0: # verbyte
471+                    f = add_two
472+                else:
473+                    f = flip_bit
474+                shares[shnum] = f(data, real_offset)
475+            d.addCallback(_do_corruption, data, shnum)
476+            ds.append(d)
477+    dl = defer.DeferredList(ds)
478+    dl.addCallback(lambda ignored: res)
479+    return dl
480 
481 def make_storagebroker(s=None, num_peers=10):
482     if not s:
483hunk ./src/allmydata/test/test_mutable.py 1177
484         return d
485 
486     def test_download_fails(self):
487-        corrupt(None, self._storage, "signature")
488-        d = self.shouldFail(UnrecoverableFileError, "test_download_anyway",
489+        d = corrupt(None, self._storage, "signature")
490+        d.addCallback(lambda ignored:
491+            self.shouldFail(UnrecoverableFileError, "test_download_anyway",
492                             "no recoverable versions",
493                             self._fn.download_best_version)
494         return d
495hunk ./src/allmydata/test/test_mutable.py 1232
496         return d
497 
498     def test_check_all_bad_sig(self):
499-        corrupt(None, self._storage, 1) # bad sig
500-        d = self._fn.check(Monitor())
501+        d = corrupt(None, self._storage, 1) # bad sig
502+        d.addCallback(lambda ignored:
503+            self._fn.check(Monitor()))
504         d.addCallback(self.check_bad, "test_check_all_bad_sig")
505         return d
506 
507hunk ./src/allmydata/test/test_mutable.py 1239
508     def test_check_all_bad_blocks(self):
509-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
510+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
511         # the Checker won't notice this.. it doesn't look at actual data
512hunk ./src/allmydata/test/test_mutable.py 1241
513-        d = self._fn.check(Monitor())
514+        d.addCallback(lambda ignored:
515+            self._fn.check(Monitor()))
516         d.addCallback(self.check_good, "test_check_all_bad_blocks")
517         return d
518 
519hunk ./src/allmydata/test/test_mutable.py 1252
520         return d
521 
522     def test_verify_all_bad_sig(self):
523-        corrupt(None, self._storage, 1) # bad sig
524-        d = self._fn.check(Monitor(), verify=True)
525+        d = corrupt(None, self._storage, 1) # bad sig
526+        d.addCallback(lambda ignored:
527+            self._fn.check(Monitor(), verify=True))
528         d.addCallback(self.check_bad, "test_verify_all_bad_sig")
529         return d
530 
531hunk ./src/allmydata/test/test_mutable.py 1259
532     def test_verify_one_bad_sig(self):
533-        corrupt(None, self._storage, 1, [9]) # bad sig
534-        d = self._fn.check(Monitor(), verify=True)
535+        d = corrupt(None, self._storage, 1, [9]) # bad sig
536+        d.addCallback(lambda ignored:
537+            self._fn.check(Monitor(), verify=True))
538         d.addCallback(self.check_bad, "test_verify_one_bad_sig")
539         return d
540 
541hunk ./src/allmydata/test/test_mutable.py 1266
542     def test_verify_one_bad_block(self):
543-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
544+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
545         # the Verifier *will* notice this, since it examines every byte
546hunk ./src/allmydata/test/test_mutable.py 1268
547-        d = self._fn.check(Monitor(), verify=True)
548+        d.addCallback(lambda ignored:
549+            self._fn.check(Monitor(), verify=True))
550         d.addCallback(self.check_bad, "test_verify_one_bad_block")
551         d.addCallback(self.check_expected_failure,
552                       CorruptShareError, "block hash tree failure",
553hunk ./src/allmydata/test/test_mutable.py 1277
554         return d
555 
556     def test_verify_one_bad_sharehash(self):
557-        corrupt(None, self._storage, "share_hash_chain", [9], 5)
558-        d = self._fn.check(Monitor(), verify=True)
559+        d = corrupt(None, self._storage, "share_hash_chain", [9], 5)
560+        d.addCallback(lambda ignored:
561+            self._fn.check(Monitor(), verify=True))
562         d.addCallback(self.check_bad, "test_verify_one_bad_sharehash")
563         d.addCallback(self.check_expected_failure,
564                       CorruptShareError, "corrupt hashes",
565hunk ./src/allmydata/test/test_mutable.py 1287
566         return d
567 
568     def test_verify_one_bad_encprivkey(self):
569-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
570-        d = self._fn.check(Monitor(), verify=True)
571+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
572+        d.addCallback(lambda ignored:
573+            self._fn.check(Monitor(), verify=True))
574         d.addCallback(self.check_bad, "test_verify_one_bad_encprivkey")
575         d.addCallback(self.check_expected_failure,
576                       CorruptShareError, "invalid privkey",
577hunk ./src/allmydata/test/test_mutable.py 1297
578         return d
579 
580     def test_verify_one_bad_encprivkey_uncheckable(self):
581-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
582+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
583         readonly_fn = self._fn.get_readonly()
584         # a read-only node has no way to validate the privkey
585hunk ./src/allmydata/test/test_mutable.py 1300
586-        d = readonly_fn.check(Monitor(), verify=True)
587+        d.addCallback(lambda ignored:
588+            readonly_fn.check(Monitor(), verify=True))
589         d.addCallback(self.check_good,
590                       "test_verify_one_bad_encprivkey_uncheckable")
591         return d
592}
593[Alter the ServermapUpdater to find MDMF files
594Kevan Carstensen <kevan@isnotajoke.com>**20100626234118
595 Ignore-this: 25f6278209c2983ba8f307cfe0fde0
596 
597 The servermapupdater should find MDMF files on a grid in the same way
598 that it finds SDMF files. This patch makes it do that.
599] {
600hunk ./src/allmydata/mutable/servermap.py 7
601 from itertools import count
602 from twisted.internet import defer
603 from twisted.python import failure
604-from foolscap.api import DeadReferenceError, RemoteException, eventually
605+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
606+                         fireEventually
607 from allmydata.util import base32, hashutil, idlib, log
608 from allmydata.storage.server import si_b2a
609 from allmydata.interfaces import IServermapUpdaterStatus
610hunk ./src/allmydata/mutable/servermap.py 17
611 from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \
612      DictOfSets, CorruptShareError, NeedMoreDataError
613 from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
614-     SIGNED_PREFIX_LENGTH
615+     SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
616 
617 class UpdateStatus:
618     implements(IServermapUpdaterStatus)
619hunk ./src/allmydata/mutable/servermap.py 254
620         """Return a set of versionids, one for each version that is currently
621         recoverable."""
622         versionmap = self.make_versionmap()
623-
624         recoverable_versions = set()
625         for (verinfo, shares) in versionmap.items():
626             (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
627hunk ./src/allmydata/mutable/servermap.py 366
628         self._servers_responded = set()
629 
630         # how much data should we read?
631+        # SDMF:
632         #  * if we only need the checkstring, then [0:75]
633         #  * if we need to validate the checkstring sig, then [543ish:799ish]
634         #  * if we need the verification key, then [107:436ish]
635hunk ./src/allmydata/mutable/servermap.py 374
636         #  * if we need the encrypted private key, we want [-1216ish:]
637         #   * but we can't read from negative offsets
638         #   * the offset table tells us the 'ish', also the positive offset
639-        # A future version of the SMDF slot format should consider using
640-        # fixed-size slots so we can retrieve less data. For now, we'll just
641-        # read 2000 bytes, which also happens to read enough actual data to
642-        # pre-fetch a 9-entry dirnode.
643+        # MDMF:
644+        #  * Checkstring? [0:72]
645+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
646+        #    the offset table will tell us for sure.
647+        #  * If we need the verification key, we have to consult the offset
648+        #    table as well.
649+        # At this point, we don't know which we are. Our filenode can
650+        # tell us, but it might be lying -- in some cases, we're
651+        # responsible for telling it which kind of file it is.
652         self._read_size = 4000
653         if mode == MODE_CHECK:
654             # we use unpack_prefix_and_signature, so we need 1k
655hunk ./src/allmydata/mutable/servermap.py 432
656         self._queries_completed = 0
657 
658         sb = self._storage_broker
659+        # All of the peers, permuted by the storage index, as usual.
660         full_peerlist = sb.get_servers_for_index(self._storage_index)
661         self.full_peerlist = full_peerlist # for use later, immutable
662         self.extra_peers = full_peerlist[:] # peers are removed as we use them
663hunk ./src/allmydata/mutable/servermap.py 439
664         self._good_peers = set() # peers who had some shares
665         self._empty_peers = set() # peers who don't have any shares
666         self._bad_peers = set() # peers to whom our queries failed
667+        self._readers = {} # peerid -> dict(sharewriters), filled in
668+                           # after responses come in.
669 
670         k = self._node.get_required_shares()
671hunk ./src/allmydata/mutable/servermap.py 443
672+        # For what cases can these conditions work?
673         if k is None:
674             # make a guess
675             k = 3
676hunk ./src/allmydata/mutable/servermap.py 456
677         self.num_peers_to_query = k + self.EPSILON
678 
679         if self.mode == MODE_CHECK:
680+            # We want to query all of the peers.
681             initial_peers_to_query = dict(full_peerlist)
682             must_query = set(initial_peers_to_query.keys())
683             self.extra_peers = []
684hunk ./src/allmydata/mutable/servermap.py 464
685             # we're planning to replace all the shares, so we want a good
686             # chance of finding them all. We will keep searching until we've
687             # seen epsilon that don't have a share.
688+            # We don't query all of the peers because that could take a while.
689             self.num_peers_to_query = N + self.EPSILON
690             initial_peers_to_query, must_query = self._build_initial_querylist()
691             self.required_num_empty_peers = self.EPSILON
692hunk ./src/allmydata/mutable/servermap.py 474
693             # might also avoid the round trip required to read the encrypted
694             # private key.
695 
696-        else:
697+        else: # MODE_READ, MODE_ANYTHING
698+            # 2k peers is good enough.
699             initial_peers_to_query, must_query = self._build_initial_querylist()
700 
701         # this is a set of peers that we are required to get responses from:
702hunk ./src/allmydata/mutable/servermap.py 490
703         # before we can consider ourselves finished, and self.extra_peers
704         # contains the overflow (peers that we should tap if we don't get
705         # enough responses)
706+        # I guess that self._must_query is a subset of
707+        # initial_peers_to_query?
708+        assert set(must_query).issubset(set(initial_peers_to_query))
709 
710         self._send_initial_requests(initial_peers_to_query)
711         self._status.timings["initial_queries"] = time.time() - self._started
712hunk ./src/allmydata/mutable/servermap.py 549
713         # errors that aren't handled by _query_failed (and errors caused by
714         # _query_failed) get logged, but we still want to check for doneness.
715         d.addErrback(log.err)
716-        d.addBoth(self._check_for_done)
717         d.addErrback(self._fatal_error)
718hunk ./src/allmydata/mutable/servermap.py 550
719+        d.addCallback(self._check_for_done)
720         return d
721 
722     def _do_read(self, ss, peerid, storage_index, shnums, readv):
723hunk ./src/allmydata/mutable/servermap.py 569
724         d = ss.callRemote("slot_readv", storage_index, shnums, readv)
725         return d
726 
727+
728+    def _got_corrupt_share(self, e, shnum, peerid, data, lp):
729+        """
730+        I am called when a remote server returns a corrupt share in
731+        response to one of our queries. By corrupt, I mean a share
732+        without a valid signature. I then record the failure, notify the
733+        server of the corruption, and record the share as bad.
734+        """
735+        f = failure.Failure(e)
736+        self.log(format="bad share: %(f_value)s", f_value=str(f),
737+                 failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
738+        # Notify the server that its share is corrupt.
739+        self.notify_server_corruption(peerid, shnum, str(e))
740+        # By flagging this as a bad peer, we won't count any of
741+        # the other shares on that peer as valid, though if we
742+        # happen to find a valid version string amongst those
743+        # shares, we'll keep track of it so that we don't need
744+        # to validate the signature on those again.
745+        self._bad_peers.add(peerid)
746+        self._last_failure = f
747+        # XXX: Use the reader for this?
748+        checkstring = data[:SIGNED_PREFIX_LENGTH]
749+        self._servermap.mark_bad_share(peerid, shnum, checkstring)
750+        self._servermap.problems.append(f)
751+
752+
753+    def _cache_good_sharedata(self, verinfo, shnum, now, data):
754+        """
755+        If one of my queries returns successfully (which means that we
756+        were able to and successfully did validate the signature), I
757+        cache the data that we initially fetched from the storage
758+        server. This will help reduce the number of roundtrips that need
759+        to occur when the file is downloaded, or when the file is
760+        updated.
761+        """
762+        self._node._add_to_cache(verinfo, shnum, 0, data, now)
763+
764+
765     def _got_results(self, datavs, peerid, readsize, stuff, started):
766         lp = self.log(format="got result from [%(peerid)s], %(numshares)d shares",
767                       peerid=idlib.shortnodeid_b2a(peerid),
768hunk ./src/allmydata/mutable/servermap.py 630
769         else:
770             self._empty_peers.add(peerid)
771 
772-        last_verinfo = None
773-        last_shnum = None
774+        ss, storage_index = stuff
775+        ds = []
776+
777         for shnum,datav in datavs.items():
778             data = datav[0]
779hunk ./src/allmydata/mutable/servermap.py 635
780-            try:
781-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
782-                last_verinfo = verinfo
783-                last_shnum = shnum
784-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
785-            except CorruptShareError, e:
786-                # log it and give the other shares a chance to be processed
787-                f = failure.Failure()
788-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
789-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
790-                self.notify_server_corruption(peerid, shnum, str(e))
791-                self._bad_peers.add(peerid)
792-                self._last_failure = f
793-                checkstring = data[:SIGNED_PREFIX_LENGTH]
794-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
795-                self._servermap.problems.append(f)
796-                pass
797-
798-        self._status.timings["cumulative_verify"] += (time.time() - now)
799+            reader = MDMFSlotReadProxy(ss,
800+                                       storage_index,
801+                                       shnum,
802+                                       data)
803+            self._readers.setdefault(peerid, dict())[shnum] = reader
804+            # our goal, with each response, is to validate the version
805+            # information and share data as best we can at this point --
806+            # we do this by validating the signature. To do this, we
807+            # need to do the following:
808+            #   - If we don't already have the public key, fetch the
809+            #     public key. We use this to validate the signature.
810+            if not self._node.get_pubkey():
811+                # fetch and set the public key.
812+                d = reader.get_verification_key()
813+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
814+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
815+                # XXX: Make self._pubkey_query_failed?
816+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
817+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
818+            else:
819+                # we already have the public key.
820+                d = defer.succeed(None)
821+            # Neither of these two branches return anything of
822+            # consequence, so the first entry in our deferredlist will
823+            # be None.
824 
825hunk ./src/allmydata/mutable/servermap.py 661
826-        if self._need_privkey and last_verinfo:
827-            # send them a request for the privkey. We send one request per
828-            # server.
829-            lp2 = self.log("sending privkey request",
830-                           parent=lp, level=log.NOISY)
831-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
832-             offsets_tuple) = last_verinfo
833-            o = dict(offsets_tuple)
834+            # - Next, we need the version information. We almost
835+            #   certainly got this by reading the first thousand or so
836+            #   bytes of the share on the storage server, so we
837+            #   shouldn't need to fetch anything at this step.
838+            d2 = reader.get_verinfo()
839+            d2.addErrback(lambda error, shnum=shnum, peerid=peerid:
840+                self._got_corrupt_share(error, shnum, peerid, data, lp))
841+            # - Next, we need the signature. For an SDMF share, it is
842+            #   likely that we fetched this when doing our initial fetch
843+            #   to get the version information. In MDMF, this lives at
844+            #   the end of the share, so unless the file is quite small,
845+            #   we'll need to do a remote fetch to get it.
846+            d3 = reader.get_signature()
847+            d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
848+                self._got_corrupt_share(error, shnum, peerid, data, lp))
849+            #  Once we have all three of these responses, we can move on
850+            #  to validating the signature
851 
852hunk ./src/allmydata/mutable/servermap.py 679
853-            self._queries_outstanding.add(peerid)
854-            readv = [ (o['enc_privkey'], (o['EOF'] - o['enc_privkey'])) ]
855-            ss = self._servermap.connections[peerid]
856-            privkey_started = time.time()
857-            d = self._do_read(ss, peerid, self._storage_index,
858-                              [last_shnum], readv)
859-            d.addCallback(self._got_privkey_results, peerid, last_shnum,
860-                          privkey_started, lp2)
861-            d.addErrback(self._privkey_query_failed, peerid, last_shnum, lp2)
862-            d.addErrback(log.err)
863-            d.addCallback(self._check_for_done)
864-            d.addErrback(self._fatal_error)
865+            # Does the node already have a privkey? If not, we'll try to
866+            # fetch it here.
867+            if self._need_privkey:
868+                d4 = reader.get_encprivkey()
869+                d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
870+                    self._try_to_validate_privkey(results, peerid, shnum, lp))
871+                d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
872+                    self._privkey_query_failed(error, shnum, data, lp))
873+            else:
874+                d4 = defer.succeed(None)
875 
876hunk ./src/allmydata/mutable/servermap.py 690
877+            dl = defer.DeferredList([d, d2, d3, d4])
878+            dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
879+                self._got_signature_one_share(results, shnum, peerid, lp))
880+            dl.addErrback(lambda error, shnum=shnum, data=data:
881+               self._got_corrupt_share(error, shnum, peerid, data, lp))
882+            dl.addCallback(lambda verinfo, shnum=shnum, peerid=peerid, data=data:
883+                self._cache_good_sharedata(verinfo, shnum, now, data))
884+            ds.append(dl)
885+        # dl is a deferred list that will fire when all of the shares
886+        # that we found on this peer are done processing. When dl fires,
887+        # we know that processing is done, so we can decrement the
888+        # semaphore-like thing that we incremented earlier.
889+        dl = defer.DeferredList(ds, fireOnOneErrback=True)
890+        # Are we done? Done means that there are no more queries to
891+        # send, that there are no outstanding queries, and that we
892+        # haven't received any queries that are still processing. If we
893+        # are done, self._check_for_done will cause the done deferred
894+        # that we returned to our caller to fire, which tells them that
895+        # they have a complete servermap, and that we won't be touching
896+        # the servermap anymore.
897+        dl.addCallback(self._check_for_done)
898+        dl.addErrback(self._fatal_error)
899         # all done!
900         self.log("_got_results done", parent=lp, level=log.NOISY)
901hunk ./src/allmydata/mutable/servermap.py 714
902+        return dl
903+
904+
905+    def _try_to_set_pubkey(self, pubkey_s, peerid, shnum, lp):
906+        if self._node.get_pubkey():
907+            return # don't go through this again if we don't have to
908+        fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
909+        assert len(fingerprint) == 32
910+        if fingerprint != self._node.get_fingerprint():
911+            raise CorruptShareError(peerid, shnum,
912+                                "pubkey doesn't match fingerprint")
913+        self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
914+        assert self._node.get_pubkey()
915+
916 
917     def notify_server_corruption(self, peerid, shnum, reason):
918         ss = self._servermap.connections[peerid]
919hunk ./src/allmydata/mutable/servermap.py 734
920         ss.callRemoteOnly("advise_corrupt_share",
921                           "mutable", self._storage_index, shnum, reason)
922 
923-    def _got_results_one_share(self, shnum, data, peerid, lp):
924+
925+    def _got_signature_one_share(self, results, shnum, peerid, lp):
926+        # It is our job to give versioninfo to our caller. We need to
927+        # raise CorruptShareError if the share is corrupt for any
928+        # reason, something that our caller will handle.
929         self.log(format="_got_results: got shnum #%(shnum)d from peerid %(peerid)s",
930                  shnum=shnum,
931                  peerid=idlib.shortnodeid_b2a(peerid),
932hunk ./src/allmydata/mutable/servermap.py 744
933                  level=log.NOISY,
934                  parent=lp)
935-
936-        # this might raise NeedMoreDataError, if the pubkey and signature
937-        # live at some weird offset. That shouldn't happen, so I'm going to
938-        # treat it as a bad share.
939-        (seqnum, root_hash, IV, k, N, segsize, datalength,
940-         pubkey_s, signature, prefix) = unpack_prefix_and_signature(data)
941-
942-        if not self._node.get_pubkey():
943-            fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
944-            assert len(fingerprint) == 32
945-            if fingerprint != self._node.get_fingerprint():
946-                raise CorruptShareError(peerid, shnum,
947-                                        "pubkey doesn't match fingerprint")
948-            self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
949-
950-        if self._need_privkey:
951-            self._try_to_extract_privkey(data, peerid, shnum, lp)
952-
953-        (ig_version, ig_seqnum, ig_root_hash, ig_IV, ig_k, ig_N,
954-         ig_segsize, ig_datalen, offsets) = unpack_header(data)
955+        _, verinfo, signature, __ = results
956+        (seqnum,
957+         root_hash,
958+         saltish,
959+         segsize,
960+         datalen,
961+         k,
962+         n,
963+         prefix,
964+         offsets) = verinfo[1]
965         offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
966 
967hunk ./src/allmydata/mutable/servermap.py 756
968-        verinfo = (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
969+        # XXX: This should be done for us in the method, so
970+        # presumably you can go in there and fix it.
971+        verinfo = (seqnum,
972+                   root_hash,
973+                   saltish,
974+                   segsize,
975+                   datalen,
976+                   k,
977+                   n,
978+                   prefix,
979                    offsets_tuple)
980hunk ./src/allmydata/mutable/servermap.py 767
981+        # This tuple uniquely identifies a share on the grid; we use it
982+        # to keep track of the ones that we've already seen.
983 
984         if verinfo not in self._valid_versions:
985hunk ./src/allmydata/mutable/servermap.py 771
986-            # it's a new pair. Verify the signature.
987-            valid = self._node.get_pubkey().verify(prefix, signature)
988+            # This is a new version tuple, and we need to validate it
989+            # against the public key before keeping track of it.
990+            assert self._node.get_pubkey()
991+            valid = self._node.get_pubkey().verify(prefix, signature[1])
992             if not valid:
993hunk ./src/allmydata/mutable/servermap.py 776
994-                raise CorruptShareError(peerid, shnum, "signature is invalid")
995+                raise CorruptShareError(peerid, shnum,
996+                                        "signature is invalid")
997 
998hunk ./src/allmydata/mutable/servermap.py 779
999-            # ok, it's a valid verinfo. Add it to the list of validated
1000-            # versions.
1001-            self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
1002-                     % (seqnum, base32.b2a(root_hash)[:4],
1003-                        idlib.shortnodeid_b2a(peerid), shnum,
1004-                        k, N, segsize, datalength),
1005-                     parent=lp)
1006-            self._valid_versions.add(verinfo)
1007-        # We now know that this is a valid candidate verinfo.
1008+        # ok, it's a valid verinfo. Add it to the list of validated
1009+        # versions.
1010+        self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
1011+                 % (seqnum, base32.b2a(root_hash)[:4],
1012+                    idlib.shortnodeid_b2a(peerid), shnum,
1013+                    k, n, segsize, datalen),
1014+                    parent=lp)
1015+        self._valid_versions.add(verinfo)
1016+        # We now know that this is a valid candidate verinfo. Whether or
1017+        # not this instance of it is valid is a matter for the next
1018+        # statement; at this point, we just know that if we see this
1019+        # version info again, that its signature checks out and that
1020+        # we're okay to skip the signature-checking step.
1021 
1022hunk ./src/allmydata/mutable/servermap.py 793
1023+        # (peerid, shnum) are bound in the method invocation.
1024         if (peerid, shnum) in self._servermap.bad_shares:
1025             # we've been told that the rest of the data in this share is
1026             # unusable, so don't add it to the servermap.
1027hunk ./src/allmydata/mutable/servermap.py 808
1028         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
1029         return verinfo
1030 
1031+
1032     def _deserialize_pubkey(self, pubkey_s):
1033         verifier = rsa.create_verifying_key_from_string(pubkey_s)
1034         return verifier
1035hunk ./src/allmydata/mutable/servermap.py 813
1036 
1037-    def _try_to_extract_privkey(self, data, peerid, shnum, lp):
1038-        try:
1039-            r = unpack_share(data)
1040-        except NeedMoreDataError, e:
1041-            # this share won't help us. oh well.
1042-            offset = e.encprivkey_offset
1043-            length = e.encprivkey_length
1044-            self.log("shnum %d on peerid %s: share was too short (%dB) "
1045-                     "to get the encprivkey; [%d:%d] ought to hold it" %
1046-                     (shnum, idlib.shortnodeid_b2a(peerid), len(data),
1047-                      offset, offset+length),
1048-                     parent=lp)
1049-            # NOTE: if uncoordinated writes are taking place, someone might
1050-            # change the share (and most probably move the encprivkey) before
1051-            # we get a chance to do one of these reads and fetch it. This
1052-            # will cause us to see a NotEnoughSharesError(unable to fetch
1053-            # privkey) instead of an UncoordinatedWriteError . This is a
1054-            # nuisance, but it will go away when we move to DSA-based mutable
1055-            # files (since the privkey will be small enough to fit in the
1056-            # write cap).
1057-
1058-            return
1059-
1060-        (seqnum, root_hash, IV, k, N, segsize, datalen,
1061-         pubkey, signature, share_hash_chain, block_hash_tree,
1062-         share_data, enc_privkey) = r
1063-
1064-        return self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1065 
1066     def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
1067hunk ./src/allmydata/mutable/servermap.py 815
1068-
1069+        """
1070+        Given a writekey from a remote server, I validate it against the
1071+        writekey stored in my node. If it is valid, then I set the
1072+        privkey and encprivkey properties of the node.
1073+        """
1074         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
1075         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
1076         if alleged_writekey != self._node.get_writekey():
1077hunk ./src/allmydata/mutable/servermap.py 892
1078         self._queries_completed += 1
1079         self._last_failure = f
1080 
1081-    def _got_privkey_results(self, datavs, peerid, shnum, started, lp):
1082-        now = time.time()
1083-        elapsed = now - started
1084-        self._status.add_per_server_time(peerid, "privkey", started, elapsed)
1085-        self._queries_outstanding.discard(peerid)
1086-        if not self._need_privkey:
1087-            return
1088-        if shnum not in datavs:
1089-            self.log("privkey wasn't there when we asked it",
1090-                     level=log.WEIRD, umid="VA9uDQ")
1091-            return
1092-        datav = datavs[shnum]
1093-        enc_privkey = datav[0]
1094-        self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1095 
1096     def _privkey_query_failed(self, f, peerid, shnum, lp):
1097         self._queries_outstanding.discard(peerid)
1098hunk ./src/allmydata/mutable/servermap.py 906
1099         self._servermap.problems.append(f)
1100         self._last_failure = f
1101 
1102+
1103     def _check_for_done(self, res):
1104         # exit paths:
1105         #  return self._send_more_queries(outstanding) : send some more queries
1106hunk ./src/allmydata/mutable/servermap.py 912
1107         #  return self._done() : all done
1108         #  return : keep waiting, no new queries
1109-
1110         lp = self.log(format=("_check_for_done, mode is '%(mode)s', "
1111                               "%(outstanding)d queries outstanding, "
1112                               "%(extra)d extra peers available, "
1113hunk ./src/allmydata/mutable/servermap.py 1117
1114         self._servermap.last_update_time = self._started
1115         # the servermap will not be touched after this
1116         self.log("servermap: %s" % self._servermap.summarize_versions())
1117+
1118         eventually(self._done_deferred.callback, self._servermap)
1119 
1120     def _fatal_error(self, f):
1121hunk ./src/allmydata/test/test_mutable.py 637
1122         d.addCallback(_created)
1123         return d
1124 
1125-    def publish_multiple(self):
1126+    def publish_mdmf(self):
1127+        # like publish_one, except that the result is guaranteed to be
1128+        # an MDMF file.
1129+        # self.CONTENTS should have more than one segment.
1130+        self.CONTENTS = "This is an MDMF file" * 100000
1131+        self._storage = FakeStorage()
1132+        self._nodemaker = make_nodemaker(self._storage)
1133+        self._storage_broker = self._nodemaker.storage_broker
1134+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
1135+        def _created(node):
1136+            self._fn = node
1137+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1138+        d.addCallback(_created)
1139+        return d
1140+
1141+
1142+    def publish_sdmf(self):
1143+        # like publish_one, except that the result is guaranteed to be
1144+        # an SDMF file
1145+        self.CONTENTS = "This is an SDMF file" * 1000
1146+        self._storage = FakeStorage()
1147+        self._nodemaker = make_nodemaker(self._storage)
1148+        self._storage_broker = self._nodemaker.storage_broker
1149+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
1150+        def _created(node):
1151+            self._fn = node
1152+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1153+        d.addCallback(_created)
1154+        return d
1155+
1156+
1157+    def publish_multiple(self, version=0):
1158         self.CONTENTS = ["Contents 0",
1159                          "Contents 1",
1160                          "Contents 2",
1161hunk ./src/allmydata/test/test_mutable.py 677
1162         self._copied_shares = {}
1163         self._storage = FakeStorage()
1164         self._nodemaker = make_nodemaker(self._storage)
1165-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0]) # seqnum=1
1166+        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
1167         def _created(node):
1168             self._fn = node
1169             # now create multiple versions of the same file, and accumulate
1170hunk ./src/allmydata/test/test_mutable.py 906
1171         return d
1172 
1173 
1174+    def test_servermapupdater_finds_mdmf_files(self):
1175+        # setUp already published an MDMF file for us. We just need to
1176+        # make sure that when we run the ServermapUpdater, the file is
1177+        # reported to have one recoverable version.
1178+        d = defer.succeed(None)
1179+        d.addCallback(lambda ignored:
1180+            self.publish_mdmf())
1181+        d.addCallback(lambda ignored:
1182+            self.make_servermap(mode=MODE_CHECK))
1183+        # Calling make_servermap also updates the servermap in the mode
1184+        # that we specify, so we just need to see what it says.
1185+        def _check_servermap(sm):
1186+            self.failUnlessEqual(len(sm.recoverable_versions()), 1)
1187+        d.addCallback(_check_servermap)
1188+        return d
1189+
1190+
1191+    def test_servermapupdater_finds_sdmf_files(self):
1192+        d = defer.succeed(None)
1193+        d.addCallback(lambda ignored:
1194+            self.publish_sdmf())
1195+        d.addCallback(lambda ignored:
1196+            self.make_servermap(mode=MODE_CHECK))
1197+        d.addCallback(lambda servermap:
1198+            self.failUnlessEqual(len(servermap.recoverable_versions()), 1))
1199+        return d
1200+
1201 
1202 class Roundtrip(unittest.TestCase, testutil.ShouldFailMixin, PublishMixin):
1203     def setUp(self):
1204hunk ./src/allmydata/test/test_mutable.py 1050
1205         return d
1206     test_no_servers_download.timeout = 15
1207 
1208+
1209     def _test_corrupt_all(self, offset, substring,
1210                           should_succeed=False, corrupt_early=True,
1211                           failure_checker=None):
1212}
1213[Make a segmented mutable uploader
1214Kevan Carstensen <kevan@isnotajoke.com>**20100626234204
1215 Ignore-this: d199af8ab0bc64d8ed2bc19c5437bfba
1216 
1217 The mutable file uploader should be able to publish files with one
1218 segment and files with multiple segments. This patch makes it do that.
1219 This is still incomplete, and rather ugly -- I need to flesh out error
1220 handling, I need to write tests, and I need to remove some of the uglier
1221 kludges in the process before I can call this done.
1222] {
1223hunk ./src/allmydata/mutable/publish.py 8
1224 from zope.interface import implements
1225 from twisted.internet import defer
1226 from twisted.python import failure
1227-from allmydata.interfaces import IPublishStatus
1228+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
1229 from allmydata.util import base32, hashutil, mathutil, idlib, log
1230 from allmydata import hashtree, codec
1231 from allmydata.storage.server import si_b2a
1232hunk ./src/allmydata/mutable/publish.py 19
1233      UncoordinatedWriteError, NotEnoughServersError
1234 from allmydata.mutable.servermap import ServerMap
1235 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
1236-     unpack_checkstring, SIGNED_PREFIX
1237+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
1238+
1239+KiB = 1024
1240+DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
1241 
1242 class PublishStatus:
1243     implements(IPublishStatus)
1244hunk ./src/allmydata/mutable/publish.py 112
1245         self._status.set_helper(False)
1246         self._status.set_progress(0.0)
1247         self._status.set_active(True)
1248+        # We use this to control how the file is written.
1249+        version = self._node.get_version()
1250+        assert version in (SDMF_VERSION, MDMF_VERSION)
1251+        self._version = version
1252 
1253     def get_status(self):
1254         return self._status
1255hunk ./src/allmydata/mutable/publish.py 134
1256         simultaneous write.
1257         """
1258 
1259-        # 1: generate shares (SDMF: files are small, so we can do it in RAM)
1260-        # 2: perform peer selection, get candidate servers
1261-        #  2a: send queries to n+epsilon servers, to determine current shares
1262-        #  2b: based upon responses, create target map
1263-        # 3: send slot_testv_and_readv_and_writev messages
1264-        # 4: as responses return, update share-dispatch table
1265-        # 4a: may need to run recovery algorithm
1266-        # 5: when enough responses are back, we're done
1267+        # 0. Setup encoding parameters, encoder, and other such things.
1268+        # 1. Encrypt, encode, and publish segments.
1269 
1270         self.log("starting publish, datalen is %s" % len(newdata))
1271         self._status.set_size(len(newdata))
1272hunk ./src/allmydata/mutable/publish.py 187
1273         self.bad_peers = set() # peerids who have errbacked/refused requests
1274 
1275         self.newdata = newdata
1276-        self.salt = os.urandom(16)
1277 
1278hunk ./src/allmydata/mutable/publish.py 188
1279+        # This will set self.segment_size, self.num_segments, and
1280+        # self.fec.
1281         self.setup_encoding_parameters()
1282 
1283         # if we experience any surprises (writes which were rejected because
1284hunk ./src/allmydata/mutable/publish.py 238
1285             self.bad_share_checkstrings[key] = old_checkstring
1286             self.connections[peerid] = self._servermap.connections[peerid]
1287 
1288-        # create the shares. We'll discard these as they are delivered. SDMF:
1289-        # we're allowed to hold everything in memory.
1290+        # Now, the process dovetails -- if this is an SDMF file, we need
1291+        # to write an SDMF file. Otherwise, we need to write an MDMF
1292+        # file.
1293+        if self._version == MDMF_VERSION:
1294+            return self._publish_mdmf()
1295+        else:
1296+            return self._publish_sdmf()
1297+        #return self.done_deferred
1298+
1299+    def _publish_mdmf(self):
1300+        # Next, we find homes for all of the shares that we don't have
1301+        # homes for yet.
1302+        # TODO: Make this part do peer selection.
1303+        self.update_goal()
1304+        self.writers = {}
1305+        # For each (peerid, shnum) in self.goal, we make an
1306+        # MDMFSlotWriteProxy for that peer. We'll use this to write
1307+        # shares to the peer.
1308+        for key in self.goal:
1309+            peerid, shnum = key
1310+            write_enabler = self._node.get_write_enabler(peerid)
1311+            renew_secret = self._node.get_renewal_secret(peerid)
1312+            cancel_secret = self._node.get_cancel_secret(peerid)
1313+            secrets = (write_enabler, renew_secret, cancel_secret)
1314+
1315+            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
1316+                                                      self.connections[peerid],
1317+                                                      self._storage_index,
1318+                                                      secrets,
1319+                                                      self._new_seqnum,
1320+                                                      self.required_shares,
1321+                                                      self.total_shares,
1322+                                                      self.segment_size,
1323+                                                      len(self.newdata))
1324+            if (peerid, shnum) in self._servermap.servermap:
1325+                old_versionid, old_timestamp = self._servermap.servermap[key]
1326+                (old_seqnum, old_root_hash, old_salt, old_segsize,
1327+                 old_datalength, old_k, old_N, old_prefix,
1328+                 old_offsets_tuple) = old_versionid
1329+                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
1330+
1331+        # Now, we start pushing shares.
1332+        self._status.timings["setup"] = time.time() - self._started
1333+        def _start_pushing(res):
1334+            self._started_pushing = time.time()
1335+            return res
1336+
1337+        # First, we encrypt, encode, and publish the shares that we need
1338+        # to encrypt, encode, and publish.
1339+
1340+        # This will eventually hold the block hash chain for each share
1341+        # that we publish. We define it this way so that empty publishes
1342+        # will still have something to write to the remote slot.
1343+        self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
1344+        self.sharehash_leaves = None # eventually [sharehashes]
1345+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
1346+                              # validate the share]
1347 
1348hunk ./src/allmydata/mutable/publish.py 296
1349+        d = defer.succeed(None)
1350+        self.log("Starting push")
1351+        for i in xrange(self.num_segments - 1):
1352+            d.addCallback(lambda ignored, i=i:
1353+                self.push_segment(i))
1354+            d.addCallback(self._turn_barrier)
1355+        # We have at least one segment, so we will have a tail segment
1356+        if self.num_segments > 0:
1357+            d.addCallback(lambda ignored:
1358+                self.push_tail_segment())
1359+
1360+        d.addCallback(lambda ignored:
1361+            self.push_encprivkey())
1362+        d.addCallback(lambda ignored:
1363+            self.push_blockhashes())
1364+        d.addCallback(lambda ignored:
1365+            self.push_sharehashes())
1366+        d.addCallback(lambda ignored:
1367+            self.push_toplevel_hashes_and_signature())
1368+        d.addCallback(lambda ignored:
1369+            self.finish_publishing())
1370+        return d
1371+
1372+
1373+    def _publish_sdmf(self):
1374         self._status.timings["setup"] = time.time() - self._started
1375hunk ./src/allmydata/mutable/publish.py 322
1376+        self.salt = os.urandom(16)
1377+
1378         d = self._encrypt_and_encode()
1379         d.addCallback(self._generate_shares)
1380         def _start_pushing(res):
1381hunk ./src/allmydata/mutable/publish.py 335
1382 
1383         return self.done_deferred
1384 
1385+
1386     def setup_encoding_parameters(self):
1387hunk ./src/allmydata/mutable/publish.py 337
1388-        segment_size = len(self.newdata)
1389+        if self._version == MDMF_VERSION:
1390+            segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
1391+        else:
1392+            segment_size = len(self.newdata) # SDMF is only one segment
1393         # this must be a multiple of self.required_shares
1394         segment_size = mathutil.next_multiple(segment_size,
1395                                               self.required_shares)
1396hunk ./src/allmydata/mutable/publish.py 350
1397                                                   segment_size)
1398         else:
1399             self.num_segments = 0
1400-        assert self.num_segments in [0, 1,] # SDMF restrictions
1401+        if self._version == SDMF_VERSION:
1402+            assert self.num_segments in (0, 1) # SDMF
1403+            return
1404+        # calculate the tail segment size.
1405+        self.tail_segment_size = len(self.newdata) % segment_size
1406+
1407+        if self.tail_segment_size == 0:
1408+            # The tail segment is the same size as the other segments.
1409+            self.tail_segment_size = segment_size
1410+
1411+        # We'll make an encoder ahead-of-time for the normal-sized
1412+        # segments (defined as any segment of segment_size size.
1413+        # (the part of the code that puts the tail segment will make its
1414+        #  own encoder for that part)
1415+        fec = codec.CRSEncoder()
1416+        fec.set_params(self.segment_size,
1417+                       self.required_shares, self.total_shares)
1418+        self.piece_size = fec.get_block_size()
1419+        self.fec = fec
1420+
1421+
1422+    def push_segment(self, segnum):
1423+        started = time.time()
1424+        segsize = self.segment_size
1425+        self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
1426+        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
1427+        assert len(data) == segsize
1428+
1429+        salt = os.urandom(16)
1430+
1431+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1432+        enc = AES(key)
1433+        crypttext = enc.process(data)
1434+        assert len(crypttext) == len(data)
1435+
1436+        now = time.time()
1437+        self._status.timings["encrypt"] = now - started
1438+        started = now
1439+
1440+        # now apply FEC
1441+
1442+        self._status.set_status("Encoding")
1443+        crypttext_pieces = [None] * self.required_shares
1444+        piece_size = self.piece_size
1445+        for i in range(len(crypttext_pieces)):
1446+            offset = i * piece_size
1447+            piece = crypttext[offset:offset+piece_size]
1448+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1449+            crypttext_pieces[i] = piece
1450+            assert len(piece) == piece_size
1451+        d = self.fec.encode(crypttext_pieces)
1452+        def _done_encoding(res):
1453+            elapsed = time.time() - started
1454+            self._status.timings["encode"] = elapsed
1455+            return res
1456+        d.addCallback(_done_encoding)
1457+
1458+        def _push_shares_and_salt(results):
1459+            shares, shareids = results
1460+            dl = []
1461+            for i in xrange(len(shares)):
1462+                sharedata = shares[i]
1463+                shareid = shareids[i]
1464+                block_hash = hashutil.block_hash(salt + sharedata)
1465+                self.blockhashes[shareid].append(block_hash)
1466+
1467+                # find the writer for this share
1468+                d = self.writers[shareid].put_block(sharedata, segnum, salt)
1469+                dl.append(d)
1470+            # TODO: Naturally, we need to check on the results of these.
1471+            return defer.DeferredList(dl)
1472+        d.addCallback(_push_shares_and_salt)
1473+        return d
1474+
1475+
1476+    def push_tail_segment(self):
1477+        # This is essentially the same as push_segment, except that we
1478+        # don't use the cached encoder that we use elsewhere.
1479+        self.log("Pushing tail segment")
1480+        started = time.time()
1481+        segsize = self.segment_size
1482+        data = self.newdata[segsize * (self.num_segments-1):]
1483+        assert len(data) == self.tail_segment_size
1484+        salt = os.urandom(16)
1485+
1486+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1487+        enc = AES(key)
1488+        crypttext = enc.process(data)
1489+        assert len(crypttext) == len(data)
1490+
1491+        now = time.time()
1492+        self._status.timings['encrypt'] = now - started
1493+        started = now
1494+
1495+        self._status.set_status("Encoding")
1496+        tail_fec = codec.CRSEncoder()
1497+        tail_fec.set_params(self.tail_segment_size,
1498+                            self.required_shares,
1499+                            self.total_shares)
1500+
1501+        crypttext_pieces = [None] * self.required_shares
1502+        piece_size = tail_fec.get_block_size()
1503+        for i in range(len(crypttext_pieces)):
1504+            offset = i * piece_size
1505+            piece = crypttext[offset:offset+piece_size]
1506+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1507+            crypttext_pieces[i] = piece
1508+            assert len(piece) == piece_size
1509+        d = tail_fec.encode(crypttext_pieces)
1510+        def _push_shares_and_salt(results):
1511+            shares, shareids = results
1512+            dl = []
1513+            for i in xrange(len(shares)):
1514+                sharedata = shares[i]
1515+                shareid = shareids[i]
1516+                block_hash = hashutil.block_hash(salt + sharedata)
1517+                self.blockhashes[shareid].append(block_hash)
1518+                # find the writer for this share
1519+                d = self.writers[shareid].put_block(sharedata,
1520+                                                    self.num_segments - 1,
1521+                                                    salt)
1522+                dl.append(d)
1523+            # TODO: Naturally, we need to check on the results of these.
1524+            return defer.DeferredList(dl)
1525+        d.addCallback(_push_shares_and_salt)
1526+        return d
1527+
1528+
1529+    def push_encprivkey(self):
1530+        started = time.time()
1531+        encprivkey = self._encprivkey
1532+        dl = []
1533+        def _spy_on_writer(results):
1534+            print results
1535+            return results
1536+        for shnum, writer in self.writers.iteritems():
1537+            d = writer.put_encprivkey(encprivkey)
1538+            dl.append(d)
1539+        d = defer.DeferredList(dl)
1540+        return d
1541+
1542+
1543+    def push_blockhashes(self):
1544+        started = time.time()
1545+        dl = []
1546+        def _spy_on_results(results):
1547+            print results
1548+            return results
1549+        self.sharehash_leaves = [None] * len(self.blockhashes)
1550+        for shnum, blockhashes in self.blockhashes.iteritems():
1551+            t = hashtree.HashTree(blockhashes)
1552+            self.blockhashes[shnum] = list(t)
1553+            # set the leaf for future use.
1554+            self.sharehash_leaves[shnum] = t[0]
1555+            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
1556+            dl.append(d)
1557+        d = defer.DeferredList(dl)
1558+        return d
1559+
1560+
1561+    def push_sharehashes(self):
1562+        share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
1563+        share_hash_chain = {}
1564+        ds = []
1565+        def _spy_on_results(results):
1566+            print results
1567+            return results
1568+        for shnum in xrange(len(self.sharehash_leaves)):
1569+            needed_indices = share_hash_tree.needed_hashes(shnum)
1570+            self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
1571+                                             for i in needed_indices] )
1572+            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
1573+            ds.append(d)
1574+        self.root_hash = share_hash_tree[0]
1575+        d = defer.DeferredList(ds)
1576+        return d
1577+
1578+
1579+    def push_toplevel_hashes_and_signature(self):
1580+        # We need to to three things here:
1581+        #   - Push the root hash and salt hash
1582+        #   - Get the checkstring of the resulting layout; sign that.
1583+        #   - Push the signature
1584+        ds = []
1585+        def _spy_on_results(results):
1586+            print results
1587+            return results
1588+        for shnum in xrange(self.total_shares):
1589+            d = self.writers[shnum].put_root_hash(self.root_hash)
1590+            ds.append(d)
1591+        d = defer.DeferredList(ds)
1592+        def _make_and_place_signature(ignored):
1593+            signable = self.writers[0].get_signable()
1594+            self.signature = self._privkey.sign(signable)
1595+
1596+            ds = []
1597+            for (shnum, writer) in self.writers.iteritems():
1598+                d = writer.put_signature(self.signature)
1599+                ds.append(d)
1600+            return defer.DeferredList(ds)
1601+        d.addCallback(_make_and_place_signature)
1602+        return d
1603+
1604+
1605+    def finish_publishing(self):
1606+        # We're almost done -- we just need to put the verification key
1607+        # and the offsets
1608+        ds = []
1609+        verification_key = self._pubkey.serialize()
1610+
1611+        def _spy_on_results(results):
1612+            print results
1613+            return results
1614+        for (shnum, writer) in self.writers.iteritems():
1615+            d = writer.put_verification_key(verification_key)
1616+            d.addCallback(lambda ignored, writer=writer:
1617+                writer.finish_publishing())
1618+            ds.append(d)
1619+        return defer.DeferredList(ds)
1620+
1621+
1622+    def _turn_barrier(self, res):
1623+        # putting this method in a Deferred chain imposes a guaranteed
1624+        # reactor turn between the pre- and post- portions of that chain.
1625+        # This can be useful to limit memory consumption: since Deferreds do
1626+        # not do tail recursion, code which uses defer.succeed(result) for
1627+        # consistency will cause objects to live for longer than you might
1628+        # normally expect.
1629+        return fireEventually(res)
1630+
1631 
1632     def _fatal_error(self, f):
1633         self.log("error during loop", failure=f, level=log.UNUSUAL)
1634hunk ./src/allmydata/mutable/publish.py 716
1635             self.log_goal(self.goal, "after update: ")
1636 
1637 
1638-
1639     def _encrypt_and_encode(self):
1640         # this returns a Deferred that fires with a list of (sharedata,
1641         # sharenum) tuples. TODO: cache the ciphertext, only produce the
1642hunk ./src/allmydata/mutable/publish.py 757
1643         d.addCallback(_done_encoding)
1644         return d
1645 
1646+
1647     def _generate_shares(self, shares_and_shareids):
1648         # this sets self.shares and self.root_hash
1649         self.log("_generate_shares")
1650hunk ./src/allmydata/mutable/publish.py 1145
1651             self._status.set_progress(1.0)
1652         eventually(self.done_deferred.callback, res)
1653 
1654-
1655hunk ./src/allmydata/test/test_mutable.py 248
1656         d.addCallback(_created)
1657         return d
1658 
1659+
1660+    def test_create_mdmf(self):
1661+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
1662+        def _created(n):
1663+            self.failUnless(isinstance(n, MutableFileNode))
1664+            self.failUnlessEqual(n.get_storage_index(), n._storage_index)
1665+            sb = self.nodemaker.storage_broker
1666+            peer0 = sorted(sb.get_all_serverids())[0]
1667+            shnums = self._storage._peers[peer0].keys()
1668+            self.failUnlessEqual(len(shnums), 1)
1669+        d.addCallback(_created)
1670+        return d
1671+
1672+
1673     def test_serialize(self):
1674         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
1675         calls = []
1676hunk ./src/allmydata/test/test_mutable.py 334
1677         d.addCallback(_created)
1678         return d
1679 
1680+
1681+    def test_create_mdmf_with_initial_contents(self):
1682+        initial_contents = "foobarbaz" * 131072 # 900KiB
1683+        d = self.nodemaker.create_mutable_file(initial_contents,
1684+                                               version=MDMF_VERSION)
1685+        def _created(n):
1686+            d = n.download_best_version()
1687+            d.addCallback(lambda data:
1688+                self.failUnlessEqual(data, initial_contents))
1689+            d.addCallback(lambda ignored:
1690+                n.overwrite(initial_contents + "foobarbaz"))
1691+            d.addCallback(lambda ignored:
1692+                n.download_best_version())
1693+            d.addCallback(lambda data:
1694+                self.failUnlessEqual(data, initial_contents +
1695+                                           "foobarbaz"))
1696+            return d
1697+        d.addCallback(_created)
1698+        return d
1699+
1700+
1701     def test_create_with_initial_contents_function(self):
1702         data = "initial contents"
1703         def _make_contents(n):
1704hunk ./src/allmydata/test/test_mutable.py 370
1705         d.addCallback(lambda data2: self.failUnlessEqual(data2, data))
1706         return d
1707 
1708+
1709+    def test_create_mdmf_with_initial_contents_function(self):
1710+        data = "initial contents" * 100000
1711+        def _make_contents(n):
1712+            self.failUnless(isinstance(n, MutableFileNode))
1713+            key = n.get_writekey()
1714+            self.failUnless(isinstance(key, str), key)
1715+            self.failUnlessEqual(len(key), 16)
1716+            return data
1717+        d = self.nodemaker.create_mutable_file(_make_contents,
1718+                                               version=MDMF_VERSION)
1719+        d.addCallback(lambda n:
1720+            n.download_best_version())
1721+        d.addCallback(lambda data2:
1722+            self.failUnlessEqual(data2, data))
1723+        return d
1724+
1725+
1726     def test_create_with_too_large_contents(self):
1727         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
1728         d = self.nodemaker.create_mutable_file(BIG)
1729}
1730[Write a segmented mutable downloader
1731Kevan Carstensen <kevan@isnotajoke.com>**20100626234314
1732 Ignore-this: d2bef531cde1b5c38f2eb28afdd4b17c
1733 
1734 The segmented mutable downloader can deal with MDMF files (files with
1735 one or more segments in MDMF format) and SDMF files (files with one
1736 segment in SDMF format). It is backwards compatible with the old
1737 file format.
1738 
1739 This patch also contains tests for the segmented mutable downloader.
1740] {
1741hunk ./src/allmydata/mutable/retrieve.py 8
1742 from twisted.internet import defer
1743 from twisted.python import failure
1744 from foolscap.api import DeadReferenceError, eventually, fireEventually
1745-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
1746-from allmydata.util import hashutil, idlib, log
1747+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
1748+                                 MDMF_VERSION, SDMF_VERSION
1749+from allmydata.util import hashutil, idlib, log, mathutil
1750 from allmydata import hashtree, codec
1751 from allmydata.storage.server import si_b2a
1752 from pycryptopp.cipher.aes import AES
1753hunk ./src/allmydata/mutable/retrieve.py 17
1754 from pycryptopp.publickey import rsa
1755 
1756 from allmydata.mutable.common import DictOfSets, CorruptShareError, UncoordinatedWriteError
1757-from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data
1758+from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data, \
1759+                                     MDMFSlotReadProxy
1760 
1761 class RetrieveStatus:
1762     implements(IRetrieveStatus)
1763hunk ./src/allmydata/mutable/retrieve.py 104
1764         self.verinfo = verinfo
1765         # during repair, we may be called upon to grab the private key, since
1766         # it wasn't picked up during a verify=False checker run, and we'll
1767-        # need it for repair to generate the a new version.
1768+        # need it for repair to generate a new version.
1769         self._need_privkey = fetch_privkey
1770         if self._node.get_privkey():
1771             self._need_privkey = False
1772hunk ./src/allmydata/mutable/retrieve.py 109
1773 
1774+        if self._need_privkey:
1775+            # TODO: Evaluate the need for this. We'll use it if we want
1776+            # to limit how many queries are on the wire for the privkey
1777+            # at once.
1778+            self._privkey_query_markers = [] # one Marker for each time we've
1779+                                             # tried to get the privkey.
1780+
1781         self._status = RetrieveStatus()
1782         self._status.set_storage_index(self._storage_index)
1783         self._status.set_helper(False)
1784hunk ./src/allmydata/mutable/retrieve.py 125
1785          offsets_tuple) = self.verinfo
1786         self._status.set_size(datalength)
1787         self._status.set_encoding(k, N)
1788+        self.readers = {}
1789 
1790     def get_status(self):
1791         return self._status
1792hunk ./src/allmydata/mutable/retrieve.py 149
1793         self.remaining_sharemap = DictOfSets()
1794         for (shnum, peerid, timestamp) in shares:
1795             self.remaining_sharemap.add(shnum, peerid)
1796+            # If the servermap update fetched anything, it fetched at least 1
1797+            # KiB, so we ask for that much.
1798+            # TODO: Change the cache methods to allow us to fetch all of the
1799+            # data that they have, then change this method to do that.
1800+            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
1801+                                                               shnum,
1802+                                                               0,
1803+                                                               1000)
1804+            ss = self.servermap.connections[peerid]
1805+            reader = MDMFSlotReadProxy(ss,
1806+                                       self._storage_index,
1807+                                       shnum,
1808+                                       any_cache)
1809+            reader.peerid = peerid
1810+            self.readers[shnum] = reader
1811+
1812 
1813         self.shares = {} # maps shnum to validated blocks
1814hunk ./src/allmydata/mutable/retrieve.py 167
1815+        self._active_readers = [] # list of active readers for this dl.
1816+        self._validated_readers = set() # set of readers that we have
1817+                                        # validated the prefix of
1818+        self._block_hash_trees = {} # shnum => hashtree
1819+        # TODO: Make this into a file-backed consumer or something to
1820+        # conserve memory.
1821+        self._plaintext = ""
1822 
1823         # how many shares do we need?
1824hunk ./src/allmydata/mutable/retrieve.py 176
1825-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1826+        (seqnum,
1827+         root_hash,
1828+         IV,
1829+         segsize,
1830+         datalength,
1831+         k,
1832+         N,
1833+         prefix,
1834          offsets_tuple) = self.verinfo
1835hunk ./src/allmydata/mutable/retrieve.py 185
1836-        assert len(self.remaining_sharemap) >= k
1837-        # we start with the lowest shnums we have available, since FEC is
1838-        # faster if we're using "primary shares"
1839-        self.active_shnums = set(sorted(self.remaining_sharemap.keys())[:k])
1840-        for shnum in self.active_shnums:
1841-            # we use an arbitrary peer who has the share. If shares are
1842-            # doubled up (more than one share per peer), we could make this
1843-            # run faster by spreading the load among multiple peers. But the
1844-            # algorithm to do that is more complicated than I want to write
1845-            # right now, and a well-provisioned grid shouldn't have multiple
1846-            # shares per peer.
1847-            peerid = list(self.remaining_sharemap[shnum])[0]
1848-            self.get_data(shnum, peerid)
1849 
1850hunk ./src/allmydata/mutable/retrieve.py 186
1851-        # control flow beyond this point: state machine. Receiving responses
1852-        # from queries is the input. We might send out more queries, or we
1853-        # might produce a result.
1854 
1855hunk ./src/allmydata/mutable/retrieve.py 187
1856+        # We need one share hash tree for the entire file; its leaves
1857+        # are the roots of the block hash trees for the shares that
1858+        # comprise it, and its root is in the verinfo.
1859+        self.share_hash_tree = hashtree.IncompleteHashTree(N)
1860+        self.share_hash_tree.set_hashes({0: root_hash})
1861+
1862+        # This will set up both the segment decoder and the tail segment
1863+        # decoder, as well as a variety of other instance variables that
1864+        # the download process will use.
1865+        self._setup_encoding_parameters()
1866+        assert len(self.remaining_sharemap) >= k
1867+
1868+        self.log("starting download")
1869+        self._add_active_peers()
1870+        # The download process beyond this is a state machine.
1871+        # _add_active_peers will select the peers that we want to use
1872+        # for the download, and then attempt to start downloading. After
1873+        # each segment, it will check for doneness, reacting to broken
1874+        # peers and corrupt shares as necessary. If it runs out of good
1875+        # peers before downloading all of the segments, _done_deferred
1876+        # will errback.  Otherwise, it will eventually callback with the
1877+        # contents of the mutable file.
1878         return self._done_deferred
1879 
1880hunk ./src/allmydata/mutable/retrieve.py 211
1881-    def get_data(self, shnum, peerid):
1882-        self.log(format="sending sh#%(shnum)d request to [%(peerid)s]",
1883-                 shnum=shnum,
1884-                 peerid=idlib.shortnodeid_b2a(peerid),
1885-                 level=log.NOISY)
1886-        ss = self.servermap.connections[peerid]
1887-        started = time.time()
1888-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1889+
1890+    def _setup_encoding_parameters(self):
1891+        """
1892+        I set up the encoding parameters, including k, n, the number
1893+        of segments associated with this file, and the segment decoder.
1894+        """
1895+        (seqnum,
1896+         root_hash,
1897+         IV,
1898+         segsize,
1899+         datalength,
1900+         k,
1901+         n,
1902+         known_prefix,
1903          offsets_tuple) = self.verinfo
1904hunk ./src/allmydata/mutable/retrieve.py 226
1905-        offsets = dict(offsets_tuple)
1906+        self._required_shares = k
1907+        self._total_shares = n
1908+        self._segment_size = segsize
1909+        self._data_length = datalength
1910+
1911+        if not IV:
1912+            self._version = MDMF_VERSION
1913+        else:
1914+            self._version = SDMF_VERSION
1915+
1916+        if datalength and segsize:
1917+            self._num_segments = mathutil.div_ceil(datalength, segsize)
1918+            self._tail_data_size = datalength % segsize
1919+        else:
1920+            self._num_segments = 0
1921+            self._tail_data_size = 0
1922 
1923hunk ./src/allmydata/mutable/retrieve.py 243
1924-        # we read the checkstring, to make sure that the data we grab is from
1925-        # the right version.
1926-        readv = [ (0, struct.calcsize(SIGNED_PREFIX)) ]
1927+        self._segment_decoder = codec.CRSDecoder()
1928+        self._segment_decoder.set_params(segsize, k, n)
1929+        self._current_segment = 0
1930 
1931hunk ./src/allmydata/mutable/retrieve.py 247
1932-        # We also read the data, and the hashes necessary to validate them
1933-        # (share_hash_chain, block_hash_tree, share_data). We don't read the
1934-        # signature or the pubkey, since that was handled during the
1935-        # servermap phase, and we'll be comparing the share hash chain
1936-        # against the roothash that was validated back then.
1937+        if  not self._tail_data_size:
1938+            self._tail_data_size = segsize
1939 
1940hunk ./src/allmydata/mutable/retrieve.py 250
1941-        readv.append( (offsets['share_hash_chain'],
1942-                       offsets['enc_privkey'] - offsets['share_hash_chain'] ) )
1943+        self._tail_segment_size = mathutil.next_multiple(self._tail_data_size,
1944+                                                         self._required_shares)
1945+        if self._tail_segment_size == self._segment_size:
1946+            self._tail_decoder = self._segment_decoder
1947+        else:
1948+            self._tail_decoder = codec.CRSDecoder()
1949+            self._tail_decoder.set_params(self._tail_segment_size,
1950+                                          self._required_shares,
1951+                                          self._total_shares)
1952 
1953hunk ./src/allmydata/mutable/retrieve.py 260
1954-        # if we need the private key (for repair), we also fetch that
1955-        if self._need_privkey:
1956-            readv.append( (offsets['enc_privkey'],
1957-                           offsets['EOF'] - offsets['enc_privkey']) )
1958+        self.log("got encoding parameters: "
1959+                 "k: %d "
1960+                 "n: %d "
1961+                 "%d segments of %d bytes each (%d byte tail segment)" % \
1962+                 (k, n, self._num_segments, self._segment_size,
1963+                  self._tail_segment_size))
1964 
1965hunk ./src/allmydata/mutable/retrieve.py 267
1966-        m = Marker()
1967-        self._outstanding_queries[m] = (peerid, shnum, started)
1968+        for i in xrange(self._total_shares):
1969+            # So we don't have to do this later.
1970+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
1971 
1972hunk ./src/allmydata/mutable/retrieve.py 271
1973-        # ask the cache first
1974-        got_from_cache = False
1975-        datavs = []
1976-        for (offset, length) in readv:
1977-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
1978-                                                            offset, length)
1979-            if data is not None:
1980-                datavs.append(data)
1981-        if len(datavs) == len(readv):
1982-            self.log("got data from cache")
1983-            got_from_cache = True
1984-            d = fireEventually({shnum: datavs})
1985-            # datavs is a dict mapping shnum to a pair of strings
1986-        else:
1987-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
1988-        self.remaining_sharemap.discard(shnum, peerid)
1989+        # If we have more than one segment, we are an SDMF file, which
1990+        # means that we need to validate the salts as we receive them.
1991+        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
1992+        self._salt_hash_tree[0] = IV # from the prefix.
1993 
1994hunk ./src/allmydata/mutable/retrieve.py 276
1995-        d.addCallback(self._got_results, m, peerid, started, got_from_cache)
1996-        d.addErrback(self._query_failed, m, peerid)
1997-        # errors that aren't handled by _query_failed (and errors caused by
1998-        # _query_failed) get logged, but we still want to check for doneness.
1999-        def _oops(f):
2000-            self.log(format="problem in _query_failed for sh#%(shnum)d to %(peerid)s",
2001-                     shnum=shnum,
2002-                     peerid=idlib.shortnodeid_b2a(peerid),
2003-                     failure=f,
2004-                     level=log.WEIRD, umid="W0xnQA")
2005-        d.addErrback(_oops)
2006-        d.addBoth(self._check_for_done)
2007-        # any error during _check_for_done means the download fails. If the
2008-        # download is successful, _check_for_done will fire _done by itself.
2009-        d.addErrback(self._done)
2010-        d.addErrback(log.err)
2011-        return d # purely for testing convenience
2012 
2013hunk ./src/allmydata/mutable/retrieve.py 277
2014-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
2015-        # isolate the callRemote to a separate method, so tests can subclass
2016-        # Publish and override it
2017-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
2018-        return d
2019+    def _add_active_peers(self):
2020+        """
2021+        I populate self._active_readers with enough active readers to
2022+        retrieve the contents of this mutable file. I am called before
2023+        downloading starts, and (eventually) after each validation
2024+        error, connection error, or other problem in the download.
2025+        """
2026+        # TODO: It would be cool to investigate other heuristics for
2027+        # reader selection. For instance, the cost (in time the user
2028+        # spends waiting for their file) of selecting a really slow peer
2029+        # that happens to have a primary share is probably more than
2030+        # selecting a really fast peer that doesn't have a primary
2031+        # share. Maybe the servermap could be extended to provide this
2032+        # information; it could keep track of latency information while
2033+        # it gathers more important data, and then this routine could
2034+        # use that to select active readers.
2035+        #
2036+        # (these and other questions would be easier to answer with a
2037+        #  robust, configurable tahoe-lafs simulator, which modeled node
2038+        #  failures, differences in node speed, and other characteristics
2039+        #  that we expect storage servers to have.  You could have
2040+        #  presets for really stable grids (like allmydata.com),
2041+        #  friendnets, make it easy to configure your own settings, and
2042+        #  then simulate the effect of big changes on these use cases
2043+        #  instead of just reasoning about what the effect might be. Out
2044+        #  of scope for MDMF, though.)
2045 
2046hunk ./src/allmydata/mutable/retrieve.py 304
2047-    def remove_peer(self, peerid):
2048-        for shnum in list(self.remaining_sharemap.keys()):
2049-            self.remaining_sharemap.discard(shnum, peerid)
2050+        # We need at least self._required_shares readers to download a
2051+        # segment.
2052+        needed = self._required_shares - len(self._active_readers)
2053+        # XXX: Why don't format= log messages work here?
2054+        self.log("adding %d peers to the active peers list" % needed)
2055 
2056hunk ./src/allmydata/mutable/retrieve.py 310
2057-    def _got_results(self, datavs, marker, peerid, started, got_from_cache):
2058-        now = time.time()
2059-        elapsed = now - started
2060-        if not got_from_cache:
2061-            self._status.add_fetch_timing(peerid, elapsed)
2062-        self.log(format="got results (%(shares)d shares) from [%(peerid)s]",
2063-                 shares=len(datavs),
2064-                 peerid=idlib.shortnodeid_b2a(peerid),
2065-                 level=log.NOISY)
2066-        self._outstanding_queries.pop(marker, None)
2067-        if not self._running:
2068-            return
2069+        # We favor lower numbered shares, since FEC is faster with
2070+        # primary shares than with other shares, and lower-numbered
2071+        # shares are more likely to be primary than higher numbered
2072+        # shares.
2073+        active_shnums = set(sorted(self.remaining_sharemap.keys()))
2074+        # We shouldn't consider adding shares that we already have; this
2075+        # will cause problems later.
2076+        active_shnums -= set([reader.shnum for reader in self._active_readers])
2077+        active_shnums = list(active_shnums)[:needed]
2078+        if len(active_shnums) < needed:
2079+            # We don't have enough readers to retrieve the file; fail.
2080+            return self._failed()
2081 
2082hunk ./src/allmydata/mutable/retrieve.py 323
2083-        # note that we only ask for a single share per query, so we only
2084-        # expect a single share back. On the other hand, we use the extra
2085-        # shares if we get them.. seems better than an assert().
2086+        for shnum in active_shnums:
2087+            self._active_readers.append(self.readers[shnum])
2088+            self.log("added reader for share %d" % shnum)
2089+        assert len(self._active_readers) == self._required_shares
2090+        # Conceptually, this is part of the _add_active_peers step. It
2091+        # validates the prefixes of newly added readers to make sure
2092+        # that they match what we are expecting for self.verinfo. If
2093+        # validation is successful, _validate_active_prefixes will call
2094+        # _download_current_segment for us. If validation is
2095+        # unsuccessful, then _validate_prefixes will remove the peer and
2096+        # call _add_active_peers again, where we will attempt to rectify
2097+        # the problem by choosing another peer.
2098+        return self._validate_active_prefixes()
2099 
2100hunk ./src/allmydata/mutable/retrieve.py 337
2101-        for shnum,datav in datavs.items():
2102-            (prefix, hash_and_data) = datav[:2]
2103-            try:
2104-                self._got_results_one_share(shnum, peerid,
2105-                                            prefix, hash_and_data)
2106-            except CorruptShareError, e:
2107-                # log it and give the other shares a chance to be processed
2108-                f = failure.Failure()
2109-                self.log(format="bad share: %(f_value)s",
2110-                         f_value=str(f.value), failure=f,
2111-                         level=log.WEIRD, umid="7fzWZw")
2112-                self.notify_server_corruption(peerid, shnum, str(e))
2113-                self.remove_peer(peerid)
2114-                self.servermap.mark_bad_share(peerid, shnum, prefix)
2115-                self._bad_shares.add( (peerid, shnum) )
2116-                self._status.problems[peerid] = f
2117-                self._last_failure = f
2118-                pass
2119-            if self._need_privkey and len(datav) > 2:
2120-                lp = None
2121-                self._try_to_validate_privkey(datav[2], peerid, shnum, lp)
2122-        # all done!
2123 
2124hunk ./src/allmydata/mutable/retrieve.py 338
2125-    def notify_server_corruption(self, peerid, shnum, reason):
2126-        ss = self.servermap.connections[peerid]
2127-        ss.callRemoteOnly("advise_corrupt_share",
2128-                          "mutable", self._storage_index, shnum, reason)
2129+    def _validate_active_prefixes(self):
2130+        """
2131+        I check to make sure that the prefixes on the peers that I am
2132+        currently reading from match the prefix that we want to see, as
2133+        said in self.verinfo.
2134 
2135hunk ./src/allmydata/mutable/retrieve.py 344
2136-    def _got_results_one_share(self, shnum, peerid,
2137-                               got_prefix, got_hash_and_data):
2138-        self.log("_got_results: got shnum #%d from peerid %s"
2139-                 % (shnum, idlib.shortnodeid_b2a(peerid)))
2140-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2141+        If I find that all of the active peers have acceptable prefixes,
2142+        I pass control to _download_current_segment, which will use
2143+        those peers to do cool things. If I find that some of the active
2144+        peers have unacceptable prefixes, I will remove them from active
2145+        peers (and from further consideration) and call
2146+        _add_active_peers to attempt to rectify the situation. I keep
2147+        track of which peers I have already validated so that I don't
2148+        need to do so again.
2149+        """
2150+        assert self._active_readers, "No more active readers"
2151+
2152+        ds = []
2153+        new_readers = set(self._active_readers) - self._validated_readers
2154+        self.log('validating %d newly-added active readers' % len(new_readers))
2155+
2156+        for reader in new_readers:
2157+            # We force a remote read here -- otherwise, we are relying
2158+            # on cached data that we already verified as valid, and we
2159+            # won't detect an uncoordinated write that has occurred
2160+            # since the last servermap update.
2161+            d = reader.get_prefix(force_remote=True)
2162+            d.addCallback(self._try_to_validate_prefix, reader)
2163+            ds.append(d)
2164+        dl = defer.DeferredList(ds, consumeErrors=True)
2165+        def _check_results(results):
2166+            # Each result in results will be of the form (success, msg).
2167+            # We don't care about msg, but success will tell us whether
2168+            # or not the checkstring validated. If it didn't, we need to
2169+            # remove the offending (peer,share) from our active readers,
2170+            # and ensure that active readers is again populated.
2171+            bad_readers = []
2172+            for i, result in enumerate(results):
2173+                if not result[0]:
2174+                    reader = self._active_readers[i]
2175+                    f = result[1]
2176+                    assert isinstance(f, failure.Failure)
2177+
2178+                    self.log("The reader %s failed to "
2179+                             "properly validate: %s" % \
2180+                             (reader, str(f.value)))
2181+                    bad_readers.append((reader, f))
2182+                else:
2183+                    reader = self._active_readers[i]
2184+                    self.log("the reader %s checks out, so we'll use it" % \
2185+                             reader)
2186+                    self._validated_readers.add(reader)
2187+                    # Each time we validate a reader, we check to see if
2188+                    # we need the private key. If we do, we politely ask
2189+                    # for it and then continue computing. If we find
2190+                    # that we haven't gotten it at the end of
2191+                    # segment decoding, then we'll take more drastic
2192+                    # measures.
2193+                    if self._need_privkey:
2194+                        d = reader.get_encprivkey()
2195+                        d.addCallback(self._try_to_validate_privkey, reader)
2196+            if bad_readers:
2197+                # We do them all at once, or else we screw up list indexing.
2198+                for (reader, f) in bad_readers:
2199+                    self._mark_bad_share(reader, f)
2200+                return self._add_active_peers()
2201+            else:
2202+                return self._download_current_segment()
2203+            # The next step will assert that it has enough active
2204+            # readers to fetch shares; we just need to remove it.
2205+        dl.addCallback(_check_results)
2206+        return dl
2207+
2208+
2209+    def _try_to_validate_prefix(self, prefix, reader):
2210+        """
2211+        I check that the prefix returned by a candidate server for
2212+        retrieval matches the prefix that the servermap knows about
2213+        (and, hence, the prefix that was validated earlier). If it does,
2214+        I return True, which means that I approve of the use of the
2215+        candidate server for segment retrieval. If it doesn't, I return
2216+        False, which means that another server must be chosen.
2217+        """
2218+        (seqnum,
2219+         root_hash,
2220+         IV,
2221+         segsize,
2222+         datalength,
2223+         k,
2224+         N,
2225+         known_prefix,
2226          offsets_tuple) = self.verinfo
2227hunk ./src/allmydata/mutable/retrieve.py 430
2228-        assert len(got_prefix) == len(prefix), (len(got_prefix), len(prefix))
2229-        if got_prefix != prefix:
2230-            msg = "someone wrote to the data since we read the servermap: prefix changed"
2231-            raise UncoordinatedWriteError(msg)
2232-        (share_hash_chain, block_hash_tree,
2233-         share_data) = unpack_share_data(self.verinfo, got_hash_and_data)
2234+        if known_prefix != prefix:
2235+            self.log("prefix from share %d doesn't match" % reader.shnum)
2236+            raise UncoordinatedWriteError("Mismatched prefix -- this could "
2237+                                          "indicate an uncoordinated write")
2238+        # Otherwise, we're okay -- no issues.
2239 
2240hunk ./src/allmydata/mutable/retrieve.py 436
2241-        assert isinstance(share_data, str)
2242-        # build the block hash tree. SDMF has only one leaf.
2243-        leaves = [hashutil.block_hash(share_data)]
2244-        t = hashtree.HashTree(leaves)
2245-        if list(t) != block_hash_tree:
2246-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
2247-        share_hash_leaf = t[0]
2248-        t2 = hashtree.IncompleteHashTree(N)
2249-        # root_hash was checked by the signature
2250-        t2.set_hashes({0: root_hash})
2251-        try:
2252-            t2.set_hashes(hashes=share_hash_chain,
2253-                          leaves={shnum: share_hash_leaf})
2254-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
2255-                IndexError), e:
2256-            msg = "corrupt hashes: %s" % (e,)
2257-            raise CorruptShareError(peerid, shnum, msg)
2258-        self.log(" data valid! len=%d" % len(share_data))
2259-        # each query comes down to this: placing validated share data into
2260-        # self.shares
2261-        self.shares[shnum] = share_data
2262 
2263hunk ./src/allmydata/mutable/retrieve.py 437
2264-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
2265+    def _remove_reader(self, reader):
2266+        """
2267+        At various points, we will wish to remove a peer from
2268+        consideration and/or use. These include, but are not necessarily
2269+        limited to:
2270 
2271hunk ./src/allmydata/mutable/retrieve.py 443
2272-        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2273-        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2274-        if alleged_writekey != self._node.get_writekey():
2275-            self.log("invalid privkey from %s shnum %d" %
2276-                     (idlib.nodeid_b2a(peerid)[:8], shnum),
2277-                     parent=lp, level=log.WEIRD, umid="YIw4tA")
2278-            return
2279+            - A connection error.
2280+            - A mismatched prefix (that is, a prefix that does not match
2281+              our conception of the version information string).
2282+            - A failing block hash, salt hash, or share hash, which can
2283+              indicate disk failure/bit flips, or network trouble.
2284 
2285hunk ./src/allmydata/mutable/retrieve.py 449
2286-        # it's good
2287-        self.log("got valid privkey from shnum %d on peerid %s" %
2288-                 (shnum, idlib.shortnodeid_b2a(peerid)),
2289-                 parent=lp)
2290-        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2291-        self._node._populate_encprivkey(enc_privkey)
2292-        self._node._populate_privkey(privkey)
2293-        self._need_privkey = False
2294+        This method will do that. I will make sure that the
2295+        (shnum,reader) combination represented by my reader argument is
2296+        not used for anything else during this download. I will not
2297+        advise the reader of any corruption, something that my callers
2298+        may wish to do on their own.
2299+        """
2300+        # TODO: When you're done writing this, see if this is ever
2301+        # actually used for something that _mark_bad_share isn't. I have
2302+        # a feeling that they will be used for very similar things, and
2303+        # that having them both here is just going to be an epic amount
2304+        # of code duplication.
2305+        #
2306+        # (well, okay, not epic, but meaningful)
2307+        self.log("removing reader %s" % reader)
2308+        # Remove the reader from _active_readers
2309+        self._active_readers.remove(reader)
2310+        # TODO: self.readers.remove(reader)?
2311+        for shnum in list(self.remaining_sharemap.keys()):
2312+            self.remaining_sharemap.discard(shnum, reader.peerid)
2313 
2314hunk ./src/allmydata/mutable/retrieve.py 469
2315-    def _query_failed(self, f, marker, peerid):
2316-        self.log(format="query to [%(peerid)s] failed",
2317-                 peerid=idlib.shortnodeid_b2a(peerid),
2318-                 level=log.NOISY)
2319-        self._status.problems[peerid] = f
2320-        self._outstanding_queries.pop(marker, None)
2321-        if not self._running:
2322-            return
2323-        self._last_failure = f
2324-        self.remove_peer(peerid)
2325-        level = log.WEIRD
2326-        if f.check(DeadReferenceError):
2327-            level = log.UNUSUAL
2328-        self.log(format="error during query: %(f_value)s",
2329-                 f_value=str(f.value), failure=f, level=level, umid="gOJB5g")
2330 
2331hunk ./src/allmydata/mutable/retrieve.py 470
2332-    def _check_for_done(self, res):
2333-        # exit paths:
2334-        #  return : keep waiting, no new queries
2335-        #  return self._send_more_queries(outstanding) : send some more queries
2336-        #  fire self._done(plaintext) : download successful
2337-        #  raise exception : download fails
2338+    def _mark_bad_share(self, reader, f):
2339+        """
2340+        I mark the (peerid, shnum) encapsulated by my reader argument as
2341+        a bad share, which means that it will not be used anywhere else.
2342 
2343hunk ./src/allmydata/mutable/retrieve.py 475
2344-        self.log(format="_check_for_done: running=%(running)s, decoding=%(decoding)s",
2345-                 running=self._running, decoding=self._decoding,
2346-                 level=log.NOISY)
2347-        if not self._running:
2348-            return
2349-        if self._decoding:
2350-            return
2351-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2352-         offsets_tuple) = self.verinfo
2353+        There are several reasons to want to mark something as a bad
2354+        share. These include:
2355 
2356hunk ./src/allmydata/mutable/retrieve.py 478
2357-        if len(self.shares) < k:
2358-            # we don't have enough shares yet
2359-            return self._maybe_send_more_queries(k)
2360-        if self._need_privkey:
2361-            # we got k shares, but none of them had a valid privkey. TODO:
2362-            # look further. Adding code to do this is a bit complicated, and
2363-            # I want to avoid that complication, and this should be pretty
2364-            # rare (k shares with bitflips in the enc_privkey but not in the
2365-            # data blocks). If we actually do get here, the subsequent repair
2366-            # will fail for lack of a privkey.
2367-            self.log("got k shares but still need_privkey, bummer",
2368-                     level=log.WEIRD, umid="MdRHPA")
2369+            - A connection error to the peer.
2370+            - A mismatched prefix (that is, a prefix that does not match
2371+              our local conception of the version information string).
2372+            - A failing block hash, salt hash, share hash, or other
2373+              integrity check.
2374 
2375hunk ./src/allmydata/mutable/retrieve.py 484
2376-        # we have enough to finish. All the shares have had their hashes
2377-        # checked, so if something fails at this point, we don't know how
2378-        # to fix it, so the download will fail.
2379+        This method will ensure that readers that we wish to mark bad
2380+        (for these reasons or other reasons) are not used for the rest
2381+        of the download. Additionally, it will attempt to tell the
2382+        remote peer (with no guarantee of success) that its share is
2383+        corrupt.
2384+        """
2385+        self.log("marking share %d on server %s as bad" % \
2386+                 (reader.shnum, reader))
2387+        self._remove_reader(reader)
2388+        self._bad_shares.add((reader.peerid, reader.shnum))
2389+        self._status.problems[reader.peerid] = f
2390+        self._last_failure = f
2391+        self.notify_server_corruption(reader.peerid, reader.shnum,
2392+                                      str(f.value))
2393 
2394hunk ./src/allmydata/mutable/retrieve.py 499
2395-        self._decoding = True # avoid reentrancy
2396-        self._status.set_status("decoding")
2397-        now = time.time()
2398-        elapsed = now - self._started
2399-        self._status.timings["fetch"] = elapsed
2400 
2401hunk ./src/allmydata/mutable/retrieve.py 500
2402-        d = defer.maybeDeferred(self._decode)
2403-        d.addCallback(self._decrypt, IV, self._node.get_readkey())
2404-        d.addBoth(self._done)
2405-        return d # purely for test convenience
2406+    def _download_current_segment(self):
2407+        """
2408+        I download, validate, decode, decrypt, and assemble the segment
2409+        that this Retrieve is currently responsible for downloading.
2410+        """
2411+        assert len(self._active_readers) >= self._required_shares
2412+        if self._current_segment < self._num_segments:
2413+            d = self._process_segment(self._current_segment)
2414+        else:
2415+            d = defer.succeed(None)
2416+        d.addCallback(self._check_for_done)
2417+        return d
2418 
2419hunk ./src/allmydata/mutable/retrieve.py 513
2420-    def _maybe_send_more_queries(self, k):
2421-        # we don't have enough shares yet. Should we send out more queries?
2422-        # There are some number of queries outstanding, each for a single
2423-        # share. If we can generate 'needed_shares' additional queries, we do
2424-        # so. If we can't, then we know this file is a goner, and we raise
2425-        # NotEnoughSharesError.
2426-        self.log(format=("_maybe_send_more_queries, have=%(have)d, k=%(k)d, "
2427-                         "outstanding=%(outstanding)d"),
2428-                 have=len(self.shares), k=k,
2429-                 outstanding=len(self._outstanding_queries),
2430-                 level=log.NOISY)
2431 
2432hunk ./src/allmydata/mutable/retrieve.py 514
2433-        remaining_shares = k - len(self.shares)
2434-        needed = remaining_shares - len(self._outstanding_queries)
2435-        if not needed:
2436-            # we have enough queries in flight already
2437+    def _process_segment(self, segnum):
2438+        """
2439+        I download, validate, decode, and decrypt one segment of the
2440+        file that this Retrieve is retrieving. This means coordinating
2441+        the process of getting k blocks of that file, validating them,
2442+        assembling them into one segment with the decoder, and then
2443+        decrypting them.
2444+        """
2445+        self.log("processing segment %d" % segnum)
2446 
2447hunk ./src/allmydata/mutable/retrieve.py 524
2448-            # TODO: but if they've been in flight for a long time, and we
2449-            # have reason to believe that new queries might respond faster
2450-            # (i.e. we've seen other queries come back faster, then consider
2451-            # sending out new queries. This could help with peers which have
2452-            # silently gone away since the servermap was updated, for which
2453-            # we're still waiting for the 15-minute TCP disconnect to happen.
2454-            self.log("enough queries are in flight, no more are needed",
2455-                     level=log.NOISY)
2456-            return
2457+        # TODO: The old code uses a marker. Should this code do that
2458+        # too? What did the Marker do?
2459+        assert len(self._active_readers) >= self._required_shares
2460+
2461+        # We need to ask each of our active readers for its block and
2462+        # salt. We will then validate those. If validation is
2463+        # successful, we will assemble the results into plaintext.
2464+        ds = []
2465+        for reader in self._active_readers:
2466+            d = reader.get_block_and_salt(segnum, queue=True)
2467+            d2 = self._get_needed_hashes(reader, segnum)
2468+            dl = defer.DeferredList([d, d2], consumeErrors=True)
2469+            dl.addCallback(self._validate_block, segnum, reader)
2470+            dl.addErrback(self._validation_or_decoding_failed, [reader])
2471+            ds.append(dl)
2472+            reader.flush()
2473+        dl = defer.DeferredList(ds)
2474+        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
2475+        return dl
2476 
2477hunk ./src/allmydata/mutable/retrieve.py 544
2478-        outstanding_shnums = set([shnum
2479-                                  for (peerid, shnum, started)
2480-                                  in self._outstanding_queries.values()])
2481-        # prefer low-numbered shares, they are more likely to be primary
2482-        available_shnums = sorted(self.remaining_sharemap.keys())
2483-        for shnum in available_shnums:
2484-            if shnum in outstanding_shnums:
2485-                # skip ones that are already in transit
2486-                continue
2487-            if shnum not in self.remaining_sharemap:
2488-                # no servers for that shnum. note that DictOfSets removes
2489-                # empty sets from the dict for us.
2490-                continue
2491-            peerid = list(self.remaining_sharemap[shnum])[0]
2492-            # get_data will remove that peerid from the sharemap, and add the
2493-            # query to self._outstanding_queries
2494-            self._status.set_status("Retrieving More Shares")
2495-            self.get_data(shnum, peerid)
2496-            needed -= 1
2497-            if not needed:
2498+
2499+    def _maybe_decode_and_decrypt_segment(self, blocks_and_salts, segnum):
2500+        """
2501+        I take the results of fetching and validating the blocks from a
2502+        callback chain in another method. If the results are such that
2503+        they tell me that validation and fetching succeeded without
2504+        incident, I will proceed with decoding and decryption.
2505+        Otherwise, I will do nothing.
2506+        """
2507+        self.log("trying to decode and decrypt segment %d" % segnum)
2508+        failures = False
2509+        for block_and_salt in blocks_and_salts:
2510+            if not block_and_salt[0] or block_and_salt[1] == None:
2511+                self.log("some validation operations failed; not proceeding")
2512+                failures = True
2513                 break
2514hunk ./src/allmydata/mutable/retrieve.py 560
2515+        if not failures:
2516+            self.log("everything looks ok, building segment %d" % segnum)
2517+            d = self._decode_blocks(blocks_and_salts, segnum)
2518+            d.addCallback(self._decrypt_segment)
2519+            d.addErrback(self._validation_or_decoding_failed,
2520+                         self._active_readers)
2521+            d.addCallback(self._set_segment)
2522+            return d
2523+        else:
2524+            return defer.succeed(None)
2525+
2526+
2527+    def _set_segment(self, segment):
2528+        """
2529+        Given a plaintext segment, I register that segment with the
2530+        target that is handling the file download.
2531+        """
2532+        self.log("got plaintext for segment %d" % self._current_segment)
2533+        self._plaintext += segment
2534+        self._current_segment += 1
2535 
2536hunk ./src/allmydata/mutable/retrieve.py 581
2537-        # at this point, we have as many outstanding queries as we can. If
2538-        # needed!=0 then we might not have enough to recover the file.
2539-        if needed:
2540-            format = ("ran out of peers: "
2541-                      "have %(have)d shares (k=%(k)d), "
2542-                      "%(outstanding)d queries in flight, "
2543-                      "need %(need)d more, "
2544-                      "found %(bad)d bad shares")
2545-            args = {"have": len(self.shares),
2546-                    "k": k,
2547-                    "outstanding": len(self._outstanding_queries),
2548-                    "need": needed,
2549-                    "bad": len(self._bad_shares),
2550-                    }
2551-            self.log(format=format,
2552-                     level=log.WEIRD, umid="ezTfjw", **args)
2553-            err = NotEnoughSharesError("%s, last failure: %s" %
2554-                                      (format % args, self._last_failure))
2555-            if self._bad_shares:
2556-                self.log("We found some bad shares this pass. You should "
2557-                         "update the servermap and try again to check "
2558-                         "more peers",
2559-                         level=log.WEIRD, umid="EFkOlA")
2560-                err.servermap = self.servermap
2561-            raise err
2562 
2563hunk ./src/allmydata/mutable/retrieve.py 582
2564+    def _validation_or_decoding_failed(self, f, readers):
2565+        """
2566+        I am called when a block or a salt fails to correctly validate, or when
2567+        the decryption or decoding operation fails for some reason.  I react to
2568+        this failure by notifying the remote server of corruption, and then
2569+        removing the remote peer from further activity.
2570+        """
2571+        assert isinstance(readers, list)
2572+        bad_shnums = [reader.shnum for reader in readers]
2573+
2574+        self.log("validation or decoding failed on share(s) %s, peer(s) %s "
2575+                 ", segment %d: %s" % \
2576+                 (bad_shnums, readers, self._current_segment, str(f)))
2577+        for reader in readers:
2578+            self._mark_bad_share(reader, f)
2579         return
2580 
2581hunk ./src/allmydata/mutable/retrieve.py 599
2582-    def _decode(self):
2583-        started = time.time()
2584-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2585-         offsets_tuple) = self.verinfo
2586 
2587hunk ./src/allmydata/mutable/retrieve.py 600
2588-        # shares_dict is a dict mapping shnum to share data, but the codec
2589-        # wants two lists.
2590-        shareids = []; shares = []
2591-        for shareid, share in self.shares.items():
2592+    def _validate_block(self, results, segnum, reader):
2593+        """
2594+        I validate a block from one share on a remote server.
2595+        """
2596+        # Grab the part of the block hash tree that is necessary to
2597+        # validate this block, then generate the block hash root.
2598+        self.log("validating share %d for segment %d" % (reader.shnum,
2599+                                                             segnum))
2600+        # Did we fail to fetch either of the things that we were
2601+        # supposed to? Fail if so.
2602+        if not results[0][0] and results[1][0]:
2603+            # handled by the errback handler.
2604+
2605+            # These all get batched into one query, so the resulting
2606+            # failure should be the same for all of them, so we can just
2607+            # use the first one.
2608+            assert isinstance(results[0][1], failure.Failure)
2609+
2610+            f = results[0][1]
2611+            raise CorruptShareError(reader.peerid,
2612+                                    reader.shnum,
2613+                                    "Connection error: %s" % str(f))
2614+
2615+        block_and_salt, block_and_sharehashes = results
2616+        block, salt = block_and_salt[1]
2617+        blockhashes, sharehashes = block_and_sharehashes[1]
2618+
2619+        blockhashes = dict(enumerate(blockhashes[1]))
2620+        self.log("the reader gave me the following blockhashes: %s" % \
2621+                 blockhashes.keys())
2622+        self.log("the reader gave me the following sharehashes: %s" % \
2623+                 sharehashes[1].keys())
2624+        bht = self._block_hash_trees[reader.shnum]
2625+
2626+        if bht.needed_hashes(segnum, include_leaf=True):
2627+            try:
2628+                bht.set_hashes(blockhashes)
2629+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2630+                    IndexError), e:
2631+                raise CorruptShareError(reader.peerid,
2632+                                        reader.shnum,
2633+                                        "block hash tree failure: %s" % e)
2634+
2635+        if self._version == MDMF_VERSION:
2636+            blockhash = hashutil.block_hash(salt + block)
2637+        else:
2638+            blockhash = hashutil.block_hash(block)
2639+        # If this works without an error, then validation is
2640+        # successful.
2641+        try:
2642+           bht.set_hashes(leaves={segnum: blockhash})
2643+        except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2644+                IndexError), e:
2645+            raise CorruptShareError(reader.peerid,
2646+                                    reader.shnum,
2647+                                    "block hash tree failure: %s" % e)
2648+
2649+        # Reaching this point means that we know that this segment
2650+        # is correct. Now we need to check to see whether the share
2651+        # hash chain is also correct.
2652+        # SDMF wrote share hash chains that didn't contain the
2653+        # leaves, which would be produced from the block hash tree.
2654+        # So we need to validate the block hash tree first. If
2655+        # successful, then bht[0] will contain the root for the
2656+        # shnum, which will be a leaf in the share hash tree, which
2657+        # will allow us to validate the rest of the tree.
2658+        if self.share_hash_tree.needed_hashes(reader.shnum,
2659+                                               include_leaf=True):
2660+            try:
2661+                self.share_hash_tree.set_hashes(hashes=sharehashes[1],
2662+                                            leaves={reader.shnum: bht[0]})
2663+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2664+                    IndexError), e:
2665+                raise CorruptShareError(reader.peerid,
2666+                                        reader.shnum,
2667+                                        "corrupt hashes: %s" % e)
2668+
2669+        # TODO: Validate the salt, too.
2670+        self.log('share %d is valid for segment %d' % (reader.shnum,
2671+                                                       segnum))
2672+        return {reader.shnum: (block, salt)}
2673+
2674+
2675+    def _get_needed_hashes(self, reader, segnum):
2676+        """
2677+        I get the hashes needed to validate segnum from the reader, then return
2678+        to my caller when this is done.
2679+        """
2680+        bht = self._block_hash_trees[reader.shnum]
2681+        needed = bht.needed_hashes(segnum, include_leaf=True)
2682+        # The root of the block hash tree is also a leaf in the share
2683+        # hash tree. So we don't need to fetch it from the remote
2684+        # server. In the case of files with one segment, this means that
2685+        # we won't fetch any block hash tree from the remote server,
2686+        # since the hash of each share of the file is the entire block
2687+        # hash tree, and is a leaf in the share hash tree. This is fine,
2688+        # since any share corruption will be detected in the share hash
2689+        # tree.
2690+        #needed.discard(0)
2691+        self.log("getting blockhashes for segment %d, share %d: %s" % \
2692+                 (segnum, reader.shnum, str(needed)))
2693+        d1 = reader.get_blockhashes(needed, queue=True, force_remote=True)
2694+        if self.share_hash_tree.needed_hashes(reader.shnum):
2695+            need = self.share_hash_tree.needed_hashes(reader.shnum)
2696+            self.log("also need sharehashes for share %d: %s" % (reader.shnum,
2697+                                                                 str(need)))
2698+            d2 = reader.get_sharehashes(need, queue=True, force_remote=True)
2699+        else:
2700+            d2 = defer.succeed({}) # the logic in the next method
2701+                                   # expects a dict
2702+        dl = defer.DeferredList([d1, d2], consumeErrors=True)
2703+        return dl
2704+
2705+
2706+    def _decode_blocks(self, blocks_and_salts, segnum):
2707+        """
2708+        I take a list of k blocks and salts, and decode that into a
2709+        single encrypted segment.
2710+        """
2711+        d = {}
2712+        # We want to merge our dictionaries to the form
2713+        # {shnum: blocks_and_salts}
2714+        #
2715+        # The dictionaries come from validate block that way, so we just
2716+        # need to merge them.
2717+        for block_and_salt in blocks_and_salts:
2718+            d.update(block_and_salt[1])
2719+
2720+        # All of these blocks should have the same salt; in SDMF, it is
2721+        # the file-wide IV, while in MDMF it is the per-segment salt. In
2722+        # either case, we just need to get one of them and use it.
2723+        #
2724+        # d.items()[0] is like (shnum, (block, salt))
2725+        # d.items()[0][1] is like (block, salt)
2726+        # d.items()[0][1][1] is the salt.
2727+        salt = d.items()[0][1][1]
2728+        # Next, extract just the blocks from the dict. We'll use the
2729+        # salt in the next step.
2730+        share_and_shareids = [(k, v[0]) for k, v in d.items()]
2731+        d2 = dict(share_and_shareids)
2732+        shareids = []
2733+        shares = []
2734+        for shareid, share in d2.items():
2735             shareids.append(shareid)
2736             shares.append(share)
2737 
2738hunk ./src/allmydata/mutable/retrieve.py 746
2739-        assert len(shareids) >= k, len(shareids)
2740+        assert len(shareids) >= self._required_shares, len(shareids)
2741         # zfec really doesn't want extra shares
2742hunk ./src/allmydata/mutable/retrieve.py 748
2743-        shareids = shareids[:k]
2744-        shares = shares[:k]
2745-
2746-        fec = codec.CRSDecoder()
2747-        fec.set_params(segsize, k, N)
2748-
2749-        self.log("params %s, we have %d shares" % ((segsize, k, N), len(shares)))
2750-        self.log("about to decode, shareids=%s" % (shareids,))
2751-        d = defer.maybeDeferred(fec.decode, shares, shareids)
2752-        def _done(buffers):
2753-            self._status.timings["decode"] = time.time() - started
2754-            self.log(" decode done, %d buffers" % len(buffers))
2755+        shareids = shareids[:self._required_shares]
2756+        shares = shares[:self._required_shares]
2757+        self.log("decoding segment %d" % segnum)
2758+        if segnum == self._num_segments - 1:
2759+            d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids)
2760+        else:
2761+            d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids)
2762+        def _process(buffers):
2763             segment = "".join(buffers)
2764hunk ./src/allmydata/mutable/retrieve.py 757
2765+            self.log(format="now decoding segment %(segnum)s of %(numsegs)s",
2766+                     segnum=segnum,
2767+                     numsegs=self._num_segments,
2768+                     level=log.NOISY)
2769             self.log(" joined length %d, datalength %d" %
2770hunk ./src/allmydata/mutable/retrieve.py 762
2771-                     (len(segment), datalength))
2772-            segment = segment[:datalength]
2773+                     (len(segment), self._data_length))
2774+            if segnum == self._num_segments - 1:
2775+                size_to_use = self._tail_data_size
2776+            else:
2777+                size_to_use = self._segment_size
2778+            segment = segment[:size_to_use]
2779             self.log(" segment len=%d" % len(segment))
2780hunk ./src/allmydata/mutable/retrieve.py 769
2781-            return segment
2782-        def _err(f):
2783-            self.log(" decode failed: %s" % f)
2784-            return f
2785-        d.addCallback(_done)
2786-        d.addErrback(_err)
2787+            return segment, salt
2788+        d.addCallback(_process)
2789         return d
2790 
2791hunk ./src/allmydata/mutable/retrieve.py 773
2792-    def _decrypt(self, crypttext, IV, readkey):
2793+
2794+    def _decrypt_segment(self, segment_and_salt):
2795+        """
2796+        I take a single segment and its salt, and decrypt it. I return
2797+        the plaintext of the segment that is in my argument.
2798+        """
2799+        segment, salt = segment_and_salt
2800         self._status.set_status("decrypting")
2801hunk ./src/allmydata/mutable/retrieve.py 781
2802+        self.log("decrypting segment %d" % self._current_segment)
2803         started = time.time()
2804hunk ./src/allmydata/mutable/retrieve.py 783
2805-        key = hashutil.ssk_readkey_data_hash(IV, readkey)
2806+        key = hashutil.ssk_readkey_data_hash(salt, self._node.get_readkey())
2807         decryptor = AES(key)
2808hunk ./src/allmydata/mutable/retrieve.py 785
2809-        plaintext = decryptor.process(crypttext)
2810+        plaintext = decryptor.process(segment)
2811         self._status.timings["decrypt"] = time.time() - started
2812         return plaintext
2813 
2814hunk ./src/allmydata/mutable/retrieve.py 789
2815-    def _done(self, res):
2816-        if not self._running:
2817+
2818+    def notify_server_corruption(self, peerid, shnum, reason):
2819+        ss = self.servermap.connections[peerid]
2820+        ss.callRemoteOnly("advise_corrupt_share",
2821+                          "mutable", self._storage_index, shnum, reason)
2822+
2823+
2824+    def _try_to_validate_privkey(self, enc_privkey, reader):
2825+
2826+        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2827+        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2828+        if alleged_writekey != self._node.get_writekey():
2829+            self.log("invalid privkey from %s shnum %d" %
2830+                     (reader, reader.shnum),
2831+                     level=log.WEIRD, umid="YIw4tA")
2832             return
2833hunk ./src/allmydata/mutable/retrieve.py 805
2834-        self._running = False
2835-        self._status.set_active(False)
2836-        self._status.timings["total"] = time.time() - self._started
2837-        # res is either the new contents, or a Failure
2838-        if isinstance(res, failure.Failure):
2839-            self.log("Retrieve done, with failure", failure=res,
2840-                     level=log.UNUSUAL)
2841-            self._status.set_status("Failed")
2842-        else:
2843-            self.log("Retrieve done, success!")
2844-            self._status.set_status("Finished")
2845-            self._status.set_progress(1.0)
2846-            # remember the encoding parameters, use them again next time
2847-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2848-             offsets_tuple) = self.verinfo
2849-            self._node._populate_required_shares(k)
2850-            self._node._populate_total_shares(N)
2851-        eventually(self._done_deferred.callback, res)
2852 
2853hunk ./src/allmydata/mutable/retrieve.py 806
2854+        # it's good
2855+        self.log("got valid privkey from shnum %d on reader %s" %
2856+                 (reader.shnum, reader))
2857+        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2858+        self._node._populate_encprivkey(enc_privkey)
2859+        self._node._populate_privkey(privkey)
2860+        self._need_privkey = False
2861+
2862+
2863+    def _check_for_done(self, res):
2864+        """
2865+        I check to see if this Retrieve object has successfully finished
2866+        its work.
2867+
2868+        I can exit in the following ways:
2869+            - If there are no more segments to download, then I exit by
2870+              causing self._done_deferred to fire with the plaintext
2871+              content requested by the caller.
2872+            - If there are still segments to be downloaded, and there
2873+              are enough active readers (readers which have not broken
2874+              and have not given us corrupt data) to continue
2875+              downloading, I send control back to
2876+              _download_current_segment.
2877+            - If there are still segments to be downloaded but there are
2878+              not enough active peers to download them, I ask
2879+              _add_active_peers to add more peers. If it is successful,
2880+              it will call _download_current_segment. If there are not
2881+              enough peers to retrieve the file, then that will cause
2882+              _done_deferred to errback.
2883+        """
2884+        self.log("checking for doneness")
2885+        if self._current_segment == self._num_segments:
2886+            # No more segments to download, we're done.
2887+            self.log("got plaintext, done")
2888+            return self._done()
2889+
2890+        if len(self._active_readers) >= self._required_shares:
2891+            # More segments to download, but we have enough good peers
2892+            # in self._active_readers that we can do that without issue,
2893+            # so go nab the next segment.
2894+            self.log("not done yet: on segment %d of %d" % \
2895+                     (self._current_segment + 1, self._num_segments))
2896+            return self._download_current_segment()
2897+
2898+        self.log("not done yet: on segment %d of %d, need to add peers" % \
2899+                 (self._current_segment + 1, self._num_segments))
2900+        return self._add_active_peers()
2901+
2902+
2903+    def _done(self):
2904+        """
2905+        I am called by _check_for_done when the download process has
2906+        finished successfully. After making some useful logging
2907+        statements, I return the decrypted contents to the owner of this
2908+        Retrieve object through self._done_deferred.
2909+        """
2910+        eventually(self._done_deferred.callback, self._plaintext)
2911+
2912+
2913+    def _failed(self):
2914+        """
2915+        I am called by _add_active_peers when there are not enough
2916+        active peers left to complete the download. After making some
2917+        useful logging statements, I return an exception to that effect
2918+        to the caller of this Retrieve object through
2919+        self._done_deferred.
2920+        """
2921+        format = ("ran out of peers: "
2922+                  "have %(have)d of %(total)d segments "
2923+                  "found %(bad)d bad shares "
2924+                  "encoding %(k)d-of-%(n)d")
2925+        args = {"have": self._current_segment,
2926+                "total": self._num_segments,
2927+                "k": self._required_shares,
2928+                "n": self._total_shares,
2929+                "bad": len(self._bad_shares)}
2930+        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
2931+                                                        str(self._last_failure)))
2932+        f = failure.Failure(e)
2933+        eventually(self._done_deferred.callback, f)
2934hunk ./src/allmydata/test/test_mutable.py 12
2935 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
2936      ssk_pubkey_fingerprint_hash
2937 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
2938-     NotEnoughSharesError
2939+     NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
2940 from allmydata.monitor import Monitor
2941 from allmydata.test.common import ShouldFailMixin
2942 from allmydata.test.no_network import GridTestMixin
2943hunk ./src/allmydata/test/test_mutable.py 28
2944 from allmydata.mutable.retrieve import Retrieve
2945 from allmydata.mutable.publish import Publish
2946 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
2947-from allmydata.mutable.layout import unpack_header, unpack_share
2948+from allmydata.mutable.layout import unpack_header, unpack_share, \
2949+                                     MDMFSlotReadProxy
2950 from allmydata.mutable.repairer import MustForceRepairError
2951 
2952 import allmydata.test.common_util as testutil
2953hunk ./src/allmydata/test/test_mutable.py 104
2954         d = fireEventually()
2955         d.addCallback(lambda res: _call())
2956         return d
2957+
2958     def callRemoteOnly(self, methname, *args, **kwargs):
2959         d = self.callRemote(methname, *args, **kwargs)
2960         d.addBoth(lambda ignore: None)
2961hunk ./src/allmydata/test/test_mutable.py 163
2962 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
2963     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
2964     # list of shnums to corrupt.
2965+    ds = []
2966     for peerid in s._peers:
2967         shares = s._peers[peerid]
2968         for shnum in shares:
2969hunk ./src/allmydata/test/test_mutable.py 190
2970                 else:
2971                     offset1 = offset
2972                     offset2 = 0
2973-                if offset1 == "pubkey":
2974+                if offset1 == "pubkey" and IV:
2975                     real_offset = 107
2976hunk ./src/allmydata/test/test_mutable.py 192
2977+                elif offset1 == "share_data" and not IV:
2978+                    real_offset = 104
2979                 elif offset1 in o:
2980                     real_offset = o[offset1]
2981                 else:
2982hunk ./src/allmydata/test/test_mutable.py 327
2983         d.addCallback(_created)
2984         return d
2985 
2986+
2987+    def test_upload_and_download_mdmf(self):
2988+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
2989+        def _created(n):
2990+            d = defer.succeed(None)
2991+            d.addCallback(lambda ignored:
2992+                n.get_servermap(MODE_READ))
2993+            def _then(servermap):
2994+                dumped = servermap.dump(StringIO())
2995+                self.failUnlessIn("3-of-10", dumped.getvalue())
2996+            d.addCallback(_then)
2997+            # Now overwrite the contents with some new contents. We want
2998+            # to make them big enough to force the file to be uploaded
2999+            # in more than one segment.
3000+            big_contents = "contents1" * 100000 # about 900 KiB
3001+            d.addCallback(lambda ignored:
3002+                n.overwrite(big_contents))
3003+            d.addCallback(lambda ignored:
3004+                n.download_best_version())
3005+            d.addCallback(lambda data:
3006+                self.failUnlessEqual(data, big_contents))
3007+            # Overwrite the contents again with some new contents. As
3008+            # before, they need to be big enough to force multiple
3009+            # segments, so that we make the downloader deal with
3010+            # multiple segments.
3011+            bigger_contents = "contents2" * 1000000 # about 9MiB
3012+            d.addCallback(lambda ignored:
3013+                n.overwrite(bigger_contents))
3014+            d.addCallback(lambda ignored:
3015+                n.download_best_version())
3016+            d.addCallback(lambda data:
3017+                self.failUnlessEqual(data, bigger_contents))
3018+            return d
3019+        d.addCallback(_created)
3020+        return d
3021+
3022+
3023     def test_create_with_initial_contents(self):
3024         d = self.nodemaker.create_mutable_file("contents 1")
3025         def _created(n):
3026hunk ./src/allmydata/test/test_mutable.py 1147
3027 
3028 
3029     def _test_corrupt_all(self, offset, substring,
3030-                          should_succeed=False, corrupt_early=True,
3031-                          failure_checker=None):
3032+                          should_succeed=False,
3033+                          corrupt_early=True,
3034+                          failure_checker=None,
3035+                          fetch_privkey=False):
3036         d = defer.succeed(None)
3037         if corrupt_early:
3038             d.addCallback(corrupt, self._storage, offset)
3039hunk ./src/allmydata/test/test_mutable.py 1167
3040                     self.failUnlessIn(substring, "".join(allproblems))
3041                 return servermap
3042             if should_succeed:
3043-                d1 = self._fn.download_version(servermap, ver)
3044+                d1 = self._fn.download_version(servermap, ver,
3045+                                               fetch_privkey)
3046                 d1.addCallback(lambda new_contents:
3047                                self.failUnlessEqual(new_contents, self.CONTENTS))
3048             else:
3049hunk ./src/allmydata/test/test_mutable.py 1175
3050                 d1 = self.shouldFail(NotEnoughSharesError,
3051                                      "_corrupt_all(offset=%s)" % (offset,),
3052                                      substring,
3053-                                     self._fn.download_version, servermap, ver)
3054+                                     self._fn.download_version, servermap,
3055+                                                                ver,
3056+                                                                fetch_privkey)
3057             if failure_checker:
3058                 d1.addCallback(failure_checker)
3059             d1.addCallback(lambda res: servermap)
3060hunk ./src/allmydata/test/test_mutable.py 1186
3061         return d
3062 
3063     def test_corrupt_all_verbyte(self):
3064-        # when the version byte is not 0, we hit an UnknownVersionError error
3065-        # in unpack_share().
3066+        # when the version byte is not 0 or 1, we hit an UnknownVersionError
3067+        # error in unpack_share().
3068         d = self._test_corrupt_all(0, "UnknownVersionError")
3069         def _check_servermap(servermap):
3070             # and the dump should mention the problems
3071hunk ./src/allmydata/test/test_mutable.py 1193
3072             s = StringIO()
3073             dump = servermap.dump(s).getvalue()
3074-            self.failUnless("10 PROBLEMS" in dump, dump)
3075+            self.failUnless("30 PROBLEMS" in dump, dump)
3076         d.addCallback(_check_servermap)
3077         return d
3078 
3079hunk ./src/allmydata/test/test_mutable.py 1263
3080         return self._test_corrupt_all("enc_privkey", None, should_succeed=True)
3081 
3082 
3083+    def test_corrupt_all_encprivkey_late(self):
3084+        # this should work for the same reason as above, but we corrupt
3085+        # after the servermap update to exercise the error handling
3086+        # code.
3087+        # We need to remove the privkey from the node, or the retrieve
3088+        # process won't know to update it.
3089+        self._fn._privkey = None
3090+        return self._test_corrupt_all("enc_privkey",
3091+                                      None, # this shouldn't fail
3092+                                      should_succeed=True,
3093+                                      corrupt_early=False,
3094+                                      fetch_privkey=True)
3095+
3096+
3097     def test_corrupt_all_seqnum_late(self):
3098         # corrupting the seqnum between mapupdate and retrieve should result
3099         # in NotEnoughSharesError, since each share will look invalid
3100hunk ./src/allmydata/test/test_mutable.py 1283
3101         def _check(res):
3102             f = res[0]
3103             self.failUnless(f.check(NotEnoughSharesError))
3104-            self.failUnless("someone wrote to the data since we read the servermap" in str(f))
3105+            self.failUnless("uncoordinated write" in str(f))
3106         return self._test_corrupt_all(1, "ran out of peers",
3107                                       corrupt_early=False,
3108                                       failure_checker=_check)
3109hunk ./src/allmydata/test/test_mutable.py 1333
3110                       self.failUnlessEqual(new_contents, self.CONTENTS))
3111         return d
3112 
3113-    def test_corrupt_some(self):
3114-        # corrupt the data of first five shares (so the servermap thinks
3115-        # they're good but retrieve marks them as bad), so that the
3116-        # MODE_READ set of 6 will be insufficient, forcing node.download to
3117-        # retry with more servers.
3118-        corrupt(None, self._storage, "share_data", range(5))
3119-        d = self.make_servermap()
3120+
3121+    def _test_corrupt_some(self, offset, mdmf=False):
3122+        if mdmf:
3123+            d = self.publish_mdmf()
3124+        else:
3125+            d = defer.succeed(None)
3126+        d.addCallback(lambda ignored:
3127+            corrupt(None, self._storage, offset, range(5)))
3128+        d.addCallback(lambda ignored:
3129+            self.make_servermap())
3130         def _do_retrieve(servermap):
3131             ver = servermap.best_recoverable_version()
3132             self.failUnless(ver)
3133hunk ./src/allmydata/test/test_mutable.py 1349
3134             return self._fn.download_best_version()
3135         d.addCallback(_do_retrieve)
3136         d.addCallback(lambda new_contents:
3137-                      self.failUnlessEqual(new_contents, self.CONTENTS))
3138+            self.failUnlessEqual(new_contents, self.CONTENTS))
3139         return d
3140 
3141hunk ./src/allmydata/test/test_mutable.py 1352
3142+
3143+    def test_corrupt_some(self):
3144+        # corrupt the data of first five shares (so the servermap thinks
3145+        # they're good but retrieve marks them as bad), so that the
3146+        # MODE_READ set of 6 will be insufficient, forcing node.download to
3147+        # retry with more servers.
3148+        return self._test_corrupt_some("share_data")
3149+
3150+
3151     def test_download_fails(self):
3152         d = corrupt(None, self._storage, "signature")
3153         d.addCallback(lambda ignored:
3154hunk ./src/allmydata/test/test_mutable.py 1366
3155             self.shouldFail(UnrecoverableFileError, "test_download_anyway",
3156                             "no recoverable versions",
3157-                            self._fn.download_best_version)
3158+                            self._fn.download_best_version))
3159         return d
3160 
3161 
3162hunk ./src/allmydata/test/test_mutable.py 1370
3163+
3164+    def test_corrupt_mdmf_block_hash_tree(self):
3165+        d = self.publish_mdmf()
3166+        d.addCallback(lambda ignored:
3167+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3168+                                   "block hash tree failure",
3169+                                   corrupt_early=False,
3170+                                   should_succeed=False))
3171+        return d
3172+
3173+
3174+    def test_corrupt_mdmf_block_hash_tree_late(self):
3175+        d = self.publish_mdmf()
3176+        d.addCallback(lambda ignored:
3177+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3178+                                   "block hash tree failure",
3179+                                   corrupt_early=True,
3180+                                   should_succeed=False))
3181+        return d
3182+
3183+
3184+    def test_corrupt_mdmf_share_data(self):
3185+        d = self.publish_mdmf()
3186+        d.addCallback(lambda ignored:
3187+            # TODO: Find out what the block size is and corrupt a
3188+            # specific block, rather than just guessing.
3189+            self._test_corrupt_all(("share_data", 12 * 40),
3190+                                    "block hash tree failure",
3191+                                    corrupt_early=True,
3192+                                    should_succeed=False))
3193+        return d
3194+
3195+
3196+    def test_corrupt_some_mdmf(self):
3197+        return self._test_corrupt_some(("share_data", 12 * 40),
3198+                                       mdmf=True)
3199+
3200+
3201 class CheckerMixin:
3202     def check_good(self, r, where):
3203         self.failUnless(r.is_healthy(), where)
3204hunk ./src/allmydata/test/test_mutable.py 2116
3205             d.addCallback(lambda res:
3206                           self.shouldFail(NotEnoughSharesError,
3207                                           "test_retrieve_surprise",
3208-                                          "ran out of peers: have 0 shares (k=3)",
3209+                                          "ran out of peers: have 0 of 1",
3210                                           n.download_version,
3211                                           self.old_map,
3212                                           self.old_map.best_recoverable_version(),
3213hunk ./src/allmydata/test/test_mutable.py 2125
3214         d.addCallback(_created)
3215         return d
3216 
3217+
3218     def test_unexpected_shares(self):
3219         # upload the file, take a servermap, shut down one of the servers,
3220         # upload it again (causing shares to appear on a new server), then
3221hunk ./src/allmydata/test/test_mutable.py 2329
3222         self.basedir = "mutable/Problems/test_privkey_query_missing"
3223         self.set_up_grid(num_servers=20)
3224         nm = self.g.clients[0].nodemaker
3225-        LARGE = "These are Larger contents" * 2000 # about 50KB
3226+        LARGE = "These are Larger contents" * 2000 # about 50KiB
3227         nm._node_cache = DevNullDictionary() # disable the nodecache
3228 
3229         d = nm.create_mutable_file(LARGE)
3230hunk ./src/allmydata/test/test_mutable.py 2342
3231         d.addCallback(_created)
3232         d.addCallback(lambda res: self.n2.get_servermap(MODE_WRITE))
3233         return d
3234+
3235+
3236+    def test_block_and_hash_query_error(self):
3237+        # This tests for what happens when a query to a remote server
3238+        # fails in either the hash validation step or the block getting
3239+        # step (because of batching, this is the same actual query).
3240+        # We need to have the storage server persist up until the point
3241+        # that its prefix is validated, then suddenly die. This
3242+        # exercises some exception handling code in Retrieve.
3243+        self.basedir = "mutable/Problems/test_block_and_hash_query_error"
3244+        self.set_up_grid(num_servers=20)
3245+        nm = self.g.clients[0].nodemaker
3246+        CONTENTS = "contents" * 2000
3247+        d = nm.create_mutable_file(CONTENTS)
3248+        def _created(node):
3249+            self._node = node
3250+        d.addCallback(_created)
3251+        d.addCallback(lambda ignored:
3252+            self._node.get_servermap(MODE_READ))
3253+        def _then(servermap):
3254+            # we have our servermap. Now we set up the servers like the
3255+            # tests above -- the first one that gets a read call should
3256+            # start throwing errors, but only after returning its prefix
3257+            # for validation. Since we'll download without fetching the
3258+            # private key, the next query to the remote server will be
3259+            # for either a block and salt or for hashes, either of which
3260+            # will exercise the error handling code.
3261+            killer = FirstServerGetsKilled()
3262+            for (serverid, ss) in nm.storage_broker.get_all_servers():
3263+                ss.post_call_notifier = killer.notify
3264+            ver = servermap.best_recoverable_version()
3265+            assert ver
3266+            return self._node.download_version(servermap, ver)
3267+        d.addCallback(_then)
3268+        d.addCallback(lambda data:
3269+            self.failUnlessEqual(data, CONTENTS))
3270+        return d
3271}
3272[mutable/checker.py: check MDMF files
3273Kevan Carstensen <kevan@isnotajoke.com>**20100628225048
3274 Ignore-this: fb697b36285d60552df6ca5ac6a37629
3275 
3276 This patch adapts the mutable file checker and verifier to check and
3277 verify MDMF files. It does this by using the new segmented downloader,
3278 which is trained to perform verification operations on request. This
3279 removes some code duplication.
3280] {
3281hunk ./src/allmydata/mutable/checker.py 12
3282 from allmydata.mutable.common import MODE_CHECK, CorruptShareError
3283 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
3284 from allmydata.mutable.layout import unpack_share, SIGNED_PREFIX_LENGTH
3285+from allmydata.mutable.retrieve import Retrieve # for verifying
3286 
3287 class MutableChecker:
3288 
3289hunk ./src/allmydata/mutable/checker.py 29
3290 
3291     def check(self, verify=False, add_lease=False):
3292         servermap = ServerMap()
3293+        # Updating the servermap in MODE_CHECK will stand a good chance
3294+        # of finding all of the shares, and getting a good idea of
3295+        # recoverability, etc, without verifying.
3296         u = ServermapUpdater(self._node, self._storage_broker, self._monitor,
3297                              servermap, MODE_CHECK, add_lease=add_lease)
3298         if self._history:
3299hunk ./src/allmydata/mutable/checker.py 55
3300         if num_recoverable:
3301             self.best_version = servermap.best_recoverable_version()
3302 
3303+        # The file is unhealthy and needs to be repaired if:
3304+        # - There are unrecoverable versions.
3305         if servermap.unrecoverable_versions():
3306             self.need_repair = True
3307hunk ./src/allmydata/mutable/checker.py 59
3308+        # - There isn't a recoverable version.
3309         if num_recoverable != 1:
3310             self.need_repair = True
3311hunk ./src/allmydata/mutable/checker.py 62
3312+        # - The best recoverable version is missing some shares.
3313         if self.best_version:
3314             available_shares = servermap.shares_available()
3315             (num_distinct_shares, k, N) = available_shares[self.best_version]
3316hunk ./src/allmydata/mutable/checker.py 73
3317 
3318     def _verify_all_shares(self, servermap):
3319         # read every byte of each share
3320+        #
3321+        # This logic is going to be very nearly the same as the
3322+        # downloader. I bet we could pass the downloader a flag that
3323+        # makes it do this, and piggyback onto that instead of
3324+        # duplicating a bunch of code.
3325+        #
3326+        # Like:
3327+        #  r = Retrieve(blah, blah, blah, verify=True)
3328+        #  d = r.download()
3329+        #  (wait, wait, wait, d.callback)
3330+        # 
3331+        #  Then, when it has finished, we can check the servermap (which
3332+        #  we provided to Retrieve) to figure out which shares are bad,
3333+        #  since the Retrieve process will have updated the servermap as
3334+        #  it went along.
3335+        #
3336+        #  By passing the verify=True flag to the constructor, we are
3337+        #  telling the downloader a few things.
3338+        #
3339+        #  1. It needs to download all N shares, not just K shares.
3340+        #  2. It doesn't need to decrypt or decode the shares, only
3341+        #     verify them.
3342         if not self.best_version:
3343             return
3344hunk ./src/allmydata/mutable/checker.py 97
3345-        versionmap = servermap.make_versionmap()
3346-        shares = versionmap[self.best_version]
3347-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3348-         offsets_tuple) = self.best_version
3349-        offsets = dict(offsets_tuple)
3350-        readv = [ (0, offsets["EOF"]) ]
3351-        dl = []
3352-        for (shnum, peerid, timestamp) in shares:
3353-            ss = servermap.connections[peerid]
3354-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
3355-            d.addCallback(self._got_answer, peerid, servermap)
3356-            dl.append(d)
3357-        return defer.DeferredList(dl, fireOnOneErrback=True, consumeErrors=True)
3358 
3359hunk ./src/allmydata/mutable/checker.py 98
3360-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
3361-        # isolate the callRemote to a separate method, so tests can subclass
3362-        # Publish and override it
3363-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
3364+        r = Retrieve(self._node, servermap, self.best_version, verify=True)
3365+        d = r.download()
3366+        d.addCallback(self._process_bad_shares)
3367         return d
3368 
3369hunk ./src/allmydata/mutable/checker.py 103
3370-    def _got_answer(self, datavs, peerid, servermap):
3371-        for shnum,datav in datavs.items():
3372-            data = datav[0]
3373-            try:
3374-                self._got_results_one_share(shnum, peerid, data)
3375-            except CorruptShareError:
3376-                f = failure.Failure()
3377-                self.need_repair = True
3378-                self.bad_shares.append( (peerid, shnum, f) )
3379-                prefix = data[:SIGNED_PREFIX_LENGTH]
3380-                servermap.mark_bad_share(peerid, shnum, prefix)
3381-                ss = servermap.connections[peerid]
3382-                self.notify_server_corruption(ss, shnum, str(f.value))
3383-
3384-    def check_prefix(self, peerid, shnum, data):
3385-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3386-         offsets_tuple) = self.best_version
3387-        got_prefix = data[:SIGNED_PREFIX_LENGTH]
3388-        if got_prefix != prefix:
3389-            raise CorruptShareError(peerid, shnum,
3390-                                    "prefix mismatch: share changed while we were reading it")
3391-
3392-    def _got_results_one_share(self, shnum, peerid, data):
3393-        self.check_prefix(peerid, shnum, data)
3394-
3395-        # the [seqnum:signature] pieces are validated by _compare_prefix,
3396-        # which checks their signature against the pubkey known to be
3397-        # associated with this file.
3398 
3399hunk ./src/allmydata/mutable/checker.py 104
3400-        (seqnum, root_hash, IV, k, N, segsize, datalen, pubkey, signature,
3401-         share_hash_chain, block_hash_tree, share_data,
3402-         enc_privkey) = unpack_share(data)
3403-
3404-        # validate [share_hash_chain,block_hash_tree,share_data]
3405-
3406-        leaves = [hashutil.block_hash(share_data)]
3407-        t = hashtree.HashTree(leaves)
3408-        if list(t) != block_hash_tree:
3409-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
3410-        share_hash_leaf = t[0]
3411-        t2 = hashtree.IncompleteHashTree(N)
3412-        # root_hash was checked by the signature
3413-        t2.set_hashes({0: root_hash})
3414-        try:
3415-            t2.set_hashes(hashes=share_hash_chain,
3416-                          leaves={shnum: share_hash_leaf})
3417-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
3418-                IndexError), e:
3419-            msg = "corrupt hashes: %s" % (e,)
3420-            raise CorruptShareError(peerid, shnum, msg)
3421-
3422-        # validate enc_privkey: only possible if we have a write-cap
3423-        if not self._node.is_readonly():
3424-            alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3425-            alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3426-            if alleged_writekey != self._node.get_writekey():
3427-                raise CorruptShareError(peerid, shnum, "invalid privkey")
3428+    def _process_bad_shares(self, bad_shares):
3429+        if bad_shares:
3430+            self.need_repair = True
3431+        self.bad_shares = bad_shares
3432 
3433hunk ./src/allmydata/mutable/checker.py 109
3434-    def notify_server_corruption(self, ss, shnum, reason):
3435-        ss.callRemoteOnly("advise_corrupt_share",
3436-                          "mutable", self._storage_index, shnum, reason)
3437 
3438     def _count_shares(self, smap, version):
3439         available_shares = smap.shares_available()
3440hunk ./src/allmydata/test/test_mutable.py 193
3441                 if offset1 == "pubkey" and IV:
3442                     real_offset = 107
3443                 elif offset1 == "share_data" and not IV:
3444-                    real_offset = 104
3445+                    real_offset = 107
3446                 elif offset1 in o:
3447                     real_offset = o[offset1]
3448                 else:
3449hunk ./src/allmydata/test/test_mutable.py 395
3450             return d
3451         d.addCallback(_created)
3452         return d
3453+    test_create_mdmf_with_initial_contents.timeout = 20
3454 
3455 
3456     def test_create_with_initial_contents_function(self):
3457hunk ./src/allmydata/test/test_mutable.py 700
3458                                            k, N, segsize, datalen)
3459                 self.failUnless(p._pubkey.verify(sig_material, signature))
3460                 #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
3461-                self.failUnless(isinstance(share_hash_chain, dict))
3462-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3463+                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3464                 for shnum,share_hash in share_hash_chain.items():
3465                     self.failUnless(isinstance(shnum, int))
3466                     self.failUnless(isinstance(share_hash, str))
3467hunk ./src/allmydata/test/test_mutable.py 820
3468                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
3469 
3470 
3471+
3472+
3473 class Servermap(unittest.TestCase, PublishMixin):
3474     def setUp(self):
3475         return self.publish_one()
3476hunk ./src/allmydata/test/test_mutable.py 951
3477         self._storage._peers = {} # delete all shares
3478         ms = self.make_servermap
3479         d = defer.succeed(None)
3480-
3481+#
3482         d.addCallback(lambda res: ms(mode=MODE_CHECK))
3483         d.addCallback(lambda sm: self.failUnlessNoneRecoverable(sm))
3484 
3485hunk ./src/allmydata/test/test_mutable.py 1440
3486         d.addCallback(self.check_good, "test_check_good")
3487         return d
3488 
3489+    def test_check_mdmf_good(self):
3490+        d = self.publish_mdmf()
3491+        d.addCallback(lambda ignored:
3492+            self._fn.check(Monitor()))
3493+        d.addCallback(self.check_good, "test_check_mdmf_good")
3494+        return d
3495+
3496     def test_check_no_shares(self):
3497         for shares in self._storage._peers.values():
3498             shares.clear()
3499hunk ./src/allmydata/test/test_mutable.py 1454
3500         d.addCallback(self.check_bad, "test_check_no_shares")
3501         return d
3502 
3503+    def test_check_mdmf_no_shares(self):
3504+        d = self.publish_mdmf()
3505+        def _then(ignored):
3506+            for share in self._storage._peers.values():
3507+                share.clear()
3508+        d.addCallback(_then)
3509+        d.addCallback(lambda ignored:
3510+            self._fn.check(Monitor()))
3511+        d.addCallback(self.check_bad, "test_check_mdmf_no_shares")
3512+        return d
3513+
3514     def test_check_not_enough_shares(self):
3515         for shares in self._storage._peers.values():
3516             for shnum in shares.keys():
3517hunk ./src/allmydata/test/test_mutable.py 1474
3518         d.addCallback(self.check_bad, "test_check_not_enough_shares")
3519         return d
3520 
3521+    def test_check_mdmf_not_enough_shares(self):
3522+        d = self.publish_mdmf()
3523+        def _then(ignored):
3524+            for shares in self._storage._peers.values():
3525+                for shnum in shares.keys():
3526+                    if shnum > 0:
3527+                        del shares[shnum]
3528+        d.addCallback(_then)
3529+        d.addCallback(lambda ignored:
3530+            self._fn.check(Monitor()))
3531+        d.addCallback(self.check_bad, "test_check_mdmf_not_enougH_shares")
3532+        return d
3533+
3534+
3535     def test_check_all_bad_sig(self):
3536         d = corrupt(None, self._storage, 1) # bad sig
3537         d.addCallback(lambda ignored:
3538hunk ./src/allmydata/test/test_mutable.py 1495
3539         d.addCallback(self.check_bad, "test_check_all_bad_sig")
3540         return d
3541 
3542+    def test_check_mdmf_all_bad_sig(self):
3543+        d = self.publish_mdmf()
3544+        d.addCallback(lambda ignored:
3545+            corrupt(None, self._storage, 1))
3546+        d.addCallback(lambda ignored:
3547+            self._fn.check(Monitor()))
3548+        d.addCallback(self.check_bad, "test_check_mdmf_all_bad_sig")
3549+        return d
3550+
3551     def test_check_all_bad_blocks(self):
3552         d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
3553         # the Checker won't notice this.. it doesn't look at actual data
3554hunk ./src/allmydata/test/test_mutable.py 1512
3555         d.addCallback(self.check_good, "test_check_all_bad_blocks")
3556         return d
3557 
3558+
3559+    def test_check_mdmf_all_bad_blocks(self):
3560+        d = self.publish_mdmf()
3561+        d.addCallback(lambda ignored:
3562+            corrupt(None, self._storage, "share_data"))
3563+        d.addCallback(lambda ignored:
3564+            self._fn.check(Monitor()))
3565+        d.addCallback(self.check_good, "test_check_mdmf_all_bad_blocks")
3566+        return d
3567+
3568     def test_verify_good(self):
3569         d = self._fn.check(Monitor(), verify=True)
3570         d.addCallback(self.check_good, "test_verify_good")
3571hunk ./src/allmydata/test/test_mutable.py 1582
3572                       "test_verify_one_bad_encprivkey_uncheckable")
3573         return d
3574 
3575+
3576+    def test_verify_mdmf_good(self):
3577+        d = self.publish_mdmf()
3578+        d.addCallback(lambda ignored:
3579+            self._fn.check(Monitor(), verify=True))
3580+        d.addCallback(self.check_good, "test_verify_mdmf_good")
3581+        return d
3582+
3583+
3584+    def test_verify_mdmf_one_bad_block(self):
3585+        d = self.publish_mdmf()
3586+        d.addCallback(lambda ignored:
3587+            corrupt(None, self._storage, "share_data", [1]))
3588+        d.addCallback(lambda ignored:
3589+            self._fn.check(Monitor(), verify=True))
3590+        # We should find one bad block here
3591+        d.addCallback(self.check_bad, "test_verify_mdmf_one_bad_block")
3592+        d.addCallback(self.check_expected_failure,
3593+                      CorruptShareError, "block hash tree failure",
3594+                      "test_verify_mdmf_one_bad_block")
3595+        return d
3596+
3597+
3598+    def test_verify_mdmf_bad_encprivkey(self):
3599+        d = self.publish_mdmf()
3600+        d.addCallback(lambda ignored:
3601+            corrupt(None, self._storage, "enc_privkey", [1]))
3602+        d.addCallback(lambda ignored:
3603+            self._fn.check(Monitor(), verify=True))
3604+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
3605+        d.addCallback(self.check_expected_failure,
3606+                      CorruptShareError, "privkey",
3607+                      "test_verify_mdmf_bad_encprivkey")
3608+        return d
3609+
3610+
3611+    def test_verify_mdmf_bad_sig(self):
3612+        d = self.publish_mdmf()
3613+        d.addCallback(lambda ignored:
3614+            corrupt(None, self._storage, 1, [1]))
3615+        d.addCallback(lambda ignored:
3616+            self._fn.check(Monitor(), verify=True))
3617+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_sig")
3618+        return d
3619+
3620+
3621+    def test_verify_mdmf_bad_encprivkey_uncheckable(self):
3622+        d = self.publish_mdmf()
3623+        d.addCallback(lambda ignored:
3624+            corrupt(None, self._storage, "enc_privkey", [1]))
3625+        d.addCallback(lambda ignored:
3626+            self._fn.get_readonly())
3627+        d.addCallback(lambda fn:
3628+            fn.check(Monitor(), verify=True))
3629+        d.addCallback(self.check_good,
3630+                      "test_verify_mdmf_bad_encprivkey_uncheckable")
3631+        return d
3632+
3633+
3634 class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
3635 
3636     def get_shares(self, s):
3637hunk ./src/allmydata/test/test_mutable.py 1706
3638         current_shares = self.old_shares[-1]
3639         self.failUnlessEqual(old_shares, current_shares)
3640 
3641+
3642     def test_unrepairable_0shares(self):
3643         d = self.publish_one()
3644         def _delete_all_shares(ign):
3645hunk ./src/allmydata/test/test_mutable.py 1721
3646         d.addCallback(_check)
3647         return d
3648 
3649+    def test_mdmf_unrepairable_0shares(self):
3650+        d = self.publish_mdmf()
3651+        def _delete_all_shares(ign):
3652+            shares = self._storage._peers
3653+            for peerid in shares:
3654+                shares[peerid] = {}
3655+        d.addCallback(_delete_all_shares)
3656+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3657+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3658+        d.addCallback(lambda crr: self.failIf(crr.get_successful()))
3659+        return d
3660+
3661+
3662     def test_unrepairable_1share(self):
3663         d = self.publish_one()
3664         def _delete_all_shares(ign):
3665hunk ./src/allmydata/test/test_mutable.py 1750
3666         d.addCallback(_check)
3667         return d
3668 
3669+    def test_mdmf_unrepairable_1share(self):
3670+        d = self.publish_mdmf()
3671+        def _delete_all_shares(ign):
3672+            shares = self._storage._peers
3673+            for peerid in shares:
3674+                for shnum in list(shares[peerid]):
3675+                    if shnum > 0:
3676+                        del shares[peerid][shnum]
3677+        d.addCallback(_delete_all_shares)
3678+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3679+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3680+        def _check(crr):
3681+            self.failUnlessEqual(crr.get_successful(), False)
3682+        d.addCallback(_check)
3683+        return d
3684+
3685+    def test_repairable_5shares(self):
3686+        d = self.publish_mdmf()
3687+        def _delete_all_shares(ign):
3688+            shares = self._storage._peers
3689+            for peerid in shares:
3690+                for shnum in list(shares[peerid]):
3691+                    if shnum > 4:
3692+                        del shares[peerid][shnum]
3693+        d.addCallback(_delete_all_shares)
3694+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3695+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3696+        def _check(crr):
3697+            self.failUnlessEqual(crr.get_successful(), True)
3698+        d.addCallback(_check)
3699+        return d
3700+
3701+    def test_mdmf_repairable_5shares(self):
3702+        d = self.publish_mdmf()
3703+        def _delete_all_shares(ign):
3704+            shares = self._storage._peers
3705+            for peerid in shares:
3706+                for shnum in list(shares[peerid]):
3707+                    if shnum > 5:
3708+                        del shares[peerid][shnum]
3709+        d.addCallback(_delete_all_shares)
3710+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3711+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3712+        def _check(crr):
3713+            self.failUnlessEqual(crr.get_successful(), True)
3714+        d.addCallback(_check)
3715+        return d
3716+
3717+
3718     def test_merge(self):
3719         self.old_shares = []
3720         d = self.publish_multiple()
3721}
3722[mutable/retrieve.py: learn how to verify mutable files
3723Kevan Carstensen <kevan@isnotajoke.com>**20100628225201
3724 Ignore-this: 989af7800c47589620918461ec989483
3725] {
3726hunk ./src/allmydata/mutable/retrieve.py 86
3727     # Retrieve object will remain tied to a specific version of the file, and
3728     # will use a single ServerMap instance.
3729 
3730-    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False):
3731+    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
3732+                 verify=False):
3733         self._node = filenode
3734         assert self._node.get_pubkey()
3735         self._storage_index = filenode.get_storage_index()
3736hunk ./src/allmydata/mutable/retrieve.py 106
3737         # during repair, we may be called upon to grab the private key, since
3738         # it wasn't picked up during a verify=False checker run, and we'll
3739         # need it for repair to generate a new version.
3740-        self._need_privkey = fetch_privkey
3741-        if self._node.get_privkey():
3742+        self._need_privkey = fetch_privkey or verify
3743+        if self._node.get_privkey() and not verify:
3744             self._need_privkey = False
3745 
3746         if self._need_privkey:
3747hunk ./src/allmydata/mutable/retrieve.py 117
3748             self._privkey_query_markers = [] # one Marker for each time we've
3749                                              # tried to get the privkey.
3750 
3751+        # verify means that we are using the downloader logic to verify all
3752+        # of our shares. This tells the downloader a few things.
3753+        #
3754+        # 1. We need to download all of the shares.
3755+        # 2. We don't need to decode or decrypt the shares, since our
3756+        #    caller doesn't care about the plaintext, only the
3757+        #    information about which shares are or are not valid.
3758+        # 3. When we are validating readers, we need to validate the
3759+        #    signature on the prefix. Do we? We already do this in the
3760+        #    servermap update?
3761+        #
3762+        # (just work on 1 and 2 for now, I guess)
3763+        self._verify = False
3764+        if verify:
3765+            self._verify = True
3766+
3767         self._status = RetrieveStatus()
3768         self._status.set_storage_index(self._storage_index)
3769         self._status.set_helper(False)
3770hunk ./src/allmydata/mutable/retrieve.py 323
3771 
3772         # We need at least self._required_shares readers to download a
3773         # segment.
3774-        needed = self._required_shares - len(self._active_readers)
3775+        if self._verify:
3776+            needed = self._total_shares
3777+        else:
3778+            needed = self._required_shares - len(self._active_readers)
3779         # XXX: Why don't format= log messages work here?
3780         self.log("adding %d peers to the active peers list" % needed)
3781 
3782hunk ./src/allmydata/mutable/retrieve.py 339
3783         # will cause problems later.
3784         active_shnums -= set([reader.shnum for reader in self._active_readers])
3785         active_shnums = list(active_shnums)[:needed]
3786-        if len(active_shnums) < needed:
3787+        if len(active_shnums) < needed and not self._verify:
3788             # We don't have enough readers to retrieve the file; fail.
3789             return self._failed()
3790 
3791hunk ./src/allmydata/mutable/retrieve.py 346
3792         for shnum in active_shnums:
3793             self._active_readers.append(self.readers[shnum])
3794             self.log("added reader for share %d" % shnum)
3795-        assert len(self._active_readers) == self._required_shares
3796+        assert len(self._active_readers) >= self._required_shares
3797         # Conceptually, this is part of the _add_active_peers step. It
3798         # validates the prefixes of newly added readers to make sure
3799         # that they match what we are expecting for self.verinfo. If
3800hunk ./src/allmydata/mutable/retrieve.py 416
3801                     # that we haven't gotten it at the end of
3802                     # segment decoding, then we'll take more drastic
3803                     # measures.
3804-                    if self._need_privkey:
3805+                    if self._need_privkey and not self._node.is_readonly():
3806                         d = reader.get_encprivkey()
3807                         d.addCallback(self._try_to_validate_privkey, reader)
3808             if bad_readers:
3809hunk ./src/allmydata/mutable/retrieve.py 423
3810                 # We do them all at once, or else we screw up list indexing.
3811                 for (reader, f) in bad_readers:
3812                     self._mark_bad_share(reader, f)
3813-                return self._add_active_peers()
3814+                if self._verify:
3815+                    if len(self._active_readers) >= self._required_shares:
3816+                        return self._download_current_segment()
3817+                    else:
3818+                        return self._failed()
3819+                else:
3820+                    return self._add_active_peers()
3821             else:
3822                 return self._download_current_segment()
3823             # The next step will assert that it has enough active
3824hunk ./src/allmydata/mutable/retrieve.py 518
3825         """
3826         self.log("marking share %d on server %s as bad" % \
3827                  (reader.shnum, reader))
3828+        prefix = self.verinfo[-2]
3829+        self.servermap.mark_bad_share(reader.peerid,
3830+                                      reader.shnum,
3831+                                      prefix)
3832         self._remove_reader(reader)
3833hunk ./src/allmydata/mutable/retrieve.py 523
3834-        self._bad_shares.add((reader.peerid, reader.shnum))
3835+        self._bad_shares.add((reader.peerid, reader.shnum, f))
3836         self._status.problems[reader.peerid] = f
3837         self._last_failure = f
3838         self.notify_server_corruption(reader.peerid, reader.shnum,
3839hunk ./src/allmydata/mutable/retrieve.py 571
3840             ds.append(dl)
3841             reader.flush()
3842         dl = defer.DeferredList(ds)
3843-        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3844+        if self._verify:
3845+            dl.addCallback(lambda ignored: "")
3846+            dl.addCallback(self._set_segment)
3847+        else:
3848+            dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3849         return dl
3850 
3851 
3852hunk ./src/allmydata/mutable/retrieve.py 701
3853         # shnum, which will be a leaf in the share hash tree, which
3854         # will allow us to validate the rest of the tree.
3855         if self.share_hash_tree.needed_hashes(reader.shnum,
3856-                                               include_leaf=True):
3857+                                              include_leaf=True) or \
3858+                                              self._verify:
3859             try:
3860                 self.share_hash_tree.set_hashes(hashes=sharehashes[1],
3861                                             leaves={reader.shnum: bht[0]})
3862hunk ./src/allmydata/mutable/retrieve.py 832
3863 
3864 
3865     def _try_to_validate_privkey(self, enc_privkey, reader):
3866-
3867         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3868         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3869         if alleged_writekey != self._node.get_writekey():
3870hunk ./src/allmydata/mutable/retrieve.py 838
3871             self.log("invalid privkey from %s shnum %d" %
3872                      (reader, reader.shnum),
3873                      level=log.WEIRD, umid="YIw4tA")
3874+            if self._verify:
3875+                self.servermap.mark_bad_share(reader.peerid, reader.shnum,
3876+                                              self.verinfo[-2])
3877+                e = CorruptShareError(reader.peerid,
3878+                                      reader.shnum,
3879+                                      "invalid privkey")
3880+                f = failure.Failure(e)
3881+                self._bad_shares.add((reader.peerid, reader.shnum, f))
3882             return
3883 
3884         # it's good
3885hunk ./src/allmydata/mutable/retrieve.py 904
3886         statements, I return the decrypted contents to the owner of this
3887         Retrieve object through self._done_deferred.
3888         """
3889-        eventually(self._done_deferred.callback, self._plaintext)
3890+        if self._verify:
3891+            ret = list(self._bad_shares)
3892+            self.log("done verifying, found %d bad shares" % len(ret))
3893+        else:
3894+            ret = self._plaintext
3895+        eventually(self._done_deferred.callback, ret)
3896 
3897 
3898     def _failed(self):
3899hunk ./src/allmydata/mutable/retrieve.py 920
3900         to the caller of this Retrieve object through
3901         self._done_deferred.
3902         """
3903-        format = ("ran out of peers: "
3904-                  "have %(have)d of %(total)d segments "
3905-                  "found %(bad)d bad shares "
3906-                  "encoding %(k)d-of-%(n)d")
3907-        args = {"have": self._current_segment,
3908-                "total": self._num_segments,
3909-                "k": self._required_shares,
3910-                "n": self._total_shares,
3911-                "bad": len(self._bad_shares)}
3912-        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
3913-                                                        str(self._last_failure)))
3914-        f = failure.Failure(e)
3915-        eventually(self._done_deferred.callback, f)
3916+        if self._verify:
3917+            ret = list(self._bad_shares)
3918+        else:
3919+            format = ("ran out of peers: "
3920+                      "have %(have)d of %(total)d segments "
3921+                      "found %(bad)d bad shares "
3922+                      "encoding %(k)d-of-%(n)d")
3923+            args = {"have": self._current_segment,
3924+                    "total": self._num_segments,
3925+                    "k": self._required_shares,
3926+                    "n": self._total_shares,
3927+                    "bad": len(self._bad_shares)}
3928+            e = NotEnoughSharesError("%s, last failure: %s" % \
3929+                                     (format % args, str(self._last_failure)))
3930+            f = failure.Failure(e)
3931+            ret = f
3932+        eventually(self._done_deferred.callback, ret)
3933}
3934[interfaces.py: add IMutableSlotWriter
3935Kevan Carstensen <kevan@isnotajoke.com>**20100630183305
3936 Ignore-this: ff9dca96ef1a009ae85485682f81ea5
3937] hunk ./src/allmydata/interfaces.py 418
3938         """
3939 
3940 
3941+class IMutableSlotWriter(Interface):
3942+    """
3943+    The interface for a writer around a mutable slot on a remote server.
3944+    """
3945+    def set_checkstring(checkstring, *args):
3946+        """
3947+        Set the checkstring that I will pass to the remote server when
3948+        writing.
3949+
3950+            @param checkstring A packed checkstring to use.
3951+
3952+        Note that implementations can differ in which semantics they
3953+        wish to support for set_checkstring -- they can, for example,
3954+        build the checkstring themselves from its constituents, or
3955+        some other thing.
3956+        """
3957+
3958+    def get_checkstring():
3959+        """
3960+        Get the checkstring that I think currently exists on the remote
3961+        server.
3962+        """
3963+
3964+    def put_block(data, segnum, salt):
3965+        """
3966+        Add a block and salt to the share.
3967+        """
3968+
3969+    def put_encprivey(encprivkey):
3970+        """
3971+        Add the encrypted private key to the share.
3972+        """
3973+
3974+    def put_blockhashes(blockhashes=list):
3975+        """
3976+        Add the block hash tree to the share.
3977+        """
3978+
3979+    def put_sharehashes(sharehashes=dict):
3980+        """
3981+        Add the share hash chain to the share.
3982+        """
3983+
3984+    def get_signable():
3985+        """
3986+        Return the part of the share that needs to be signed.
3987+        """
3988+
3989+    def put_signature(signature):
3990+        """
3991+        Add the signature to the share.
3992+        """
3993+
3994+    def put_verification_key(verification_key):
3995+        """
3996+        Add the verification key to the share.
3997+        """
3998+
3999+    def finish_publishing():
4000+        """
4001+        Do anything necessary to finish writing the share to a remote
4002+        server. I require that no further publishing needs to take place
4003+        after this method has been called.
4004+        """
4005+
4006+
4007 class IURI(Interface):
4008     def init_from_string(uri):
4009         """Accept a string (as created by my to_string() method) and populate
4010[test/test_mutable.py: temporarily disable two tests that are now irrelevant
4011Kevan Carstensen <kevan@isnotajoke.com>**20100701232806
4012 Ignore-this: 701e143567f3954812ca6960af1d6ac7
4013] {
4014hunk ./src/allmydata/test/test_mutable.py 651
4015             self.failUnlessEqual(len(share_ids), 10)
4016         d.addCallback(_done)
4017         return d
4018+    test_encrypt.todo = "Write an equivalent of this for the new uploader"
4019 
4020     def test_generate(self):
4021         nm = make_nodemaker()
4022hunk ./src/allmydata/test/test_mutable.py 713
4023                 self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
4024         d.addCallback(_generated)
4025         return d
4026+    test_generate.todo = "Write an equivalent of this for the new uploader"
4027 
4028     # TODO: when we publish to 20 peers, we should get one share per peer on 10
4029     # when we publish to 3 peers, we should get either 3 or 4 shares per peer
4030}
4031[Add MDMF reader and writer, and SDMF writer
4032Kevan Carstensen <kevan@isnotajoke.com>**20100702225531
4033 Ignore-this: bf6276a91d27dcb4e779b0eb82ea1843
4034 
4035 The MDMF/SDMF reader MDMF writer, and SDMF writer are similar to the
4036 object proxies that exist for immutable files. They abstract away
4037 details of connection, state, and caching from their callers (in this
4038 case, the download, servermap updater, and uploader), and expose methods
4039 to get and set information on the remote server.
4040 
4041 MDMFSlotReadProxy reads a mutable file from the server, doing the right
4042 thing (in most cases) regardless of whether the file is MDMF or SDMF. It
4043 allows callers to tell it how to batch and flush reads.
4044 
4045 MDMFSlotWriteProxy writes an MDMF mutable file to a server.
4046 
4047 SDMFSlotWriteProxy writes an SDMF mutable file to a server.
4048 
4049 This patch also includes tests for MDMFSlotReadProxy,
4050 SDMFSlotWriteProxy, and MDMFSlotWriteProxy.
4051] {
4052hunk ./src/allmydata/mutable/layout.py 4
4053 
4054 import struct
4055 from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError
4056+from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \
4057+                                 MDMF_VERSION, IMutableSlotWriter
4058+from allmydata.util import mathutil, observer
4059+from twisted.python import failure
4060+from twisted.internet import defer
4061+from zope.interface import implements
4062+
4063+
4064+# These strings describe the format of the packed structs they help process
4065+# Here's what they mean:
4066+#
4067+#  PREFIX:
4068+#    >: Big-endian byte order; the most significant byte is first (leftmost).
4069+#    B: The version information; an 8 bit version identifier. Stored as
4070+#       an unsigned char. This is currently 00 00 00 00; our modifications
4071+#       will turn it into 00 00 00 01.
4072+#    Q: The sequence number; this is sort of like a revision history for
4073+#       mutable files; they start at 1 and increase as they are changed after
4074+#       being uploaded. Stored as an unsigned long long, which is 8 bytes in
4075+#       length.
4076+#  32s: The root hash of the share hash tree. We use sha-256d, so we use 32
4077+#       characters = 32 bytes to store the value.
4078+#  16s: The salt for the readkey. This is a 16-byte random value, stored as
4079+#       16 characters.
4080+#
4081+#  SIGNED_PREFIX additions, things that are covered by the signature:
4082+#    B: The "k" encoding parameter. We store this as an 8-bit character,
4083+#       which is convenient because our erasure coding scheme cannot
4084+#       encode if you ask for more than 255 pieces.
4085+#    B: The "N" encoding parameter. Stored as an 8-bit character for the
4086+#       same reasons as above.
4087+#    Q: The segment size of the uploaded file. This will essentially be the
4088+#       length of the file in SDMF. An unsigned long long, so we can store
4089+#       files of quite large size.
4090+#    Q: The data length of the uploaded file. Modulo padding, this will be
4091+#       the same of the data length field. Like the data length field, it is
4092+#       an unsigned long long and can be quite large.
4093+#
4094+#   HEADER additions:
4095+#     L: The offset of the signature of this. An unsigned long.
4096+#     L: The offset of the share hash chain. An unsigned long.
4097+#     L: The offset of the block hash tree. An unsigned long.
4098+#     L: The offset of the share data. An unsigned long.
4099+#     Q: The offset of the encrypted private key. An unsigned long long, to
4100+#        account for the possibility of a lot of share data.
4101+#     Q: The offset of the EOF. An unsigned long long, to account for the
4102+#        possibility of a lot of share data.
4103+#
4104+#  After all of these, we have the following:
4105+#    - The verification key: Occupies the space between the end of the header
4106+#      and the start of the signature (i.e.: data[HEADER_LENGTH:o['signature']].
4107+#    - The signature, which goes from the signature offset to the share hash
4108+#      chain offset.
4109+#    - The share hash chain, which goes from the share hash chain offset to
4110+#      the block hash tree offset.
4111+#    - The share data, which goes from the share data offset to the encrypted
4112+#      private key offset.
4113+#    - The encrypted private key offset, which goes until the end of the file.
4114+#
4115+#  The block hash tree in this encoding has only one share, so the offset of
4116+#  the share data will be 32 bits more than the offset of the block hash tree.
4117+#  Given this, we may need to check to see how many bytes a reasonably sized
4118+#  block hash tree will take up.
4119 
4120 PREFIX = ">BQ32s16s" # each version has a different prefix
4121 SIGNED_PREFIX = ">BQ32s16s BBQQ" # this is covered by the signature
4122hunk ./src/allmydata/mutable/layout.py 73
4123 SIGNED_PREFIX_LENGTH = struct.calcsize(SIGNED_PREFIX)
4124 HEADER = ">BQ32s16s BBQQ LLLLQQ" # includes offsets
4125 HEADER_LENGTH = struct.calcsize(HEADER)
4126+OFFSETS = ">LLLLQQ"
4127+OFFSETS_LENGTH = struct.calcsize(OFFSETS)
4128 
4129 def unpack_header(data):
4130     o = {}
4131hunk ./src/allmydata/mutable/layout.py 194
4132     return (share_hash_chain, block_hash_tree, share_data)
4133 
4134 
4135-def pack_checkstring(seqnum, root_hash, IV):
4136+def pack_checkstring(seqnum, root_hash, IV, version=0):
4137     return struct.pack(PREFIX,
4138hunk ./src/allmydata/mutable/layout.py 196
4139-                       0, # version,
4140+                       version,
4141                        seqnum,
4142                        root_hash,
4143                        IV)
4144hunk ./src/allmydata/mutable/layout.py 269
4145                            encprivkey])
4146     return final_share
4147 
4148+def pack_prefix(seqnum, root_hash, IV,
4149+                required_shares, total_shares,
4150+                segment_size, data_length):
4151+    prefix = struct.pack(SIGNED_PREFIX,
4152+                         0, # version,
4153+                         seqnum,
4154+                         root_hash,
4155+                         IV,
4156+                         required_shares,
4157+                         total_shares,
4158+                         segment_size,
4159+                         data_length,
4160+                         )
4161+    return prefix
4162+
4163+
4164+class SDMFSlotWriteProxy:
4165+    implements(IMutableSlotWriter)
4166+    """
4167+    I represent a remote write slot for an SDMF mutable file. I build a
4168+    share in memory, and then write it in one piece to the remote
4169+    server. This mimics how SDMF shares were built before MDMF (and the
4170+    new MDMF uploader), but provides that functionality in a way that
4171+    allows the MDMF uploader to be built without much special-casing for
4172+    file format, which makes the uploader code more readable.
4173+    """
4174+    def __init__(self,
4175+                 shnum,
4176+                 rref, # a remote reference to a storage server
4177+                 storage_index,
4178+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4179+                 seqnum, # the sequence number of the mutable file
4180+                 required_shares,
4181+                 total_shares,
4182+                 segment_size,
4183+                 data_length): # the length of the original file
4184+        self.shnum = shnum
4185+        self._rref = rref
4186+        self._storage_index = storage_index
4187+        self._secrets = secrets
4188+        self._seqnum = seqnum
4189+        self._required_shares = required_shares
4190+        self._total_shares = total_shares
4191+        self._segment_size = segment_size
4192+        self._data_length = data_length
4193+
4194+        # This is an SDMF file, so it should have only one segment, so,
4195+        # modulo padding of the data length, the segment size and the
4196+        # data length should be the same.
4197+        expected_segment_size = mathutil.next_multiple(data_length,
4198+                                                       self._required_shares)
4199+        assert expected_segment_size == segment_size
4200+
4201+        self._block_size = self._segment_size / self._required_shares
4202+
4203+        # This is meant to mimic how SDMF files were built before MDMF
4204+        # entered the picture: we generate each share in its entirety,
4205+        # then push it off to the storage server in one write. When
4206+        # callers call set_*, they are just populating this dict.
4207+        # finish_publishing will stitch these pieces together into a
4208+        # coherent share, and then write the coherent share to the
4209+        # storage server.
4210+        self._share_pieces = {}
4211+
4212+        # This tells the write logic what checkstring to use when
4213+        # writing remote shares.
4214+        self._testvs = []
4215+
4216+        self._readvs = [(0, struct.calcsize(PREFIX))]
4217+
4218+
4219+    def set_checkstring(self, checkstring_or_seqnum,
4220+                              root_hash=None,
4221+                              salt=None):
4222+        """
4223+        Set the checkstring that I will pass to the remote server when
4224+        writing.
4225+
4226+            @param checkstring_or_seqnum: A packed checkstring to use,
4227+                   or a sequence number. I will treat this as a checkstr
4228+
4229+        Note that implementations can differ in which semantics they
4230+        wish to support for set_checkstring -- they can, for example,
4231+        build the checkstring themselves from its constituents, or
4232+        some other thing.
4233+        """
4234+        if root_hash and salt:
4235+            checkstring = struct.pack(PREFIX,
4236+                                      0,
4237+                                      checkstring_or_seqnum,
4238+                                      root_hash,
4239+                                      salt)
4240+        else:
4241+            checkstring = checkstring_or_seqnum
4242+        self._testvs = [(0, len(checkstring), "eq", checkstring)]
4243+
4244+
4245+    def get_checkstring(self):
4246+        """
4247+        Get the checkstring that I think currently exists on the remote
4248+        server.
4249+        """
4250+        if self._testvs:
4251+            return self._testvs[0][3]
4252+        return ""
4253+
4254+
4255+    def put_block(self, data, segnum, salt):
4256+        """
4257+        Add a block and salt to the share.
4258+        """
4259+        # SDMF files have only one segment
4260+        assert segnum == 0
4261+        assert len(data) == self._block_size
4262+        assert len(salt) == SALT_SIZE
4263+
4264+        self._share_pieces['sharedata'] = data
4265+        self._share_pieces['salt'] = salt
4266+
4267+        # TODO: Figure out something intelligent to return.
4268+        return defer.succeed(None)
4269+
4270+
4271+    def put_encprivkey(self, encprivkey):
4272+        """
4273+        Add the encrypted private key to the share.
4274+        """
4275+        self._share_pieces['encprivkey'] = encprivkey
4276+
4277+        return defer.succeed(None)
4278+
4279+
4280+    def put_blockhashes(self, blockhashes):
4281+        """
4282+        Add the block hash tree to the share.
4283+        """
4284+        assert isinstance(blockhashes, list)
4285+        for h in blockhashes:
4286+            assert len(h) == HASH_SIZE
4287+
4288+        # serialize the blockhashes, then set them.
4289+        blockhashes_s = "".join(blockhashes)
4290+        self._share_pieces['block_hash_tree'] = blockhashes_s
4291+
4292+        return defer.succeed(None)
4293+
4294+
4295+    def put_sharehashes(self, sharehashes):
4296+        """
4297+        Add the share hash chain to the share.
4298+        """
4299+        assert isinstance(sharehashes, dict)
4300+        for h in sharehashes.itervalues():
4301+            assert len(h) == HASH_SIZE
4302+
4303+        # serialize the sharehashes, then set them.
4304+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4305+                                 for i in sorted(sharehashes.keys())])
4306+        self._share_pieces['share_hash_chain'] = sharehashes_s
4307+
4308+        return defer.succeed(None)
4309+
4310+
4311+    def put_root_hash(self, root_hash):
4312+        """
4313+        Add the root hash to the share.
4314+        """
4315+        assert len(root_hash) == HASH_SIZE
4316+
4317+        self._share_pieces['root_hash'] = root_hash
4318+
4319+        return defer.succeed(None)
4320+
4321+
4322+    def put_salt(self, salt):
4323+        """
4324+        Add a salt to an empty SDMF file.
4325+        """
4326+        assert len(salt) == SALT_SIZE
4327+
4328+        self._share_pieces['salt'] = salt
4329+        self._share_pieces['sharedata'] = ""
4330+
4331+
4332+    def get_signable(self):
4333+        """
4334+        Return the part of the share that needs to be signed.
4335+
4336+        SDMF writers need to sign the packed representation of the
4337+        first eight fields of the remote share, that is:
4338+            - version number (0)
4339+            - sequence number
4340+            - root of the share hash tree
4341+            - salt
4342+            - k
4343+            - n
4344+            - segsize
4345+            - datalen
4346+
4347+        This method is responsible for returning that to callers.
4348+        """
4349+        return struct.pack(SIGNED_PREFIX,
4350+                           0,
4351+                           self._seqnum,
4352+                           self._share_pieces['root_hash'],
4353+                           self._share_pieces['salt'],
4354+                           self._required_shares,
4355+                           self._total_shares,
4356+                           self._segment_size,
4357+                           self._data_length)
4358+
4359+
4360+    def put_signature(self, signature):
4361+        """
4362+        Add the signature to the share.
4363+        """
4364+        self._share_pieces['signature'] = signature
4365+
4366+        return defer.succeed(None)
4367+
4368+
4369+    def put_verification_key(self, verification_key):
4370+        """
4371+        Add the verification key to the share.
4372+        """
4373+        self._share_pieces['verification_key'] = verification_key
4374+
4375+        return defer.succeed(None)
4376+
4377+
4378+    def get_verinfo(self):
4379+        """
4380+        I return my verinfo tuple. This is used by the ServermapUpdater
4381+        to keep track of versions of mutable files.
4382+
4383+        The verinfo tuple for MDMF files contains:
4384+            - seqnum
4385+            - root hash
4386+            - a blank (nothing)
4387+            - segsize
4388+            - datalen
4389+            - k
4390+            - n
4391+            - prefix (the thing that you sign)
4392+            - a tuple of offsets
4393+
4394+        We include the nonce in MDMF to simplify processing of version
4395+        information tuples.
4396+
4397+        The verinfo tuple for SDMF files is the same, but contains a
4398+        16-byte IV instead of a hash of salts.
4399+        """
4400+        return (self._seqnum,
4401+                self._share_pieces['root_hash'],
4402+                self._share_pieces['salt'],
4403+                self._segment_size,
4404+                self._data_length,
4405+                self._required_shares,
4406+                self._total_shares,
4407+                self.get_signable(),
4408+                self._get_offsets_tuple())
4409+
4410+    def _get_offsets_dict(self):
4411+        post_offset = HEADER_LENGTH
4412+        offsets = {}
4413+
4414+        verification_key_length = len(self._share_pieces['verification_key'])
4415+        o1 = offsets['signature'] = post_offset + verification_key_length
4416+
4417+        signature_length = len(self._share_pieces['signature'])
4418+        o2 = offsets['share_hash_chain'] = o1 + signature_length
4419+
4420+        share_hash_chain_length = len(self._share_pieces['share_hash_chain'])
4421+        o3 = offsets['block_hash_tree'] = o2 + share_hash_chain_length
4422+
4423+        block_hash_tree_length = len(self._share_pieces['block_hash_tree'])
4424+        o4 = offsets['share_data'] = o3 + block_hash_tree_length
4425+
4426+        share_data_length = len(self._share_pieces['sharedata'])
4427+        o5 = offsets['enc_privkey'] = o4 + share_data_length
4428+
4429+        encprivkey_length = len(self._share_pieces['encprivkey'])
4430+        offsets['EOF'] = o5 + encprivkey_length
4431+        return offsets
4432+
4433+
4434+    def _get_offsets_tuple(self):
4435+        offsets = self._get_offsets_dict()
4436+        return tuple([(key, value) for key, value in offsets.items()])
4437+
4438+
4439+    def _pack_offsets(self):
4440+        offsets = self._get_offsets_dict()
4441+        return struct.pack(">LLLLQQ",
4442+                           offsets['signature'],
4443+                           offsets['share_hash_chain'],
4444+                           offsets['block_hash_tree'],
4445+                           offsets['share_data'],
4446+                           offsets['enc_privkey'],
4447+                           offsets['EOF'])
4448+
4449+
4450+    def finish_publishing(self):
4451+        """
4452+        Do anything necessary to finish writing the share to a remote
4453+        server. I require that no further publishing needs to take place
4454+        after this method has been called.
4455+        """
4456+        for k in ["sharedata", "encprivkey", "signature", "verification_key",
4457+                  "share_hash_chain", "block_hash_tree"]:
4458+            assert k in self._share_pieces
4459+        # This is the only method that actually writes something to the
4460+        # remote server.
4461+        # First, we need to pack the share into data that we can write
4462+        # to the remote server in one write.
4463+        offsets = self._pack_offsets()
4464+        prefix = self.get_signable()
4465+        final_share = "".join([prefix,
4466+                               offsets,
4467+                               self._share_pieces['verification_key'],
4468+                               self._share_pieces['signature'],
4469+                               self._share_pieces['share_hash_chain'],
4470+                               self._share_pieces['block_hash_tree'],
4471+                               self._share_pieces['sharedata'],
4472+                               self._share_pieces['encprivkey']])
4473+
4474+        # Our only data vector is going to be writing the final share,
4475+        # in its entirely.
4476+        datavs = [(0, final_share)]
4477+
4478+        if not self._testvs:
4479+            # Our caller has not provided us with another checkstring
4480+            # yet, so we assume that we are writing a new share, and set
4481+            # a test vector that will allow a new share to be written.
4482+            self._testvs = []
4483+            self._testvs.append(tuple([0, 1, "eq", ""]))
4484+            new_share = True
4485+
4486+        tw_vectors = {}
4487+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
4488+        return self._rref.callRemote("slot_testv_and_readv_and_writev",
4489+                                     self._storage_index,
4490+                                     self._secrets,
4491+                                     tw_vectors,
4492+                                     # TODO is it useful to read something?
4493+                                     self._readvs)
4494+
4495+
4496+MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
4497+MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
4498+MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
4499+MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
4500+MDMFCHECKSTRING = ">BQ32s"
4501+MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
4502+MDMFOFFSETS = ">QQQQQQ"
4503+MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
4504+
4505+class MDMFSlotWriteProxy:
4506+    implements(IMutableSlotWriter)
4507+
4508+    """
4509+    I represent a remote write slot for an MDMF mutable file.
4510+
4511+    I abstract away from my caller the details of block and salt
4512+    management, and the implementation of the on-disk format for MDMF
4513+    shares.
4514+    """
4515+    # Expected layout, MDMF:
4516+    # offset:     size:       name:
4517+    #-- signed part --
4518+    # 0           1           version number (01)
4519+    # 1           8           sequence number
4520+    # 9           32          share tree root hash
4521+    # 41          1           The "k" encoding parameter
4522+    # 42          1           The "N" encoding parameter
4523+    # 43          8           The segment size of the uploaded file
4524+    # 51          8           The data length of the original plaintext
4525+    #-- end signed part --
4526+    # 59          8           The offset of the encrypted private key
4527+    # 67          8           The offset of the block hash tree
4528+    # 75          8           The offset of the share hash chain
4529+    # 83          8           The offset of the signature
4530+    # 91          8           The offset of the verification key
4531+    # 99          8           The offset of the EOF
4532+    #
4533+    # followed by salts and share data, the encrypted private key, the
4534+    # block hash tree, the salt hash tree, the share hash chain, a
4535+    # signature over the first eight fields, and a verification key.
4536+    #
4537+    # The checkstring is the first three fields -- the version number,
4538+    # sequence number, root hash and root salt hash. This is consistent
4539+    # in meaning to what we have with SDMF files, except now instead of
4540+    # using the literal salt, we use a value derived from all of the
4541+    # salts -- the share hash root.
4542+    #
4543+    # The salt is stored before the block for each segment. The block
4544+    # hash tree is computed over the combination of block and salt for
4545+    # each segment. In this way, we get integrity checking for both
4546+    # block and salt with the current block hash tree arrangement.
4547+    #
4548+    # The ordering of the offsets is different to reflect the dependencies
4549+    # that we'll run into with an MDMF file. The expected write flow is
4550+    # something like this:
4551+    #
4552+    #   0: Initialize with the sequence number, encoding parameters and
4553+    #      data length. From this, we can deduce the number of segments,
4554+    #      and where they should go.. We can also figure out where the
4555+    #      encrypted private key should go, because we can figure out how
4556+    #      big the share data will be.
4557+    #
4558+    #   1: Encrypt, encode, and upload the file in chunks. Do something
4559+    #      like
4560+    #
4561+    #       put_block(data, segnum, salt)
4562+    #
4563+    #      to write a block and a salt to the disk. We can do both of
4564+    #      these operations now because we have enough of the offsets to
4565+    #      know where to put them.
4566+    #
4567+    #   2: Put the encrypted private key. Use:
4568+    #
4569+    #        put_encprivkey(encprivkey)
4570+    #
4571+    #      Now that we know the length of the private key, we can fill
4572+    #      in the offset for the block hash tree.
4573+    #
4574+    #   3: We're now in a position to upload the block hash tree for
4575+    #      a share. Put that using something like:
4576+    #       
4577+    #        put_blockhashes(block_hash_tree)
4578+    #
4579+    #      Note that block_hash_tree is a list of hashes -- we'll take
4580+    #      care of the details of serializing that appropriately. When
4581+    #      we get the block hash tree, we are also in a position to
4582+    #      calculate the offset for the share hash chain, and fill that
4583+    #      into the offsets table.
4584+    #
4585+    #   4: At the same time, we're in a position to upload the salt hash
4586+    #      tree. This is a Merkle tree over all of the salts. We use a
4587+    #      Merkle tree so that we can validate each block,salt pair as
4588+    #      we download them later. We do this using
4589+    #
4590+    #        put_salthashes(salt_hash_tree)
4591+    #
4592+    #      When you do this, I automatically put the root of the tree
4593+    #      (the hash at index 0 of the list) in its appropriate slot in
4594+    #      the signed prefix of the share.
4595+    #
4596+    #   5: We're now in a position to upload the share hash chain for
4597+    #      a share. Do that with something like:
4598+    #     
4599+    #        put_sharehashes(share_hash_chain)
4600+    #
4601+    #      share_hash_chain should be a dictionary mapping shnums to
4602+    #      32-byte hashes -- the wrapper handles serialization.
4603+    #      We'll know where to put the signature at this point, also.
4604+    #      The root of this tree will be put explicitly in the next
4605+    #      step.
4606+    #
4607+    #      TODO: Why? Why not just include it in the tree here?
4608+    #
4609+    #   6: Before putting the signature, we must first put the
4610+    #      root_hash. Do this with:
4611+    #
4612+    #        put_root_hash(root_hash).
4613+    #     
4614+    #      In terms of knowing where to put this value, it was always
4615+    #      possible to place it, but it makes sense semantically to
4616+    #      place it after the share hash tree, so that's why you do it
4617+    #      in this order.
4618+    #
4619+    #   6: With the root hash put, we can now sign the header. Use:
4620+    #
4621+    #        get_signable()
4622+    #
4623+    #      to get the part of the header that you want to sign, and use:
4624+    #       
4625+    #        put_signature(signature)
4626+    #
4627+    #      to write your signature to the remote server.
4628+    #
4629+    #   6: Add the verification key, and finish. Do:
4630+    #
4631+    #        put_verification_key(key)
4632+    #
4633+    #      and
4634+    #
4635+    #        finish_publish()
4636+    #
4637+    # Checkstring management:
4638+    #
4639+    # To write to a mutable slot, we have to provide test vectors to ensure
4640+    # that we are writing to the same data that we think we are. These
4641+    # vectors allow us to detect uncoordinated writes; that is, writes
4642+    # where both we and some other shareholder are writing to the
4643+    # mutable slot, and to report those back to the parts of the program
4644+    # doing the writing.
4645+    #
4646+    # With SDMF, this was easy -- all of the share data was written in
4647+    # one go, so it was easy to detect uncoordinated writes, and we only
4648+    # had to do it once. With MDMF, not all of the file is written at
4649+    # once.
4650+    #
4651+    # If a share is new, we write out as much of the header as we can
4652+    # before writing out anything else. This gives other writers a
4653+    # canary that they can use to detect uncoordinated writes, and, if
4654+    # they do the same thing, gives us the same canary. We them update
4655+    # the share. We won't be able to write out two fields of the header
4656+    # -- the share tree hash and the salt hash -- until we finish
4657+    # writing out the share. We only require the writer to provide the
4658+    # initial checkstring, and keep track of what it should be after
4659+    # updates ourselves.
4660+    #
4661+    # If we haven't written anything yet, then on the first write (which
4662+    # will probably be a block + salt of a share), we'll also write out
4663+    # the header. On subsequent passes, we'll expect to see the header.
4664+    # This changes in two places:
4665+    #
4666+    #   - When we write out the salt hash
4667+    #   - When we write out the root of the share hash tree
4668+    #
4669+    # since these values will change the header. It is possible that we
4670+    # can just make those be written in one operation to minimize
4671+    # disruption.
4672+    def __init__(self,
4673+                 shnum,
4674+                 rref, # a remote reference to a storage server
4675+                 storage_index,
4676+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4677+                 seqnum, # the sequence number of the mutable file
4678+                 required_shares,
4679+                 total_shares,
4680+                 segment_size,
4681+                 data_length): # the length of the original file
4682+        self.shnum = shnum
4683+        self._rref = rref
4684+        self._storage_index = storage_index
4685+        self._seqnum = seqnum
4686+        self._required_shares = required_shares
4687+        assert self.shnum >= 0 and self.shnum < total_shares
4688+        self._total_shares = total_shares
4689+        # We build up the offset table as we write things. It is the
4690+        # last thing we write to the remote server.
4691+        self._offsets = {}
4692+        self._testvs = []
4693+        self._secrets = secrets
4694+        # The segment size needs to be a multiple of the k parameter --
4695+        # any padding should have been carried out by the publisher
4696+        # already.
4697+        assert segment_size % required_shares == 0
4698+        self._segment_size = segment_size
4699+        self._data_length = data_length
4700+
4701+        # These are set later -- we define them here so that we can
4702+        # check for their existence easily
4703+
4704+        # This is the root of the share hash tree -- the Merkle tree
4705+        # over the roots of the block hash trees computed for shares in
4706+        # this upload.
4707+        self._root_hash = None
4708+
4709+        # We haven't yet written anything to the remote bucket. By
4710+        # setting this, we tell the _write method as much. The write
4711+        # method will then know that it also needs to add a write vector
4712+        # for the checkstring (or what we have of it) to the first write
4713+        # request. We'll then record that value for future use.  If
4714+        # we're expecting something to be there already, we need to call
4715+        # set_checkstring before we write anything to tell the first
4716+        # write about that.
4717+        self._written = False
4718+
4719+        # When writing data to the storage servers, we get a read vector
4720+        # for free. We'll read the checkstring, which will help us
4721+        # figure out what's gone wrong if a write fails.
4722+        self._readv = [(0, struct.calcsize(MDMFCHECKSTRING))]
4723+
4724+        # We calculate the number of segments because it tells us
4725+        # where the salt part of the file ends/share segment begins,
4726+        # and also because it provides a useful amount of bounds checking.
4727+        self._num_segments = mathutil.div_ceil(self._data_length,
4728+                                               self._segment_size)
4729+        self._block_size = self._segment_size / self._required_shares
4730+        # We also calculate the share size, to help us with block
4731+        # constraints later.
4732+        tail_size = self._data_length % self._segment_size
4733+        if not tail_size:
4734+            self._tail_block_size = self._block_size
4735+        else:
4736+            self._tail_block_size = mathutil.next_multiple(tail_size,
4737+                                                           self._required_shares)
4738+            self._tail_block_size /= self._required_shares
4739+
4740+        # We already know where the sharedata starts; right after the end
4741+        # of the header (which is defined as the signable part + the offsets)
4742+        # We can also calculate where the encrypted private key begins
4743+        # from what we know know.
4744+        self._actual_block_size = self._block_size + SALT_SIZE
4745+        data_size = self._actual_block_size * (self._num_segments - 1)
4746+        data_size += self._tail_block_size
4747+        data_size += SALT_SIZE
4748+        self._offsets['enc_privkey'] = MDMFHEADERSIZE
4749+        self._offsets['enc_privkey'] += data_size
4750+        # We'll wait for the rest. Callers can now call my "put_block" and
4751+        # "set_checkstring" methods.
4752+
4753+
4754+    def set_checkstring(self,
4755+                        seqnum_or_checkstring,
4756+                        root_hash=None,
4757+                        salt=None):
4758+        """
4759+        Set checkstring checkstring for the given shnum.
4760+
4761+        This can be invoked in one of two ways.
4762+
4763+        With one argument, I assume that you are giving me a literal
4764+        checkstring -- e.g., the output of get_checkstring. I will then
4765+        set that checkstring as it is. This form is used by unit tests.
4766+
4767+        With two arguments, I assume that you are giving me a sequence
4768+        number and root hash to make a checkstring from. In that case, I
4769+        will build a checkstring and set it for you. This form is used
4770+        by the publisher.
4771+
4772+        By default, I assume that I am writing new shares to the grid.
4773+        If you don't explcitly set your own checkstring, I will use
4774+        one that requires that the remote share not exist. You will want
4775+        to use this method if you are updating a share in-place;
4776+        otherwise, writes will fail.
4777+        """
4778+        # You're allowed to overwrite checkstrings with this method;
4779+        # I assume that users know what they are doing when they call
4780+        # it.
4781+        if root_hash:
4782+            checkstring = struct.pack(MDMFCHECKSTRING,
4783+                                      1,
4784+                                      seqnum_or_checkstring,
4785+                                      root_hash)
4786+        else:
4787+            checkstring = seqnum_or_checkstring
4788+
4789+        if checkstring == "":
4790+            # We special-case this, since len("") = 0, but we need
4791+            # length of 1 for the case of an empty share to work on the
4792+            # storage server, which is what a checkstring that is the
4793+            # empty string means.
4794+            self._testvs = []
4795+        else:
4796+            self._testvs = []
4797+            self._testvs.append((0, len(checkstring), "eq", checkstring))
4798+
4799+
4800+    def __repr__(self):
4801+        return "MDMFSlotWriteProxy for share %d" % self.shnum
4802+
4803+
4804+    def get_checkstring(self):
4805+        """
4806+        Given a share number, I return a representation of what the
4807+        checkstring for that share on the server will look like.
4808+
4809+        I am mostly used for tests.
4810+        """
4811+        if self._root_hash:
4812+            roothash = self._root_hash
4813+        else:
4814+            roothash = "\x00" * 32
4815+        return struct.pack(MDMFCHECKSTRING,
4816+                           1,
4817+                           self._seqnum,
4818+                           roothash)
4819+
4820+
4821+    def put_block(self, data, segnum, salt):
4822+        """
4823+        Put the encrypted-and-encoded data segment in the slot, along
4824+        with the salt.
4825+        """
4826+        if segnum >= self._num_segments:
4827+            raise LayoutInvalid("I won't overwrite the private key")
4828+        if len(salt) != SALT_SIZE:
4829+            raise LayoutInvalid("I was given a salt of size %d, but "
4830+                                "I wanted a salt of size %d")
4831+        if segnum + 1 == self._num_segments:
4832+            if len(data) != self._tail_block_size:
4833+                raise LayoutInvalid("I was given the wrong size block to write")
4834+        elif len(data) != self._block_size:
4835+            raise LayoutInvalid("I was given the wrong size block to write")
4836+
4837+        # We want to write at len(MDMFHEADER) + segnum * block_size.
4838+
4839+        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
4840+        data = salt + data
4841+
4842+        datavs = [tuple([offset, data])]
4843+        return self._write(datavs)
4844+
4845+
4846+    def put_encprivkey(self, encprivkey):
4847+        """
4848+        Put the encrypted private key in the remote slot.
4849+        """
4850+        assert self._offsets
4851+        assert self._offsets['enc_privkey']
4852+        # You shouldn't re-write the encprivkey after the block hash
4853+        # tree is written, since that could cause the private key to run
4854+        # into the block hash tree. Before it writes the block hash
4855+        # tree, the block hash tree writing method writes the offset of
4856+        # the salt hash tree. So that's a good indicator of whether or
4857+        # not the block hash tree has been written.
4858+        if "share_hash_chain" in self._offsets:
4859+            raise LayoutInvalid("You must write this before the block hash tree")
4860+
4861+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
4862+        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
4863+        def _on_failure():
4864+            del(self._offsets['block_hash_tree'])
4865+        return self._write(datavs, on_failure=_on_failure)
4866+
4867+
4868+    def put_blockhashes(self, blockhashes):
4869+        """
4870+        Put the block hash tree in the remote slot.
4871+
4872+        The encrypted private key must be put before the block hash
4873+        tree, since we need to know how large it is to know where the
4874+        block hash tree should go. The block hash tree must be put
4875+        before the salt hash tree, since its size determines the
4876+        offset of the share hash chain.
4877+        """
4878+        assert self._offsets
4879+        assert isinstance(blockhashes, list)
4880+        if "block_hash_tree" not in self._offsets:
4881+            raise LayoutInvalid("You must put the encrypted private key "
4882+                                "before you put the block hash tree")
4883+        # If written, the share hash chain causes the signature offset
4884+        # to be defined.
4885+        if "signature" in self._offsets:
4886+            raise LayoutInvalid("You must put the block hash tree before "
4887+                                "you put the share hash chain")
4888+        blockhashes_s = "".join(blockhashes)
4889+        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
4890+        datavs = []
4891+        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
4892+        def _on_failure():
4893+            del(self._offsets['share_hash_chain'])
4894+        return self._write(datavs, on_failure=_on_failure)
4895+
4896+
4897+    def put_sharehashes(self, sharehashes):
4898+        """
4899+        Put the share hash chain in the remote slot.
4900+
4901+        The salt hash tree must be put before the share hash chain,
4902+        since we need to know where the salt hash tree ends before we
4903+        can know where the share hash chain starts. The share hash chain
4904+        must be put before the signature, since the length of the packed
4905+        share hash chain determines the offset of the signature. Also,
4906+        semantically, you must know what the root of the salt hash tree
4907+        is before you can generate a valid signature.
4908+        """
4909+        assert isinstance(sharehashes, dict)
4910+        if "share_hash_chain" not in self._offsets:
4911+            raise LayoutInvalid("You need to put the salt hash tree before "
4912+                                "you can put the share hash chain")
4913+        # The signature comes after the share hash chain. If the
4914+        # signature has already been written, we must not write another
4915+        # share hash chain. The signature writes the verification key
4916+        # offset when it gets sent to the remote server, so we look for
4917+        # that.
4918+        if "verification_key" in self._offsets:
4919+            raise LayoutInvalid("You must write the share hash chain "
4920+                                "before you write the signature")
4921+        datavs = []
4922+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4923+                                  for i in sorted(sharehashes.keys())])
4924+        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
4925+        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
4926+        def _on_failure():
4927+            del(self._offsets['signature'])
4928+        return self._write(datavs, on_failure=_on_failure)
4929+
4930+
4931+    def put_root_hash(self, roothash):
4932+        """
4933+        Put the root hash (the root of the share hash tree) in the
4934+        remote slot.
4935+        """
4936+        # It does not make sense to be able to put the root
4937+        # hash without first putting the share hashes, since you need
4938+        # the share hashes to generate the root hash.
4939+        #
4940+        # Signature is defined by the routine that places the share hash
4941+        # chain, so it's a good thing to look for in finding out whether
4942+        # or not the share hash chain exists on the remote server.
4943+        if "signature" not in self._offsets:
4944+            raise LayoutInvalid("You need to put the share hash chain "
4945+                                "before you can put the root share hash")
4946+        if len(roothash) != HASH_SIZE:
4947+            raise LayoutInvalid("hashes and salts must be exactly %d bytes"
4948+                                 % HASH_SIZE)
4949+        datavs = []
4950+        self._root_hash = roothash
4951+        # To write both of these values, we update the checkstring on
4952+        # the remote server, which includes them
4953+        checkstring = self.get_checkstring()
4954+        datavs.append(tuple([0, checkstring]))
4955+        # This write, if successful, changes the checkstring, so we need
4956+        # to update our internal checkstring to be consistent with the
4957+        # one on the server.
4958+        def _on_success():
4959+            self._testvs = [(0, len(checkstring), "eq", checkstring)]
4960+        def _on_failure():
4961+            self._root_hash = None
4962+        return self._write(datavs,
4963+                           on_success=_on_success,
4964+                           on_failure=_on_failure)
4965+
4966+
4967+    def get_signable(self):
4968+        """
4969+        Get the first seven fields of the mutable file; the parts that
4970+        are signed.
4971+        """
4972+        if not self._root_hash:
4973+            raise LayoutInvalid("You need to set the root hash "
4974+                                "before getting something to "
4975+                                "sign")
4976+        return struct.pack(MDMFSIGNABLEHEADER,
4977+                           1,
4978+                           self._seqnum,
4979+                           self._root_hash,
4980+                           self._required_shares,
4981+                           self._total_shares,
4982+                           self._segment_size,
4983+                           self._data_length)
4984+
4985+
4986+    def put_signature(self, signature):
4987+        """
4988+        Put the signature field to the remote slot.
4989+
4990+        I require that the root hash and share hash chain have been put
4991+        to the grid before I will write the signature to the grid.
4992+        """
4993+        if "signature" not in self._offsets:
4994+            raise LayoutInvalid("You must put the share hash chain "
4995+        # It does not make sense to put a signature without first
4996+        # putting the root hash and the salt hash (since otherwise
4997+        # the signature would be incomplete), so we don't allow that.
4998+                       "before putting the signature")
4999+        if not self._root_hash:
5000+            raise LayoutInvalid("You must complete the signed prefix "
5001+                                "before computing a signature")
5002+        # If we put the signature after we put the verification key, we
5003+        # could end up running into the verification key, and will
5004+        # probably screw up the offsets as well. So we don't allow that.
5005+        # The method that writes the verification key defines the EOF
5006+        # offset before writing the verification key, so look for that.
5007+        if "EOF" in self._offsets:
5008+            raise LayoutInvalid("You must write the signature before the verification key")
5009+
5010+        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
5011+        datavs = []
5012+        datavs.append(tuple([self._offsets['signature'], signature]))
5013+        def _on_failure():
5014+            del(self._offsets['verification_key'])
5015+        return self._write(datavs, on_failure=_on_failure)
5016+
5017+
5018+    def put_verification_key(self, verification_key):
5019+        """
5020+        Put the verification key into the remote slot.
5021+
5022+        I require that the signature have been written to the storage
5023+        server before I allow the verification key to be written to the
5024+        remote server.
5025+        """
5026+        if "verification_key" not in self._offsets:
5027+            raise LayoutInvalid("You must put the signature before you "
5028+                                "can put the verification key")
5029+        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
5030+        datavs = []
5031+        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
5032+        def _on_failure():
5033+            del(self._offsets['EOF'])
5034+        return self._write(datavs, on_failure=_on_failure)
5035+
5036+    def _get_offsets_tuple(self):
5037+        return tuple([(key, value) for key, value in self._offsets.items()])
5038+
5039+    def get_verinfo(self):
5040+        return (self._seqnum,
5041+                self._root_hash,
5042+                self._required_shares,
5043+                self._total_shares,
5044+                self._segment_size,
5045+                self._data_length,
5046+                self.get_signable(),
5047+                self._get_offsets_tuple())
5048+
5049+
5050+    def finish_publishing(self):
5051+        """
5052+        Write the offset table and encoding parameters to the remote
5053+        slot, since that's the only thing we have yet to publish at this
5054+        point.
5055+        """
5056+        if "EOF" not in self._offsets:
5057+            raise LayoutInvalid("You must put the verification key before "
5058+                                "you can publish the offsets")
5059+        offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
5060+        offsets = struct.pack(MDMFOFFSETS,
5061+                              self._offsets['enc_privkey'],
5062+                              self._offsets['block_hash_tree'],
5063+                              self._offsets['share_hash_chain'],
5064+                              self._offsets['signature'],
5065+                              self._offsets['verification_key'],
5066+                              self._offsets['EOF'])
5067+        datavs = []
5068+        datavs.append(tuple([offsets_offset, offsets]))
5069+        encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
5070+        params = struct.pack(">BBQQ",
5071+                             self._required_shares,
5072+                             self._total_shares,
5073+                             self._segment_size,
5074+                             self._data_length)
5075+        datavs.append(tuple([encoding_parameters_offset, params]))
5076+        return self._write(datavs)
5077+
5078+
5079+    def _write(self, datavs, on_failure=None, on_success=None):
5080+        """I write the data vectors in datavs to the remote slot."""
5081+        tw_vectors = {}
5082+        new_share = False
5083+        if not self._testvs:
5084+            self._testvs = []
5085+            self._testvs.append(tuple([0, 1, "eq", ""]))
5086+            new_share = True
5087+        if not self._written:
5088+            # Write a new checkstring to the share when we write it, so
5089+            # that we have something to check later.
5090+            new_checkstring = self.get_checkstring()
5091+            datavs.append((0, new_checkstring))
5092+            def _first_write():
5093+                self._written = True
5094+                self._testvs = [(0, len(new_checkstring), "eq", new_checkstring)]
5095+            on_success = _first_write
5096+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
5097+        datalength = sum([len(x[1]) for x in datavs])
5098+        d = self._rref.callRemote("slot_testv_and_readv_and_writev",
5099+                                  self._storage_index,
5100+                                  self._secrets,
5101+                                  tw_vectors,
5102+                                  self._readv)
5103+        def _result(results):
5104+            if isinstance(results, failure.Failure) or not results[0]:
5105+                # Do nothing; the write was unsuccessful.
5106+                if on_failure: on_failure()
5107+            else:
5108+                if on_success: on_success()
5109+            return results
5110+        d.addCallback(_result)
5111+        return d
5112+
5113+
5114+class MDMFSlotReadProxy:
5115+    """
5116+    I read from a mutable slot filled with data written in the MDMF data
5117+    format (which is described above).
5118+
5119+    I can be initialized with some amount of data, which I will use (if
5120+    it is valid) to eliminate some of the need to fetch it from servers.
5121+    """
5122+    def __init__(self,
5123+                 rref,
5124+                 storage_index,
5125+                 shnum,
5126+                 data=""):
5127+        # Start the initialization process.
5128+        self._rref = rref
5129+        self._storage_index = storage_index
5130+        self.shnum = shnum
5131+
5132+        # Before doing anything, the reader is probably going to want to
5133+        # verify that the signature is correct. To do that, they'll need
5134+        # the verification key, and the signature. To get those, we'll
5135+        # need the offset table. So fetch the offset table on the
5136+        # assumption that that will be the first thing that a reader is
5137+        # going to do.
5138+
5139+        # The fact that these encoding parameters are None tells us
5140+        # that we haven't yet fetched them from the remote share, so we
5141+        # should. We could just not set them, but the checks will be
5142+        # easier to read if we don't have to use hasattr.
5143+        self._version_number = None
5144+        self._sequence_number = None
5145+        self._root_hash = None
5146+        # Filled in if we're dealing with an SDMF file. Unused
5147+        # otherwise.
5148+        self._salt = None
5149+        self._required_shares = None
5150+        self._total_shares = None
5151+        self._segment_size = None
5152+        self._data_length = None
5153+        self._offsets = None
5154+
5155+        # If the user has chosen to initialize us with some data, we'll
5156+        # try to satisfy subsequent data requests with that data before
5157+        # asking the storage server for it. If
5158+        self._data = data
5159+        # The way callers interact with cache in the filenode returns
5160+        # None if there isn't any cached data, but the way we index the
5161+        # cached data requires a string, so convert None to "".
5162+        if self._data == None:
5163+            self._data = ""
5164+
5165+        self._queue_observers = observer.ObserverList()
5166+        self._queue_errbacks = observer.ObserverList()
5167+        self._readvs = []
5168+
5169+
5170+    def _maybe_fetch_offsets_and_header(self, force_remote=False):
5171+        """
5172+        I fetch the offset table and the header from the remote slot if
5173+        I don't already have them. If I do have them, I do nothing and
5174+        return an empty Deferred.
5175+        """
5176+        if self._offsets:
5177+            return defer.succeed(None)
5178+        # At this point, we may be either SDMF or MDMF. Fetching 107
5179+        # bytes will be enough to get header and offsets for both SDMF and
5180+        # MDMF, though we'll be left with 4 more bytes than we
5181+        # need if this ends up being MDMF. This is probably less
5182+        # expensive than the cost of a second roundtrip.
5183+        readvs = [(0, 107)]
5184+        d = self._read(readvs, force_remote)
5185+        d.addCallback(self._process_encoding_parameters)
5186+        d.addCallback(self._process_offsets)
5187+        return d
5188+
5189+
5190+    def _process_encoding_parameters(self, encoding_parameters):
5191+        assert self.shnum in encoding_parameters
5192+        encoding_parameters = encoding_parameters[self.shnum][0]
5193+        # The first byte is the version number. It will tell us what
5194+        # to do next.
5195+        (verno,) = struct.unpack(">B", encoding_parameters[:1])
5196+        if verno == MDMF_VERSION:
5197+            read_size = MDMFHEADERWITHOUTOFFSETSSIZE
5198+            (verno,
5199+             seqnum,
5200+             root_hash,
5201+             k,
5202+             n,
5203+             segsize,
5204+             datalen) = struct.unpack(MDMFHEADERWITHOUTOFFSETS,
5205+                                      encoding_parameters[:read_size])
5206+            if segsize == 0 and datalen == 0:
5207+                # Empty file, no segments.
5208+                self._num_segments = 0
5209+            else:
5210+                self._num_segments = mathutil.div_ceil(datalen, segsize)
5211+
5212+        elif verno == SDMF_VERSION:
5213+            read_size = SIGNED_PREFIX_LENGTH
5214+            (verno,
5215+             seqnum,
5216+             root_hash,
5217+             salt,
5218+             k,
5219+             n,
5220+             segsize,
5221+             datalen) = struct.unpack(">BQ32s16s BBQQ",
5222+                                encoding_parameters[:SIGNED_PREFIX_LENGTH])
5223+            self._salt = salt
5224+            if segsize == 0 and datalen == 0:
5225+                # empty file
5226+                self._num_segments = 0
5227+            else:
5228+                # non-empty SDMF files have one segment.
5229+                self._num_segments = 1
5230+        else:
5231+            raise UnknownVersionError("You asked me to read mutable file "
5232+                                      "version %d, but I only understand "
5233+                                      "%d and %d" % (verno, SDMF_VERSION,
5234+                                                     MDMF_VERSION))
5235+
5236+        self._version_number = verno
5237+        self._sequence_number = seqnum
5238+        self._root_hash = root_hash
5239+        self._required_shares = k
5240+        self._total_shares = n
5241+        self._segment_size = segsize
5242+        self._data_length = datalen
5243+
5244+        self._block_size = self._segment_size / self._required_shares
5245+        # We can upload empty files, and need to account for this fact
5246+        # so as to avoid zero-division and zero-modulo errors.
5247+        if datalen > 0:
5248+            tail_size = self._data_length % self._segment_size
5249+        else:
5250+            tail_size = 0
5251+        if not tail_size:
5252+            self._tail_block_size = self._block_size
5253+        else:
5254+            self._tail_block_size = mathutil.next_multiple(tail_size,
5255+                                                    self._required_shares)
5256+            self._tail_block_size /= self._required_shares
5257+
5258+        return encoding_parameters
5259+
5260+
5261+    def _process_offsets(self, offsets):
5262+        if self._version_number == 0:
5263+            read_size = OFFSETS_LENGTH
5264+            read_offset = SIGNED_PREFIX_LENGTH
5265+            end = read_size + read_offset
5266+            (signature,
5267+             share_hash_chain,
5268+             block_hash_tree,
5269+             share_data,
5270+             enc_privkey,
5271+             EOF) = struct.unpack(">LLLLQQ",
5272+                                  offsets[read_offset:end])
5273+            self._offsets = {}
5274+            self._offsets['signature'] = signature
5275+            self._offsets['share_data'] = share_data
5276+            self._offsets['block_hash_tree'] = block_hash_tree
5277+            self._offsets['share_hash_chain'] = share_hash_chain
5278+            self._offsets['enc_privkey'] = enc_privkey
5279+            self._offsets['EOF'] = EOF
5280+
5281+        elif self._version_number == 1:
5282+            read_offset = MDMFHEADERWITHOUTOFFSETSSIZE
5283+            read_length = MDMFOFFSETS_LENGTH
5284+            end = read_offset + read_length
5285+            (encprivkey,
5286+             blockhashes,
5287+             sharehashes,
5288+             signature,
5289+             verification_key,
5290+             eof) = struct.unpack(MDMFOFFSETS,
5291+                                  offsets[read_offset:end])
5292+            self._offsets = {}
5293+            self._offsets['enc_privkey'] = encprivkey
5294+            self._offsets['block_hash_tree'] = blockhashes
5295+            self._offsets['share_hash_chain'] = sharehashes
5296+            self._offsets['signature'] = signature
5297+            self._offsets['verification_key'] = verification_key
5298+            self._offsets['EOF'] = eof
5299+
5300+
5301+    def get_block_and_salt(self, segnum, queue=False):
5302+        """
5303+        I return (block, salt), where block is the block data and
5304+        salt is the salt used to encrypt that segment.
5305+        """
5306+        d = self._maybe_fetch_offsets_and_header()
5307+        def _then(ignored):
5308+            if self._version_number == 1:
5309+                base_share_offset = MDMFHEADERSIZE
5310+            else:
5311+                base_share_offset = self._offsets['share_data']
5312+
5313+            if segnum + 1 > self._num_segments:
5314+                raise LayoutInvalid("Not a valid segment number")
5315+
5316+            if self._version_number == 0:
5317+                share_offset = base_share_offset + self._block_size * segnum
5318+            else:
5319+                share_offset = base_share_offset + (self._block_size + \
5320+                                                    SALT_SIZE) * segnum
5321+            if segnum + 1 == self._num_segments:
5322+                data = self._tail_block_size
5323+            else:
5324+                data = self._block_size
5325+
5326+            if self._version_number == 1:
5327+                data += SALT_SIZE
5328+
5329+            readvs = [(share_offset, data)]
5330+            return readvs
5331+        d.addCallback(_then)
5332+        d.addCallback(lambda readvs:
5333+            self._read(readvs, queue=queue))
5334+        def _process_results(results):
5335+            assert self.shnum in results
5336+            if self._version_number == 0:
5337+                # We only read the share data, but we know the salt from
5338+                # when we fetched the header
5339+                data = results[self.shnum]
5340+                if not data:
5341+                    data = ""
5342+                else:
5343+                    assert len(data) == 1
5344+                    data = data[0]
5345+                salt = self._salt
5346+            else:
5347+                data = results[self.shnum]
5348+                if not data:
5349+                    salt = data = ""
5350+                else:
5351+                    salt_and_data = results[self.shnum][0]
5352+                    salt = salt_and_data[:SALT_SIZE]
5353+                    data = salt_and_data[SALT_SIZE:]
5354+            return data, salt
5355+        d.addCallback(_process_results)
5356+        return d
5357+
5358+
5359+    def get_blockhashes(self, needed=None, queue=False, force_remote=False):
5360+        """
5361+        I return the block hash tree
5362+
5363+        I take an optional argument, needed, which is a set of indices
5364+        correspond to hashes that I should fetch. If this argument is
5365+        missing, I will fetch the entire block hash tree; otherwise, I
5366+        may attempt to fetch fewer hashes, based on what needed says
5367+        that I should do. Note that I may fetch as many hashes as I
5368+        want, so long as the set of hashes that I do fetch is a superset
5369+        of the ones that I am asked for, so callers should be prepared
5370+        to tolerate additional hashes.
5371+        """
5372+        # TODO: Return only the parts of the block hash tree necessary
5373+        # to validate the blocknum provided?
5374+        # This is a good idea, but it is hard to implement correctly. It
5375+        # is bad to fetch any one block hash more than once, so we
5376+        # probably just want to fetch the whole thing at once and then
5377+        # serve it.
5378+        if needed == set([]):
5379+            return defer.succeed([])
5380+        d = self._maybe_fetch_offsets_and_header()
5381+        def _then(ignored):
5382+            blockhashes_offset = self._offsets['block_hash_tree']
5383+            if self._version_number == 1:
5384+                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
5385+            else:
5386+                blockhashes_length = self._offsets['share_data'] - blockhashes_offset
5387+            readvs = [(blockhashes_offset, blockhashes_length)]
5388+            return readvs
5389+        d.addCallback(_then)
5390+        d.addCallback(lambda readvs:
5391+            self._read(readvs, queue=queue, force_remote=force_remote))
5392+        def _build_block_hash_tree(results):
5393+            assert self.shnum in results
5394+
5395+            rawhashes = results[self.shnum][0]
5396+            results = [rawhashes[i:i+HASH_SIZE]
5397+                       for i in range(0, len(rawhashes), HASH_SIZE)]
5398+            return results
5399+        d.addCallback(_build_block_hash_tree)
5400+        return d
5401+
5402+
5403+    def get_sharehashes(self, needed=None, queue=False, force_remote=False):
5404+        """
5405+        I return the part of the share hash chain placed to validate
5406+        this share.
5407+
5408+        I take an optional argument, needed. Needed is a set of indices
5409+        that correspond to the hashes that I should fetch. If needed is
5410+        not present, I will fetch and return the entire share hash
5411+        chain. Otherwise, I may fetch and return any part of the share
5412+        hash chain that is a superset of the part that I am asked to
5413+        fetch. Callers should be prepared to deal with more hashes than
5414+        they've asked for.
5415+        """
5416+        if needed == set([]):
5417+            return defer.succeed([])
5418+        d = self._maybe_fetch_offsets_and_header()
5419+
5420+        def _make_readvs(ignored):
5421+            sharehashes_offset = self._offsets['share_hash_chain']
5422+            if self._version_number == 0:
5423+                sharehashes_length = self._offsets['block_hash_tree'] - sharehashes_offset
5424+            else:
5425+                sharehashes_length = self._offsets['signature'] - sharehashes_offset
5426+            readvs = [(sharehashes_offset, sharehashes_length)]
5427+            return readvs
5428+        d.addCallback(_make_readvs)
5429+        d.addCallback(lambda readvs:
5430+            self._read(readvs, queue=queue, force_remote=force_remote))
5431+        def _build_share_hash_chain(results):
5432+            assert self.shnum in results
5433+
5434+            sharehashes = results[self.shnum][0]
5435+            results = [sharehashes[i:i+(HASH_SIZE + 2)]
5436+                       for i in range(0, len(sharehashes), HASH_SIZE + 2)]
5437+            results = dict([struct.unpack(">H32s", data)
5438+                            for data in results])
5439+            return results
5440+        d.addCallback(_build_share_hash_chain)
5441+        return d
5442+
5443+
5444+    def get_encprivkey(self, queue=False):
5445+        """
5446+        I return the encrypted private key.
5447+        """
5448+        d = self._maybe_fetch_offsets_and_header()
5449+
5450+        def _make_readvs(ignored):
5451+            privkey_offset = self._offsets['enc_privkey']
5452+            if self._version_number == 0:
5453+                privkey_length = self._offsets['EOF'] - privkey_offset
5454+            else:
5455+                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
5456+            readvs = [(privkey_offset, privkey_length)]
5457+            return readvs
5458+        d.addCallback(_make_readvs)
5459+        d.addCallback(lambda readvs:
5460+            self._read(readvs, queue=queue))
5461+        def _process_results(results):
5462+            assert self.shnum in results
5463+            privkey = results[self.shnum][0]
5464+            return privkey
5465+        d.addCallback(_process_results)
5466+        return d
5467+
5468+
5469+    def get_signature(self, queue=False):
5470+        """
5471+        I return the signature of my share.
5472+        """
5473+        d = self._maybe_fetch_offsets_and_header()
5474+
5475+        def _make_readvs(ignored):
5476+            signature_offset = self._offsets['signature']
5477+            if self._version_number == 1:
5478+                signature_length = self._offsets['verification_key'] - signature_offset
5479+            else:
5480+                signature_length = self._offsets['share_hash_chain'] - signature_offset
5481+            readvs = [(signature_offset, signature_length)]
5482+            return readvs
5483+        d.addCallback(_make_readvs)
5484+        d.addCallback(lambda readvs:
5485+            self._read(readvs, queue=queue))
5486+        def _process_results(results):
5487+            assert self.shnum in results
5488+            signature = results[self.shnum][0]
5489+            return signature
5490+        d.addCallback(_process_results)
5491+        return d
5492+
5493+
5494+    def get_verification_key(self, queue=False):
5495+        """
5496+        I return the verification key.
5497+        """
5498+        d = self._maybe_fetch_offsets_and_header()
5499+
5500+        def _make_readvs(ignored):
5501+            if self._version_number == 1:
5502+                vk_offset = self._offsets['verification_key']
5503+                vk_length = self._offsets['EOF'] - vk_offset
5504+            else:
5505+                vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5506+                vk_length = self._offsets['signature'] - vk_offset
5507+            readvs = [(vk_offset, vk_length)]
5508+            return readvs
5509+        d.addCallback(_make_readvs)
5510+        d.addCallback(lambda readvs:
5511+            self._read(readvs, queue=queue))
5512+        def _process_results(results):
5513+            assert self.shnum in results
5514+            verification_key = results[self.shnum][0]
5515+            return verification_key
5516+        d.addCallback(_process_results)
5517+        return d
5518+
5519+
5520+    def get_encoding_parameters(self):
5521+        """
5522+        I return (k, n, segsize, datalen)
5523+        """
5524+        d = self._maybe_fetch_offsets_and_header()
5525+        d.addCallback(lambda ignored:
5526+            (self._required_shares,
5527+             self._total_shares,
5528+             self._segment_size,
5529+             self._data_length))
5530+        return d
5531+
5532+
5533+    def get_seqnum(self):
5534+        """
5535+        I return the sequence number for this share.
5536+        """
5537+        d = self._maybe_fetch_offsets_and_header()
5538+        d.addCallback(lambda ignored:
5539+            self._sequence_number)
5540+        return d
5541+
5542+
5543+    def get_root_hash(self):
5544+        """
5545+        I return the root of the block hash tree
5546+        """
5547+        d = self._maybe_fetch_offsets_and_header()
5548+        d.addCallback(lambda ignored: self._root_hash)
5549+        return d
5550+
5551+
5552+    def get_checkstring(self):
5553+        """
5554+        I return the packed representation of the following:
5555+
5556+            - version number
5557+            - sequence number
5558+            - root hash
5559+            - salt hash
5560+
5561+        which my users use as a checkstring to detect other writers.
5562+        """
5563+        d = self._maybe_fetch_offsets_and_header()
5564+        def _build_checkstring(ignored):
5565+            if self._salt:
5566+                checkstring = strut.pack(PREFIX,
5567+                                         self._version_number,
5568+                                         self._sequence_number,
5569+                                         self._root_hash,
5570+                                         self._salt)
5571+            else:
5572+                checkstring = struct.pack(MDMFCHECKSTRING,
5573+                                          self._version_number,
5574+                                          self._sequence_number,
5575+                                          self._root_hash)
5576+
5577+            return checkstring
5578+        d.addCallback(_build_checkstring)
5579+        return d
5580+
5581+
5582+    def get_prefix(self, force_remote):
5583+        d = self._maybe_fetch_offsets_and_header(force_remote)
5584+        d.addCallback(lambda ignored:
5585+            self._build_prefix())
5586+        return d
5587+
5588+
5589+    def _build_prefix(self):
5590+        # The prefix is another name for the part of the remote share
5591+        # that gets signed. It consists of everything up to and
5592+        # including the datalength, packed by struct.
5593+        if self._version_number == SDMF_VERSION:
5594+            return struct.pack(SIGNED_PREFIX,
5595+                           self._version_number,
5596+                           self._sequence_number,
5597+                           self._root_hash,
5598+                           self._salt,
5599+                           self._required_shares,
5600+                           self._total_shares,
5601+                           self._segment_size,
5602+                           self._data_length)
5603+
5604+        else:
5605+            return struct.pack(MDMFSIGNABLEHEADER,
5606+                           self._version_number,
5607+                           self._sequence_number,
5608+                           self._root_hash,
5609+                           self._required_shares,
5610+                           self._total_shares,
5611+                           self._segment_size,
5612+                           self._data_length)
5613+
5614+
5615+    def _get_offsets_tuple(self):
5616+        # The offsets tuple is another component of the version
5617+        # information tuple. It is basically our offsets dictionary,
5618+        # itemized and in a tuple.
5619+        return self._offsets.copy()
5620+
5621+
5622+    def get_verinfo(self):
5623+        """
5624+        I return my verinfo tuple. This is used by the ServermapUpdater
5625+        to keep track of versions of mutable files.
5626+
5627+        The verinfo tuple for MDMF files contains:
5628+            - seqnum
5629+            - root hash
5630+            - a blank (nothing)
5631+            - segsize
5632+            - datalen
5633+            - k
5634+            - n
5635+            - prefix (the thing that you sign)
5636+            - a tuple of offsets
5637+
5638+        We include the nonce in MDMF to simplify processing of version
5639+        information tuples.
5640+
5641+        The verinfo tuple for SDMF files is the same, but contains a
5642+        16-byte IV instead of a hash of salts.
5643+        """
5644+        d = self._maybe_fetch_offsets_and_header()
5645+        def _build_verinfo(ignored):
5646+            if self._version_number == SDMF_VERSION:
5647+                salt_to_use = self._salt
5648+            else:
5649+                salt_to_use = None
5650+            return (self._sequence_number,
5651+                    self._root_hash,
5652+                    salt_to_use,
5653+                    self._segment_size,
5654+                    self._data_length,
5655+                    self._required_shares,
5656+                    self._total_shares,
5657+                    self._build_prefix(),
5658+                    self._get_offsets_tuple())
5659+        d.addCallback(_build_verinfo)
5660+        return d
5661+
5662+
5663+    def flush(self):
5664+        """
5665+        I flush my queue of read vectors.
5666+        """
5667+        d = self._read(self._readvs)
5668+        def _then(results):
5669+            self._readvs = []
5670+            if isinstance(results, failure.Failure):
5671+                self._queue_errbacks.notify(results)
5672+            else:
5673+                self._queue_observers.notify(results)
5674+            self._queue_observers = observer.ObserverList()
5675+            self._queue_errbacks = observer.ObserverList()
5676+        d.addBoth(_then)
5677+
5678+
5679+    def _read(self, readvs, force_remote=False, queue=False):
5680+        unsatisfiable = filter(lambda x: x[0] + x[1] > len(self._data), readvs)
5681+        # TODO: It's entirely possible to tweak this so that it just
5682+        # fulfills the requests that it can, and not demand that all
5683+        # requests are satisfiable before running it.
5684+        if not unsatisfiable and not force_remote:
5685+            results = [self._data[offset:offset+length]
5686+                       for (offset, length) in readvs]
5687+            results = {self.shnum: results}
5688+            return defer.succeed(results)
5689+        else:
5690+            if queue:
5691+                start = len(self._readvs)
5692+                self._readvs += readvs
5693+                end = len(self._readvs)
5694+                def _get_results(results, start, end):
5695+                    if not self.shnum in results:
5696+                        return {self._shnum: [""]}
5697+                    return {self.shnum: results[self.shnum][start:end]}
5698+                d = defer.Deferred()
5699+                d.addCallback(_get_results, start, end)
5700+                self._queue_observers.subscribe(d.callback)
5701+                self._queue_errbacks.subscribe(d.errback)
5702+                return d
5703+            return self._rref.callRemote("slot_readv",
5704+                                         self._storage_index,
5705+                                         [self.shnum],
5706+                                         readvs)
5707+
5708+
5709+    def is_sdmf(self):
5710+        """I tell my caller whether or not my remote file is SDMF or MDMF
5711+        """
5712+        d = self._maybe_fetch_offsets_and_header()
5713+        d.addCallback(lambda ignored:
5714+            self._version_number == 0)
5715+        return d
5716+
5717+
5718+class LayoutInvalid(Exception):
5719+    """
5720+    This isn't a valid MDMF mutable file
5721+    """
5722hunk ./src/allmydata/test/test_storage.py 2
5723 
5724-import time, os.path, stat, re, simplejson, struct
5725+import time, os.path, stat, re, simplejson, struct, shutil
5726 
5727 from twisted.trial import unittest
5728 
5729hunk ./src/allmydata/test/test_storage.py 22
5730 from allmydata.storage.expirer import LeaseCheckingCrawler
5731 from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \
5732      ReadBucketProxy
5733-from allmydata.interfaces import BadWriteEnablerError
5734-from allmydata.test.common import LoggingServiceParent
5735+from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
5736+                                     LayoutInvalid, MDMFSIGNABLEHEADER, \
5737+                                     SIGNED_PREFIX, MDMFHEADER, \
5738+                                     MDMFOFFSETS, SDMFSlotWriteProxy
5739+from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
5740+                                 SDMF_VERSION
5741+from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
5742 from allmydata.test.common_web import WebRenderingMixin
5743 from allmydata.web.storage import StorageStatus, remove_prefix
5744 
5745hunk ./src/allmydata/test/test_storage.py 106
5746 
5747 class RemoteBucket:
5748 
5749+    def __init__(self):
5750+        self.read_count = 0
5751+        self.write_count = 0
5752+
5753     def callRemote(self, methname, *args, **kwargs):
5754         def _call():
5755             meth = getattr(self.target, "remote_" + methname)
5756hunk ./src/allmydata/test/test_storage.py 114
5757             return meth(*args, **kwargs)
5758+
5759+        if methname == "slot_readv":
5760+            self.read_count += 1
5761+        if "writev" in methname:
5762+            self.write_count += 1
5763+
5764         return defer.maybeDeferred(_call)
5765 
5766hunk ./src/allmydata/test/test_storage.py 122
5767+
5768 class BucketProxy(unittest.TestCase):
5769     def make_bucket(self, name, size):
5770         basedir = os.path.join("storage", "BucketProxy", name)
5771hunk ./src/allmydata/test/test_storage.py 1313
5772         self.failUnless(os.path.exists(prefixdir), prefixdir)
5773         self.failIf(os.path.exists(bucketdir), bucketdir)
5774 
5775+
5776+class MDMFProxies(unittest.TestCase, ShouldFailMixin):
5777+    def setUp(self):
5778+        self.sparent = LoggingServiceParent()
5779+        self._lease_secret = itertools.count()
5780+        self.ss = self.create("MDMFProxies storage test server")
5781+        self.rref = RemoteBucket()
5782+        self.rref.target = self.ss
5783+        self.secrets = (self.write_enabler("we_secret"),
5784+                        self.renew_secret("renew_secret"),
5785+                        self.cancel_secret("cancel_secret"))
5786+        self.segment = "aaaaaa"
5787+        self.block = "aa"
5788+        self.salt = "a" * 16
5789+        self.block_hash = "a" * 32
5790+        self.block_hash_tree = [self.block_hash for i in xrange(6)]
5791+        self.share_hash = self.block_hash
5792+        self.share_hash_chain = dict([(i, self.share_hash) for i in xrange(6)])
5793+        self.signature = "foobarbaz"
5794+        self.verification_key = "vvvvvv"
5795+        self.encprivkey = "private"
5796+        self.root_hash = self.block_hash
5797+        self.salt_hash = self.root_hash
5798+        self.salt_hash_tree = [self.salt_hash for i in xrange(6)]
5799+        self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree)
5800+        self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain)
5801+        # blockhashes and salt hashes are serialized in the same way,
5802+        # only we lop off the first element and store that in the
5803+        # header.
5804+        self.salt_hash_tree_s = self.serialize_blockhashes(self.salt_hash_tree[1:])
5805+
5806+
5807+    def tearDown(self):
5808+        self.sparent.stopService()
5809+        shutil.rmtree(self.workdir("MDMFProxies storage test server"))
5810+
5811+
5812+    def write_enabler(self, we_tag):
5813+        return hashutil.tagged_hash("we_blah", we_tag)
5814+
5815+
5816+    def renew_secret(self, tag):
5817+        return hashutil.tagged_hash("renew_blah", str(tag))
5818+
5819+
5820+    def cancel_secret(self, tag):
5821+        return hashutil.tagged_hash("cancel_blah", str(tag))
5822+
5823+
5824+    def workdir(self, name):
5825+        basedir = os.path.join("storage", "MutableServer", name)
5826+        return basedir
5827+
5828+
5829+    def create(self, name):
5830+        workdir = self.workdir(name)
5831+        ss = StorageServer(workdir, "\x00" * 20)
5832+        ss.setServiceParent(self.sparent)
5833+        return ss
5834+
5835+
5836+    def build_test_mdmf_share(self, tail_segment=False, empty=False):
5837+        # Start with the checkstring
5838+        data = struct.pack(">BQ32s",
5839+                           1,
5840+                           0,
5841+                           self.root_hash)
5842+        self.checkstring = data
5843+        # Next, the encoding parameters
5844+        if tail_segment:
5845+            data += struct.pack(">BBQQ",
5846+                                3,
5847+                                10,
5848+                                6,
5849+                                33)
5850+        elif empty:
5851+            data += struct.pack(">BBQQ",
5852+                                3,
5853+                                10,
5854+                                0,
5855+                                0)
5856+        else:
5857+            data += struct.pack(">BBQQ",
5858+                                3,
5859+                                10,
5860+                                6,
5861+                                36)
5862+        # Now we'll build the offsets.
5863+        sharedata = ""
5864+        if not tail_segment and not empty:
5865+            for i in xrange(6):
5866+                sharedata += self.salt + self.block
5867+        elif tail_segment:
5868+            for i in xrange(5):
5869+                sharedata += self.salt + self.block
5870+            sharedata += self.salt + "a"
5871+
5872+        # The encrypted private key comes after the shares + salts
5873+        offset_size = struct.calcsize(MDMFOFFSETS)
5874+        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
5875+        # The blockhashes come after the private key
5876+        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
5877+        # The sharehashes come after the salt hashes
5878+        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
5879+        # The signature comes after the share hash chain
5880+        signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
5881+        # The verification key comes after the signature
5882+        verification_offset = signature_offset + len(self.signature)
5883+        # The EOF comes after the verification key
5884+        eof_offset = verification_offset + len(self.verification_key)
5885+        data += struct.pack(MDMFOFFSETS,
5886+                            encrypted_private_key_offset,
5887+                            blockhashes_offset,
5888+                            sharehashes_offset,
5889+                            signature_offset,
5890+                            verification_offset,
5891+                            eof_offset)
5892+        self.offsets = {}
5893+        self.offsets['enc_privkey'] = encrypted_private_key_offset
5894+        self.offsets['block_hash_tree'] = blockhashes_offset
5895+        self.offsets['share_hash_chain'] = sharehashes_offset
5896+        self.offsets['signature'] = signature_offset
5897+        self.offsets['verification_key'] = verification_offset
5898+        self.offsets['EOF'] = eof_offset
5899+        # Next, we'll add in the salts and share data,
5900+        data += sharedata
5901+        # the private key,
5902+        data += self.encprivkey
5903+        # the block hash tree,
5904+        data += self.block_hash_tree_s
5905+        # the share hash chain,
5906+        data += self.share_hash_chain_s
5907+        # the signature,
5908+        data += self.signature
5909+        # and the verification key
5910+        data += self.verification_key
5911+        return data
5912+
5913+
5914+    def write_test_share_to_server(self,
5915+                                   storage_index,
5916+                                   tail_segment=False,
5917+                                   empty=False):
5918+        """
5919+        I write some data for the read tests to read to self.ss
5920+
5921+        If tail_segment=True, then I will write a share that has a
5922+        smaller tail segment than other segments.
5923+        """
5924+        write = self.ss.remote_slot_testv_and_readv_and_writev
5925+        data = self.build_test_mdmf_share(tail_segment, empty)
5926+        # Finally, we write the whole thing to the storage server in one
5927+        # pass.
5928+        testvs = [(0, 1, "eq", "")]
5929+        tws = {}
5930+        tws[0] = (testvs, [(0, data)], None)
5931+        readv = [(0, 1)]
5932+        results = write(storage_index, self.secrets, tws, readv)
5933+        self.failUnless(results[0])
5934+
5935+
5936+    def build_test_sdmf_share(self, empty=False):
5937+        if empty:
5938+            sharedata = ""
5939+        else:
5940+            sharedata = self.segment * 6
5941+        self.sharedata = sharedata
5942+        blocksize = len(sharedata) / 3
5943+        block = sharedata[:blocksize]
5944+        self.blockdata = block
5945+        prefix = struct.pack(">BQ32s16s BBQQ",
5946+                             0, # version,
5947+                             0,
5948+                             self.root_hash,
5949+                             self.salt,
5950+                             3,
5951+                             10,
5952+                             len(sharedata),
5953+                             len(sharedata),
5954+                            )
5955+        post_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5956+        signature_offset = post_offset + len(self.verification_key)
5957+        sharehashes_offset = signature_offset + len(self.signature)
5958+        blockhashes_offset = sharehashes_offset + len(self.share_hash_chain_s)
5959+        sharedata_offset = blockhashes_offset + len(self.block_hash_tree_s)
5960+        encprivkey_offset = sharedata_offset + len(block)
5961+        eof_offset = encprivkey_offset + len(self.encprivkey)
5962+        offsets = struct.pack(">LLLLQQ",
5963+                              signature_offset,
5964+                              sharehashes_offset,
5965+                              blockhashes_offset,
5966+                              sharedata_offset,
5967+                              encprivkey_offset,
5968+                              eof_offset)
5969+        final_share = "".join([prefix,
5970+                           offsets,
5971+                           self.verification_key,
5972+                           self.signature,
5973+                           self.share_hash_chain_s,
5974+                           self.block_hash_tree_s,
5975+                           block,
5976+                           self.encprivkey])
5977+        self.offsets = {}
5978+        self.offsets['signature'] = signature_offset
5979+        self.offsets['share_hash_chain'] = sharehashes_offset
5980+        self.offsets['block_hash_tree'] = blockhashes_offset
5981+        self.offsets['share_data'] = sharedata_offset
5982+        self.offsets['enc_privkey'] = encprivkey_offset
5983+        self.offsets['EOF'] = eof_offset
5984+        return final_share
5985+
5986+
5987+    def write_sdmf_share_to_server(self,
5988+                                   storage_index,
5989+                                   empty=False):
5990+        # Some tests need SDMF shares to verify that we can still
5991+        # read them. This method writes one, which resembles but is not
5992+        assert self.rref
5993+        write = self.ss.remote_slot_testv_and_readv_and_writev
5994+        share = self.build_test_sdmf_share(empty)
5995+        testvs = [(0, 1, "eq", "")]
5996+        tws = {}
5997+        tws[0] = (testvs, [(0, share)], None)
5998+        readv = []
5999+        results = write(storage_index, self.secrets, tws, readv)
6000+        self.failUnless(results[0])
6001+
6002+
6003+    def test_read(self):
6004+        self.write_test_share_to_server("si1")
6005+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6006+        # Check that every method equals what we expect it to.
6007+        d = defer.succeed(None)
6008+        def _check_block_and_salt((block, salt)):
6009+            self.failUnlessEqual(block, self.block)
6010+            self.failUnlessEqual(salt, self.salt)
6011+
6012+        for i in xrange(6):
6013+            d.addCallback(lambda ignored, i=i:
6014+                mr.get_block_and_salt(i))
6015+            d.addCallback(_check_block_and_salt)
6016+
6017+        d.addCallback(lambda ignored:
6018+            mr.get_encprivkey())
6019+        d.addCallback(lambda encprivkey:
6020+            self.failUnlessEqual(self.encprivkey, encprivkey))
6021+
6022+        d.addCallback(lambda ignored:
6023+            mr.get_blockhashes())
6024+        d.addCallback(lambda blockhashes:
6025+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6026+
6027+        d.addCallback(lambda ignored:
6028+            mr.get_sharehashes())
6029+        d.addCallback(lambda sharehashes:
6030+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6031+
6032+        d.addCallback(lambda ignored:
6033+            mr.get_signature())
6034+        d.addCallback(lambda signature:
6035+            self.failUnlessEqual(signature, self.signature))
6036+
6037+        d.addCallback(lambda ignored:
6038+            mr.get_verification_key())
6039+        d.addCallback(lambda verification_key:
6040+            self.failUnlessEqual(verification_key, self.verification_key))
6041+
6042+        d.addCallback(lambda ignored:
6043+            mr.get_seqnum())
6044+        d.addCallback(lambda seqnum:
6045+            self.failUnlessEqual(seqnum, 0))
6046+
6047+        d.addCallback(lambda ignored:
6048+            mr.get_root_hash())
6049+        d.addCallback(lambda root_hash:
6050+            self.failUnlessEqual(self.root_hash, root_hash))
6051+
6052+        d.addCallback(lambda ignored:
6053+            mr.get_seqnum())
6054+        d.addCallback(lambda seqnum:
6055+            self.failUnlessEqual(0, seqnum))
6056+
6057+        d.addCallback(lambda ignored:
6058+            mr.get_encoding_parameters())
6059+        def _check_encoding_parameters((k, n, segsize, datalen)):
6060+            self.failUnlessEqual(k, 3)
6061+            self.failUnlessEqual(n, 10)
6062+            self.failUnlessEqual(segsize, 6)
6063+            self.failUnlessEqual(datalen, 36)
6064+        d.addCallback(_check_encoding_parameters)
6065+
6066+        d.addCallback(lambda ignored:
6067+            mr.get_checkstring())
6068+        d.addCallback(lambda checkstring:
6069+            self.failUnlessEqual(checkstring, checkstring))
6070+        return d
6071+
6072+
6073+    def test_read_with_different_tail_segment_size(self):
6074+        self.write_test_share_to_server("si1", tail_segment=True)
6075+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6076+        d = mr.get_block_and_salt(5)
6077+        def _check_tail_segment(results):
6078+            block, salt = results
6079+            self.failUnlessEqual(len(block), 1)
6080+            self.failUnlessEqual(block, "a")
6081+        d.addCallback(_check_tail_segment)
6082+        return d
6083+
6084+
6085+    def test_get_block_with_invalid_segnum(self):
6086+        self.write_test_share_to_server("si1")
6087+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6088+        d = defer.succeed(None)
6089+        d.addCallback(lambda ignored:
6090+            self.shouldFail(LayoutInvalid, "test invalid segnum",
6091+                            None,
6092+                            mr.get_block_and_salt, 7))
6093+        return d
6094+
6095+
6096+    def test_get_encoding_parameters_first(self):
6097+        self.write_test_share_to_server("si1")
6098+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6099+        d = mr.get_encoding_parameters()
6100+        def _check_encoding_parameters((k, n, segment_size, datalen)):
6101+            self.failUnlessEqual(k, 3)
6102+            self.failUnlessEqual(n, 10)
6103+            self.failUnlessEqual(segment_size, 6)
6104+            self.failUnlessEqual(datalen, 36)
6105+        d.addCallback(_check_encoding_parameters)
6106+        return d
6107+
6108+
6109+    def test_get_seqnum_first(self):
6110+        self.write_test_share_to_server("si1")
6111+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6112+        d = mr.get_seqnum()
6113+        d.addCallback(lambda seqnum:
6114+            self.failUnlessEqual(seqnum, 0))
6115+        return d
6116+
6117+
6118+    def test_get_root_hash_first(self):
6119+        self.write_test_share_to_server("si1")
6120+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6121+        d = mr.get_root_hash()
6122+        d.addCallback(lambda root_hash:
6123+            self.failUnlessEqual(root_hash, self.root_hash))
6124+        return d
6125+
6126+
6127+    def test_get_checkstring_first(self):
6128+        self.write_test_share_to_server("si1")
6129+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6130+        d = mr.get_checkstring()
6131+        d.addCallback(lambda checkstring:
6132+            self.failUnlessEqual(checkstring, self.checkstring))
6133+        return d
6134+
6135+
6136+    def test_write_read_vectors(self):
6137+        # When writing for us, the storage server will return to us a
6138+        # read vector, along with its result. If a write fails because
6139+        # the test vectors failed, this read vector can help us to
6140+        # diagnose the problem. This test ensures that the read vector
6141+        # is working appropriately.
6142+        mw = self._make_new_mw("si1", 0)
6143+        d = defer.succeed(None)
6144+
6145+        # Write one share. This should return a checkstring of nothing,
6146+        # since there is no data there.
6147+        d.addCallback(lambda ignored:
6148+            mw.put_block(self.block, 0, self.salt))
6149+        def _check_first_write(results):
6150+            result, readvs = results
6151+            self.failUnless(result)
6152+            self.failIf(readvs)
6153+        d.addCallback(_check_first_write)
6154+        # Now, there should be a different checkstring returned when
6155+        # we write other shares
6156+        d.addCallback(lambda ignored:
6157+            mw.put_block(self.block, 1, self.salt))
6158+        def _check_next_write(results):
6159+            result, readvs = results
6160+            self.failUnless(result)
6161+            self.expected_checkstring = mw.get_checkstring()
6162+            self.failUnlessIn(0, readvs)
6163+            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
6164+        d.addCallback(_check_next_write)
6165+        # Add the other four shares
6166+        for i in xrange(2, 6):
6167+            d.addCallback(lambda ignored, i=i:
6168+                mw.put_block(self.block, i, self.salt))
6169+            d.addCallback(_check_next_write)
6170+        # Add the encrypted private key
6171+        d.addCallback(lambda ignored:
6172+            mw.put_encprivkey(self.encprivkey))
6173+        d.addCallback(_check_next_write)
6174+        # Add the block hash tree and share hash tree
6175+        d.addCallback(lambda ignored:
6176+            mw.put_blockhashes(self.block_hash_tree))
6177+        d.addCallback(_check_next_write)
6178+        d.addCallback(lambda ignored:
6179+            mw.put_sharehashes(self.share_hash_chain))
6180+        d.addCallback(_check_next_write)
6181+        # Add the root hash and the salt hash. This should change the
6182+        # checkstring, but not in a way that we'll be able to see right
6183+        # now, since the read vectors are applied before the write
6184+        # vectors.
6185+        d.addCallback(lambda ignored:
6186+            mw.put_root_hash(self.root_hash))
6187+        def _check_old_testv_after_new_one_is_written(results):
6188+            result, readvs = results
6189+            self.failUnless(result)
6190+            self.failUnlessIn(0, readvs)
6191+            self.failUnlessEqual(self.expected_checkstring,
6192+                                 readvs[0][0])
6193+            new_checkstring = mw.get_checkstring()
6194+            self.failIfEqual(new_checkstring,
6195+                             readvs[0][0])
6196+        d.addCallback(_check_old_testv_after_new_one_is_written)
6197+        # Now add the signature. This should succeed, meaning that the
6198+        # data gets written and the read vector matches what the writer
6199+        # thinks should be there.
6200+        d.addCallback(lambda ignored:
6201+            mw.put_signature(self.signature))
6202+        d.addCallback(_check_next_write)
6203+        # The checkstring remains the same for the rest of the process.
6204+        return d
6205+
6206+
6207+    def test_blockhashes_after_share_hash_chain(self):
6208+        mw = self._make_new_mw("si1", 0)
6209+        d = defer.succeed(None)
6210+        # Put everything up to and including the share hash chain
6211+        for i in xrange(6):
6212+            d.addCallback(lambda ignored, i=i:
6213+                mw.put_block(self.block, i, self.salt))
6214+        d.addCallback(lambda ignored:
6215+            mw.put_encprivkey(self.encprivkey))
6216+        d.addCallback(lambda ignored:
6217+            mw.put_blockhashes(self.block_hash_tree))
6218+        d.addCallback(lambda ignored:
6219+            mw.put_sharehashes(self.share_hash_chain))
6220+
6221+        # Now try to put the block hash tree again.
6222+        d.addCallback(lambda ignored:
6223+            self.shouldFail(LayoutInvalid, "test repeat salthashes",
6224+                            None,
6225+                            mw.put_blockhashes, self.block_hash_tree))
6226+        return d
6227+
6228+
6229+    def test_encprivkey_after_blockhashes(self):
6230+        mw = self._make_new_mw("si1", 0)
6231+        d = defer.succeed(None)
6232+        # Put everything up to and including the block hash tree
6233+        for i in xrange(6):
6234+            d.addCallback(lambda ignored, i=i:
6235+                mw.put_block(self.block, i, self.salt))
6236+        d.addCallback(lambda ignored:
6237+            mw.put_encprivkey(self.encprivkey))
6238+        d.addCallback(lambda ignored:
6239+            mw.put_blockhashes(self.block_hash_tree))
6240+        d.addCallback(lambda ignored:
6241+            self.shouldFail(LayoutInvalid, "out of order private key",
6242+                            None,
6243+                            mw.put_encprivkey, self.encprivkey))
6244+        return d
6245+
6246+
6247+    def test_share_hash_chain_after_signature(self):
6248+        mw = self._make_new_mw("si1", 0)
6249+        d = defer.succeed(None)
6250+        # Put everything up to and including the signature
6251+        for i in xrange(6):
6252+            d.addCallback(lambda ignored, i=i:
6253+                mw.put_block(self.block, i, self.salt))
6254+        d.addCallback(lambda ignored:
6255+            mw.put_encprivkey(self.encprivkey))
6256+        d.addCallback(lambda ignored:
6257+            mw.put_blockhashes(self.block_hash_tree))
6258+        d.addCallback(lambda ignored:
6259+            mw.put_sharehashes(self.share_hash_chain))
6260+        d.addCallback(lambda ignored:
6261+            mw.put_root_hash(self.root_hash))
6262+        d.addCallback(lambda ignored:
6263+            mw.put_signature(self.signature))
6264+        # Now try to put the share hash chain again. This should fail
6265+        d.addCallback(lambda ignored:
6266+            self.shouldFail(LayoutInvalid, "out of order share hash chain",
6267+                            None,
6268+                            mw.put_sharehashes, self.share_hash_chain))
6269+        return d
6270+
6271+
6272+    def test_signature_after_verification_key(self):
6273+        mw = self._make_new_mw("si1", 0)
6274+        d = defer.succeed(None)
6275+        # Put everything up to and including the verification key.
6276+        for i in xrange(6):
6277+            d.addCallback(lambda ignored, i=i:
6278+                mw.put_block(self.block, i, self.salt))
6279+        d.addCallback(lambda ignored:
6280+            mw.put_encprivkey(self.encprivkey))
6281+        d.addCallback(lambda ignored:
6282+            mw.put_blockhashes(self.block_hash_tree))
6283+        d.addCallback(lambda ignored:
6284+            mw.put_sharehashes(self.share_hash_chain))
6285+        d.addCallback(lambda ignored:
6286+            mw.put_root_hash(self.root_hash))
6287+        d.addCallback(lambda ignored:
6288+            mw.put_signature(self.signature))
6289+        d.addCallback(lambda ignored:
6290+            mw.put_verification_key(self.verification_key))
6291+        # Now try to put the signature again. This should fail
6292+        d.addCallback(lambda ignored:
6293+            self.shouldFail(LayoutInvalid, "signature after verification",
6294+                            None,
6295+                            mw.put_signature, self.signature))
6296+        return d
6297+
6298+
6299+    def test_uncoordinated_write(self):
6300+        # Make two mutable writers, both pointing to the same storage
6301+        # server, both at the same storage index, and try writing to the
6302+        # same share.
6303+        mw1 = self._make_new_mw("si1", 0)
6304+        mw2 = self._make_new_mw("si1", 0)
6305+        d = defer.succeed(None)
6306+        def _check_success(results):
6307+            result, readvs = results
6308+            self.failUnless(result)
6309+
6310+        def _check_failure(results):
6311+            result, readvs = results
6312+            self.failIf(result)
6313+
6314+        d.addCallback(lambda ignored:
6315+            mw1.put_block(self.block, 0, self.salt))
6316+        d.addCallback(_check_success)
6317+        d.addCallback(lambda ignored:
6318+            mw2.put_block(self.block, 0, self.salt))
6319+        d.addCallback(_check_failure)
6320+        return d
6321+
6322+
6323+    def test_invalid_salt_size(self):
6324+        # Salts need to be 16 bytes in size. Writes that attempt to
6325+        # write more or less than this should be rejected.
6326+        mw = self._make_new_mw("si1", 0)
6327+        invalid_salt = "a" * 17 # 17 bytes
6328+        another_invalid_salt = "b" * 15 # 15 bytes
6329+        d = defer.succeed(None)
6330+        d.addCallback(lambda ignored:
6331+            self.shouldFail(LayoutInvalid, "salt too big",
6332+                            None,
6333+                            mw.put_block, self.block, 0, invalid_salt))
6334+        d.addCallback(lambda ignored:
6335+            self.shouldFail(LayoutInvalid, "salt too small",
6336+                            None,
6337+                            mw.put_block, self.block, 0,
6338+                            another_invalid_salt))
6339+        return d
6340+
6341+
6342+    def test_write_test_vectors(self):
6343+        # If we give the write proxy a bogus test vector at
6344+        # any point during the process, it should fail to write.
6345+        mw = self._make_new_mw("si1", 0)
6346+        mw.set_checkstring("this is a lie")
6347+        # The initial write should be expecting to find the improbable
6348+        # checkstring above in place; finding nothing, it should fail.
6349+        d = defer.succeed(None)
6350+        d.addCallback(lambda ignored:
6351+            mw.put_block(self.block, 0, self.salt))
6352+        def _check_failure(results):
6353+            result, readv = results
6354+            self.failIf(result)
6355+        d.addCallback(_check_failure)
6356+        # Now set the checkstring to the empty string, which
6357+        # indicates that no share is there.
6358+        d.addCallback(lambda ignored:
6359+            mw.set_checkstring(""))
6360+        d.addCallback(lambda ignored:
6361+            mw.put_block(self.block, 0, self.salt))
6362+        def _check_success(results):
6363+            result, readv = results
6364+            self.failUnless(result)
6365+        d.addCallback(_check_success)
6366+        # Now set the checkstring to something wrong
6367+        d.addCallback(lambda ignored:
6368+            mw.set_checkstring("something wrong"))
6369+        # This should fail to do anything
6370+        d.addCallback(lambda ignored:
6371+            mw.put_block(self.block, 1, self.salt))
6372+        d.addCallback(_check_failure)
6373+        # Now set it back to what it should be.
6374+        d.addCallback(lambda ignored:
6375+            mw.set_checkstring(mw.get_checkstring()))
6376+        for i in xrange(1, 6):
6377+            d.addCallback(lambda ignored, i=i:
6378+                mw.put_block(self.block, i, self.salt))
6379+            d.addCallback(_check_success)
6380+        d.addCallback(lambda ignored:
6381+            mw.put_encprivkey(self.encprivkey))
6382+        d.addCallback(_check_success)
6383+        d.addCallback(lambda ignored:
6384+            mw.put_blockhashes(self.block_hash_tree))
6385+        d.addCallback(_check_success)
6386+        d.addCallback(lambda ignored:
6387+            mw.put_sharehashes(self.share_hash_chain))
6388+        d.addCallback(_check_success)
6389+        def _keep_old_checkstring(ignored):
6390+            self.old_checkstring = mw.get_checkstring()
6391+            mw.set_checkstring("foobarbaz")
6392+        d.addCallback(_keep_old_checkstring)
6393+        d.addCallback(lambda ignored:
6394+            mw.put_root_hash(self.root_hash))
6395+        d.addCallback(_check_failure)
6396+        d.addCallback(lambda ignored:
6397+            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
6398+        def _restore_old_checkstring(ignored):
6399+            mw.set_checkstring(self.old_checkstring)
6400+        d.addCallback(_restore_old_checkstring)
6401+        d.addCallback(lambda ignored:
6402+            mw.put_root_hash(self.root_hash))
6403+        d.addCallback(_check_success)
6404+        # The checkstring should have been set appropriately for us on
6405+        # the last write; if we try to change it to something else,
6406+        # that change should cause the verification key step to fail.
6407+        d.addCallback(lambda ignored:
6408+            mw.set_checkstring("something else"))
6409+        d.addCallback(lambda ignored:
6410+            mw.put_signature(self.signature))
6411+        d.addCallback(_check_failure)
6412+        d.addCallback(lambda ignored:
6413+            mw.set_checkstring(mw.get_checkstring()))
6414+        d.addCallback(lambda ignored:
6415+            mw.put_signature(self.signature))
6416+        d.addCallback(_check_success)
6417+        d.addCallback(lambda ignored:
6418+            mw.put_verification_key(self.verification_key))
6419+        d.addCallback(_check_success)
6420+        return d
6421+
6422+
6423+    def test_offset_only_set_on_success(self):
6424+        # The write proxy should be smart enough to detect when a write
6425+        # has failed, and to temper its definition of progress based on
6426+        # that.
6427+        mw = self._make_new_mw("si1", 0)
6428+        d = defer.succeed(None)
6429+        for i in xrange(1, 6):
6430+            d.addCallback(lambda ignored, i=i:
6431+                mw.put_block(self.block, i, self.salt))
6432+        def _break_checkstring(ignored):
6433+            self._old_checkstring = mw.get_checkstring()
6434+            mw.set_checkstring("foobarbaz")
6435+
6436+        def _fix_checkstring(ignored):
6437+            mw.set_checkstring(self._old_checkstring)
6438+
6439+        d.addCallback(_break_checkstring)
6440+
6441+        # Setting the encrypted private key shouldn't work now, which is
6442+        # to be expected and is tested elsewhere. We also want to make
6443+        # sure that we can't add the block hash tree after a failed
6444+        # write of this sort.
6445+        d.addCallback(lambda ignored:
6446+            mw.put_encprivkey(self.encprivkey))
6447+        d.addCallback(lambda ignored:
6448+            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
6449+                            None,
6450+                            mw.put_blockhashes, self.block_hash_tree))
6451+        d.addCallback(_fix_checkstring)
6452+        d.addCallback(lambda ignored:
6453+            mw.put_encprivkey(self.encprivkey))
6454+        d.addCallback(_break_checkstring)
6455+        d.addCallback(lambda ignored:
6456+            mw.put_blockhashes(self.block_hash_tree))
6457+        d.addCallback(lambda ignored:
6458+            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
6459+                            None,
6460+                            mw.put_sharehashes, self.share_hash_chain))
6461+        d.addCallback(_fix_checkstring)
6462+        d.addCallback(lambda ignored:
6463+            mw.put_blockhashes(self.block_hash_tree))
6464+        d.addCallback(_break_checkstring)
6465+        d.addCallback(lambda ignored:
6466+            mw.put_sharehashes(self.share_hash_chain))
6467+        d.addCallback(lambda ignored:
6468+            self.shouldFail(LayoutInvalid, "out-of-order root hash",
6469+                            None,
6470+                            mw.put_root_hash, self.root_hash))
6471+        d.addCallback(_fix_checkstring)
6472+        d.addCallback(lambda ignored:
6473+            mw.put_sharehashes(self.share_hash_chain))
6474+        d.addCallback(_break_checkstring)
6475+        d.addCallback(lambda ignored:
6476+            mw.put_root_hash(self.root_hash))
6477+        d.addCallback(lambda ignored:
6478+            self.shouldFail(LayoutInvalid, "out-of-order signature",
6479+                            None,
6480+                            mw.put_signature, self.signature))
6481+        d.addCallback(_fix_checkstring)
6482+        d.addCallback(lambda ignored:
6483+            mw.put_root_hash(self.root_hash))
6484+        d.addCallback(_break_checkstring)
6485+        d.addCallback(lambda ignored:
6486+            mw.put_signature(self.signature))
6487+        d.addCallback(lambda ignored:
6488+            self.shouldFail(LayoutInvalid, "out-of-order verification key",
6489+                            None,
6490+                            mw.put_verification_key,
6491+                            self.verification_key))
6492+        d.addCallback(_fix_checkstring)
6493+        d.addCallback(lambda ignored:
6494+            mw.put_signature(self.signature))
6495+        d.addCallback(_break_checkstring)
6496+        d.addCallback(lambda ignored:
6497+            mw.put_verification_key(self.verification_key))
6498+        d.addCallback(lambda ignored:
6499+            self.shouldFail(LayoutInvalid, "out-of-order finish",
6500+                            None,
6501+                            mw.finish_publishing))
6502+        return d
6503+
6504+
6505+    def serialize_blockhashes(self, blockhashes):
6506+        return "".join(blockhashes)
6507+
6508+
6509+    def serialize_sharehashes(self, sharehashes):
6510+        ret = "".join([struct.pack(">H32s", i, sharehashes[i])
6511+                        for i in sorted(sharehashes.keys())])
6512+        return ret
6513+
6514+
6515+    def test_write(self):
6516+        # This translates to a file with 6 6-byte segments, and with 2-byte
6517+        # blocks.
6518+        mw = self._make_new_mw("si1", 0)
6519+        mw2 = self._make_new_mw("si1", 1)
6520+        # Test writing some blocks.
6521+        read = self.ss.remote_slot_readv
6522+        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
6523+        written_block_size = 2 + len(self.salt)
6524+        written_block = self.block + self.salt
6525+        def _check_block_write(i, share):
6526+            self.failUnlessEqual(read("si1", [share], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
6527+                                {share: [written_block]})
6528+        d = defer.succeed(None)
6529+        for i in xrange(6):
6530+            d.addCallback(lambda ignored, i=i:
6531+                mw.put_block(self.block, i, self.salt))
6532+            d.addCallback(lambda ignored, i=i:
6533+                _check_block_write(i, 0))
6534+        # Now try the same thing, but with share 1 instead of share 0.
6535+        for i in xrange(6):
6536+            d.addCallback(lambda ignored, i=i:
6537+                mw2.put_block(self.block, i, self.salt))
6538+            d.addCallback(lambda ignored, i=i:
6539+                _check_block_write(i, 1))
6540+
6541+        # Next, we make a fake encrypted private key, and put it onto the
6542+        # storage server.
6543+        d.addCallback(lambda ignored:
6544+            mw.put_encprivkey(self.encprivkey))
6545+        expected_private_key_offset = expected_sharedata_offset + \
6546+                                      len(written_block) * 6
6547+        self.failUnlessEqual(len(self.encprivkey), 7)
6548+        d.addCallback(lambda ignored:
6549+            self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
6550+                                 {0: [self.encprivkey]}))
6551+
6552+        # Next, we put a fake block hash tree.
6553+        d.addCallback(lambda ignored:
6554+            mw.put_blockhashes(self.block_hash_tree))
6555+        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
6556+        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
6557+        d.addCallback(lambda ignored:
6558+            self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
6559+                                 {0: [self.block_hash_tree_s]}))
6560+
6561+        # Next, put a fake share hash chain
6562+        d.addCallback(lambda ignored:
6563+            mw.put_sharehashes(self.share_hash_chain))
6564+        expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
6565+        d.addCallback(lambda ignored:
6566+            self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
6567+                                 {0: [self.share_hash_chain_s]}))
6568+
6569+        # Next, we put what is supposed to be the root hash of
6570+        # our share hash tree but isn't       
6571+        d.addCallback(lambda ignored:
6572+            mw.put_root_hash(self.root_hash))
6573+        # The root hash gets inserted at byte 9 (its position is in the header,
6574+        # and is fixed).
6575+        def _check(ignored):
6576+            self.failUnlessEqual(read("si1", [0], [(9, 32)]),
6577+                                 {0: [self.root_hash]})
6578+        d.addCallback(_check)
6579+
6580+        # Next, we put a signature of the header block.
6581+        d.addCallback(lambda ignored:
6582+            mw.put_signature(self.signature))
6583+        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
6584+        self.failUnlessEqual(len(self.signature), 9)
6585+        d.addCallback(lambda ignored:
6586+            self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
6587+                                 {0: [self.signature]}))
6588+
6589+        # Next, we put the verification key
6590+        d.addCallback(lambda ignored:
6591+            mw.put_verification_key(self.verification_key))
6592+        expected_verification_key_offset = expected_signature_offset + len(self.signature)
6593+        self.failUnlessEqual(len(self.verification_key), 6)
6594+        d.addCallback(lambda ignored:
6595+            self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
6596+                                 {0: [self.verification_key]}))
6597+
6598+        def _check_signable(ignored):
6599+            # Make sure that the signable is what we think it should be.
6600+            signable = mw.get_signable()
6601+            verno, seq, roothash, k, n, segsize, datalen = \
6602+                                            struct.unpack(">BQ32sBBQQ",
6603+                                                          signable)
6604+            self.failUnlessEqual(verno, 1)
6605+            self.failUnlessEqual(seq, 0)
6606+            self.failUnlessEqual(roothash, self.root_hash)
6607+            self.failUnlessEqual(k, 3)
6608+            self.failUnlessEqual(n, 10)
6609+            self.failUnlessEqual(segsize, 6)
6610+            self.failUnlessEqual(datalen, 36)
6611+        d.addCallback(_check_signable)
6612+        # Next, we cause the offset table to be published.
6613+        d.addCallback(lambda ignored:
6614+            mw.finish_publishing())
6615+        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
6616+
6617+        def _check_offsets(ignored):
6618+            # Check the version number to make sure that it is correct.
6619+            expected_version_number = struct.pack(">B", 1)
6620+            self.failUnlessEqual(read("si1", [0], [(0, 1)]),
6621+                                 {0: [expected_version_number]})
6622+            # Check the sequence number to make sure that it is correct
6623+            expected_sequence_number = struct.pack(">Q", 0)
6624+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
6625+                                 {0: [expected_sequence_number]})
6626+            # Check that the encoding parameters (k, N, segement size, data
6627+            # length) are what they should be. These are  3, 10, 6, 36
6628+            expected_k = struct.pack(">B", 3)
6629+            self.failUnlessEqual(read("si1", [0], [(41, 1)]),
6630+                                 {0: [expected_k]})
6631+            expected_n = struct.pack(">B", 10)
6632+            self.failUnlessEqual(read("si1", [0], [(42, 1)]),
6633+                                 {0: [expected_n]})
6634+            expected_segment_size = struct.pack(">Q", 6)
6635+            self.failUnlessEqual(read("si1", [0], [(43, 8)]),
6636+                                 {0: [expected_segment_size]})
6637+            expected_data_length = struct.pack(">Q", 36)
6638+            self.failUnlessEqual(read("si1", [0], [(51, 8)]),
6639+                                 {0: [expected_data_length]})
6640+            expected_offset = struct.pack(">Q", expected_private_key_offset)
6641+            self.failUnlessEqual(read("si1", [0], [(59, 8)]),
6642+                                 {0: [expected_offset]})
6643+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
6644+            self.failUnlessEqual(read("si1", [0], [(67, 8)]),
6645+                                 {0: [expected_offset]})
6646+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
6647+            self.failUnlessEqual(read("si1", [0], [(75, 8)]),
6648+                                 {0: [expected_offset]})
6649+            expected_offset = struct.pack(">Q", expected_signature_offset)
6650+            self.failUnlessEqual(read("si1", [0], [(83, 8)]),
6651+                                 {0: [expected_offset]})
6652+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
6653+            self.failUnlessEqual(read("si1", [0], [(91, 8)]),
6654+                                 {0: [expected_offset]})
6655+            expected_offset = struct.pack(">Q", expected_eof_offset)
6656+            self.failUnlessEqual(read("si1", [0], [(99, 8)]),
6657+                                 {0: [expected_offset]})
6658+        d.addCallback(_check_offsets)
6659+        return d
6660+
6661+    def _make_new_mw(self, si, share, datalength=36):
6662+        # This is a file of size 36 bytes. Since it has a segment
6663+        # size of 6, we know that it has 6 byte segments, which will
6664+        # be split into blocks of 2 bytes because our FEC k
6665+        # parameter is 3.
6666+        mw = MDMFSlotWriteProxy(share, self.rref, si, self.secrets, 0, 3, 10,
6667+                                6, datalength)
6668+        return mw
6669+
6670+
6671+    def test_write_rejected_with_too_many_blocks(self):
6672+        mw = self._make_new_mw("si0", 0)
6673+
6674+        # Try writing too many blocks. We should not be able to write
6675+        # more than 6
6676+        # blocks into each share.
6677+        d = defer.succeed(None)
6678+        for i in xrange(6):
6679+            d.addCallback(lambda ignored, i=i:
6680+                mw.put_block(self.block, i, self.salt))
6681+        d.addCallback(lambda ignored:
6682+            self.shouldFail(LayoutInvalid, "too many blocks",
6683+                            None,
6684+                            mw.put_block, self.block, 7, self.salt))
6685+        return d
6686+
6687+
6688+    def test_write_rejected_with_invalid_salt(self):
6689+        # Try writing an invalid salt. Salts are 16 bytes -- any more or
6690+        # less should cause an error.
6691+        mw = self._make_new_mw("si1", 0)
6692+        bad_salt = "a" * 17 # 17 bytes
6693+        d = defer.succeed(None)
6694+        d.addCallback(lambda ignored:
6695+            self.shouldFail(LayoutInvalid, "test_invalid_salt",
6696+                            None, mw.put_block, self.block, 7, bad_salt))
6697+        return d
6698+
6699+
6700+    def test_write_rejected_with_invalid_root_hash(self):
6701+        # Try writing an invalid root hash. This should be SHA256d, and
6702+        # 32 bytes long as a result.
6703+        mw = self._make_new_mw("si2", 0)
6704+        # 17 bytes != 32 bytes
6705+        invalid_root_hash = "a" * 17
6706+        d = defer.succeed(None)
6707+        # Before this test can work, we need to put some blocks + salts,
6708+        # a block hash tree, and a share hash tree. Otherwise, we'll see
6709+        # failures that match what we are looking for, but are caused by
6710+        # the constraints imposed on operation ordering.
6711+        for i in xrange(6):
6712+            d.addCallback(lambda ignored, i=i:
6713+                mw.put_block(self.block, i, self.salt))
6714+        d.addCallback(lambda ignored:
6715+            mw.put_encprivkey(self.encprivkey))
6716+        d.addCallback(lambda ignored:
6717+            mw.put_blockhashes(self.block_hash_tree))
6718+        d.addCallback(lambda ignored:
6719+            mw.put_sharehashes(self.share_hash_chain))
6720+        d.addCallback(lambda ignored:
6721+            self.shouldFail(LayoutInvalid, "invalid root hash",
6722+                            None, mw.put_root_hash, invalid_root_hash))
6723+        return d
6724+
6725+
6726+    def test_write_rejected_with_invalid_blocksize(self):
6727+        # The blocksize implied by the writer that we get from
6728+        # _make_new_mw is 2bytes -- any more or any less than this
6729+        # should be cause for failure, unless it is the tail segment, in
6730+        # which case it may not be failure.
6731+        invalid_block = "a"
6732+        mw = self._make_new_mw("si3", 0, 33) # implies a tail segment with
6733+                                             # one byte blocks
6734+        # 1 bytes != 2 bytes
6735+        d = defer.succeed(None)
6736+        d.addCallback(lambda ignored, invalid_block=invalid_block:
6737+            self.shouldFail(LayoutInvalid, "test blocksize too small",
6738+                            None, mw.put_block, invalid_block, 0,
6739+                            self.salt))
6740+        invalid_block = invalid_block * 3
6741+        # 3 bytes != 2 bytes
6742+        d.addCallback(lambda ignored:
6743+            self.shouldFail(LayoutInvalid, "test blocksize too large",
6744+                            None,
6745+                            mw.put_block, invalid_block, 0, self.salt))
6746+        for i in xrange(5):
6747+            d.addCallback(lambda ignored, i=i:
6748+                mw.put_block(self.block, i, self.salt))
6749+        # Try to put an invalid tail segment
6750+        d.addCallback(lambda ignored:
6751+            self.shouldFail(LayoutInvalid, "test invalid tail segment",
6752+                            None,
6753+                            mw.put_block, self.block, 5, self.salt))
6754+        valid_block = "a"
6755+        d.addCallback(lambda ignored:
6756+            mw.put_block(valid_block, 5, self.salt))
6757+        return d
6758+
6759+
6760+    def test_write_enforces_order_constraints(self):
6761+        # We require that the MDMFSlotWriteProxy be interacted with in a
6762+        # specific way.
6763+        # That way is:
6764+        # 0: __init__
6765+        # 1: write blocks and salts
6766+        # 2: Write the encrypted private key
6767+        # 3: Write the block hashes
6768+        # 4: Write the share hashes
6769+        # 5: Write the root hash and salt hash
6770+        # 6: Write the signature and verification key
6771+        # 7: Write the file.
6772+        #
6773+        # Some of these can be performed out-of-order, and some can't.
6774+        # The dependencies that I want to test here are:
6775+        #  - Private key before block hashes
6776+        #  - share hashes and block hashes before root hash
6777+        #  - root hash before signature
6778+        #  - signature before verification key
6779+        mw0 = self._make_new_mw("si0", 0)
6780+        # Write some shares
6781+        d = defer.succeed(None)
6782+        for i in xrange(6):
6783+            d.addCallback(lambda ignored, i=i:
6784+                mw0.put_block(self.block, i, self.salt))
6785+        # Try to write the block hashes before writing the encrypted
6786+        # private key
6787+        d.addCallback(lambda ignored:
6788+            self.shouldFail(LayoutInvalid, "block hashes before key",
6789+                            None, mw0.put_blockhashes,
6790+                            self.block_hash_tree))
6791+
6792+        # Write the private key.
6793+        d.addCallback(lambda ignored:
6794+            mw0.put_encprivkey(self.encprivkey))
6795+
6796+
6797+        # Try to write the share hash chain without writing the block
6798+        # hash tree
6799+        d.addCallback(lambda ignored:
6800+            self.shouldFail(LayoutInvalid, "share hash chain before "
6801+                                           "salt hash tree",
6802+                            None,
6803+                            mw0.put_sharehashes, self.share_hash_chain))
6804+
6805+        # Try to write the root hash and without writing either the
6806+        # block hashes or the or the share hashes
6807+        d.addCallback(lambda ignored:
6808+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6809+                            None,
6810+                            mw0.put_root_hash, self.root_hash))
6811+
6812+        # Now write the block hashes and try again
6813+        d.addCallback(lambda ignored:
6814+            mw0.put_blockhashes(self.block_hash_tree))
6815+
6816+        d.addCallback(lambda ignored:
6817+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6818+                            None, mw0.put_root_hash, self.root_hash))
6819+
6820+        # We haven't yet put the root hash on the share, so we shouldn't
6821+        # be able to sign it.
6822+        d.addCallback(lambda ignored:
6823+            self.shouldFail(LayoutInvalid, "signature before root hash",
6824+                            None, mw0.put_signature, self.signature))
6825+
6826+        d.addCallback(lambda ignored:
6827+            self.failUnlessRaises(LayoutInvalid, mw0.get_signable))
6828+
6829+        # ..and, since that fails, we also shouldn't be able to put the
6830+        # verification key.
6831+        d.addCallback(lambda ignored:
6832+            self.shouldFail(LayoutInvalid, "key before signature",
6833+                            None, mw0.put_verification_key,
6834+                            self.verification_key))
6835+
6836+        # Now write the share hashes.
6837+        d.addCallback(lambda ignored:
6838+            mw0.put_sharehashes(self.share_hash_chain))
6839+        # We should be able to write the root hash now too
6840+        d.addCallback(lambda ignored:
6841+            mw0.put_root_hash(self.root_hash))
6842+
6843+        # We should still be unable to put the verification key
6844+        d.addCallback(lambda ignored:
6845+            self.shouldFail(LayoutInvalid, "key before signature",
6846+                            None, mw0.put_verification_key,
6847+                            self.verification_key))
6848+
6849+        d.addCallback(lambda ignored:
6850+            mw0.put_signature(self.signature))
6851+
6852+        # We shouldn't be able to write the offsets to the remote server
6853+        # until the offset table is finished; IOW, until we have written
6854+        # the verification key.
6855+        d.addCallback(lambda ignored:
6856+            self.shouldFail(LayoutInvalid, "offsets before verification key",
6857+                            None,
6858+                            mw0.finish_publishing))
6859+
6860+        d.addCallback(lambda ignored:
6861+            mw0.put_verification_key(self.verification_key))
6862+        return d
6863+
6864+
6865+    def test_end_to_end(self):
6866+        mw = self._make_new_mw("si1", 0)
6867+        # Write a share using the mutable writer, and make sure that the
6868+        # reader knows how to read everything back to us.
6869+        d = defer.succeed(None)
6870+        for i in xrange(6):
6871+            d.addCallback(lambda ignored, i=i:
6872+                mw.put_block(self.block, i, self.salt))
6873+        d.addCallback(lambda ignored:
6874+            mw.put_encprivkey(self.encprivkey))
6875+        d.addCallback(lambda ignored:
6876+            mw.put_blockhashes(self.block_hash_tree))
6877+        d.addCallback(lambda ignored:
6878+            mw.put_sharehashes(self.share_hash_chain))
6879+        d.addCallback(lambda ignored:
6880+            mw.put_root_hash(self.root_hash))
6881+        d.addCallback(lambda ignored:
6882+            mw.put_signature(self.signature))
6883+        d.addCallback(lambda ignored:
6884+            mw.put_verification_key(self.verification_key))
6885+        d.addCallback(lambda ignored:
6886+            mw.finish_publishing())
6887+
6888+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6889+        def _check_block_and_salt((block, salt)):
6890+            self.failUnlessEqual(block, self.block)
6891+            self.failUnlessEqual(salt, self.salt)
6892+
6893+        for i in xrange(6):
6894+            d.addCallback(lambda ignored, i=i:
6895+                mr.get_block_and_salt(i))
6896+            d.addCallback(_check_block_and_salt)
6897+
6898+        d.addCallback(lambda ignored:
6899+            mr.get_encprivkey())
6900+        d.addCallback(lambda encprivkey:
6901+            self.failUnlessEqual(self.encprivkey, encprivkey))
6902+
6903+        d.addCallback(lambda ignored:
6904+            mr.get_blockhashes())
6905+        d.addCallback(lambda blockhashes:
6906+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6907+
6908+        d.addCallback(lambda ignored:
6909+            mr.get_sharehashes())
6910+        d.addCallback(lambda sharehashes:
6911+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6912+
6913+        d.addCallback(lambda ignored:
6914+            mr.get_signature())
6915+        d.addCallback(lambda signature:
6916+            self.failUnlessEqual(signature, self.signature))
6917+
6918+        d.addCallback(lambda ignored:
6919+            mr.get_verification_key())
6920+        d.addCallback(lambda verification_key:
6921+            self.failUnlessEqual(verification_key, self.verification_key))
6922+
6923+        d.addCallback(lambda ignored:
6924+            mr.get_seqnum())
6925+        d.addCallback(lambda seqnum:
6926+            self.failUnlessEqual(seqnum, 0))
6927+
6928+        d.addCallback(lambda ignored:
6929+            mr.get_root_hash())
6930+        d.addCallback(lambda root_hash:
6931+            self.failUnlessEqual(self.root_hash, root_hash))
6932+
6933+        d.addCallback(lambda ignored:
6934+            mr.get_encoding_parameters())
6935+        def _check_encoding_parameters((k, n, segsize, datalen)):
6936+            self.failUnlessEqual(k, 3)
6937+            self.failUnlessEqual(n, 10)
6938+            self.failUnlessEqual(segsize, 6)
6939+            self.failUnlessEqual(datalen, 36)
6940+        d.addCallback(_check_encoding_parameters)
6941+
6942+        d.addCallback(lambda ignored:
6943+            mr.get_checkstring())
6944+        d.addCallback(lambda checkstring:
6945+            self.failUnlessEqual(checkstring, mw.get_checkstring()))
6946+        return d
6947+
6948+
6949+    def test_is_sdmf(self):
6950+        # The MDMFSlotReadProxy should also know how to read SDMF files,
6951+        # since it will encounter them on the grid. Callers use the
6952+        # is_sdmf method to test this.
6953+        self.write_sdmf_share_to_server("si1")
6954+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6955+        d = mr.is_sdmf()
6956+        d.addCallback(lambda issdmf:
6957+            self.failUnless(issdmf))
6958+        return d
6959+
6960+
6961+    def test_reads_sdmf(self):
6962+        # The slot read proxy should, naturally, know how to tell us
6963+        # about data in the SDMF format
6964+        self.write_sdmf_share_to_server("si1")
6965+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6966+        d = defer.succeed(None)
6967+        d.addCallback(lambda ignored:
6968+            mr.is_sdmf())
6969+        d.addCallback(lambda issdmf:
6970+            self.failUnless(issdmf))
6971+
6972+        # What do we need to read?
6973+        #  - The sharedata
6974+        #  - The salt
6975+        d.addCallback(lambda ignored:
6976+            mr.get_block_and_salt(0))
6977+        def _check_block_and_salt(results):
6978+            block, salt = results
6979+            # Our original file is 36 bytes long. Then each share is 12
6980+            # bytes in size. The share is composed entirely of the
6981+            # letter a. self.block contains 2 as, so 6 * self.block is
6982+            # what we are looking for.
6983+            self.failUnlessEqual(block, self.block * 6)
6984+            self.failUnlessEqual(salt, self.salt)
6985+        d.addCallback(_check_block_and_salt)
6986+
6987+        #  - The blockhashes
6988+        d.addCallback(lambda ignored:
6989+            mr.get_blockhashes())
6990+        d.addCallback(lambda blockhashes:
6991+            self.failUnlessEqual(self.block_hash_tree,
6992+                                 blockhashes,
6993+                                 blockhashes))
6994+        #  - The sharehashes
6995+        d.addCallback(lambda ignored:
6996+            mr.get_sharehashes())
6997+        d.addCallback(lambda sharehashes:
6998+            self.failUnlessEqual(self.share_hash_chain,
6999+                                 sharehashes))
7000+        #  - The keys
7001+        d.addCallback(lambda ignored:
7002+            mr.get_encprivkey())
7003+        d.addCallback(lambda encprivkey:
7004+            self.failUnlessEqual(encprivkey, self.encprivkey, encprivkey))
7005+        d.addCallback(lambda ignored:
7006+            mr.get_verification_key())
7007+        d.addCallback(lambda verification_key:
7008+            self.failUnlessEqual(verification_key,
7009+                                 self.verification_key,
7010+                                 verification_key))
7011+        #  - The signature
7012+        d.addCallback(lambda ignored:
7013+            mr.get_signature())
7014+        d.addCallback(lambda signature:
7015+            self.failUnlessEqual(signature, self.signature, signature))
7016+
7017+        #  - The sequence number
7018+        d.addCallback(lambda ignored:
7019+            mr.get_seqnum())
7020+        d.addCallback(lambda seqnum:
7021+            self.failUnlessEqual(seqnum, 0, seqnum))
7022+
7023+        #  - The root hash
7024+        d.addCallback(lambda ignored:
7025+            mr.get_root_hash())
7026+        d.addCallback(lambda root_hash:
7027+            self.failUnlessEqual(root_hash, self.root_hash, root_hash))
7028+        return d
7029+
7030+
7031+    def test_only_reads_one_segment_sdmf(self):
7032+        # SDMF shares have only one segment, so it doesn't make sense to
7033+        # read more segments than that. The reader should know this and
7034+        # complain if we try to do that.
7035+        self.write_sdmf_share_to_server("si1")
7036+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7037+        d = defer.succeed(None)
7038+        d.addCallback(lambda ignored:
7039+            mr.is_sdmf())
7040+        d.addCallback(lambda issdmf:
7041+            self.failUnless(issdmf))
7042+        d.addCallback(lambda ignored:
7043+            self.shouldFail(LayoutInvalid, "test bad segment",
7044+                            None,
7045+                            mr.get_block_and_salt, 1))
7046+        return d
7047+
7048+
7049+    def test_read_with_prefetched_mdmf_data(self):
7050+        # The MDMFSlotReadProxy will prefill certain fields if you pass
7051+        # it data that you have already fetched. This is useful for
7052+        # cases like the Servermap, which prefetches ~2kb of data while
7053+        # finding out which shares are on the remote peer so that it
7054+        # doesn't waste round trips.
7055+        mdmf_data = self.build_test_mdmf_share()
7056+        self.write_test_share_to_server("si1")
7057+        def _make_mr(ignored, length):
7058+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:length])
7059+            return mr
7060+
7061+        d = defer.succeed(None)
7062+        # This should be enough to fill in both the encoding parameters
7063+        # and the table of offsets, which will complete the version
7064+        # information tuple.
7065+        d.addCallback(_make_mr, 107)
7066+        d.addCallback(lambda mr:
7067+            mr.get_verinfo())
7068+        def _check_verinfo(verinfo):
7069+            self.failUnless(verinfo)
7070+            self.failUnlessEqual(len(verinfo), 9)
7071+            (seqnum,
7072+             root_hash,
7073+             salt_hash,
7074+             segsize,
7075+             datalen,
7076+             k,
7077+             n,
7078+             prefix,
7079+             offsets) = verinfo
7080+            self.failUnlessEqual(seqnum, 0)
7081+            self.failUnlessEqual(root_hash, self.root_hash)
7082+            self.failUnlessEqual(segsize, 6)
7083+            self.failUnlessEqual(datalen, 36)
7084+            self.failUnlessEqual(k, 3)
7085+            self.failUnlessEqual(n, 10)
7086+            expected_prefix = struct.pack(MDMFSIGNABLEHEADER,
7087+                                          1,
7088+                                          seqnum,
7089+                                          root_hash,
7090+                                          k,
7091+                                          n,
7092+                                          segsize,
7093+                                          datalen)
7094+            self.failUnlessEqual(expected_prefix, prefix)
7095+            self.failUnlessEqual(self.rref.read_count, 0)
7096+        d.addCallback(_check_verinfo)
7097+        # This is not enough data to read a block and a share, so the
7098+        # wrapper should attempt to read this from the remote server.
7099+        d.addCallback(_make_mr, 107)
7100+        d.addCallback(lambda mr:
7101+            mr.get_block_and_salt(0))
7102+        def _check_block_and_salt((block, salt)):
7103+            self.failUnlessEqual(block, self.block)
7104+            self.failUnlessEqual(salt, self.salt)
7105+            self.failUnlessEqual(self.rref.read_count, 1)
7106+        # This should be enough data to read one block.
7107+        d.addCallback(_make_mr, 249)
7108+        d.addCallback(lambda mr:
7109+            mr.get_block_and_salt(0))
7110+        d.addCallback(_check_block_and_salt)
7111+        return d
7112+
7113+
7114+    def test_read_with_prefetched_sdmf_data(self):
7115+        sdmf_data = self.build_test_sdmf_share()
7116+        self.write_sdmf_share_to_server("si1")
7117+        def _make_mr(ignored, length):
7118+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:length])
7119+            return mr
7120+
7121+        d = defer.succeed(None)
7122+        # This should be enough to get us the encoding parameters,
7123+        # offset table, and everything else we need to build a verinfo
7124+        # string.
7125+        d.addCallback(_make_mr, 107)
7126+        d.addCallback(lambda mr:
7127+            mr.get_verinfo())
7128+        def _check_verinfo(verinfo):
7129+            self.failUnless(verinfo)
7130+            self.failUnlessEqual(len(verinfo), 9)
7131+            (seqnum,
7132+             root_hash,
7133+             salt,
7134+             segsize,
7135+             datalen,
7136+             k,
7137+             n,
7138+             prefix,
7139+             offsets) = verinfo
7140+            self.failUnlessEqual(seqnum, 0)
7141+            self.failUnlessEqual(root_hash, self.root_hash)
7142+            self.failUnlessEqual(salt, self.salt)
7143+            self.failUnlessEqual(segsize, 36)
7144+            self.failUnlessEqual(datalen, 36)
7145+            self.failUnlessEqual(k, 3)
7146+            self.failUnlessEqual(n, 10)
7147+            expected_prefix = struct.pack(SIGNED_PREFIX,
7148+                                          0,
7149+                                          seqnum,
7150+                                          root_hash,
7151+                                          salt,
7152+                                          k,
7153+                                          n,
7154+                                          segsize,
7155+                                          datalen)
7156+            self.failUnlessEqual(expected_prefix, prefix)
7157+            self.failUnlessEqual(self.rref.read_count, 0)
7158+        d.addCallback(_check_verinfo)
7159+        # This shouldn't be enough to read any share data.
7160+        d.addCallback(_make_mr, 107)
7161+        d.addCallback(lambda mr:
7162+            mr.get_block_and_salt(0))
7163+        def _check_block_and_salt((block, salt)):
7164+            self.failUnlessEqual(block, self.block * 6)
7165+            self.failUnlessEqual(salt, self.salt)
7166+            # TODO: Fix the read routine so that it reads only the data
7167+            #       that it has cached if it can't read all of it.
7168+            self.failUnlessEqual(self.rref.read_count, 2)
7169+
7170+        # This should be enough to read share data.
7171+        d.addCallback(_make_mr, self.offsets['share_data'])
7172+        d.addCallback(lambda mr:
7173+            mr.get_block_and_salt(0))
7174+        d.addCallback(_check_block_and_salt)
7175+        return d
7176+
7177+
7178+    def test_read_with_empty_mdmf_file(self):
7179+        # Some tests upload a file with no contents to test things
7180+        # unrelated to the actual handling of the content of the file.
7181+        # The reader should behave intelligently in these cases.
7182+        self.write_test_share_to_server("si1", empty=True)
7183+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7184+        # We should be able to get the encoding parameters, and they
7185+        # should be correct.
7186+        d = defer.succeed(None)
7187+        d.addCallback(lambda ignored:
7188+            mr.get_encoding_parameters())
7189+        def _check_encoding_parameters(params):
7190+            self.failUnlessEqual(len(params), 4)
7191+            k, n, segsize, datalen = params
7192+            self.failUnlessEqual(k, 3)
7193+            self.failUnlessEqual(n, 10)
7194+            self.failUnlessEqual(segsize, 0)
7195+            self.failUnlessEqual(datalen, 0)
7196+        d.addCallback(_check_encoding_parameters)
7197+
7198+        # We should not be able to fetch a block, since there are no
7199+        # blocks to fetch
7200+        d.addCallback(lambda ignored:
7201+            self.shouldFail(LayoutInvalid, "get block on empty file",
7202+                            None,
7203+                            mr.get_block_and_salt, 0))
7204+        return d
7205+
7206+
7207+    def test_read_with_empty_sdmf_file(self):
7208+        self.write_sdmf_share_to_server("si1", empty=True)
7209+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7210+        # We should be able to get the encoding parameters, and they
7211+        # should be correct
7212+        d = defer.succeed(None)
7213+        d.addCallback(lambda ignored:
7214+            mr.get_encoding_parameters())
7215+        def _check_encoding_parameters(params):
7216+            self.failUnlessEqual(len(params), 4)
7217+            k, n, segsize, datalen = params
7218+            self.failUnlessEqual(k, 3)
7219+            self.failUnlessEqual(n, 10)
7220+            self.failUnlessEqual(segsize, 0)
7221+            self.failUnlessEqual(datalen, 0)
7222+        d.addCallback(_check_encoding_parameters)
7223+
7224+        # It does not make sense to get a block in this format, so we
7225+        # should not be able to.
7226+        d.addCallback(lambda ignored:
7227+            self.shouldFail(LayoutInvalid, "get block on an empty file",
7228+                            None,
7229+                            mr.get_block_and_salt, 0))
7230+        return d
7231+
7232+
7233+    def test_verinfo_with_sdmf_file(self):
7234+        self.write_sdmf_share_to_server("si1")
7235+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7236+        # We should be able to get the version information.
7237+        d = defer.succeed(None)
7238+        d.addCallback(lambda ignored:
7239+            mr.get_verinfo())
7240+        def _check_verinfo(verinfo):
7241+            self.failUnless(verinfo)
7242+            self.failUnlessEqual(len(verinfo), 9)
7243+            (seqnum,
7244+             root_hash,
7245+             salt,
7246+             segsize,
7247+             datalen,
7248+             k,
7249+             n,
7250+             prefix,
7251+             offsets) = verinfo
7252+            self.failUnlessEqual(seqnum, 0)
7253+            self.failUnlessEqual(root_hash, self.root_hash)
7254+            self.failUnlessEqual(salt, self.salt)
7255+            self.failUnlessEqual(segsize, 36)
7256+            self.failUnlessEqual(datalen, 36)
7257+            self.failUnlessEqual(k, 3)
7258+            self.failUnlessEqual(n, 10)
7259+            expected_prefix = struct.pack(">BQ32s16s BBQQ",
7260+                                          0,
7261+                                          seqnum,
7262+                                          root_hash,
7263+                                          salt,
7264+                                          k,
7265+                                          n,
7266+                                          segsize,
7267+                                          datalen)
7268+            self.failUnlessEqual(prefix, expected_prefix)
7269+            self.failUnlessEqual(offsets, self.offsets)
7270+        d.addCallback(_check_verinfo)
7271+        return d
7272+
7273+
7274+    def test_verinfo_with_mdmf_file(self):
7275+        self.write_test_share_to_server("si1")
7276+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7277+        d = defer.succeed(None)
7278+        d.addCallback(lambda ignored:
7279+            mr.get_verinfo())
7280+        def _check_verinfo(verinfo):
7281+            self.failUnless(verinfo)
7282+            self.failUnlessEqual(len(verinfo), 9)
7283+            (seqnum,
7284+             root_hash,
7285+             IV,
7286+             segsize,
7287+             datalen,
7288+             k,
7289+             n,
7290+             prefix,
7291+             offsets) = verinfo
7292+            self.failUnlessEqual(seqnum, 0)
7293+            self.failUnlessEqual(root_hash, self.root_hash)
7294+            self.failIf(IV)
7295+            self.failUnlessEqual(segsize, 6)
7296+            self.failUnlessEqual(datalen, 36)
7297+            self.failUnlessEqual(k, 3)
7298+            self.failUnlessEqual(n, 10)
7299+            expected_prefix = struct.pack(">BQ32s BBQQ",
7300+                                          1,
7301+                                          seqnum,
7302+                                          root_hash,
7303+                                          k,
7304+                                          n,
7305+                                          segsize,
7306+                                          datalen)
7307+            self.failUnlessEqual(prefix, expected_prefix)
7308+            self.failUnlessEqual(offsets, self.offsets)
7309+        d.addCallback(_check_verinfo)
7310+        return d
7311+
7312+
7313+    def test_reader_queue(self):
7314+        self.write_test_share_to_server('si1')
7315+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7316+        d1 = mr.get_block_and_salt(0, queue=True)
7317+        d2 = mr.get_blockhashes(queue=True)
7318+        d3 = mr.get_sharehashes(queue=True)
7319+        d4 = mr.get_signature(queue=True)
7320+        d5 = mr.get_verification_key(queue=True)
7321+        dl = defer.DeferredList([d1, d2, d3, d4, d5])
7322+        mr.flush()
7323+        def _print(results):
7324+            self.failUnlessEqual(len(results), 5)
7325+            # We have one read for version information and offsets, and
7326+            # one for everything else.
7327+            self.failUnlessEqual(self.rref.read_count, 2)
7328+            block, salt = results[0][1] # results[0] is a boolean that says
7329+                                           # whether or not the operation
7330+                                           # worked.
7331+            self.failUnlessEqual(self.block, block)
7332+            self.failUnlessEqual(self.salt, salt)
7333+
7334+            blockhashes = results[1][1]
7335+            self.failUnlessEqual(self.block_hash_tree, blockhashes)
7336+
7337+            sharehashes = results[2][1]
7338+            self.failUnlessEqual(self.share_hash_chain, sharehashes)
7339+
7340+            signature = results[3][1]
7341+            self.failUnlessEqual(self.signature, signature)
7342+
7343+            verification_key = results[4][1]
7344+            self.failUnlessEqual(self.verification_key, verification_key)
7345+        dl.addCallback(_print)
7346+        return dl
7347+
7348+
7349+    def test_sdmf_writer(self):
7350+        # Go through the motions of writing an SDMF share to the storage
7351+        # server. Then read the storage server to see that the share got
7352+        # written in the way that we think it should have.
7353+
7354+        # We do this first so that the necessary instance variables get
7355+        # set the way we want them for the tests below.
7356+        data = self.build_test_sdmf_share()
7357+        sdmfr = SDMFSlotWriteProxy(0,
7358+                                   self.rref,
7359+                                   "si1",
7360+                                   self.secrets,
7361+                                   0, 3, 10, 36, 36)
7362+        # Put the block and salt.
7363+        sdmfr.put_block(self.blockdata, 0, self.salt)
7364+
7365+        # Put the encprivkey
7366+        sdmfr.put_encprivkey(self.encprivkey)
7367+
7368+        # Put the block and share hash chains
7369+        sdmfr.put_blockhashes(self.block_hash_tree)
7370+        sdmfr.put_sharehashes(self.share_hash_chain)
7371+        sdmfr.put_root_hash(self.root_hash)
7372+
7373+        # Put the signature
7374+        sdmfr.put_signature(self.signature)
7375+
7376+        # Put the verification key
7377+        sdmfr.put_verification_key(self.verification_key)
7378+
7379+        # Now check to make sure that nothing has been written yet.
7380+        self.failUnlessEqual(self.rref.write_count, 0)
7381+
7382+        # Now finish publishing
7383+        d = sdmfr.finish_publishing()
7384+        def _then(ignored):
7385+            self.failUnlessEqual(self.rref.write_count, 1)
7386+            read = self.ss.remote_slot_readv
7387+            self.failUnlessEqual(read("si1", [0], [(0, len(data))]),
7388+                                 {0: [data]})
7389+        d.addCallback(_then)
7390+        return d
7391+
7392+
7393+    def test_sdmf_writer_preexisting_share(self):
7394+        data = self.build_test_sdmf_share()
7395+        self.write_sdmf_share_to_server("si1")
7396+
7397+        # Now there is a share on the storage server. To successfully
7398+        # write, we need to set the checkstring correctly. When we
7399+        # don't, no write should occur.
7400+        sdmfw = SDMFSlotWriteProxy(0,
7401+                                   self.rref,
7402+                                   "si1",
7403+                                   self.secrets,
7404+                                   1, 3, 10, 36, 36)
7405+        sdmfw.put_block(self.blockdata, 0, self.salt)
7406+
7407+        # Put the encprivkey
7408+        sdmfw.put_encprivkey(self.encprivkey)
7409+
7410+        # Put the block and share hash chains
7411+        sdmfw.put_blockhashes(self.block_hash_tree)
7412+        sdmfw.put_sharehashes(self.share_hash_chain)
7413+
7414+        # Put the root hash
7415+        sdmfw.put_root_hash(self.root_hash)
7416+
7417+        # Put the signature
7418+        sdmfw.put_signature(self.signature)
7419+
7420+        # Put the verification key
7421+        sdmfw.put_verification_key(self.verification_key)
7422+
7423+        # We shouldn't have a checkstring yet
7424+        self.failUnlessEqual(sdmfw.get_checkstring(), "")
7425+
7426+        d = sdmfw.finish_publishing()
7427+        def _then(results):
7428+            self.failIf(results[0])
7429+            # this is the correct checkstring
7430+            self._expected_checkstring = results[1][0][0]
7431+            return self._expected_checkstring
7432+
7433+        d.addCallback(_then)
7434+        d.addCallback(sdmfw.set_checkstring)
7435+        d.addCallback(lambda ignored:
7436+            sdmfw.get_checkstring())
7437+        d.addCallback(lambda checkstring:
7438+            self.failUnlessEqual(checkstring, self._expected_checkstring))
7439+        d.addCallback(lambda ignored:
7440+            sdmfw.finish_publishing())
7441+        def _then_again(results):
7442+            self.failUnless(results[0])
7443+            read = self.ss.remote_slot_readv
7444+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
7445+                                 {0: [struct.pack(">Q", 1)]})
7446+            self.failUnlessEqual(read("si1", [0], [(9, len(data) - 9)]),
7447+                                 {0: [data[9:]]})
7448+        d.addCallback(_then_again)
7449+        return d
7450+
7451+
7452 class Stats(unittest.TestCase):
7453 
7454     def setUp(self):
7455}
7456[mutable/publish.py: cleanup + simplification
7457Kevan Carstensen <kevan@isnotajoke.com>**20100702225554
7458 Ignore-this: 36a58424ceceffb1ddc55cc5934399e2
7459] {
7460hunk ./src/allmydata/mutable/publish.py 19
7461      UncoordinatedWriteError, NotEnoughServersError
7462 from allmydata.mutable.servermap import ServerMap
7463 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
7464-     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
7465+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy, \
7466+     SDMFSlotWriteProxy
7467 
7468 KiB = 1024
7469 DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
7470hunk ./src/allmydata/mutable/publish.py 24
7471+PUSHING_BLOCKS_STATE = 0
7472+PUSHING_EVERYTHING_ELSE_STATE = 1
7473+DONE_STATE = 2
7474 
7475 class PublishStatus:
7476     implements(IPublishStatus)
7477hunk ./src/allmydata/mutable/publish.py 229
7478 
7479         self.bad_share_checkstrings = {}
7480 
7481+        # This is set at the last step of the publishing process.
7482+        self.versioninfo = ""
7483+
7484         # we use the servermap to populate the initial goal: this way we will
7485         # try to update each existing share in place.
7486         for (peerid, shnum) in self._servermap.servermap:
7487hunk ./src/allmydata/mutable/publish.py 245
7488             self.bad_share_checkstrings[key] = old_checkstring
7489             self.connections[peerid] = self._servermap.connections[peerid]
7490 
7491-        # Now, the process dovetails -- if this is an SDMF file, we need
7492-        # to write an SDMF file. Otherwise, we need to write an MDMF
7493-        # file.
7494-        if self._version == MDMF_VERSION:
7495-            return self._publish_mdmf()
7496-        else:
7497-            return self._publish_sdmf()
7498-        #return self.done_deferred
7499-
7500-    def _publish_mdmf(self):
7501-        # Next, we find homes for all of the shares that we don't have
7502-        # homes for yet.
7503         # TODO: Make this part do peer selection.
7504         self.update_goal()
7505         self.writers = {}
7506hunk ./src/allmydata/mutable/publish.py 248
7507-        # For each (peerid, shnum) in self.goal, we make an
7508-        # MDMFSlotWriteProxy for that peer. We'll use this to write
7509+        if self._version == MDMF_VERSION:
7510+            writer_class = MDMFSlotWriteProxy
7511+        else:
7512+            writer_class = SDMFSlotWriteProxy
7513+
7514+        # For each (peerid, shnum) in self.goal, we make a
7515+        # write proxy for that peer. We'll use this to write
7516         # shares to the peer.
7517         for key in self.goal:
7518             peerid, shnum = key
7519hunk ./src/allmydata/mutable/publish.py 263
7520             cancel_secret = self._node.get_cancel_secret(peerid)
7521             secrets = (write_enabler, renew_secret, cancel_secret)
7522 
7523-            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
7524-                                                      self.connections[peerid],
7525-                                                      self._storage_index,
7526-                                                      secrets,
7527-                                                      self._new_seqnum,
7528-                                                      self.required_shares,
7529-                                                      self.total_shares,
7530-                                                      self.segment_size,
7531-                                                      len(self.newdata))
7532+            self.writers[shnum] =  writer_class(shnum,
7533+                                                self.connections[peerid],
7534+                                                self._storage_index,
7535+                                                secrets,
7536+                                                self._new_seqnum,
7537+                                                self.required_shares,
7538+                                                self.total_shares,
7539+                                                self.segment_size,
7540+                                                len(self.newdata))
7541+            self.writers[shnum].peerid = peerid
7542             if (peerid, shnum) in self._servermap.servermap:
7543                 old_versionid, old_timestamp = self._servermap.servermap[key]
7544                 (old_seqnum, old_root_hash, old_salt, old_segsize,
7545hunk ./src/allmydata/mutable/publish.py 278
7546                  old_datalength, old_k, old_N, old_prefix,
7547                  old_offsets_tuple) = old_versionid
7548-                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
7549+                self.writers[shnum].set_checkstring(old_seqnum,
7550+                                                    old_root_hash,
7551+                                                    old_salt)
7552+            elif (peerid, shnum) in self.bad_share_checkstrings:
7553+                old_checkstring = self.bad_share_checkstrings[(peerid, shnum)]
7554+                self.writers[shnum].set_checkstring(old_checkstring)
7555+
7556+        # Our remote shares will not have a complete checkstring until
7557+        # after we are done writing share data and have started to write
7558+        # blocks. In the meantime, we need to know what to look for when
7559+        # writing, so that we can detect UncoordinatedWriteErrors.
7560+        self._checkstring = self.writers.values()[0].get_checkstring()
7561 
7562         # Now, we start pushing shares.
7563         self._status.timings["setup"] = time.time() - self._started
7564hunk ./src/allmydata/mutable/publish.py 293
7565-        def _start_pushing(res):
7566-            self._started_pushing = time.time()
7567-            return res
7568-
7569         # First, we encrypt, encode, and publish the shares that we need
7570         # to encrypt, encode, and publish.
7571 
7572hunk ./src/allmydata/mutable/publish.py 306
7573 
7574         d = defer.succeed(None)
7575         self.log("Starting push")
7576-        for i in xrange(self.num_segments - 1):
7577-            d.addCallback(lambda ignored, i=i:
7578-                self.push_segment(i))
7579-            d.addCallback(self._turn_barrier)
7580-        # We have at least one segment, so we will have a tail segment
7581-        if self.num_segments > 0:
7582-            d.addCallback(lambda ignored:
7583-                self.push_tail_segment())
7584-
7585-        d.addCallback(lambda ignored:
7586-            self.push_encprivkey())
7587-        d.addCallback(lambda ignored:
7588-            self.push_blockhashes())
7589-        d.addCallback(lambda ignored:
7590-            self.push_sharehashes())
7591-        d.addCallback(lambda ignored:
7592-            self.push_toplevel_hashes_and_signature())
7593-        d.addCallback(lambda ignored:
7594-            self.finish_publishing())
7595-        return d
7596-
7597-
7598-    def _publish_sdmf(self):
7599-        self._status.timings["setup"] = time.time() - self._started
7600-        self.salt = os.urandom(16)
7601 
7602hunk ./src/allmydata/mutable/publish.py 307
7603-        d = self._encrypt_and_encode()
7604-        d.addCallback(self._generate_shares)
7605-        def _start_pushing(res):
7606-            self._started_pushing = time.time()
7607-            return res
7608-        d.addCallback(_start_pushing)
7609-        d.addCallback(self.loop) # trigger delivery
7610-        d.addErrback(self._fatal_error)
7611+        self._state = PUSHING_BLOCKS_STATE
7612+        self._push()
7613 
7614         return self.done_deferred
7615 
7616hunk ./src/allmydata/mutable/publish.py 327
7617                                                   segment_size)
7618         else:
7619             self.num_segments = 0
7620+
7621+        self.log("building encoding parameters for file")
7622+        self.log("got segsize %d" % self.segment_size)
7623+        self.log("got %d segments" % self.num_segments)
7624+
7625         if self._version == SDMF_VERSION:
7626             assert self.num_segments in (0, 1) # SDMF
7627hunk ./src/allmydata/mutable/publish.py 334
7628-            return
7629         # calculate the tail segment size.
7630hunk ./src/allmydata/mutable/publish.py 335
7631-        self.tail_segment_size = len(self.newdata) % segment_size
7632 
7633hunk ./src/allmydata/mutable/publish.py 336
7634-        if self.tail_segment_size == 0:
7635+        if segment_size and self.newdata:
7636+            self.tail_segment_size = len(self.newdata) % segment_size
7637+        else:
7638+            self.tail_segment_size = 0
7639+
7640+        if self.tail_segment_size == 0 and segment_size:
7641             # The tail segment is the same size as the other segments.
7642             self.tail_segment_size = segment_size
7643 
7644hunk ./src/allmydata/mutable/publish.py 345
7645-        # We'll make an encoder ahead-of-time for the normal-sized
7646-        # segments (defined as any segment of segment_size size.
7647-        # (the part of the code that puts the tail segment will make its
7648-        #  own encoder for that part)
7649+        # Make FEC encoders
7650         fec = codec.CRSEncoder()
7651         fec.set_params(self.segment_size,
7652                        self.required_shares, self.total_shares)
7653hunk ./src/allmydata/mutable/publish.py 352
7654         self.piece_size = fec.get_block_size()
7655         self.fec = fec
7656 
7657+        if self.tail_segment_size == self.segment_size:
7658+            self.tail_fec = self.fec
7659+        else:
7660+            tail_fec = codec.CRSEncoder()
7661+            tail_fec.set_params(self.tail_segment_size,
7662+                                self.required_shares,
7663+                                self.total_shares)
7664+            self.tail_fec = tail_fec
7665+
7666+        self._current_segment = 0
7667+
7668+
7669+    def _push(self, ignored=None):
7670+        """
7671+        I manage state transitions. In particular, I see that we still
7672+        have a good enough number of writers to complete the upload
7673+        successfully.
7674+        """
7675+        # Can we still successfully publish this file?
7676+        # TODO: Keep track of outstanding queries before aborting the
7677+        #       process.
7678+        if len(self.writers) <= self.required_shares or self.surprised:
7679+            return self._failure()
7680+
7681+        # Figure out what we need to do next. Each of these needs to
7682+        # return a deferred so that we don't block execution when this
7683+        # is first called in the upload method.
7684+        if self._state == PUSHING_BLOCKS_STATE:
7685+            return self.push_segment(self._current_segment)
7686+
7687+        # XXX: Do we want more granularity in states? Is that useful at
7688+        #      all?
7689+        #      Yes -- quicker reaction to UCW.
7690+        elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
7691+            return self.push_everything_else()
7692+
7693+        # If we make it to this point, we were successful in placing the
7694+        # file.
7695+        return self._done(None)
7696+
7697 
7698     def push_segment(self, segnum):
7699hunk ./src/allmydata/mutable/publish.py 394
7700+        if self.num_segments == 0 and self._version == SDMF_VERSION:
7701+            self._add_dummy_salts()
7702+
7703+        if segnum == self.num_segments:
7704+            # We don't have any more segments to push.
7705+            self._state = PUSHING_EVERYTHING_ELSE_STATE
7706+            return self._push()
7707+
7708+        d = self._encode_segment(segnum)
7709+        d.addCallback(self._push_segment, segnum)
7710+        def _increment_segnum(ign):
7711+            self._current_segment += 1
7712+        # XXX: I don't think we need to do addBoth here -- any errBacks
7713+        # should be handled within push_segment.
7714+        d.addBoth(_increment_segnum)
7715+        d.addBoth(self._push)
7716+
7717+
7718+    def _add_dummy_salts(self):
7719+        """
7720+        SDMF files need a salt even if they're empty, or the signature
7721+        won't make sense. This method adds a dummy salt to each of our
7722+        SDMF writers so that they can write the signature later.
7723+        """
7724+        salt = os.urandom(16)
7725+        assert self._version == SDMF_VERSION
7726+
7727+        for writer in self.writers.itervalues():
7728+            writer.put_salt(salt)
7729+
7730+
7731+    def _encode_segment(self, segnum):
7732+        """
7733+        I encrypt and encode the segment segnum.
7734+        """
7735         started = time.time()
7736hunk ./src/allmydata/mutable/publish.py 430
7737-        segsize = self.segment_size
7738+
7739+        if segnum + 1 == self.num_segments:
7740+            segsize = self.tail_segment_size
7741+        else:
7742+            segsize = self.segment_size
7743+
7744+
7745+        offset = self.segment_size * segnum
7746+        length = segsize + offset
7747         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
7748hunk ./src/allmydata/mutable/publish.py 440
7749-        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
7750+        data = self.newdata[offset:length]
7751         assert len(data) == segsize
7752 
7753         salt = os.urandom(16)
7754hunk ./src/allmydata/mutable/publish.py 455
7755         started = now
7756 
7757         # now apply FEC
7758+        if segnum + 1 == self.num_segments:
7759+            fec = self.tail_fec
7760+        else:
7761+            fec = self.fec
7762 
7763         self._status.set_status("Encoding")
7764         crypttext_pieces = [None] * self.required_shares
7765hunk ./src/allmydata/mutable/publish.py 462
7766-        piece_size = self.piece_size
7767+        piece_size = fec.get_block_size()
7768         for i in range(len(crypttext_pieces)):
7769             offset = i * piece_size
7770             piece = crypttext[offset:offset+piece_size]
7771hunk ./src/allmydata/mutable/publish.py 469
7772             piece = piece + "\x00"*(piece_size - len(piece)) # padding
7773             crypttext_pieces[i] = piece
7774             assert len(piece) == piece_size
7775-        d = self.fec.encode(crypttext_pieces)
7776+        d = fec.encode(crypttext_pieces)
7777         def _done_encoding(res):
7778             elapsed = time.time() - started
7779             self._status.timings["encode"] = elapsed
7780hunk ./src/allmydata/mutable/publish.py 473
7781-            return res
7782+            return (res, salt)
7783         d.addCallback(_done_encoding)
7784hunk ./src/allmydata/mutable/publish.py 475
7785-
7786-        def _push_shares_and_salt(results):
7787-            shares, shareids = results
7788-            dl = []
7789-            for i in xrange(len(shares)):
7790-                sharedata = shares[i]
7791-                shareid = shareids[i]
7792-                block_hash = hashutil.block_hash(salt + sharedata)
7793-                self.blockhashes[shareid].append(block_hash)
7794-
7795-                # find the writer for this share
7796-                d = self.writers[shareid].put_block(sharedata, segnum, salt)
7797-                dl.append(d)
7798-            # TODO: Naturally, we need to check on the results of these.
7799-            return defer.DeferredList(dl)
7800-        d.addCallback(_push_shares_and_salt)
7801         return d
7802 
7803 
7804hunk ./src/allmydata/mutable/publish.py 478
7805-    def push_tail_segment(self):
7806-        # This is essentially the same as push_segment, except that we
7807-        # don't use the cached encoder that we use elsewhere.
7808-        self.log("Pushing tail segment")
7809+    def _push_segment(self, encoded_and_salt, segnum):
7810+        """
7811+        I push (data, salt) as segment number segnum.
7812+        """
7813+        results, salt = encoded_and_salt
7814+        shares, shareids = results
7815         started = time.time()
7816hunk ./src/allmydata/mutable/publish.py 485
7817-        segsize = self.segment_size
7818-        data = self.newdata[segsize * (self.num_segments-1):]
7819-        assert len(data) == self.tail_segment_size
7820-        salt = os.urandom(16)
7821-
7822-        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
7823-        enc = AES(key)
7824-        crypttext = enc.process(data)
7825-        assert len(crypttext) == len(data)
7826+        dl = []
7827+        for i in xrange(len(shares)):
7828+            sharedata = shares[i]
7829+            shareid = shareids[i]
7830+            if self._version == MDMF_VERSION:
7831+                hashed = salt + sharedata
7832+            else:
7833+                hashed = sharedata
7834+            block_hash = hashutil.block_hash(hashed)
7835+            self.blockhashes[shareid].append(block_hash)
7836 
7837hunk ./src/allmydata/mutable/publish.py 496
7838-        now = time.time()
7839-        self._status.timings['encrypt'] = now - started
7840-        started = now
7841+            # find the writer for this share
7842+            writer = self.writers[shareid]
7843+            d = writer.put_block(sharedata, segnum, salt)
7844+            d.addCallback(self._got_write_answer, writer, started)
7845+            d.addErrback(self._connection_problem, writer)
7846+            dl.append(d)
7847+            # TODO: Naturally, we need to check on the results of these.
7848+        return defer.DeferredList(dl)
7849 
7850hunk ./src/allmydata/mutable/publish.py 505
7851-        self._status.set_status("Encoding")
7852-        tail_fec = codec.CRSEncoder()
7853-        tail_fec.set_params(self.tail_segment_size,
7854-                            self.required_shares,
7855-                            self.total_shares)
7856 
7857hunk ./src/allmydata/mutable/publish.py 506
7858-        crypttext_pieces = [None] * self.required_shares
7859-        piece_size = tail_fec.get_block_size()
7860-        for i in range(len(crypttext_pieces)):
7861-            offset = i * piece_size
7862-            piece = crypttext[offset:offset+piece_size]
7863-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
7864-            crypttext_pieces[i] = piece
7865-            assert len(piece) == piece_size
7866-        d = tail_fec.encode(crypttext_pieces)
7867-        def _push_shares_and_salt(results):
7868-            shares, shareids = results
7869-            dl = []
7870-            for i in xrange(len(shares)):
7871-                sharedata = shares[i]
7872-                shareid = shareids[i]
7873-                block_hash = hashutil.block_hash(salt + sharedata)
7874-                self.blockhashes[shareid].append(block_hash)
7875-                # find the writer for this share
7876-                d = self.writers[shareid].put_block(sharedata,
7877-                                                    self.num_segments - 1,
7878-                                                    salt)
7879-                dl.append(d)
7880-            # TODO: Naturally, we need to check on the results of these.
7881-            return defer.DeferredList(dl)
7882-        d.addCallback(_push_shares_and_salt)
7883+    def push_everything_else(self):
7884+        """
7885+        I put everything else associated with a share.
7886+        """
7887+        encprivkey = self._encprivkey
7888+        d = self.push_encprivkey()
7889+        d.addCallback(self.push_blockhashes)
7890+        d.addCallback(self.push_sharehashes)
7891+        d.addCallback(self.push_toplevel_hashes_and_signature)
7892+        d.addCallback(self.finish_publishing)
7893+        def _change_state(ignored):
7894+            self._state = DONE_STATE
7895+        d.addCallback(_change_state)
7896+        d.addCallback(self._push)
7897         return d
7898 
7899 
7900hunk ./src/allmydata/mutable/publish.py 527
7901         started = time.time()
7902         encprivkey = self._encprivkey
7903         dl = []
7904-        def _spy_on_writer(results):
7905-            print results
7906-            return results
7907-        for shnum, writer in self.writers.iteritems():
7908+        for writer in self.writers.itervalues():
7909             d = writer.put_encprivkey(encprivkey)
7910hunk ./src/allmydata/mutable/publish.py 529
7911+            d.addCallback(self._got_write_answer, writer, started)
7912+            d.addErrback(self._connection_problem, writer)
7913             dl.append(d)
7914         d = defer.DeferredList(dl)
7915         return d
7916hunk ./src/allmydata/mutable/publish.py 536
7917 
7918 
7919-    def push_blockhashes(self):
7920+    def push_blockhashes(self, ignored):
7921         started = time.time()
7922         dl = []
7923hunk ./src/allmydata/mutable/publish.py 539
7924-        def _spy_on_results(results):
7925-            print results
7926-            return results
7927         self.sharehash_leaves = [None] * len(self.blockhashes)
7928         for shnum, blockhashes in self.blockhashes.iteritems():
7929             t = hashtree.HashTree(blockhashes)
7930hunk ./src/allmydata/mutable/publish.py 545
7931             self.blockhashes[shnum] = list(t)
7932             # set the leaf for future use.
7933             self.sharehash_leaves[shnum] = t[0]
7934-            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
7935+            writer = self.writers[shnum]
7936+            d = writer.put_blockhashes(self.blockhashes[shnum])
7937+            d.addCallback(self._got_write_answer, writer, started)
7938+            d.addErrback(self._connection_problem, self.writers[shnum])
7939             dl.append(d)
7940         d = defer.DeferredList(dl)
7941         return d
7942hunk ./src/allmydata/mutable/publish.py 554
7943 
7944 
7945-    def push_sharehashes(self):
7946+    def push_sharehashes(self, ignored):
7947+        started = time.time()
7948         share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
7949         share_hash_chain = {}
7950         ds = []
7951hunk ./src/allmydata/mutable/publish.py 559
7952-        def _spy_on_results(results):
7953-            print results
7954-            return results
7955         for shnum in xrange(len(self.sharehash_leaves)):
7956             needed_indices = share_hash_tree.needed_hashes(shnum)
7957             self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
7958hunk ./src/allmydata/mutable/publish.py 563
7959                                              for i in needed_indices] )
7960-            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
7961+            writer = self.writers[shnum]
7962+            d = writer.put_sharehashes(self.sharehashes[shnum])
7963+            d.addCallback(self._got_write_answer, writer, started)
7964+            d.addErrback(self._connection_problem, writer)
7965             ds.append(d)
7966         self.root_hash = share_hash_tree[0]
7967         d = defer.DeferredList(ds)
7968hunk ./src/allmydata/mutable/publish.py 573
7969         return d
7970 
7971 
7972-    def push_toplevel_hashes_and_signature(self):
7973+    def push_toplevel_hashes_and_signature(self, ignored):
7974         # We need to to three things here:
7975         #   - Push the root hash and salt hash
7976         #   - Get the checkstring of the resulting layout; sign that.
7977hunk ./src/allmydata/mutable/publish.py 578
7978         #   - Push the signature
7979+        started = time.time()
7980         ds = []
7981hunk ./src/allmydata/mutable/publish.py 580
7982-        def _spy_on_results(results):
7983-            print results
7984-            return results
7985         for shnum in xrange(self.total_shares):
7986hunk ./src/allmydata/mutable/publish.py 581
7987-            d = self.writers[shnum].put_root_hash(self.root_hash)
7988+            writer = self.writers[shnum]
7989+            d = writer.put_root_hash(self.root_hash)
7990+            d.addCallback(self._got_write_answer, writer, started)
7991             ds.append(d)
7992         d = defer.DeferredList(ds)
7993hunk ./src/allmydata/mutable/publish.py 586
7994-        def _make_and_place_signature(ignored):
7995-            signable = self.writers[0].get_signable()
7996-            self.signature = self._privkey.sign(signable)
7997-
7998-            ds = []
7999-            for (shnum, writer) in self.writers.iteritems():
8000-                d = writer.put_signature(self.signature)
8001-                ds.append(d)
8002-            return defer.DeferredList(ds)
8003-        d.addCallback(_make_and_place_signature)
8004+        d.addCallback(self._update_checkstring)
8005+        d.addCallback(self._make_and_place_signature)
8006         return d
8007 
8008 
8009hunk ./src/allmydata/mutable/publish.py 591
8010-    def finish_publishing(self):
8011+    def _update_checkstring(self, ignored):
8012+        """
8013+        After putting the root hash, MDMF files will have the
8014+        checkstring written to the storage server. This means that we
8015+        can update our copy of the checkstring so we can detect
8016+        uncoordinated writes. SDMF files will have the same checkstring,
8017+        so we need not do anything.
8018+        """
8019+        self._checkstring = self.writers.values()[0].get_checkstring()
8020+
8021+
8022+    def _make_and_place_signature(self, ignored):
8023+        """
8024+        I create and place the signature.
8025+        """
8026+        started = time.time()
8027+        signable = self.writers[0].get_signable()
8028+        self.signature = self._privkey.sign(signable)
8029+
8030+        ds = []
8031+        for (shnum, writer) in self.writers.iteritems():
8032+            d = writer.put_signature(self.signature)
8033+            d.addCallback(self._got_write_answer, writer, started)
8034+            d.addErrback(self._connection_problem, writer)
8035+            ds.append(d)
8036+        return defer.DeferredList(ds)
8037+
8038+
8039+    def finish_publishing(self, ignored):
8040         # We're almost done -- we just need to put the verification key
8041         # and the offsets
8042hunk ./src/allmydata/mutable/publish.py 622
8043+        started = time.time()
8044         ds = []
8045         verification_key = self._pubkey.serialize()
8046 
8047hunk ./src/allmydata/mutable/publish.py 626
8048-        def _spy_on_results(results):
8049-            print results
8050-            return results
8051+
8052+        # TODO: Bad, since we remove from this same dict. We need to
8053+        # make a copy, or just use a non-iterated value.
8054         for (shnum, writer) in self.writers.iteritems():
8055             d = writer.put_verification_key(verification_key)
8056hunk ./src/allmydata/mutable/publish.py 631
8057+            d.addCallback(self._got_write_answer, writer, started)
8058+            d.addCallback(self._record_verinfo)
8059             d.addCallback(lambda ignored, writer=writer:
8060                 writer.finish_publishing())
8061hunk ./src/allmydata/mutable/publish.py 635
8062+            d.addCallback(self._got_write_answer, writer, started)
8063+            d.addErrback(self._connection_problem, writer)
8064             ds.append(d)
8065         return defer.DeferredList(ds)
8066 
8067hunk ./src/allmydata/mutable/publish.py 641
8068 
8069-    def _turn_barrier(self, res):
8070-        # putting this method in a Deferred chain imposes a guaranteed
8071-        # reactor turn between the pre- and post- portions of that chain.
8072-        # This can be useful to limit memory consumption: since Deferreds do
8073-        # not do tail recursion, code which uses defer.succeed(result) for
8074-        # consistency will cause objects to live for longer than you might
8075-        # normally expect.
8076-        return fireEventually(res)
8077+    def _record_verinfo(self, ignored):
8078+        self.versioninfo = self.writers.values()[0].get_verinfo()
8079 
8080 
8081hunk ./src/allmydata/mutable/publish.py 645
8082-    def _fatal_error(self, f):
8083-        self.log("error during loop", failure=f, level=log.UNUSUAL)
8084-        self._done(f)
8085+    def _connection_problem(self, f, writer):
8086+        """
8087+        We ran into a connection problem while working with writer, and
8088+        need to deal with that.
8089+        """
8090+        self.log("found problem: %s" % str(f))
8091+        self._last_failure = f
8092+        del(self.writers[writer.shnum])
8093 
8094hunk ./src/allmydata/mutable/publish.py 654
8095-    def _update_status(self):
8096-        self._status.set_status("Sending Shares: %d placed out of %d, "
8097-                                "%d messages outstanding" %
8098-                                (len(self.placed),
8099-                                 len(self.goal),
8100-                                 len(self.outstanding)))
8101-        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
8102 
8103     def loop(self, ignored=None):
8104         self.log("entering loop", level=log.NOISY)
8105hunk ./src/allmydata/mutable/publish.py 778
8106             self.log_goal(self.goal, "after update: ")
8107 
8108 
8109-    def _encrypt_and_encode(self):
8110-        # this returns a Deferred that fires with a list of (sharedata,
8111-        # sharenum) tuples. TODO: cache the ciphertext, only produce the
8112-        # shares that we care about.
8113-        self.log("_encrypt_and_encode")
8114-
8115-        self._status.set_status("Encrypting")
8116-        started = time.time()
8117+    def _got_write_answer(self, answer, writer, started):
8118+        if not answer:
8119+            # SDMF writers only pretend to write when readers set their
8120+            # blocks, salts, and so on -- they actually just write once,
8121+            # at the end of the upload process. In fake writes, they
8122+            # return defer.succeed(None). If we see that, we shouldn't
8123+            # bother checking it.
8124+            return
8125 
8126hunk ./src/allmydata/mutable/publish.py 787
8127-        key = hashutil.ssk_readkey_data_hash(self.salt, self.readkey)
8128-        enc = AES(key)
8129-        crypttext = enc.process(self.newdata)
8130-        assert len(crypttext) == len(self.newdata)
8131+        peerid = writer.peerid
8132+        lp = self.log("_got_write_answer from %s, share %d" %
8133+                      (idlib.shortnodeid_b2a(peerid), writer.shnum))
8134 
8135         now = time.time()
8136hunk ./src/allmydata/mutable/publish.py 792
8137-        self._status.timings["encrypt"] = now - started
8138-        started = now
8139-
8140-        # now apply FEC
8141-
8142-        self._status.set_status("Encoding")
8143-        fec = codec.CRSEncoder()
8144-        fec.set_params(self.segment_size,
8145-                       self.required_shares, self.total_shares)
8146-        piece_size = fec.get_block_size()
8147-        crypttext_pieces = [None] * self.required_shares
8148-        for i in range(len(crypttext_pieces)):
8149-            offset = i * piece_size
8150-            piece = crypttext[offset:offset+piece_size]
8151-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
8152-            crypttext_pieces[i] = piece
8153-            assert len(piece) == piece_size
8154-
8155-        d = fec.encode(crypttext_pieces)
8156-        def _done_encoding(res):
8157-            elapsed = time.time() - started
8158-            self._status.timings["encode"] = elapsed
8159-            return res
8160-        d.addCallback(_done_encoding)
8161-        return d
8162-
8163-
8164-    def _generate_shares(self, shares_and_shareids):
8165-        # this sets self.shares and self.root_hash
8166-        self.log("_generate_shares")
8167-        self._status.set_status("Generating Shares")
8168-        started = time.time()
8169-
8170-        # we should know these by now
8171-        privkey = self._privkey
8172-        encprivkey = self._encprivkey
8173-        pubkey = self._pubkey
8174-
8175-        (shares, share_ids) = shares_and_shareids
8176-
8177-        assert len(shares) == len(share_ids)
8178-        assert len(shares) == self.total_shares
8179-        all_shares = {}
8180-        block_hash_trees = {}
8181-        share_hash_leaves = [None] * len(shares)
8182-        for i in range(len(shares)):
8183-            share_data = shares[i]
8184-            shnum = share_ids[i]
8185-            all_shares[shnum] = share_data
8186-
8187-            # build the block hash tree. SDMF has only one leaf.
8188-            leaves = [hashutil.block_hash(share_data)]
8189-            t = hashtree.HashTree(leaves)
8190-            block_hash_trees[shnum] = list(t)
8191-            share_hash_leaves[shnum] = t[0]
8192-        for leaf in share_hash_leaves:
8193-            assert leaf is not None
8194-        share_hash_tree = hashtree.HashTree(share_hash_leaves)
8195-        share_hash_chain = {}
8196-        for shnum in range(self.total_shares):
8197-            needed_hashes = share_hash_tree.needed_hashes(shnum)
8198-            share_hash_chain[shnum] = dict( [ (i, share_hash_tree[i])
8199-                                              for i in needed_hashes ] )
8200-        root_hash = share_hash_tree[0]
8201-        assert len(root_hash) == 32
8202-        self.log("my new root_hash is %s" % base32.b2a(root_hash))
8203-        self._new_version_info = (self._new_seqnum, root_hash, self.salt)
8204-
8205-        prefix = pack_prefix(self._new_seqnum, root_hash, self.salt,
8206-                             self.required_shares, self.total_shares,
8207-                             self.segment_size, len(self.newdata))
8208-
8209-        # now pack the beginning of the share. All shares are the same up
8210-        # to the signature, then they have divergent share hash chains,
8211-        # then completely different block hash trees + salt + share data,
8212-        # then they all share the same encprivkey at the end. The sizes
8213-        # of everything are the same for all shares.
8214-
8215-        sign_started = time.time()
8216-        signature = privkey.sign(prefix)
8217-        self._status.timings["sign"] = time.time() - sign_started
8218-
8219-        verification_key = pubkey.serialize()
8220-
8221-        final_shares = {}
8222-        for shnum in range(self.total_shares):
8223-            final_share = pack_share(prefix,
8224-                                     verification_key,
8225-                                     signature,
8226-                                     share_hash_chain[shnum],
8227-                                     block_hash_trees[shnum],
8228-                                     all_shares[shnum],
8229-                                     encprivkey)
8230-            final_shares[shnum] = final_share
8231-        elapsed = time.time() - started
8232-        self._status.timings["pack"] = elapsed
8233-        self.shares = final_shares
8234-        self.root_hash = root_hash
8235-
8236-        # we also need to build up the version identifier for what we're
8237-        # pushing. Extract the offsets from one of our shares.
8238-        assert final_shares
8239-        offsets = unpack_header(final_shares.values()[0])[-1]
8240-        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
8241-        verinfo = (self._new_seqnum, root_hash, self.salt,
8242-                   self.segment_size, len(self.newdata),
8243-                   self.required_shares, self.total_shares,
8244-                   prefix, offsets_tuple)
8245-        self.versioninfo = verinfo
8246-
8247-
8248-
8249-    def _send_shares(self, needed):
8250-        self.log("_send_shares")
8251-
8252-        # we're finally ready to send out our shares. If we encounter any
8253-        # surprises here, it's because somebody else is writing at the same
8254-        # time. (Note: in the future, when we remove the _query_peers() step
8255-        # and instead speculate about [or remember] which shares are where,
8256-        # surprises here are *not* indications of UncoordinatedWriteError,
8257-        # and we'll need to respond to them more gracefully.)
8258-
8259-        # needed is a set of (peerid, shnum) tuples. The first thing we do is
8260-        # organize it by peerid.
8261-
8262-        peermap = DictOfSets()
8263-        for (peerid, shnum) in needed:
8264-            peermap.add(peerid, shnum)
8265-
8266-        # the next thing is to build up a bunch of test vectors. The
8267-        # semantics of Publish are that we perform the operation if the world
8268-        # hasn't changed since the ServerMap was constructed (more or less).
8269-        # For every share we're trying to place, we create a test vector that
8270-        # tests to see if the server*share still corresponds to the
8271-        # map.
8272-
8273-        all_tw_vectors = {} # maps peerid to tw_vectors
8274-        sm = self._servermap.servermap
8275-
8276-        for key in needed:
8277-            (peerid, shnum) = key
8278-
8279-            if key in sm:
8280-                # an old version of that share already exists on the
8281-                # server, according to our servermap. We will create a
8282-                # request that attempts to replace it.
8283-                old_versionid, old_timestamp = sm[key]
8284-                (old_seqnum, old_root_hash, old_salt, old_segsize,
8285-                 old_datalength, old_k, old_N, old_prefix,
8286-                 old_offsets_tuple) = old_versionid
8287-                old_checkstring = pack_checkstring(old_seqnum,
8288-                                                   old_root_hash,
8289-                                                   old_salt)
8290-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8291-
8292-            elif key in self.bad_share_checkstrings:
8293-                old_checkstring = self.bad_share_checkstrings[key]
8294-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8295-
8296-            else:
8297-                # add a testv that requires the share not exist
8298-
8299-                # Unfortunately, foolscap-0.2.5 has a bug in the way inbound
8300-                # constraints are handled. If the same object is referenced
8301-                # multiple times inside the arguments, foolscap emits a
8302-                # 'reference' token instead of a distinct copy of the
8303-                # argument. The bug is that these 'reference' tokens are not
8304-                # accepted by the inbound constraint code. To work around
8305-                # this, we need to prevent python from interning the
8306-                # (constant) tuple, by creating a new copy of this vector
8307-                # each time.
8308-
8309-                # This bug is fixed in foolscap-0.2.6, and even though this
8310-                # version of Tahoe requires foolscap-0.3.1 or newer, we are
8311-                # supposed to be able to interoperate with older versions of
8312-                # Tahoe which are allowed to use older versions of foolscap,
8313-                # including foolscap-0.2.5 . In addition, I've seen other
8314-                # foolscap problems triggered by 'reference' tokens (see #541
8315-                # for details). So we must keep this workaround in place.
8316-
8317-                #testv = (0, 1, 'eq', "")
8318-                testv = tuple([0, 1, 'eq', ""])
8319-
8320-            testvs = [testv]
8321-            # the write vector is simply the share
8322-            writev = [(0, self.shares[shnum])]
8323-
8324-            if peerid not in all_tw_vectors:
8325-                all_tw_vectors[peerid] = {}
8326-                # maps shnum to (testvs, writevs, new_length)
8327-            assert shnum not in all_tw_vectors[peerid]
8328-
8329-            all_tw_vectors[peerid][shnum] = (testvs, writev, None)
8330-
8331-        # we read the checkstring back from each share, however we only use
8332-        # it to detect whether there was a new share that we didn't know
8333-        # about. The success or failure of the write will tell us whether
8334-        # there was a collision or not. If there is a collision, the first
8335-        # thing we'll do is update the servermap, which will find out what
8336-        # happened. We could conceivably reduce a roundtrip by using the
8337-        # readv checkstring to populate the servermap, but really we'd have
8338-        # to read enough data to validate the signatures too, so it wouldn't
8339-        # be an overall win.
8340-        read_vector = [(0, struct.calcsize(SIGNED_PREFIX))]
8341-
8342-        # ok, send the messages!
8343-        self.log("sending %d shares" % len(all_tw_vectors), level=log.NOISY)
8344-        started = time.time()
8345-        for (peerid, tw_vectors) in all_tw_vectors.items():
8346-
8347-            write_enabler = self._node.get_write_enabler(peerid)
8348-            renew_secret = self._node.get_renewal_secret(peerid)
8349-            cancel_secret = self._node.get_cancel_secret(peerid)
8350-            secrets = (write_enabler, renew_secret, cancel_secret)
8351-            shnums = tw_vectors.keys()
8352-
8353-            for shnum in shnums:
8354-                self.outstanding.add( (peerid, shnum) )
8355-
8356-            d = self._do_testreadwrite(peerid, secrets,
8357-                                       tw_vectors, read_vector)
8358-            d.addCallbacks(self._got_write_answer, self._got_write_error,
8359-                           callbackArgs=(peerid, shnums, started),
8360-                           errbackArgs=(peerid, shnums, started))
8361-            # tolerate immediate errback, like with DeadReferenceError
8362-            d.addBoth(fireEventually)
8363-            d.addCallback(self.loop)
8364-            d.addErrback(self._fatal_error)
8365-
8366-        self._update_status()
8367-        self.log("%d shares sent" % len(all_tw_vectors), level=log.NOISY)
8368+        elapsed = now - started
8369 
8370hunk ./src/allmydata/mutable/publish.py 794
8371-    def _do_testreadwrite(self, peerid, secrets,
8372-                          tw_vectors, read_vector):
8373-        storage_index = self._storage_index
8374-        ss = self.connections[peerid]
8375+        self._status.add_per_server_time(peerid, elapsed)
8376 
8377hunk ./src/allmydata/mutable/publish.py 796
8378-        #print "SS[%s] is %s" % (idlib.shortnodeid_b2a(peerid), ss), ss.tracker.interfaceName
8379-        d = ss.callRemote("slot_testv_and_readv_and_writev",
8380-                          storage_index,
8381-                          secrets,
8382-                          tw_vectors,
8383-                          read_vector)
8384-        return d
8385+        wrote, read_data = answer
8386 
8387hunk ./src/allmydata/mutable/publish.py 798
8388-    def _got_write_answer(self, answer, peerid, shnums, started):
8389-        lp = self.log("_got_write_answer from %s" %
8390-                      idlib.shortnodeid_b2a(peerid))
8391-        for shnum in shnums:
8392-            self.outstanding.discard( (peerid, shnum) )
8393+        surprise_shares = set(read_data.keys()) - set([writer.shnum])
8394 
8395hunk ./src/allmydata/mutable/publish.py 800
8396-        now = time.time()
8397-        elapsed = now - started
8398-        self._status.add_per_server_time(peerid, elapsed)
8399+        # We need to remove from surprise_shares any shares that we are
8400+        # knowingly also writing to that peer from other writers.
8401 
8402hunk ./src/allmydata/mutable/publish.py 803
8403-        wrote, read_data = answer
8404+        # TODO: Precompute this.
8405+        known_shnums = [x.shnum for x in self.writers.values()
8406+                        if x.peerid == peerid]
8407+        surprise_shares -= set(known_shnums)
8408+        self.log("found the following surprise shares: %s" %
8409+                 str(surprise_shares))
8410 
8411hunk ./src/allmydata/mutable/publish.py 810
8412-        surprise_shares = set(read_data.keys()) - set(shnums)
8413+        # Now surprise shares contains all of the shares that we did not
8414+        # expect to be there.
8415 
8416         surprised = False
8417         for shnum in surprise_shares:
8418hunk ./src/allmydata/mutable/publish.py 817
8419             # read_data is a dict mapping shnum to checkstring (SIGNED_PREFIX)
8420             checkstring = read_data[shnum][0]
8421-            their_version_info = unpack_checkstring(checkstring)
8422-            if their_version_info == self._new_version_info:
8423+            # What we want to do here is to see if their (seqnum,
8424+            # roothash, salt) is the same as our (seqnum, roothash,
8425+            # salt), or the equivalent for MDMF. The best way to do this
8426+            # is to store a packed representation of our checkstring
8427+            # somewhere, then not bother unpacking the other
8428+            # checkstring.
8429+            if checkstring == self._checkstring:
8430                 # they have the right share, somehow
8431 
8432                 if (peerid,shnum) in self.goal:
8433hunk ./src/allmydata/mutable/publish.py 902
8434             self.log("our testv failed, so the write did not happen",
8435                      parent=lp, level=log.WEIRD, umid="8sc26g")
8436             self.surprised = True
8437-            self.bad_peers.add(peerid) # don't ask them again
8438+            # TODO: This needs to
8439+            self.bad_peers.add(writer) # don't ask them again
8440             # use the checkstring to add information to the log message
8441             for (shnum,readv) in read_data.items():
8442                 checkstring = readv[0]
8443hunk ./src/allmydata/mutable/publish.py 928
8444             # self.loop() will take care of finding new homes
8445             return
8446 
8447-        for shnum in shnums:
8448-            self.placed.add( (peerid, shnum) )
8449-            # and update the servermap
8450-            self._servermap.add_new_share(peerid, shnum,
8451+        # and update the servermap
8452+        # self.versioninfo is set during the last phase of publishing.
8453+        # If we get there, we know that responses correspond to placed
8454+        # shares, and can safely execute these statements.
8455+        if self.versioninfo:
8456+            self.log("wrote successfully: adding new share to servermap")
8457+            self._servermap.add_new_share(peerid, writer.shnum,
8458                                           self.versioninfo, started)
8459hunk ./src/allmydata/mutable/publish.py 936
8460-
8461-        # self.loop() will take care of checking to see if we're done
8462-        return
8463+            self.placed.add( (peerid, writer.shnum) )
8464 
8465hunk ./src/allmydata/mutable/publish.py 938
8466-    def _got_write_error(self, f, peerid, shnums, started):
8467-        for shnum in shnums:
8468-            self.outstanding.discard( (peerid, shnum) )
8469-        self.bad_peers.add(peerid)
8470-        if self._first_write_error is None:
8471-            self._first_write_error = f
8472-        self.log(format="error while writing shares %(shnums)s to peerid %(peerid)s",
8473-                 shnums=list(shnums), peerid=idlib.shortnodeid_b2a(peerid),
8474-                 failure=f,
8475-                 level=log.UNUSUAL)
8476         # self.loop() will take care of checking to see if we're done
8477         return
8478 
8479hunk ./src/allmydata/mutable/publish.py 949
8480         now = time.time()
8481         self._status.timings["total"] = now - self._started
8482         self._status.set_active(False)
8483-        if isinstance(res, failure.Failure):
8484-            self.log("Publish done, with failure", failure=res,
8485-                     level=log.WEIRD, umid="nRsR9Q")
8486-            self._status.set_status("Failed")
8487-        elif self.surprised:
8488-            self.log("Publish done, UncoordinatedWriteError", level=log.UNUSUAL)
8489-            self._status.set_status("UncoordinatedWriteError")
8490-            # deliver a failure
8491-            res = failure.Failure(UncoordinatedWriteError())
8492-            # TODO: recovery
8493-        else:
8494-            self.log("Publish done, success")
8495-            self._status.set_status("Finished")
8496-            self._status.set_progress(1.0)
8497+        self.log("Publish done, success")
8498+        self._status.set_status("Finished")
8499+        self._status.set_progress(1.0)
8500         eventually(self.done_deferred.callback, res)
8501 
8502hunk ./src/allmydata/mutable/publish.py 954
8503+    def _failure(self):
8504+
8505+        if not self.surprised:
8506+            # We ran out of servers
8507+            self.log("Publish ran out of good servers, "
8508+                     "last failure was: %s" % str(self._last_failure))
8509+            e = NotEnoughServersError("Ran out of non-bad servers, "
8510+                                      "last failure was %s" %
8511+                                      str(self._last_failure))
8512+        else:
8513+            # We ran into shares that we didn't recognize, which means
8514+            # that we need to return an UncoordinatedWriteError.
8515+            self.log("Publish failed with UncoordinatedWriteError")
8516+            e = UncoordinatedWriteError()
8517+        f = failure.Failure(e)
8518+        eventually(self.done_deferred.callback, f)
8519}
8520[test/test_mutable.py: remove tests that are no longer relevant
8521Kevan Carstensen <kevan@isnotajoke.com>**20100702225710
8522 Ignore-this: 90a26b4cc4b2e190a635474ba7097e21
8523] hunk ./src/allmydata/test/test_mutable.py 627
8524         return d
8525 
8526 
8527-class MakeShares(unittest.TestCase):
8528-    def test_encrypt(self):
8529-        nm = make_nodemaker()
8530-        CONTENTS = "some initial contents"
8531-        d = nm.create_mutable_file(CONTENTS)
8532-        def _created(fn):
8533-            p = Publish(fn, nm.storage_broker, None)
8534-            p.salt = "SALT" * 4
8535-            p.readkey = "\x00" * 16
8536-            p.newdata = CONTENTS
8537-            p.required_shares = 3
8538-            p.total_shares = 10
8539-            p.setup_encoding_parameters()
8540-            return p._encrypt_and_encode()
8541-        d.addCallback(_created)
8542-        def _done(shares_and_shareids):
8543-            (shares, share_ids) = shares_and_shareids
8544-            self.failUnlessEqual(len(shares), 10)
8545-            for sh in shares:
8546-                self.failUnless(isinstance(sh, str))
8547-                self.failUnlessEqual(len(sh), 7)
8548-            self.failUnlessEqual(len(share_ids), 10)
8549-        d.addCallback(_done)
8550-        return d
8551-    test_encrypt.todo = "Write an equivalent of this for the new uploader"
8552-
8553-    def test_generate(self):
8554-        nm = make_nodemaker()
8555-        CONTENTS = "some initial contents"
8556-        d = nm.create_mutable_file(CONTENTS)
8557-        def _created(fn):
8558-            self._fn = fn
8559-            p = Publish(fn, nm.storage_broker, None)
8560-            self._p = p
8561-            p.newdata = CONTENTS
8562-            p.required_shares = 3
8563-            p.total_shares = 10
8564-            p.setup_encoding_parameters()
8565-            p._new_seqnum = 3
8566-            p.salt = "SALT" * 4
8567-            # make some fake shares
8568-            shares_and_ids = ( ["%07d" % i for i in range(10)], range(10) )
8569-            p._privkey = fn.get_privkey()
8570-            p._encprivkey = fn.get_encprivkey()
8571-            p._pubkey = fn.get_pubkey()
8572-            return p._generate_shares(shares_and_ids)
8573-        d.addCallback(_created)
8574-        def _generated(res):
8575-            p = self._p
8576-            final_shares = p.shares
8577-            root_hash = p.root_hash
8578-            self.failUnlessEqual(len(root_hash), 32)
8579-            self.failUnless(isinstance(final_shares, dict))
8580-            self.failUnlessEqual(len(final_shares), 10)
8581-            self.failUnlessEqual(sorted(final_shares.keys()), range(10))
8582-            for i,sh in final_shares.items():
8583-                self.failUnless(isinstance(sh, str))
8584-                # feed the share through the unpacker as a sanity-check
8585-                pieces = unpack_share(sh)
8586-                (u_seqnum, u_root_hash, IV, k, N, segsize, datalen,
8587-                 pubkey, signature, share_hash_chain, block_hash_tree,
8588-                 share_data, enc_privkey) = pieces
8589-                self.failUnlessEqual(u_seqnum, 3)
8590-                self.failUnlessEqual(u_root_hash, root_hash)
8591-                self.failUnlessEqual(k, 3)
8592-                self.failUnlessEqual(N, 10)
8593-                self.failUnlessEqual(segsize, 21)
8594-                self.failUnlessEqual(datalen, len(CONTENTS))
8595-                self.failUnlessEqual(pubkey, p._pubkey.serialize())
8596-                sig_material = struct.pack(">BQ32s16s BBQQ",
8597-                                           0, p._new_seqnum, root_hash, IV,
8598-                                           k, N, segsize, datalen)
8599-                self.failUnless(p._pubkey.verify(sig_material, signature))
8600-                #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
8601-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
8602-                for shnum,share_hash in share_hash_chain.items():
8603-                    self.failUnless(isinstance(shnum, int))
8604-                    self.failUnless(isinstance(share_hash, str))
8605-                    self.failUnlessEqual(len(share_hash), 32)
8606-                self.failUnless(isinstance(block_hash_tree, list))
8607-                self.failUnlessEqual(len(block_hash_tree), 1) # very small tree
8608-                self.failUnlessEqual(IV, "SALT"*4)
8609-                self.failUnlessEqual(len(share_data), len("%07d" % 1))
8610-                self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
8611-        d.addCallback(_generated)
8612-        return d
8613-    test_generate.todo = "Write an equivalent of this for the new uploader"
8614-
8615-    # TODO: when we publish to 20 peers, we should get one share per peer on 10
8616-    # when we publish to 3 peers, we should get either 3 or 4 shares per peer
8617-    # when we publish to zero peers, we should get a NotEnoughSharesError
8618-
8619 class PublishMixin:
8620     def publish_one(self):
8621         # publish a file and create shares, which can then be manipulated
8622[interfaces.py: create IMutableUploadable
8623Kevan Carstensen <kevan@isnotajoke.com>**20100706215217
8624 Ignore-this: bee202ec2bfbd8e41f2d4019cce176c7
8625] hunk ./src/allmydata/interfaces.py 1693
8626         """The upload is finished, and whatever filehandle was in use may be
8627         closed."""
8628 
8629+
8630+class IMutableUploadable(Interface):
8631+    """
8632+    I represent content that is due to be uploaded to a mutable filecap.
8633+    """
8634+    # This is somewhat simpler than the IUploadable interface above
8635+    # because mutable files do not need to be concerned with possibly
8636+    # generating a CHK, nor with per-file keys. It is a subset of the
8637+    # methods in IUploadable, though, so we could just as well implement
8638+    # the mutable uploadables as IUploadables that don't happen to use
8639+    # those methods (with the understanding that the unused methods will
8640+    # never be called on such objects)
8641+    def get_size():
8642+        """
8643+        Returns a Deferred that fires with the size of the content held
8644+        by the uploadable.
8645+        """
8646+
8647+    def read(length):
8648+        """
8649+        Returns a list of strings which, when concatenated, are the next
8650+        length bytes of the file, or fewer if there are fewer bytes
8651+        between the current location and the end of the file.
8652+        """
8653+
8654+    def close():
8655+        """
8656+        The process that used the Uploadable is finished using it, so
8657+        the uploadable may be closed.
8658+        """
8659+
8660 class IUploadResults(Interface):
8661     """I am returned by upload() methods. I contain a number of public
8662     attributes which can be read to determine the results of the upload. Some
8663[mutable/publish.py: add MutableDataHandle and MutableFileHandle
8664Kevan Carstensen <kevan@isnotajoke.com>**20100706215257
8665 Ignore-this: 295ea3bc2a962fd14fb7877fc76c011c
8666] {
8667hunk ./src/allmydata/mutable/publish.py 8
8668 from zope.interface import implements
8669 from twisted.internet import defer
8670 from twisted.python import failure
8671-from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
8672+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \
8673+                                 IMutableUploadable
8674 from allmydata.util import base32, hashutil, mathutil, idlib, log
8675 from allmydata import hashtree, codec
8676 from allmydata.storage.server import si_b2a
8677hunk ./src/allmydata/mutable/publish.py 971
8678             e = UncoordinatedWriteError()
8679         f = failure.Failure(e)
8680         eventually(self.done_deferred.callback, f)
8681+
8682+
8683+class MutableFileHandle:
8684+    """
8685+    I am a mutable uploadable built around a filehandle-like object,
8686+    usually either a StringIO instance or a handle to an actual file.
8687+    """
8688+    implements(IMutableUploadable)
8689+
8690+    def __init__(self, filehandle):
8691+        # The filehandle is defined as a generally file-like object that
8692+        # has these two methods. We don't care beyond that.
8693+        assert hasattr(filehandle, "read")
8694+        assert hasattr(filehandle, "close")
8695+
8696+        self._filehandle = filehandle
8697+
8698+
8699+    def get_size(self):
8700+        """
8701+        I return the amount of data in my filehandle.
8702+        """
8703+        if not hasattr(self, "_size"):
8704+            old_position = self._filehandle.tell()
8705+            # Seek to the end of the file by seeking 0 bytes from the
8706+            # file's end
8707+            self._filehandle.seek(0, os.SEEK_END)
8708+            self._size = self._filehandle.tell()
8709+            # Restore the previous position, in case this was called
8710+            # after a read.
8711+            self._filehandle.seek(old_position)
8712+            assert self._filehandle.tell() == old_position
8713+
8714+        assert hasattr(self, "_size")
8715+        return self._size
8716+
8717+
8718+    def read(self, length):
8719+        """
8720+        I return some data (up to length bytes) from my filehandle.
8721+
8722+        In most cases, I return length bytes. If I don't, it is because
8723+        length is longer than the distance between my current position
8724+        in the file that I represent and its end. In that case, I return
8725+        as many bytes as I can before going over the EOF.
8726+        """
8727+        return [self._filehandle.read(length)]
8728+
8729+
8730+    def close(self):
8731+        """
8732+        I close the underlying filehandle. Any further operations on the
8733+        filehandle fail at this point.
8734+        """
8735+        self._filehandle.close()
8736+
8737+
8738+class MutableDataHandle(MutableFileHandle):
8739+    """
8740+    I am a mutable uploadable built around a string, which I then cast
8741+    into a StringIO and treat as a filehandle.
8742+    """
8743+
8744+    def __init__(self, s):
8745+        # Take a string and return a file-like uploadable.
8746+        assert isinstance(s, str)
8747+
8748+        MutableFileHandle.__init__(self, StringIO(s))
8749}
8750[mutable/publish.py: reorganize in preparation of file-like uploadables
8751Kevan Carstensen <kevan@isnotajoke.com>**20100706215541
8752 Ignore-this: 5346c9f919ee5b73807c8f287c64e8ce
8753] {
8754hunk ./src/allmydata/mutable/publish.py 4
8755 
8756 
8757 import os, struct, time
8758+from StringIO import StringIO
8759 from itertools import count
8760 from zope.interface import implements
8761 from twisted.internet import defer
8762hunk ./src/allmydata/mutable/publish.py 118
8763         self._status.set_helper(False)
8764         self._status.set_progress(0.0)
8765         self._status.set_active(True)
8766-        # We use this to control how the file is written.
8767-        version = self._node.get_version()
8768-        assert version in (SDMF_VERSION, MDMF_VERSION)
8769-        self._version = version
8770+        self._version = self._node.get_version()
8771+        assert self._version in (SDMF_VERSION, MDMF_VERSION)
8772+
8773 
8774     def get_status(self):
8775         return self._status
8776hunk ./src/allmydata/mutable/publish.py 141
8777 
8778         # 0. Setup encoding parameters, encoder, and other such things.
8779         # 1. Encrypt, encode, and publish segments.
8780+        self.data = StringIO(newdata)
8781+        self.datalength = len(newdata)
8782 
8783hunk ./src/allmydata/mutable/publish.py 144
8784-        self.log("starting publish, datalen is %s" % len(newdata))
8785-        self._status.set_size(len(newdata))
8786+        self.log("starting publish, datalen is %s" % self.datalength)
8787+        self._status.set_size(self.datalength)
8788         self._status.set_status("Started")
8789         self._started = time.time()
8790 
8791hunk ./src/allmydata/mutable/publish.py 193
8792         self.full_peerlist = full_peerlist # for use later, immutable
8793         self.bad_peers = set() # peerids who have errbacked/refused requests
8794 
8795-        self.newdata = newdata
8796-
8797         # This will set self.segment_size, self.num_segments, and
8798         # self.fec.
8799         self.setup_encoding_parameters()
8800hunk ./src/allmydata/mutable/publish.py 272
8801                                                 self.required_shares,
8802                                                 self.total_shares,
8803                                                 self.segment_size,
8804-                                                len(self.newdata))
8805+                                                self.datalength)
8806             self.writers[shnum].peerid = peerid
8807             if (peerid, shnum) in self._servermap.servermap:
8808                 old_versionid, old_timestamp = self._servermap.servermap[key]
8809hunk ./src/allmydata/mutable/publish.py 318
8810         if self._version == MDMF_VERSION:
8811             segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
8812         else:
8813-            segment_size = len(self.newdata) # SDMF is only one segment
8814+            segment_size = self.datalength # SDMF is only one segment
8815         # this must be a multiple of self.required_shares
8816         segment_size = mathutil.next_multiple(segment_size,
8817                                               self.required_shares)
8818hunk ./src/allmydata/mutable/publish.py 324
8819         self.segment_size = segment_size
8820         if segment_size:
8821-            self.num_segments = mathutil.div_ceil(len(self.newdata),
8822+            self.num_segments = mathutil.div_ceil(self.datalength,
8823                                                   segment_size)
8824         else:
8825             self.num_segments = 0
8826hunk ./src/allmydata/mutable/publish.py 337
8827             assert self.num_segments in (0, 1) # SDMF
8828         # calculate the tail segment size.
8829 
8830-        if segment_size and self.newdata:
8831-            self.tail_segment_size = len(self.newdata) % segment_size
8832+        if segment_size and self.datalength:
8833+            self.tail_segment_size = self.datalength % segment_size
8834         else:
8835             self.tail_segment_size = 0
8836 
8837hunk ./src/allmydata/mutable/publish.py 438
8838             segsize = self.segment_size
8839 
8840 
8841-        offset = self.segment_size * segnum
8842-        length = segsize + offset
8843         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
8844hunk ./src/allmydata/mutable/publish.py 439
8845-        data = self.newdata[offset:length]
8846+        data = self.data.read(segsize)
8847+
8848         assert len(data) == segsize
8849 
8850         salt = os.urandom(16)
8851hunk ./src/allmydata/mutable/publish.py 502
8852             d.addCallback(self._got_write_answer, writer, started)
8853             d.addErrback(self._connection_problem, writer)
8854             dl.append(d)
8855-            # TODO: Naturally, we need to check on the results of these.
8856         return defer.DeferredList(dl)
8857 
8858 
8859}
8860[test/test_mutable.py: write tests for MutableFileHandle and MutableDataHandle
8861Kevan Carstensen <kevan@isnotajoke.com>**20100706215649
8862 Ignore-this: df719a0c52b4bbe9be4fae206c7ab3e7
8863] {
8864hunk ./src/allmydata/test/test_mutable.py 2
8865 
8866-import struct
8867+import struct, os
8868 from cStringIO import StringIO
8869 from twisted.trial import unittest
8870 from twisted.internet import defer, reactor
8871hunk ./src/allmydata/test/test_mutable.py 26
8872      NeedMoreDataError, UnrecoverableFileError, UncoordinatedWriteError, \
8873      NotEnoughServersError, CorruptShareError
8874 from allmydata.mutable.retrieve import Retrieve
8875-from allmydata.mutable.publish import Publish
8876+from allmydata.mutable.publish import Publish, MutableFileHandle, \
8877+                                      MutableDataHandle
8878 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
8879 from allmydata.mutable.layout import unpack_header, unpack_share, \
8880                                      MDMFSlotReadProxy
8881hunk ./src/allmydata/test/test_mutable.py 2465
8882         d.addCallback(lambda data:
8883             self.failUnlessEqual(data, CONTENTS))
8884         return d
8885+
8886+
8887+class FileHandle(unittest.TestCase):
8888+    def setUp(self):
8889+        self.test_data = "Test Data" * 50000
8890+        self.sio = StringIO(self.test_data)
8891+        self.uploadable = MutableFileHandle(self.sio)
8892+
8893+
8894+    def test_filehandle_read(self):
8895+        self.basedir = "mutable/FileHandle/test_filehandle_read"
8896+        chunk_size = 10
8897+        for i in xrange(0, len(self.test_data), chunk_size):
8898+            data = self.uploadable.read(chunk_size)
8899+            data = "".join(data)
8900+            start = i
8901+            end = i + chunk_size
8902+            self.failUnlessEqual(data, self.test_data[start:end])
8903+
8904+
8905+    def test_filehandle_get_size(self):
8906+        self.basedir = "mutable/FileHandle/test_filehandle_get_size"
8907+        actual_size = len(self.test_data)
8908+        size = self.uploadable.get_size()
8909+        self.failUnlessEqual(size, actual_size)
8910+
8911+
8912+    def test_filehandle_get_size_out_of_order(self):
8913+        # We should be able to call get_size whenever we want without
8914+        # disturbing the location of the seek pointer.
8915+        chunk_size = 100
8916+        data = self.uploadable.read(chunk_size)
8917+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8918+
8919+        # Now get the size.
8920+        size = self.uploadable.get_size()
8921+        self.failUnlessEqual(size, len(self.test_data))
8922+
8923+        # Now get more data. We should be right where we left off.
8924+        more_data = self.uploadable.read(chunk_size)
8925+        start = chunk_size
8926+        end = chunk_size * 2
8927+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
8928+
8929+
8930+    def test_filehandle_file(self):
8931+        # Make sure that the MutableFileHandle works on a file as well
8932+        # as a StringIO object, since in some cases it will be asked to
8933+        # deal with files.
8934+        self.basedir = self.mktemp()
8935+        # necessary? What am I doing wrong here?
8936+        os.mkdir(self.basedir)
8937+        f_path = os.path.join(self.basedir, "test_file")
8938+        f = open(f_path, "w")
8939+        f.write(self.test_data)
8940+        f.close()
8941+        f = open(f_path, "r")
8942+
8943+        uploadable = MutableFileHandle(f)
8944+
8945+        data = uploadable.read(len(self.test_data))
8946+        self.failUnlessEqual("".join(data), self.test_data)
8947+        size = uploadable.get_size()
8948+        self.failUnlessEqual(size, len(self.test_data))
8949+
8950+
8951+    def test_close(self):
8952+        # Make sure that the MutableFileHandle closes its handle when
8953+        # told to do so.
8954+        self.uploadable.close()
8955+        self.failUnless(self.sio.closed)
8956+
8957+
8958+class DataHandle(unittest.TestCase):
8959+    def setUp(self):
8960+        self.test_data = "Test Data" * 50000
8961+        self.uploadable = MutableDataHandle(self.test_data)
8962+
8963+
8964+    def test_datahandle_read(self):
8965+        chunk_size = 10
8966+        for i in xrange(0, len(self.test_data), chunk_size):
8967+            data = self.uploadable.read(chunk_size)
8968+            data = "".join(data)
8969+            start = i
8970+            end = i + chunk_size
8971+            self.failUnlessEqual(data, self.test_data[start:end])
8972+
8973+
8974+    def test_datahandle_get_size(self):
8975+        actual_size = len(self.test_data)
8976+        size = self.uploadable.get_size()
8977+        self.failUnlessEqual(size, actual_size)
8978+
8979+
8980+    def test_datahandle_get_size_out_of_order(self):
8981+        # We should be able to call get_size whenever we want without
8982+        # disturbing the location of the seek pointer.
8983+        chunk_size = 100
8984+        data = self.uploadable.read(chunk_size)
8985+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8986+
8987+        # Now get the size.
8988+        size = self.uploadable.get_size()
8989+        self.failUnlessEqual(size, len(self.test_data))
8990+
8991+        # Now get more data. We should be right where we left off.
8992+        more_data = self.uploadable.read(chunk_size)
8993+        start = chunk_size
8994+        end = chunk_size * 2
8995+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
8996}
8997[Alter tests to work with the new APIs
8998Kevan Carstensen <kevan@isnotajoke.com>**20100708000031
8999 Ignore-this: 1f377904ac61ce40e9a04716fbd2ad95
9000] {
9001hunk ./src/allmydata/test/common.py 12
9002 from allmydata import uri, dirnode, client
9003 from allmydata.introducer.server import IntroducerNode
9004 from allmydata.interfaces import IMutableFileNode, IImmutableFileNode, \
9005-     FileTooLargeError, NotEnoughSharesError, ICheckable
9006+     FileTooLargeError, NotEnoughSharesError, ICheckable, \
9007+     IMutableUploadable
9008 from allmydata.check_results import CheckResults, CheckAndRepairResults, \
9009      DeepCheckResults, DeepCheckAndRepairResults
9010 from allmydata.mutable.common import CorruptShareError
9011hunk ./src/allmydata/test/common.py 18
9012 from allmydata.mutable.layout import unpack_header
9013+from allmydata.mutable.publish import MutableDataHandle
9014 from allmydata.storage.server import storage_index_to_dir
9015 from allmydata.storage.mutable import MutableShareFile
9016 from allmydata.util import hashutil, log, fileutil, pollmixin
9017hunk ./src/allmydata/test/common.py 182
9018         self.init_from_cap(make_mutable_file_cap())
9019     def create(self, contents, key_generator=None, keysize=None):
9020         initial_contents = self._get_initial_contents(contents)
9021-        if len(initial_contents) > self.MUTABLE_SIZELIMIT:
9022+        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
9023             raise FileTooLargeError("SDMF is limited to one segment, and "
9024hunk ./src/allmydata/test/common.py 184
9025-                                    "%d > %d" % (len(initial_contents),
9026+                                    "%d > %d" % (initial_contents.get_size(),
9027                                                  self.MUTABLE_SIZELIMIT))
9028hunk ./src/allmydata/test/common.py 186
9029-        self.all_contents[self.storage_index] = initial_contents
9030+        data = initial_contents.read(initial_contents.get_size())
9031+        data = "".join(data)
9032+        self.all_contents[self.storage_index] = data
9033         return defer.succeed(self)
9034     def _get_initial_contents(self, contents):
9035hunk ./src/allmydata/test/common.py 191
9036-        if isinstance(contents, str):
9037-            return contents
9038         if contents is None:
9039hunk ./src/allmydata/test/common.py 192
9040-            return ""
9041+            return MutableDataHandle("")
9042+
9043+        if IMutableUploadable.providedBy(contents):
9044+            return contents
9045+
9046         assert callable(contents), "%s should be callable, not %s" % \
9047                (contents, type(contents))
9048         return contents(self)
9049hunk ./src/allmydata/test/common.py 309
9050         return defer.succeed(self.all_contents[self.storage_index])
9051 
9052     def overwrite(self, new_contents):
9053-        if len(new_contents) > self.MUTABLE_SIZELIMIT:
9054+        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
9055             raise FileTooLargeError("SDMF is limited to one segment, and "
9056hunk ./src/allmydata/test/common.py 311
9057-                                    "%d > %d" % (len(new_contents),
9058+                                    "%d > %d" % (new_contents.get_size(),
9059                                                  self.MUTABLE_SIZELIMIT))
9060         assert not self.is_readonly()
9061hunk ./src/allmydata/test/common.py 314
9062-        self.all_contents[self.storage_index] = new_contents
9063+        new_data = new_contents.read(new_contents.get_size())
9064+        new_data = "".join(new_data)
9065+        self.all_contents[self.storage_index] = new_data
9066         return defer.succeed(None)
9067     def modify(self, modifier):
9068         # this does not implement FileTooLargeError, but the real one does
9069hunk ./src/allmydata/test/common.py 324
9070     def _modify(self, modifier):
9071         assert not self.is_readonly()
9072         old_contents = self.all_contents[self.storage_index]
9073-        self.all_contents[self.storage_index] = modifier(old_contents, None, True)
9074+        new_data = modifier(old_contents, None, True)
9075+        if new_data is not None:
9076+            new_data = new_data.read(new_data.get_size())
9077+            new_data = "".join(new_data)
9078+        self.all_contents[self.storage_index] = new_data
9079         return None
9080 
9081 def make_mutable_file_cap():
9082hunk ./src/allmydata/test/test_checker.py 11
9083 from allmydata.test.no_network import GridTestMixin
9084 from allmydata.immutable.upload import Data
9085 from allmydata.test.common_web import WebRenderingMixin
9086+from allmydata.mutable.publish import MutableDataHandle
9087 
9088 class FakeClient:
9089     def get_storage_broker(self):
9090hunk ./src/allmydata/test/test_checker.py 291
9091         def _stash_immutable(ur):
9092             self.imm = c0.create_node_from_uri(ur.uri)
9093         d.addCallback(_stash_immutable)
9094-        d.addCallback(lambda ign: c0.create_mutable_file("contents"))
9095+        d.addCallback(lambda ign:
9096+            c0.create_mutable_file(MutableDataHandle("contents")))
9097         def _stash_mutable(node):
9098             self.mut = node
9099         d.addCallback(_stash_mutable)
9100hunk ./src/allmydata/test/test_cli.py 12
9101 from allmydata.util import fileutil, hashutil, base32
9102 from allmydata import uri
9103 from allmydata.immutable import upload
9104+from allmydata.mutable.publish import MutableDataHandle
9105 from allmydata.dirnode import normalize
9106 
9107 # Test that the scripts can be imported -- although the actual tests of their
9108hunk ./src/allmydata/test/test_cli.py 1975
9109         self.set_up_grid()
9110         c0 = self.g.clients[0]
9111         DATA = "data" * 100
9112-        d = c0.create_mutable_file(DATA)
9113+        DATA_uploadable = MutableDataHandle(DATA)
9114+        d = c0.create_mutable_file(DATA_uploadable)
9115         def _stash_uri(n):
9116             self.uri = n.get_uri()
9117         d.addCallback(_stash_uri)
9118hunk ./src/allmydata/test/test_cli.py 2077
9119                                            upload.Data("literal",
9120                                                         convergence="")))
9121         d.addCallback(_stash_uri, "small")
9122-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"1"))
9123+        d.addCallback(lambda ign:
9124+            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
9125         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9126         d.addCallback(_stash_uri, "mutable")
9127 
9128hunk ./src/allmydata/test/test_cli.py 2096
9129         # root/small
9130         # root/mutable
9131 
9132+        # We haven't broken anything yet, so this should all be healthy.
9133         d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
9134                                               self.rooturi))
9135         def _check2((rc, out, err)):
9136hunk ./src/allmydata/test/test_cli.py 2111
9137                             in lines, out)
9138         d.addCallback(_check2)
9139 
9140+        # Similarly, all of these results should be as we expect them to
9141+        # be for a healthy file layout.
9142         d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
9143         def _check_stats((rc, out, err)):
9144             self.failUnlessReallyEqual(err, "")
9145hunk ./src/allmydata/test/test_cli.py 2128
9146             self.failUnlessIn(" 317-1000 : 1    (1000 B, 1000 B)", lines)
9147         d.addCallback(_check_stats)
9148 
9149+        # Now we break things.
9150         def _clobber_shares(ignored):
9151             shares = self.find_uri_shares(self.uris[u"gööd"])
9152             self.failUnlessReallyEqual(len(shares), 10)
9153hunk ./src/allmydata/test/test_cli.py 2147
9154         d.addCallback(_clobber_shares)
9155 
9156         # root
9157-        # root/gööd  [9 shares]
9158+        # root/gööd  [1 missing share]
9159         # root/small
9160         # root/mutable [1 corrupt share]
9161 
9162hunk ./src/allmydata/test/test_cli.py 2153
9163         d.addCallback(lambda ign:
9164                       self.do_cli("deep-check", "--verbose", self.rooturi))
9165+        # This should reveal the missing share, but not the corrupt
9166+        # share, since we didn't tell the deep check operation to also
9167+        # verify.
9168         def _check3((rc, out, err)):
9169             self.failUnlessReallyEqual(err, "")
9170             self.failUnlessReallyEqual(rc, 0)
9171hunk ./src/allmydata/test/test_cli.py 2204
9172                                   "--verbose", "--verify", "--repair",
9173                                   self.rooturi))
9174         def _check6((rc, out, err)):
9175+            # We've just repaired the directory. There is no reason for
9176+            # that repair to be unsuccessful.
9177             self.failUnlessReallyEqual(err, "")
9178             self.failUnlessReallyEqual(rc, 0)
9179             lines = out.splitlines()
9180hunk ./src/allmydata/test/test_deepcheck.py 9
9181 from twisted.internet import threads # CLI tests use deferToThread
9182 from allmydata.immutable import upload
9183 from allmydata.mutable.common import UnrecoverableFileError
9184+from allmydata.mutable.publish import MutableDataHandle
9185 from allmydata.util import idlib
9186 from allmydata.util import base32
9187 from allmydata.scripts import runner
9188hunk ./src/allmydata/test/test_deepcheck.py 38
9189         self.basedir = "deepcheck/MutableChecker/good"
9190         self.set_up_grid()
9191         CONTENTS = "a little bit of data"
9192-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9193+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9194+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9195         def _created(node):
9196             self.node = node
9197             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9198hunk ./src/allmydata/test/test_deepcheck.py 61
9199         self.basedir = "deepcheck/MutableChecker/corrupt"
9200         self.set_up_grid()
9201         CONTENTS = "a little bit of data"
9202-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9203+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9204+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9205         def _stash_and_corrupt(node):
9206             self.node = node
9207             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9208hunk ./src/allmydata/test/test_deepcheck.py 99
9209         self.basedir = "deepcheck/MutableChecker/delete_share"
9210         self.set_up_grid()
9211         CONTENTS = "a little bit of data"
9212-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9213+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9214+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9215         def _stash_and_delete(node):
9216             self.node = node
9217             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9218hunk ./src/allmydata/test/test_deepcheck.py 223
9219             self.root = n
9220             self.root_uri = n.get_uri()
9221         d.addCallback(_created_root)
9222-        d.addCallback(lambda ign: c0.create_mutable_file("mutable file contents"))
9223+        d.addCallback(lambda ign:
9224+            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
9225         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
9226         def _created_mutable(n):
9227             self.mutable = n
9228hunk ./src/allmydata/test/test_deepcheck.py 965
9229     def create_mangled(self, ignored, name):
9230         nodetype, mangletype = name.split("-", 1)
9231         if nodetype == "mutable":
9232-            d = self.g.clients[0].create_mutable_file("mutable file contents")
9233+            mutable_uploadable = MutableDataHandle("mutable file contents")
9234+            d = self.g.clients[0].create_mutable_file(mutable_uploadable)
9235             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
9236         elif nodetype == "large":
9237             large = upload.Data("Lots of data\n" * 1000 + name + "\n", None)
9238hunk ./src/allmydata/test/test_dirnode.py 1305
9239     implements(IMutableFileNode)
9240     counter = 0
9241     def __init__(self, initial_contents=""):
9242-        self.data = self._get_initial_contents(initial_contents)
9243+        data = self._get_initial_contents(initial_contents)
9244+        self.data = data.read(data.get_size())
9245+        self.data = "".join(self.data)
9246+
9247         counter = FakeMutableFile.counter
9248         FakeMutableFile.counter += 1
9249         writekey = hashutil.ssk_writekey_hash(str(counter))
9250hunk ./src/allmydata/test/test_dirnode.py 1355
9251         pass
9252 
9253     def modify(self, modifier):
9254-        self.data = modifier(self.data, None, True)
9255+        data = modifier(self.data, None, True)
9256+        self.data = data.read(data.get_size())
9257+        self.data = "".join(self.data)
9258         return defer.succeed(None)
9259 
9260 class FakeNodeMaker(NodeMaker):
9261hunk ./src/allmydata/test/test_hung_server.py 10
9262 from allmydata.util.consumer import download_to_data
9263 from allmydata.immutable import upload
9264 from allmydata.mutable.common import UnrecoverableFileError
9265+from allmydata.mutable.publish import MutableDataHandle
9266 from allmydata.storage.common import storage_index_to_dir
9267 from allmydata.test.no_network import GridTestMixin
9268 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
9269hunk ./src/allmydata/test/test_hung_server.py 96
9270         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
9271 
9272         if mutable:
9273-            d = nm.create_mutable_file(mutable_plaintext)
9274+            uploadable = MutableDataHandle(mutable_plaintext)
9275+            d = nm.create_mutable_file(uploadable)
9276             def _uploaded_mutable(node):
9277                 self.uri = node.get_uri()
9278                 self.shares = self.find_uri_shares(self.uri)
9279hunk ./src/allmydata/test/test_mutable.py 297
9280             d.addCallback(lambda smap: smap.dump(StringIO()))
9281             d.addCallback(lambda sio:
9282                           self.failUnless("3-of-10" in sio.getvalue()))
9283-            d.addCallback(lambda res: n.overwrite("contents 1"))
9284+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9285             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9286             d.addCallback(lambda res: n.download_best_version())
9287             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9288hunk ./src/allmydata/test/test_mutable.py 304
9289             d.addCallback(lambda res: n.get_size_of_best_version())
9290             d.addCallback(lambda size:
9291                           self.failUnlessEqual(size, len("contents 1")))
9292-            d.addCallback(lambda res: n.overwrite("contents 2"))
9293+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9294             d.addCallback(lambda res: n.download_best_version())
9295             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9296             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9297hunk ./src/allmydata/test/test_mutable.py 308
9298-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9299+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9300             d.addCallback(lambda res: n.download_best_version())
9301             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9302             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9303hunk ./src/allmydata/test/test_mutable.py 320
9304             # mapupdate-to-retrieve data caching (i.e. make the shares larger
9305             # than the default readsize, which is 2000 bytes). A 15kB file
9306             # will have 5kB shares.
9307-            d.addCallback(lambda res: n.overwrite("large size file" * 1000))
9308+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
9309             d.addCallback(lambda res: n.download_best_version())
9310             d.addCallback(lambda res:
9311                           self.failUnlessEqual(res, "large size file" * 1000))
9312hunk ./src/allmydata/test/test_mutable.py 343
9313             # to make them big enough to force the file to be uploaded
9314             # in more than one segment.
9315             big_contents = "contents1" * 100000 # about 900 KiB
9316+            big_contents_uploadable = MutableDataHandle(big_contents)
9317             d.addCallback(lambda ignored:
9318hunk ./src/allmydata/test/test_mutable.py 345
9319-                n.overwrite(big_contents))
9320+                n.overwrite(big_contents_uploadable))
9321             d.addCallback(lambda ignored:
9322                 n.download_best_version())
9323             d.addCallback(lambda data:
9324hunk ./src/allmydata/test/test_mutable.py 355
9325             # segments, so that we make the downloader deal with
9326             # multiple segments.
9327             bigger_contents = "contents2" * 1000000 # about 9MiB
9328+            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
9329             d.addCallback(lambda ignored:
9330hunk ./src/allmydata/test/test_mutable.py 357
9331-                n.overwrite(bigger_contents))
9332+                n.overwrite(bigger_contents_uploadable))
9333             d.addCallback(lambda ignored:
9334                 n.download_best_version())
9335             d.addCallback(lambda data:
9336hunk ./src/allmydata/test/test_mutable.py 368
9337 
9338 
9339     def test_create_with_initial_contents(self):
9340-        d = self.nodemaker.create_mutable_file("contents 1")
9341+        upload1 = MutableDataHandle("contents 1")
9342+        d = self.nodemaker.create_mutable_file(upload1)
9343         def _created(n):
9344             d = n.download_best_version()
9345             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9346hunk ./src/allmydata/test/test_mutable.py 373
9347-            d.addCallback(lambda res: n.overwrite("contents 2"))
9348+            upload2 = MutableDataHandle("contents 2")
9349+            d.addCallback(lambda res: n.overwrite(upload2))
9350             d.addCallback(lambda res: n.download_best_version())
9351             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9352             return d
9353hunk ./src/allmydata/test/test_mutable.py 380
9354         d.addCallback(_created)
9355         return d
9356+    test_create_with_initial_contents.timeout = 15
9357 
9358 
9359     def test_create_mdmf_with_initial_contents(self):
9360hunk ./src/allmydata/test/test_mutable.py 385
9361         initial_contents = "foobarbaz" * 131072 # 900KiB
9362-        d = self.nodemaker.create_mutable_file(initial_contents,
9363+        initial_contents_uploadable = MutableDataHandle(initial_contents)
9364+        d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
9365                                                version=MDMF_VERSION)
9366         def _created(n):
9367             d = n.download_best_version()
9368hunk ./src/allmydata/test/test_mutable.py 392
9369             d.addCallback(lambda data:
9370                 self.failUnlessEqual(data, initial_contents))
9371+            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
9372             d.addCallback(lambda ignored:
9373hunk ./src/allmydata/test/test_mutable.py 394
9374-                n.overwrite(initial_contents + "foobarbaz"))
9375+                n.overwrite(uploadable2))
9376             d.addCallback(lambda ignored:
9377                 n.download_best_version())
9378             d.addCallback(lambda data:
9379hunk ./src/allmydata/test/test_mutable.py 413
9380             key = n.get_writekey()
9381             self.failUnless(isinstance(key, str), key)
9382             self.failUnlessEqual(len(key), 16) # AES key size
9383-            return data
9384+            return MutableDataHandle(data)
9385         d = self.nodemaker.create_mutable_file(_make_contents)
9386         def _created(n):
9387             return n.download_best_version()
9388hunk ./src/allmydata/test/test_mutable.py 429
9389             key = n.get_writekey()
9390             self.failUnless(isinstance(key, str), key)
9391             self.failUnlessEqual(len(key), 16)
9392-            return data
9393+            return MutableDataHandle(data)
9394         d = self.nodemaker.create_mutable_file(_make_contents,
9395                                                version=MDMF_VERSION)
9396         d.addCallback(lambda n:
9397hunk ./src/allmydata/test/test_mutable.py 441
9398 
9399     def test_create_with_too_large_contents(self):
9400         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9401-        d = self.nodemaker.create_mutable_file(BIG)
9402+        BIG_uploadable = MutableDataHandle(BIG)
9403+        d = self.nodemaker.create_mutable_file(BIG_uploadable)
9404         def _created(n):
9405hunk ./src/allmydata/test/test_mutable.py 444
9406-            d = n.overwrite(BIG)
9407+            other_BIG_uploadable = MutableDataHandle(BIG)
9408+            d = n.overwrite(other_BIG_uploadable)
9409             return d
9410         d.addCallback(_created)
9411         return d
9412hunk ./src/allmydata/test/test_mutable.py 459
9413 
9414     def test_modify(self):
9415         def _modifier(old_contents, servermap, first_time):
9416-            return old_contents + "line2"
9417+            new_contents = old_contents + "line2"
9418+            return MutableDataHandle(new_contents)
9419         def _non_modifier(old_contents, servermap, first_time):
9420hunk ./src/allmydata/test/test_mutable.py 462
9421-            return old_contents
9422+            return MutableDataHandle(old_contents)
9423         def _none_modifier(old_contents, servermap, first_time):
9424             return None
9425         def _error_modifier(old_contents, servermap, first_time):
9426hunk ./src/allmydata/test/test_mutable.py 468
9427             raise ValueError("oops")
9428         def _toobig_modifier(old_contents, servermap, first_time):
9429-            return "b" * (self.OLD_MAX_SEGMENT_SIZE+1)
9430+            new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9431+            return MutableDataHandle(new_content)
9432         calls = []
9433         def _ucw_error_modifier(old_contents, servermap, first_time):
9434             # simulate an UncoordinatedWriteError once
9435hunk ./src/allmydata/test/test_mutable.py 476
9436             calls.append(1)
9437             if len(calls) <= 1:
9438                 raise UncoordinatedWriteError("simulated")
9439-            return old_contents + "line3"
9440+            new_contents = old_contents + "line3"
9441+            return MutableDataHandle(new_contents)
9442         def _ucw_error_non_modifier(old_contents, servermap, first_time):
9443             # simulate an UncoordinatedWriteError once, and don't actually
9444             # modify the contents on subsequent invocations
9445hunk ./src/allmydata/test/test_mutable.py 484
9446             calls.append(1)
9447             if len(calls) <= 1:
9448                 raise UncoordinatedWriteError("simulated")
9449-            return old_contents
9450+            return MutableDataHandle(old_contents)
9451 
9452hunk ./src/allmydata/test/test_mutable.py 486
9453-        d = self.nodemaker.create_mutable_file("line1")
9454+        initial_contents = "line1"
9455+        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
9456         def _created(n):
9457             d = n.modify(_modifier)
9458             d.addCallback(lambda res: n.download_best_version())
9459hunk ./src/allmydata/test/test_mutable.py 548
9460 
9461     def test_modify_backoffer(self):
9462         def _modifier(old_contents, servermap, first_time):
9463-            return old_contents + "line2"
9464+            return MutableDataHandle(old_contents + "line2")
9465         calls = []
9466         def _ucw_error_modifier(old_contents, servermap, first_time):
9467             # simulate an UncoordinatedWriteError once
9468hunk ./src/allmydata/test/test_mutable.py 555
9469             calls.append(1)
9470             if len(calls) <= 1:
9471                 raise UncoordinatedWriteError("simulated")
9472-            return old_contents + "line3"
9473+            return MutableDataHandle(old_contents + "line3")
9474         def _always_ucw_error_modifier(old_contents, servermap, first_time):
9475             raise UncoordinatedWriteError("simulated")
9476         def _backoff_stopper(node, f):
9477hunk ./src/allmydata/test/test_mutable.py 570
9478         giveuper._delay = 0.1
9479         giveuper.factor = 1
9480 
9481-        d = self.nodemaker.create_mutable_file("line1")
9482+        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
9483         def _created(n):
9484             d = n.modify(_modifier)
9485             d.addCallback(lambda res: n.download_best_version())
9486hunk ./src/allmydata/test/test_mutable.py 620
9487             d.addCallback(lambda smap: smap.dump(StringIO()))
9488             d.addCallback(lambda sio:
9489                           self.failUnless("3-of-10" in sio.getvalue()))
9490-            d.addCallback(lambda res: n.overwrite("contents 1"))
9491+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9492             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9493             d.addCallback(lambda res: n.download_best_version())
9494             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9495hunk ./src/allmydata/test/test_mutable.py 624
9496-            d.addCallback(lambda res: n.overwrite("contents 2"))
9497+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9498             d.addCallback(lambda res: n.download_best_version())
9499             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9500             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9501hunk ./src/allmydata/test/test_mutable.py 628
9502-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9503+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9504             d.addCallback(lambda res: n.download_best_version())
9505             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9506             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9507hunk ./src/allmydata/test/test_mutable.py 646
9508         # publish a file and create shares, which can then be manipulated
9509         # later.
9510         self.CONTENTS = "New contents go here" * 1000
9511+        self.uploadable = MutableDataHandle(self.CONTENTS)
9512         self._storage = FakeStorage()
9513         self._nodemaker = make_nodemaker(self._storage)
9514         self._storage_broker = self._nodemaker.storage_broker
9515hunk ./src/allmydata/test/test_mutable.py 650
9516-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9517+        d = self._nodemaker.create_mutable_file(self.uploadable)
9518         def _created(node):
9519             self._fn = node
9520             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9521hunk ./src/allmydata/test/test_mutable.py 662
9522         # an MDMF file.
9523         # self.CONTENTS should have more than one segment.
9524         self.CONTENTS = "This is an MDMF file" * 100000
9525+        self.uploadable = MutableDataHandle(self.CONTENTS)
9526         self._storage = FakeStorage()
9527         self._nodemaker = make_nodemaker(self._storage)
9528         self._storage_broker = self._nodemaker.storage_broker
9529hunk ./src/allmydata/test/test_mutable.py 666
9530-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
9531+        d = self._nodemaker.create_mutable_file(self.uploadable, version=MDMF_VERSION)
9532         def _created(node):
9533             self._fn = node
9534             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9535hunk ./src/allmydata/test/test_mutable.py 678
9536         # like publish_one, except that the result is guaranteed to be
9537         # an SDMF file
9538         self.CONTENTS = "This is an SDMF file" * 1000
9539+        self.uploadable = MutableDataHandle(self.CONTENTS)
9540         self._storage = FakeStorage()
9541         self._nodemaker = make_nodemaker(self._storage)
9542         self._storage_broker = self._nodemaker.storage_broker
9543hunk ./src/allmydata/test/test_mutable.py 682
9544-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
9545+        d = self._nodemaker.create_mutable_file(self.uploadable, version=SDMF_VERSION)
9546         def _created(node):
9547             self._fn = node
9548             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9549hunk ./src/allmydata/test/test_mutable.py 696
9550                          "Contents 2",
9551                          "Contents 3a",
9552                          "Contents 3b"]
9553+        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
9554         self._copied_shares = {}
9555         self._storage = FakeStorage()
9556         self._nodemaker = make_nodemaker(self._storage)
9557hunk ./src/allmydata/test/test_mutable.py 700
9558-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
9559+        d = self._nodemaker.create_mutable_file(self.uploadables[0], version=version) # seqnum=1
9560         def _created(node):
9561             self._fn = node
9562             # now create multiple versions of the same file, and accumulate
9563hunk ./src/allmydata/test/test_mutable.py 707
9564             # their shares, so we can mix and match them later.
9565             d = defer.succeed(None)
9566             d.addCallback(self._copy_shares, 0)
9567-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[1])) #s2
9568+            d.addCallback(lambda res: node.overwrite(self.uploadables[1])) #s2
9569             d.addCallback(self._copy_shares, 1)
9570hunk ./src/allmydata/test/test_mutable.py 709
9571-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[2])) #s3
9572+            d.addCallback(lambda res: node.overwrite(self.uploadables[2])) #s3
9573             d.addCallback(self._copy_shares, 2)
9574hunk ./src/allmydata/test/test_mutable.py 711
9575-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[3])) #s4a
9576+            d.addCallback(lambda res: node.overwrite(self.uploadables[3])) #s4a
9577             d.addCallback(self._copy_shares, 3)
9578             # now we replace all the shares with version s3, and upload a new
9579             # version to get s4b.
9580hunk ./src/allmydata/test/test_mutable.py 717
9581             rollback = dict([(i,2) for i in range(10)])
9582             d.addCallback(lambda res: self._set_versions(rollback))
9583-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[4])) #s4b
9584+            d.addCallback(lambda res: node.overwrite(self.uploadables[4])) #s4b
9585             d.addCallback(self._copy_shares, 4)
9586             # we leave the storage in state 4
9587             return d
9588hunk ./src/allmydata/test/test_mutable.py 826
9589         # create a new file, which is large enough to knock the privkey out
9590         # of the early part of the file
9591         LARGE = "These are Larger contents" * 200 # about 5KB
9592-        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE))
9593+        LARGE_uploadable = MutableDataHandle(LARGE)
9594+        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
9595         def _created(large_fn):
9596             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
9597             return self.make_servermap(MODE_WRITE, large_fn2)
9598hunk ./src/allmydata/test/test_mutable.py 1842
9599 class MultipleEncodings(unittest.TestCase):
9600     def setUp(self):
9601         self.CONTENTS = "New contents go here"
9602+        self.uploadable = MutableDataHandle(self.CONTENTS)
9603         self._storage = FakeStorage()
9604         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
9605         self._storage_broker = self._nodemaker.storage_broker
9606hunk ./src/allmydata/test/test_mutable.py 1846
9607-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9608+        d = self._nodemaker.create_mutable_file(self.uploadable)
9609         def _created(node):
9610             self._fn = node
9611         d.addCallback(_created)
9612hunk ./src/allmydata/test/test_mutable.py 1872
9613         s = self._storage
9614         s._peers = {} # clear existing storage
9615         p2 = Publish(fn2, self._storage_broker, None)
9616-        d = p2.publish(data)
9617+        uploadable = MutableDataHandle(data)
9618+        d = p2.publish(uploadable)
9619         def _published(res):
9620             shares = s._peers
9621             s._peers = {}
9622hunk ./src/allmydata/test/test_mutable.py 2049
9623         self._set_versions(target)
9624 
9625         def _modify(oldversion, servermap, first_time):
9626-            return oldversion + " modified"
9627+            return MutableDataHandle(oldversion + " modified")
9628         d = self._fn.modify(_modify)
9629         d.addCallback(lambda res: self._fn.download_best_version())
9630         expected = self.CONTENTS[2] + " modified"
9631hunk ./src/allmydata/test/test_mutable.py 2175
9632         self.basedir = "mutable/Problems/test_publish_surprise"
9633         self.set_up_grid()
9634         nm = self.g.clients[0].nodemaker
9635-        d = nm.create_mutable_file("contents 1")
9636+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9637         def _created(n):
9638             d = defer.succeed(None)
9639             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9640hunk ./src/allmydata/test/test_mutable.py 2185
9641             d.addCallback(_got_smap1)
9642             # then modify the file, leaving the old map untouched
9643             d.addCallback(lambda res: log.msg("starting winning write"))
9644-            d.addCallback(lambda res: n.overwrite("contents 2"))
9645+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9646             # now attempt to modify the file with the old servermap. This
9647             # will look just like an uncoordinated write, in which every
9648             # single share got updated between our mapupdate and our publish
9649hunk ./src/allmydata/test/test_mutable.py 2194
9650                           self.shouldFail(UncoordinatedWriteError,
9651                                           "test_publish_surprise", None,
9652                                           n.upload,
9653-                                          "contents 2a", self.old_map))
9654+                                          MutableDataHandle("contents 2a"), self.old_map))
9655             return d
9656         d.addCallback(_created)
9657         return d
9658hunk ./src/allmydata/test/test_mutable.py 2203
9659         self.basedir = "mutable/Problems/test_retrieve_surprise"
9660         self.set_up_grid()
9661         nm = self.g.clients[0].nodemaker
9662-        d = nm.create_mutable_file("contents 1")
9663+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9664         def _created(n):
9665             d = defer.succeed(None)
9666             d.addCallback(lambda res: n.get_servermap(MODE_READ))
9667hunk ./src/allmydata/test/test_mutable.py 2213
9668             d.addCallback(_got_smap1)
9669             # then modify the file, leaving the old map untouched
9670             d.addCallback(lambda res: log.msg("starting winning write"))
9671-            d.addCallback(lambda res: n.overwrite("contents 2"))
9672+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9673             # now attempt to retrieve the old version with the old servermap.
9674             # This will look like someone has changed the file since we
9675             # updated the servermap.
9676hunk ./src/allmydata/test/test_mutable.py 2241
9677         self.basedir = "mutable/Problems/test_unexpected_shares"
9678         self.set_up_grid()
9679         nm = self.g.clients[0].nodemaker
9680-        d = nm.create_mutable_file("contents 1")
9681+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9682         def _created(n):
9683             d = defer.succeed(None)
9684             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9685hunk ./src/allmydata/test/test_mutable.py 2253
9686                 self.g.remove_server(peer0)
9687                 # then modify the file, leaving the old map untouched
9688                 log.msg("starting winning write")
9689-                return n.overwrite("contents 2")
9690+                return n.overwrite(MutableDataHandle("contents 2"))
9691             d.addCallback(_got_smap1)
9692             # now attempt to modify the file with the old servermap. This
9693             # will look just like an uncoordinated write, in which every
9694hunk ./src/allmydata/test/test_mutable.py 2263
9695                           self.shouldFail(UncoordinatedWriteError,
9696                                           "test_surprise", None,
9697                                           n.upload,
9698-                                          "contents 2a", self.old_map))
9699+                                          MutableDataHandle("contents 2a"), self.old_map))
9700             return d
9701         d.addCallback(_created)
9702         return d
9703hunk ./src/allmydata/test/test_mutable.py 2267
9704+    test_unexpected_shares.timeout = 15
9705 
9706     def test_bad_server(self):
9707         # Break one server, then create the file: the initial publish should
9708hunk ./src/allmydata/test/test_mutable.py 2303
9709         d.addCallback(_break_peer0)
9710         # now "create" the file, using the pre-established key, and let the
9711         # initial publish finally happen
9712-        d.addCallback(lambda res: nm.create_mutable_file("contents 1"))
9713+        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
9714         # that ought to work
9715         def _got_node(n):
9716             d = n.download_best_version()
9717hunk ./src/allmydata/test/test_mutable.py 2312
9718             def _break_peer1(res):
9719                 self.connection1.broken = True
9720             d.addCallback(_break_peer1)
9721-            d.addCallback(lambda res: n.overwrite("contents 2"))
9722+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9723             # that ought to work too
9724             d.addCallback(lambda res: n.download_best_version())
9725             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9726hunk ./src/allmydata/test/test_mutable.py 2344
9727         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
9728         self.g.break_server(peerids[0])
9729 
9730-        d = nm.create_mutable_file("contents 1")
9731+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9732         def _created(n):
9733             d = n.download_best_version()
9734             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9735hunk ./src/allmydata/test/test_mutable.py 2352
9736             def _break_second_server(res):
9737                 self.g.break_server(peerids[1])
9738             d.addCallback(_break_second_server)
9739-            d.addCallback(lambda res: n.overwrite("contents 2"))
9740+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9741             # that ought to work too
9742             d.addCallback(lambda res: n.download_best_version())
9743             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9744hunk ./src/allmydata/test/test_mutable.py 2371
9745         d = self.shouldFail(NotEnoughServersError,
9746                             "test_publish_all_servers_bad",
9747                             "Ran out of non-bad servers",
9748-                            nm.create_mutable_file, "contents")
9749+                            nm.create_mutable_file, MutableDataHandle("contents"))
9750         return d
9751 
9752     def test_publish_no_servers(self):
9753hunk ./src/allmydata/test/test_mutable.py 2383
9754         d = self.shouldFail(NotEnoughServersError,
9755                             "test_publish_no_servers",
9756                             "Ran out of non-bad servers",
9757-                            nm.create_mutable_file, "contents")
9758+                            nm.create_mutable_file, MutableDataHandle("contents"))
9759         return d
9760     test_publish_no_servers.timeout = 30
9761 
9762hunk ./src/allmydata/test/test_mutable.py 2401
9763         # we need some contents that are large enough to push the privkey out
9764         # of the early part of the file
9765         LARGE = "These are Larger contents" * 2000 # about 50KB
9766-        d = nm.create_mutable_file(LARGE)
9767+        LARGE_uploadable = MutableDataHandle(LARGE)
9768+        d = nm.create_mutable_file(LARGE_uploadable)
9769         def _created(n):
9770             self.uri = n.get_uri()
9771             self.n2 = nm.create_from_cap(self.uri)
9772hunk ./src/allmydata/test/test_mutable.py 2438
9773         self.set_up_grid(num_servers=20)
9774         nm = self.g.clients[0].nodemaker
9775         LARGE = "These are Larger contents" * 2000 # about 50KiB
9776+        LARGE_uploadable = MutableDataHandle(LARGE)
9777         nm._node_cache = DevNullDictionary() # disable the nodecache
9778 
9779hunk ./src/allmydata/test/test_mutable.py 2441
9780-        d = nm.create_mutable_file(LARGE)
9781+        d = nm.create_mutable_file(LARGE_uploadable)
9782         def _created(n):
9783             self.uri = n.get_uri()
9784             self.n2 = nm.create_from_cap(self.uri)
9785hunk ./src/allmydata/test/test_mutable.py 2464
9786         self.set_up_grid(num_servers=20)
9787         nm = self.g.clients[0].nodemaker
9788         CONTENTS = "contents" * 2000
9789-        d = nm.create_mutable_file(CONTENTS)
9790+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9791+        d = nm.create_mutable_file(CONTENTS_uploadable)
9792         def _created(node):
9793             self._node = node
9794         d.addCallback(_created)
9795hunk ./src/allmydata/test/test_system.py 22
9796 from allmydata.monitor import Monitor
9797 from allmydata.mutable.common import NotWriteableError
9798 from allmydata.mutable import layout as mutable_layout
9799+from allmydata.mutable.publish import MutableDataHandle
9800 from foolscap.api import DeadReferenceError
9801 from twisted.python.failure import Failure
9802 from twisted.web.client import getPage
9803hunk ./src/allmydata/test/test_system.py 460
9804     def test_mutable(self):
9805         self.basedir = "system/SystemTest/test_mutable"
9806         DATA = "initial contents go here."  # 25 bytes % 3 != 0
9807+        DATA_uploadable = MutableDataHandle(DATA)
9808         NEWDATA = "new contents yay"
9809hunk ./src/allmydata/test/test_system.py 462
9810+        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
9811         NEWERDATA = "this is getting old"
9812hunk ./src/allmydata/test/test_system.py 464
9813+        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
9814 
9815         d = self.set_up_nodes(use_key_generator=True)
9816 
9817hunk ./src/allmydata/test/test_system.py 471
9818         def _create_mutable(res):
9819             c = self.clients[0]
9820             log.msg("starting create_mutable_file")
9821-            d1 = c.create_mutable_file(DATA)
9822+            d1 = c.create_mutable_file(DATA_uploadable)
9823             def _done(res):
9824                 log.msg("DONE: %s" % (res,))
9825                 self._mutable_node_1 = res
9826hunk ./src/allmydata/test/test_system.py 558
9827             self.failUnlessEqual(res, DATA)
9828             # replace the data
9829             log.msg("starting replace1")
9830-            d1 = newnode.overwrite(NEWDATA)
9831+            d1 = newnode.overwrite(NEWDATA_uploadable)
9832             d1.addCallback(lambda res: newnode.download_best_version())
9833             return d1
9834         d.addCallback(_check_download_3)
9835hunk ./src/allmydata/test/test_system.py 572
9836             newnode2 = self.clients[3].create_node_from_uri(uri)
9837             self._newnode3 = self.clients[3].create_node_from_uri(uri)
9838             log.msg("starting replace2")
9839-            d1 = newnode1.overwrite(NEWERDATA)
9840+            d1 = newnode1.overwrite(NEWERDATA_uploadable)
9841             d1.addCallback(lambda res: newnode2.download_best_version())
9842             return d1
9843         d.addCallback(_check_download_4)
9844hunk ./src/allmydata/test/test_system.py 642
9845         def _check_empty_file(res):
9846             # make sure we can create empty files, this usually screws up the
9847             # segsize math
9848-            d1 = self.clients[2].create_mutable_file("")
9849+            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
9850             d1.addCallback(lambda newnode: newnode.download_best_version())
9851             d1.addCallback(lambda res: self.failUnlessEqual("", res))
9852             return d1
9853hunk ./src/allmydata/test/test_system.py 673
9854                                  self.key_generator_svc.key_generator.pool_size + size_delta)
9855 
9856         d.addCallback(check_kg_poolsize, 0)
9857-        d.addCallback(lambda junk: self.clients[3].create_mutable_file('hello, world'))
9858+        d.addCallback(lambda junk:
9859+            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
9860         d.addCallback(check_kg_poolsize, -1)
9861         d.addCallback(lambda junk: self.clients[3].create_dirnode())
9862         d.addCallback(check_kg_poolsize, -2)
9863hunk ./src/allmydata/test/test_web.py 3183
9864         def _stash_mutable_uri(n, which):
9865             self.uris[which] = n.get_uri()
9866             assert isinstance(self.uris[which], str)
9867-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9868+        d.addCallback(lambda ign:
9869+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9870         d.addCallback(_stash_mutable_uri, "corrupt")
9871         d.addCallback(lambda ign:
9872                       c0.upload(upload.Data("literal", convergence="")))
9873hunk ./src/allmydata/test/test_web.py 3330
9874         def _stash_mutable_uri(n, which):
9875             self.uris[which] = n.get_uri()
9876             assert isinstance(self.uris[which], str)
9877-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9878+        d.addCallback(lambda ign:
9879+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9880         d.addCallback(_stash_mutable_uri, "corrupt")
9881 
9882         def _compute_fileurls(ignored):
9883hunk ./src/allmydata/test/test_web.py 3993
9884         def _stash_mutable_uri(n, which):
9885             self.uris[which] = n.get_uri()
9886             assert isinstance(self.uris[which], str)
9887-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
9888+        d.addCallback(lambda ign:
9889+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
9890         d.addCallback(_stash_mutable_uri, "mutable")
9891 
9892         def _compute_fileurls(ignored):
9893hunk ./src/allmydata/test/test_web.py 4093
9894                                                         convergence="")))
9895         d.addCallback(_stash_uri, "small")
9896 
9897-        d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
9898+        d.addCallback(lambda ign:
9899+            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
9900         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9901         d.addCallback(_stash_uri, "mutable")
9902 
9903}
9904[Alter mutable files to use file-like objects for publishing instead of strings.
9905Kevan Carstensen <kevan@isnotajoke.com>**20100708000732
9906 Ignore-this: 8dd07d95386b6d540bc21289f981ebd0
9907] {
9908hunk ./src/allmydata/dirnode.py 11
9909 from allmydata.mutable.common import NotWriteableError
9910 from allmydata.mutable.filenode import MutableFileNode
9911 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
9912+from allmydata.mutable.publish import MutableDataHandle
9913 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
9914      IImmutableFileNode, IMutableFileNode, \
9915      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
9916hunk ./src/allmydata/dirnode.py 104
9917 
9918         del children[self.name]
9919         new_contents = self.node._pack_contents(children)
9920-        return new_contents
9921+        uploadable = MutableDataHandle(new_contents)
9922+        return uploadable
9923 
9924 
9925 class MetadataSetter:
9926hunk ./src/allmydata/dirnode.py 130
9927 
9928         children[name] = (child, metadata)
9929         new_contents = self.node._pack_contents(children)
9930-        return new_contents
9931+        uploadable = MutableDataHandle(new_contents)
9932+        return uploadable
9933 
9934 
9935 class Adder:
9936hunk ./src/allmydata/dirnode.py 175
9937 
9938             children[name] = (child, metadata)
9939         new_contents = self.node._pack_contents(children)
9940-        return new_contents
9941+        uploadable = MutableDataHandle(new_contents)
9942+        return uploadable
9943 
9944 def _encrypt_rw_uri(writekey, rw_uri):
9945     precondition(isinstance(rw_uri, str), rw_uri)
9946hunk ./src/allmydata/mutable/filenode.py 7
9947 from zope.interface import implements
9948 from twisted.internet import defer, reactor
9949 from foolscap.api import eventually
9950-from allmydata.interfaces import IMutableFileNode, \
9951-     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
9952+from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
9953+                                 NotEnoughSharesError, \
9954+                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
9955 from allmydata.util import hashutil, log
9956 from allmydata.util.assertutil import precondition
9957 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
9958hunk ./src/allmydata/mutable/filenode.py 16
9959 from allmydata.monitor import Monitor
9960 from pycryptopp.cipher.aes import AES
9961 
9962-from allmydata.mutable.publish import Publish
9963+from allmydata.mutable.publish import Publish, MutableFileHandle, \
9964+                                      MutableDataHandle
9965 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
9966      ResponseCache, UncoordinatedWriteError
9967 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
9968hunk ./src/allmydata/mutable/filenode.py 133
9969         return self._upload(initial_contents, None)
9970 
9971     def _get_initial_contents(self, contents):
9972-        if isinstance(contents, str):
9973-            return contents
9974         if contents is None:
9975hunk ./src/allmydata/mutable/filenode.py 134
9976-            return ""
9977+            return MutableDataHandle("")
9978+
9979+        if IMutableUploadable.providedBy(contents):
9980+            return contents
9981+
9982         assert callable(contents), "%s should be callable, not %s" % \
9983                (contents, type(contents))
9984         return contents(self)
9985hunk ./src/allmydata/mutable/filenode.py 353
9986     def overwrite(self, new_contents):
9987         return self._do_serialized(self._overwrite, new_contents)
9988     def _overwrite(self, new_contents):
9989+        assert IMutableUploadable.providedBy(new_contents)
9990+
9991         servermap = ServerMap()
9992         d = self._update_servermap(servermap, mode=MODE_WRITE)
9993         d.addCallback(lambda ignored: self._upload(new_contents, servermap))
9994hunk ./src/allmydata/mutable/filenode.py 431
9995                 # recovery when it observes UCWE, we need to do a second
9996                 # publish. See #551 for details. We'll basically loop until
9997                 # we managed an uncontested publish.
9998-                new_contents = old_contents
9999-            precondition(isinstance(new_contents, str),
10000-                         "Modifier function must return a string or None")
10001+                old_uploadable = MutableDataHandle(old_contents)
10002+                new_contents = old_uploadable
10003+            precondition((IMutableUploadable.providedBy(new_contents) or
10004+                          new_contents is None),
10005+                         "Modifier function must return an IMutableUploadable "
10006+                         "or None")
10007             return self._upload(new_contents, servermap)
10008         d.addCallback(_apply)
10009         return d
10010hunk ./src/allmydata/mutable/filenode.py 472
10011         return self._do_serialized(self._upload, new_contents, servermap)
10012     def _upload(self, new_contents, servermap):
10013         assert self._pubkey, "update_servermap must be called before publish"
10014+        assert IMutableUploadable.providedBy(new_contents)
10015+
10016         p = Publish(self, self._storage_broker, servermap)
10017         if self._history:
10018hunk ./src/allmydata/mutable/filenode.py 476
10019-            self._history.notify_publish(p.get_status(), len(new_contents))
10020+            self._history.notify_publish(p.get_status(), new_contents.get_size())
10021         d = p.publish(new_contents)
10022hunk ./src/allmydata/mutable/filenode.py 478
10023-        d.addCallback(self._did_upload, len(new_contents))
10024+        d.addCallback(self._did_upload, new_contents.get_size())
10025         return d
10026     def _did_upload(self, res, size):
10027         self._most_recent_size = size
10028hunk ./src/allmydata/mutable/publish.py 141
10029 
10030         # 0. Setup encoding parameters, encoder, and other such things.
10031         # 1. Encrypt, encode, and publish segments.
10032-        self.data = StringIO(newdata)
10033-        self.datalength = len(newdata)
10034+        assert IMutableUploadable.providedBy(newdata)
10035+
10036+        self.data = newdata
10037+        self.datalength = newdata.get_size()
10038 
10039         self.log("starting publish, datalen is %s" % self.datalength)
10040         self._status.set_size(self.datalength)
10041hunk ./src/allmydata/mutable/publish.py 442
10042 
10043         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
10044         data = self.data.read(segsize)
10045+        # XXX: This is dumb. Why return a list?
10046+        data = "".join(data)
10047 
10048         assert len(data) == segsize
10049 
10050hunk ./src/allmydata/mutable/repairer.py 5
10051 from zope.interface import implements
10052 from twisted.internet import defer
10053 from allmydata.interfaces import IRepairResults, ICheckResults
10054+from allmydata.mutable.publish import MutableDataHandle
10055 
10056 class RepairResults:
10057     implements(IRepairResults)
10058hunk ./src/allmydata/mutable/repairer.py 108
10059             raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.")
10060 
10061         d = self.node.download_version(smap, best_version, fetch_privkey=True)
10062+        d.addCallback(lambda data:
10063+            MutableDataHandle(data))
10064         d.addCallback(self.node.upload, smap)
10065         d.addCallback(self.get_results, smap)
10066         return d
10067hunk ./src/allmydata/nodemaker.py 9
10068 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
10069 from allmydata.immutable.upload import Data
10070 from allmydata.mutable.filenode import MutableFileNode
10071+from allmydata.mutable.publish import MutableDataHandle
10072 from allmydata.dirnode import DirectoryNode, pack_children
10073 from allmydata.unknown import UnknownNode
10074 from allmydata import uri
10075merger 0.0 (
10076merger 0.0 (
10077hunk ./src/allmydata/nodemaker.py 107
10078-                                     pack_children(n, initial_children))
10079+                                     pack_children(n, initial_children),
10080+                                     version)
10081hunk ./src/allmydata/nodemaker.py 107
10082-                                     pack_children(n, initial_children))
10083+                                     pack_children(initial_children, n.get_writekey()))
10084)
10085hunk ./src/allmydata/nodemaker.py 107
10086-                                     pack_children(n, initial_children),
10087+                                     MutableDataHandle(
10088+                                        pack_children(n, initial_children)),
10089)
10090hunk ./src/allmydata/web/filenode.py 12
10091 from allmydata.interfaces import ExistingChildError
10092 from allmydata.monitor import Monitor
10093 from allmydata.immutable.upload import FileHandle
10094+from allmydata.mutable.publish import MutableFileHandle
10095 from allmydata.util import log, base32
10096 
10097 from allmydata.web.common import text_plain, WebError, RenderMixin, \
10098hunk ./src/allmydata/web/filenode.py 27
10099         # a new file is being uploaded in our place.
10100         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10101         if mutable:
10102-            req.content.seek(0)
10103-            data = req.content.read()
10104+            data = MutableFileHandle(req.content)
10105             d = client.create_mutable_file(data)
10106             def _uploaded(newnode):
10107                 d2 = self.parentnode.set_node(self.name, newnode,
10108hunk ./src/allmydata/web/filenode.py 61
10109         d.addCallback(lambda res: childnode.get_uri())
10110         return d
10111 
10112-    def _read_data_from_formpost(self, req):
10113-        # SDMF: files are small, and we can only upload data, so we read
10114-        # the whole file into memory before uploading.
10115-        contents = req.fields["file"]
10116-        contents.file.seek(0)
10117-        data = contents.file.read()
10118-        return data
10119 
10120     def replace_me_with_a_formpost(self, req, client, replace):
10121         # create a new file, maybe mutable, maybe immutable
10122hunk ./src/allmydata/web/filenode.py 66
10123         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10124 
10125+        # create an immutable file
10126+        contents = req.fields["file"]
10127         if mutable:
10128hunk ./src/allmydata/web/filenode.py 69
10129-            data = self._read_data_from_formpost(req)
10130-            d = client.create_mutable_file(data)
10131+            uploadable = MutableFileHandle(contents.file)
10132+            d = client.create_mutable_file(uploadable)
10133             def _uploaded(newnode):
10134                 d2 = self.parentnode.set_node(self.name, newnode,
10135                                               overwrite=replace)
10136hunk ./src/allmydata/web/filenode.py 78
10137                 return d2
10138             d.addCallback(_uploaded)
10139             return d
10140-        # create an immutable file
10141-        contents = req.fields["file"]
10142+
10143         uploadable = FileHandle(contents.file, convergence=client.convergence)
10144         d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
10145         d.addCallback(lambda newnode: newnode.get_uri())
10146hunk ./src/allmydata/web/filenode.py 84
10147         return d
10148 
10149+
10150 class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
10151     def __init__(self, client, parentnode, name):
10152         rend.Page.__init__(self)
10153hunk ./src/allmydata/web/filenode.py 278
10154 
10155     def replace_my_contents(self, req):
10156         req.content.seek(0)
10157-        new_contents = req.content.read()
10158+        new_contents = MutableFileHandle(req.content)
10159         d = self.node.overwrite(new_contents)
10160         d.addCallback(lambda res: self.node.get_uri())
10161         return d
10162hunk ./src/allmydata/web/filenode.py 286
10163     def replace_my_contents_with_a_formpost(self, req):
10164         # we have a mutable file. Get the data from the formpost, and replace
10165         # the mutable file's contents with it.
10166-        new_contents = self._read_data_from_formpost(req)
10167+        new_contents = req.fields['file']
10168+        new_contents = MutableFileHandle(new_contents.file)
10169+
10170         d = self.node.overwrite(new_contents)
10171         d.addCallback(lambda res: self.node.get_uri())
10172         return d
10173hunk ./src/allmydata/web/unlinked.py 7
10174 from twisted.internet import defer
10175 from nevow import rend, url, tags as T
10176 from allmydata.immutable.upload import FileHandle
10177+from allmydata.mutable.publish import MutableFileHandle
10178 from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
10179      convert_children_json, WebError
10180 from allmydata.web import status
10181hunk ./src/allmydata/web/unlinked.py 23
10182 def PUTUnlinkedSSK(req, client):
10183     # SDMF: files are small, and we can only upload data
10184     req.content.seek(0)
10185-    data = req.content.read()
10186+    data = MutableFileHandle(req.content)
10187     d = client.create_mutable_file(data)
10188     d.addCallback(lambda n: n.get_uri())
10189     return d
10190hunk ./src/allmydata/web/unlinked.py 87
10191     # "POST /uri", to create an unlinked file.
10192     # SDMF: files are small, and we can only upload data
10193     contents = req.fields["file"]
10194-    contents.file.seek(0)
10195-    data = contents.file.read()
10196+    data = MutableFileHandle(contents.file)
10197     d = client.create_mutable_file(data)
10198     d.addCallback(lambda n: n.get_uri())
10199     return d
10200}
10201[test/test_sftp.py: alter a setup routine to work with new mutable file APIs.
10202Kevan Carstensen <kevan@isnotajoke.com>**20100708193522
10203 Ignore-this: 434bbe1347072076c0836d26fca8ac8a
10204] {
10205hunk ./src/allmydata/test/test_sftp.py 32
10206 
10207 from allmydata.util.consumer import download_to_data
10208 from allmydata.immutable import upload
10209+from allmydata.mutable import publish
10210 from allmydata.test.no_network import GridTestMixin
10211 from allmydata.test.common import ShouldFailMixin
10212 from allmydata.test.common_util import ReallyEqualMixin
10213hunk ./src/allmydata/test/test_sftp.py 84
10214         return d
10215 
10216     def _set_up_tree(self):
10217-        d = self.client.create_mutable_file("mutable file contents")
10218+        u = publish.MutableDataHandle("mutable file contents")
10219+        d = self.client.create_mutable_file(u)
10220         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
10221         def _created_mutable(n):
10222             self.mutable = n
10223}
10224[mutable/publish.py: make MutableFileHandle seek to the beginning of its file handle before reading.
10225Kevan Carstensen <kevan@isnotajoke.com>**20100708193600
10226 Ignore-this: 453a737dc62a79c77b3d360fed9000ab
10227] hunk ./src/allmydata/mutable/publish.py 989
10228         assert hasattr(filehandle, "close")
10229 
10230         self._filehandle = filehandle
10231+        # We must start reading at the beginning of the file, or we risk
10232+        # encountering errors when the data read does not match the size
10233+        # reported to the uploader.
10234+        self._filehandle.seek(0)
10235 
10236 
10237     def get_size(self):
10238[Refactor download interfaces to be more uniform, per #993
10239Kevan Carstensen <kevan@isnotajoke.com>**20100709232912
10240 Ignore-this: 277c5699c4a2dd7c03ecfa0a28458f5b
10241] {
10242hunk ./src/allmydata/immutable/filenode.py 10
10243 from foolscap.api import eventually
10244 from allmydata.interfaces import IImmutableFileNode, ICheckable, \
10245      IDownloadTarget, IUploadResults
10246-from allmydata.util import dictutil, log, base32
10247+from allmydata.util import dictutil, log, base32, consumer
10248 from allmydata.uri import CHKFileURI, LiteralFileURI
10249 from allmydata.immutable.checker import Checker
10250 from allmydata.check_results import CheckResults, CheckAndRepairResults
10251hunk ./src/allmydata/immutable/filenode.py 318
10252                       self.download_cache.read(consumer, offset, size))
10253         return d
10254 
10255+    # IReadable, IFileNode
10256+
10257+    def get_best_readable_version(self):
10258+        """
10259+        Return an IReadable of the best version of this file. Since
10260+        immutable files can have only one version, we just return the
10261+        current filenode.
10262+        """
10263+        return self
10264+
10265+
10266+    def download_best_version(self):
10267+        """
10268+        Download the best version of this file, returning its contents
10269+        as a bytestring. Since there is only one version of an immutable
10270+        file, we download and return the contents of this file.
10271+        """
10272+        d = consumer.download_to_data(self)
10273+        return d
10274+
10275+    # for an immutable file, download_to_data (specified in IReadable)
10276+    # is the same as download_best_version (specified in IFileNode). For
10277+    # mutable files, the difference is more meaningful, since they can
10278+    # have multiple versions.
10279+    download_to_data = download_best_version
10280+
10281+
10282+    # get_size() (IReadable), get_current_size() (IFilesystemNode), and
10283+    # get_size_of_best_version(IFileNode) are all the same for immutable
10284+    # files.
10285+    get_size_of_best_version = get_current_size
10286+
10287+
10288 class LiteralProducer:
10289     implements(IPushProducer)
10290     def resumeProducing(self):
10291hunk ./src/allmydata/immutable/filenode.py 409
10292         d = basic.FileSender().beginFileTransfer(StringIO(data), consumer)
10293         d.addCallback(lambda lastSent: consumer)
10294         return d
10295+
10296+    # IReadable, IFileNode, IFilesystemNode
10297+    def get_best_readable_version(self):
10298+        return self
10299+
10300+
10301+    def download_best_version(self):
10302+        return defer.succeed(self.u.data)
10303+
10304+
10305+    download_to_data = download_best_version
10306+    get_size_of_best_version = get_current_size
10307hunk ./src/allmydata/interfaces.py 563
10308 class MustNotBeUnknownRWError(CapConstraintError):
10309     """Cannot add an unknown child cap specified in a rw_uri field."""
10310 
10311+
10312+class IReadable(Interface):
10313+    """I represent a readable object -- either an immutable file, or a
10314+    specific version of a mutable file.
10315+    """
10316+
10317+    def is_readonly():
10318+        """Return True if this reference provides mutable access to the given
10319+        file or directory (i.e. if you can modify it), or False if not. Note
10320+        that even if this reference is read-only, someone else may hold a
10321+        read-write reference to it.
10322+
10323+        For an IReadable returned by get_best_readable_version(), this will
10324+        always return True, but for instances of subinterfaces such as
10325+        IMutableFileVersion, it may return False."""
10326+
10327+    def is_mutable():
10328+        """Return True if this file or directory is mutable (by *somebody*,
10329+        not necessarily you), False if it is is immutable. Note that a file
10330+        might be mutable overall, but your reference to it might be
10331+        read-only. On the other hand, all references to an immutable file
10332+        will be read-only; there are no read-write references to an immutable
10333+        file."""
10334+
10335+    def get_storage_index():
10336+        """Return the storage index of the file."""
10337+
10338+    def get_size():
10339+        """Return the length (in bytes) of this readable object."""
10340+
10341+    def download_to_data():
10342+        """Download all of the file contents. I return a Deferred that fires
10343+        with the contents as a byte string."""
10344+
10345+    def read(consumer, offset=0, size=None):
10346+        """Download a portion (possibly all) of the file's contents, making
10347+        them available to the given IConsumer. Return a Deferred that fires
10348+        (with the consumer) when the consumer is unregistered (either because
10349+        the last byte has been given to it, or because the consumer threw an
10350+        exception during write(), possibly because it no longer wants to
10351+        receive data). The portion downloaded will start at 'offset' and
10352+        contain 'size' bytes (or the remainder of the file if size==None).
10353+
10354+        The consumer will be used in non-streaming mode: an IPullProducer
10355+        will be attached to it.
10356+
10357+        The consumer will not receive data right away: several network trips
10358+        must occur first. The order of events will be::
10359+
10360+         consumer.registerProducer(p, streaming)
10361+          (if streaming == False)::
10362+           consumer does p.resumeProducing()
10363+            consumer.write(data)
10364+           consumer does p.resumeProducing()
10365+            consumer.write(data).. (repeat until all data is written)
10366+         consumer.unregisterProducer()
10367+         deferred.callback(consumer)
10368+
10369+        If a download error occurs, or an exception is raised by
10370+        consumer.registerProducer() or consumer.write(), I will call
10371+        consumer.unregisterProducer() and then deliver the exception via
10372+        deferred.errback(). To cancel the download, the consumer should call
10373+        p.stopProducing(), which will result in an exception being delivered
10374+        via deferred.errback().
10375+
10376+        See src/allmydata/util/consumer.py for an example of a simple
10377+        download-to-memory consumer.
10378+        """
10379+
10380+
10381+class IMutableFileVersion(IReadable):
10382+    """I provide access to a particular version of a mutable file. The
10383+    access is read/write if I was obtained from a filenode derived from
10384+    a write cap, or read-only if the filenode was derived from a read cap.
10385+    """
10386+
10387+    def get_sequence_number():
10388+        """Return the sequence number of this version."""
10389+
10390+    def get_servermap():
10391+        """Return the IMutableFileServerMap instance that was used to create
10392+        this object.
10393+        """
10394+
10395+    def get_writekey():
10396+        """Return this filenode's writekey, or None if the node does not have
10397+        write-capability. This may be used to assist with data structures
10398+        that need to make certain data available only to writers, such as the
10399+        read-write child caps in dirnodes. The recommended process is to have
10400+        reader-visible data be submitted to the filenode in the clear (where
10401+        it will be encrypted by the filenode using the readkey), but encrypt
10402+        writer-visible data using this writekey.
10403+        """
10404+
10405+    # TODO: Can this be overwrite instead of replace?
10406+    def replace(new_contents):
10407+        """Replace the contents of the mutable file, provided that no other
10408+        node has published (or is attempting to publish, concurrently) a
10409+        newer version of the file than this one.
10410+
10411+        I will avoid modifying any share that is different than the version
10412+        given by get_sequence_number(). However, if another node is writing
10413+        to the file at the same time as me, I may manage to update some shares
10414+        while they update others. If I see any evidence of this, I will signal
10415+        UncoordinatedWriteError, and the file will be left in an inconsistent
10416+        state (possibly the version you provided, possibly the old version,
10417+        possibly somebody else's version, and possibly a mix of shares from
10418+        all of these).
10419+
10420+        The recommended response to UncoordinatedWriteError is to either
10421+        return it to the caller (since they failed to coordinate their
10422+        writes), or to attempt some sort of recovery. It may be sufficient to
10423+        wait a random interval (with exponential backoff) and repeat your
10424+        operation. If I do not signal UncoordinatedWriteError, then I was
10425+        able to write the new version without incident.
10426+
10427+        I return a Deferred that fires (with a PublishStatus object) when the
10428+        update has completed.
10429+        """
10430+
10431+    def modify(modifier_cb):
10432+        """Modify the contents of the file, by downloading this version,
10433+        applying the modifier function (or bound method), then uploading
10434+        the new version. This will succeed as long as no other node
10435+        publishes a version between the download and the upload.
10436+        I return a Deferred that fires (with a PublishStatus object) when
10437+        the update is complete.
10438+
10439+        The modifier callable will be given three arguments: a string (with
10440+        the old contents), a 'first_time' boolean, and a servermap. As with
10441+        download_to_data(), the old contents will be from this version,
10442+        but the modifier can use the servermap to make other decisions
10443+        (such as refusing to apply the delta if there are multiple parallel
10444+        versions, or if there is evidence of a newer unrecoverable version).
10445+        'first_time' will be True the first time the modifier is called,
10446+        and False on any subsequent calls.
10447+
10448+        The callable should return a string with the new contents. The
10449+        callable must be prepared to be called multiple times, and must
10450+        examine the input string to see if the change that it wants to make
10451+        is already present in the old version. If it does not need to make
10452+        any changes, it can either return None, or return its input string.
10453+
10454+        If the modifier raises an exception, it will be returned in the
10455+        errback.
10456+        """
10457+
10458+
10459 # The hierarchy looks like this:
10460 #  IFilesystemNode
10461 #   IFileNode
10462hunk ./src/allmydata/interfaces.py 801
10463     def raise_error():
10464         """Raise any error associated with this node."""
10465 
10466+    # XXX: These may not be appropriate outside the context of an IReadable.
10467     def get_size():
10468         """Return the length (in bytes) of the data this node represents. For
10469         directory nodes, I return the size of the backing store. I return
10470hunk ./src/allmydata/interfaces.py 818
10471 class IFileNode(IFilesystemNode):
10472     """I am a node which represents a file: a sequence of bytes. I am not a
10473     container, like IDirectoryNode."""
10474+    def get_best_readable_version():
10475+        """Return a Deferred that fires with an IReadable for the 'best'
10476+        available version of the file. The IReadable provides only read
10477+        access, even if this filenode was derived from a write cap.
10478 
10479hunk ./src/allmydata/interfaces.py 823
10480-class IImmutableFileNode(IFileNode):
10481-    def read(consumer, offset=0, size=None):
10482-        """Download a portion (possibly all) of the file's contents, making
10483-        them available to the given IConsumer. Return a Deferred that fires
10484-        (with the consumer) when the consumer is unregistered (either because
10485-        the last byte has been given to it, or because the consumer threw an
10486-        exception during write(), possibly because it no longer wants to
10487-        receive data). The portion downloaded will start at 'offset' and
10488-        contain 'size' bytes (or the remainder of the file if size==None).
10489-
10490-        The consumer will be used in non-streaming mode: an IPullProducer
10491-        will be attached to it.
10492+        For an immutable file, there is only one version. For a mutable
10493+        file, the 'best' version is the recoverable version with the
10494+        highest sequence number. If no uncoordinated writes have occurred,
10495+        and if enough shares are available, then this will be the most
10496+        recent version that has been uploaded. If no version is recoverable,
10497+        the Deferred will errback with an UnrecoverableFileError.
10498+        """
10499 
10500hunk ./src/allmydata/interfaces.py 831
10501-        The consumer will not receive data right away: several network trips
10502-        must occur first. The order of events will be::
10503+    def download_best_version():
10504+        """Download the contents of the version that would be returned
10505+        by get_best_readable_version(). This is equivalent to calling
10506+        download_to_data() on the IReadable given by that method.
10507 
10508hunk ./src/allmydata/interfaces.py 836
10509-         consumer.registerProducer(p, streaming)
10510-          (if streaming == False)::
10511-           consumer does p.resumeProducing()
10512-            consumer.write(data)
10513-           consumer does p.resumeProducing()
10514-            consumer.write(data).. (repeat until all data is written)
10515-         consumer.unregisterProducer()
10516-         deferred.callback(consumer)
10517+        I return a Deferred that fires with a byte string when the file
10518+        has been fully downloaded. To support streaming download, use
10519+        the 'read' method of IReadable. If no version is recoverable,
10520+        the Deferred will errback with an UnrecoverableFileError.
10521+        """
10522 
10523hunk ./src/allmydata/interfaces.py 842
10524-        If a download error occurs, or an exception is raised by
10525-        consumer.registerProducer() or consumer.write(), I will call
10526-        consumer.unregisterProducer() and then deliver the exception via
10527-        deferred.errback(). To cancel the download, the consumer should call
10528-        p.stopProducing(), which will result in an exception being delivered
10529-        via deferred.errback().
10530+    def get_size_of_best_version():
10531+        """Find the size of the version that would be returned by
10532+        get_best_readable_version().
10533 
10534hunk ./src/allmydata/interfaces.py 846
10535-        See src/allmydata/util/consumer.py for an example of a simple
10536-        download-to-memory consumer.
10537+        I return a Deferred that fires with an integer. If no version
10538+        is recoverable, the Deferred will errback with an
10539+        UnrecoverableFileError.
10540         """
10541 
10542hunk ./src/allmydata/interfaces.py 851
10543+
10544+class IImmutableFileNode(IFileNode, IReadable):
10545+    """I am a node representing an immutable file. Immutable files have
10546+    only one version"""
10547+
10548+
10549 class IMutableFileNode(IFileNode):
10550     """I provide access to a 'mutable file', which retains its identity
10551     regardless of what contents are put in it.
10552hunk ./src/allmydata/interfaces.py 916
10553     only be retrieved and updated all-at-once, as a single big string. Future
10554     versions of our mutable files will remove this restriction.
10555     """
10556-
10557-    def download_best_version():
10558-        """Download the 'best' available version of the file, meaning one of
10559-        the recoverable versions with the highest sequence number. If no
10560+    def get_best_mutable_version():
10561+        """Return a Deferred that fires with an IMutableFileVersion for
10562+        the 'best' available version of the file. The best version is
10563+        the recoverable version with the highest sequence number. If no
10564         uncoordinated writes have occurred, and if enough shares are
10565hunk ./src/allmydata/interfaces.py 921
10566-        available, then this will be the most recent version that has been
10567-        uploaded.
10568-
10569-        I update an internal servermap with MODE_READ, determine which
10570-        version of the file is indicated by
10571-        servermap.best_recoverable_version(), and return a Deferred that
10572-        fires with its contents. If no version is recoverable, the Deferred
10573-        will errback with UnrecoverableFileError.
10574-        """
10575-
10576-    def get_size_of_best_version():
10577-        """Find the size of the version that would be downloaded with
10578-        download_best_version(), without actually downloading the whole file.
10579+        available, then this will be the most recent version that has
10580+        been uploaded.
10581 
10582hunk ./src/allmydata/interfaces.py 924
10583-        I return a Deferred that fires with an integer.
10584+        If no version is recoverable, the Deferred will errback with an
10585+        UnrecoverableFileError.
10586         """
10587 
10588     def overwrite(new_contents):
10589hunk ./src/allmydata/interfaces.py 964
10590         errback.
10591         """
10592 
10593-
10594     def get_servermap(mode):
10595         """Return a Deferred that fires with an IMutableFileServerMap
10596         instance, updated using the given mode.
10597hunk ./src/allmydata/test/test_filenode.py 98
10598         def _check_segment(res):
10599             self.failUnlessEqual(res, DATA[1:1+5])
10600         d.addCallback(_check_segment)
10601+        d.addCallback(lambda ignored:
10602+            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
10603+        d.addCallback(lambda ignored:
10604+            fn1.get_size_of_best_version())
10605+        d.addCallback(lambda size:
10606+            self.failUnlessEqual(size, len(DATA)))
10607+        d.addCallback(lambda ignored:
10608+            fn1.download_to_data())
10609+        d.addCallback(lambda data:
10610+            self.failUnlessEqual(data, DATA))
10611+        d.addCallback(lambda ignored:
10612+            fn1.download_best_version())
10613+        d.addCallback(lambda data:
10614+            self.failUnlessEqual(data, DATA))
10615 
10616         return d
10617 
10618hunk ./src/allmydata/test/test_immutable.py 153
10619         return d
10620 
10621 
10622+    def test_download_to_data(self):
10623+        d = self.n.download_to_data()
10624+        d.addCallback(lambda data:
10625+            self.failUnlessEqual(data, common.TEST_DATA))
10626+        return d
10627+
10628+
10629+    def test_download_best_version(self):
10630+        d = self.n.download_best_version()
10631+        d.addCallback(lambda data:
10632+            self.failUnlessEqual(data, common.TEST_DATA))
10633+        return d
10634+
10635+
10636+    def test_get_best_readable_version(self):
10637+        n = self.n.get_best_readable_version()
10638+        self.failUnlessEqual(n, self.n)
10639+
10640+    def test_get_size_of_best_version(self):
10641+        d = self.n.get_size_of_best_version()
10642+        d.addCallback(lambda size:
10643+            self.failUnlessEqual(size, len(common.TEST_DATA)))
10644+        return d
10645+
10646+
10647 # XXX extend these tests to show bad behavior of various kinds from servers: raising exception from each remove_foo() method, for example
10648 
10649 # XXX test disconnect DeadReferenceError from get_buckets and get_block_whatsit
10650}
10651[frontends/sftpd.py: alter a mutable file overwrite to work with the new API
10652Kevan Carstensen <kevan@isnotajoke.com>**20100717014446
10653 Ignore-this: 2c57bbc8d9ab97a0e9af0634c00efc86
10654] {
10655hunk ./src/allmydata/frontends/sftpd.py 33
10656 from allmydata.interfaces import IFileNode, IDirectoryNode, ExistingChildError, \
10657      NoSuchChildError, ChildOfWrongTypeError
10658 from allmydata.mutable.common import NotWriteableError
10659+from allmydata.mutable.publish import MutableFileHandle
10660 from allmydata.immutable.upload import FileHandle
10661 from allmydata.dirnode import update_metadata
10662 from allmydata.util.fileutil import EncryptedTemporaryFile
10663merger 0.0 (
10664hunk ./src/allmydata/frontends/sftpd.py 664
10665-            # TODO: use download interface described in #993 when implemented.
10666hunk ./src/allmydata/frontends/sftpd.py 664
10667-            # TODO: use download interface described in #993 when implemented.
10668-            if filenode.is_mutable():
10669-                self.async.addCallback(lambda ign: filenode.download_best_version())
10670-                def _downloaded(data):
10671-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
10672-                    self.consumer.write(data)
10673-                    self.consumer.finish()
10674-                    return None
10675-                self.async.addCallback(_downloaded)
10676-            else:
10677-                download_size = filenode.get_size()
10678-                assert download_size is not None, "download_size is None"
10679+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
10680+
10681+            def _read(version):
10682+                download_size = version.get_size()
10683+                assert download_size is not None
10684+
10685)
10686hunk ./src/allmydata/frontends/sftpd.py 677
10687                 download_size = filenode.get_size()
10688                 assert download_size is not None, "download_size is None"
10689                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
10690-                def _read(ign):
10691-                    if noisy: self.log("_read immutable", level=NOISY)
10692-                    filenode.read(self.consumer, 0, None)
10693-                self.async.addCallback(_read)
10694+
10695+                if noisy: self.log("_read", level=NOISY)
10696+                version.read(self.consumer, 0, None)
10697+            self.async.addCallback(_read)
10698 
10699         eventually(self.async.callback, None)
10700 
10701hunk ./src/allmydata/frontends/sftpd.py 824
10702                     assert parent and childname, (parent, childname, self.metadata)
10703                     d2.addCallback(lambda ign: parent.set_metadata_for(childname, self.metadata))
10704 
10705-                d2.addCallback(lambda ign: self.consumer.get_current_size())
10706-                d2.addCallback(lambda size: self.consumer.read(0, size))
10707-                d2.addCallback(lambda new_contents: self.filenode.overwrite(new_contents))
10708+                d2.addCallback(lambda ign: self.filenode.overwrite(MutableFileHandle(self.consumer.get_file())))
10709             else:
10710                 def _add_file(ign):
10711                     self.log("_add_file childname=%r" % (childname,), level=OPERATIONAL)
10712}
10713[mutable/filenode.py: implement most of IVersion, per #993
10714Kevan Carstensen <kevan@isnotajoke.com>**20100717014516
10715 Ignore-this: d4551142b32ea97040ce0e98a394fde5
10716] {
10717hunk ./src/allmydata/mutable/filenode.py 8
10718 from twisted.internet import defer, reactor
10719 from foolscap.api import eventually
10720 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
10721-                                 NotEnoughSharesError, \
10722-                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
10723-from allmydata.util import hashutil, log
10724+     NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
10725+     IMutableFileVersion
10726+from allmydata.util import hashutil, log, consumer
10727 from allmydata.util.assertutil import precondition
10728 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
10729 from allmydata.monitor import Monitor
10730hunk ./src/allmydata/mutable/filenode.py 17
10731 from pycryptopp.cipher.aes import AES
10732 
10733 from allmydata.mutable.publish import Publish, MutableFileHandle, \
10734-                                      MutableDataHandle
10735+                                      MutableData
10736 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
10737      ResponseCache, UncoordinatedWriteError
10738 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
10739hunk ./src/allmydata/mutable/filenode.py 134
10740 
10741     def _get_initial_contents(self, contents):
10742         if contents is None:
10743-            return MutableDataHandle("")
10744+            return MutableData("")
10745 
10746         if IMutableUploadable.providedBy(contents):
10747             return contents
10748hunk ./src/allmydata/mutable/filenode.py 208
10749 
10750     def get_size(self):
10751         return self._most_recent_size
10752+
10753     def get_current_size(self):
10754         d = self.get_size_of_best_version()
10755         d.addCallback(self._stash_size)
10756hunk ./src/allmydata/mutable/filenode.py 213
10757         return d
10758+
10759     def _stash_size(self, size):
10760         self._most_recent_size = size
10761         return size
10762hunk ./src/allmydata/mutable/filenode.py 272
10763             return cmp(self.__class__, them.__class__)
10764         return cmp(self._uri, them._uri)
10765 
10766-    def _do_serialized(self, cb, *args, **kwargs):
10767-        # note: to avoid deadlock, this callable is *not* allowed to invoke
10768-        # other serialized methods within this (or any other)
10769-        # MutableFileNode. The callable should be a bound method of this same
10770-        # MFN instance.
10771-        d = defer.Deferred()
10772-        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
10773-        # we need to put off d.callback until this Deferred is finished being
10774-        # processed. Otherwise the caller's subsequent activities (like,
10775-        # doing other things with this node) can cause reentrancy problems in
10776-        # the Deferred code itself
10777-        self._serializer.addBoth(lambda res: eventually(d.callback, res))
10778-        # add a log.err just in case something really weird happens, because
10779-        # self._serializer stays around forever, therefore we won't see the
10780-        # usual Unhandled Error in Deferred that would give us a hint.
10781-        self._serializer.addErrback(log.err)
10782-        return d
10783 
10784     #################################
10785     # ICheckable
10786hunk ./src/allmydata/mutable/filenode.py 297
10787 
10788 
10789     #################################
10790-    # IMutableFileNode
10791+    # IFileNode
10792+
10793+    def get_best_readable_version(self):
10794+        """
10795+        I return a Deferred that fires with a MutableFileVersion
10796+        representing the best readable version of the file that I
10797+        represent
10798+        """
10799+        return self.get_readable_version()
10800+
10801+
10802+    def get_readable_version(self, servermap=None, version=None):
10803+        """
10804+        I return a Deferred that fires with an MutableFileVersion for my
10805+        version argument, if there is a recoverable file of that version
10806+        on the grid. If there is no recoverable version, I fire with an
10807+        UnrecoverableFileError.
10808+
10809+        If a servermap is provided, I look in there for the requested
10810+        version. If no servermap is provided, I create and update a new
10811+        one.
10812+
10813+        If no version is provided, then I return a MutableFileVersion
10814+        representing the best recoverable version of the file.
10815+        """
10816+        d = self._get_version_from_servermap(MODE_READ, servermap, version)
10817+        def _build_version((servermap, their_version)):
10818+            assert their_version in servermap.recoverable_versions()
10819+            assert their_version in servermap.make_versionmap()
10820+
10821+            mfv = MutableFileVersion(self,
10822+                                     servermap,
10823+                                     their_version,
10824+                                     self._storage_index,
10825+                                     self._storage_broker,
10826+                                     self._readkey,
10827+                                     history=self._history)
10828+            assert mfv.is_readonly()
10829+            # our caller can use this to download the contents of the
10830+            # mutable file.
10831+            return mfv
10832+        return d.addCallback(_build_version)
10833+
10834+
10835+    def _get_version_from_servermap(self,
10836+                                    mode,
10837+                                    servermap=None,
10838+                                    version=None):
10839+        """
10840+        I return a Deferred that fires with (servermap, version).
10841+
10842+        This function performs validation and a servermap update. If it
10843+        returns (servermap, version), the caller can assume that:
10844+            - servermap was last updated in mode.
10845+            - version is recoverable, and corresponds to the servermap.
10846+
10847+        If version and servermap are provided to me, I will validate
10848+        that version exists in the servermap, and that the servermap was
10849+        updated correctly.
10850+
10851+        If version is not provided, but servermap is, I will validate
10852+        the servermap and return the best recoverable version that I can
10853+        find in the servermap.
10854+
10855+        If the version is provided but the servermap isn't, I will
10856+        obtain a servermap that has been updated in the correct mode and
10857+        validate that version is found and recoverable.
10858+
10859+        If neither servermap nor version are provided, I will obtain a
10860+        servermap updated in the correct mode, and return the best
10861+        recoverable version that I can find in there.
10862+        """
10863+        # XXX: wording ^^^^
10864+        if servermap and servermap.last_update_mode == mode:
10865+            d = defer.succeed(servermap)
10866+        else:
10867+            d = self._get_servermap(mode)
10868+
10869+        def _get_version(servermap, version):
10870+            if version and version not in servermap.recoverable_versions():
10871+                version = None
10872+            else:
10873+                version = servermap.best_recoverable_version()
10874+            if not version:
10875+                raise UnrecoverableFileError("no recoverable versions")
10876+            return (servermap, version)
10877+        return d.addCallback(_get_version, version)
10878+
10879 
10880     def download_best_version(self):
10881hunk ./src/allmydata/mutable/filenode.py 387
10882+        """
10883+        I return a Deferred that fires with the contents of the best
10884+        version of this mutable file.
10885+        """
10886         return self._do_serialized(self._download_best_version)
10887hunk ./src/allmydata/mutable/filenode.py 392
10888+
10889+
10890     def _download_best_version(self):
10891hunk ./src/allmydata/mutable/filenode.py 395
10892-        servermap = ServerMap()
10893-        d = self._try_once_to_download_best_version(servermap, MODE_READ)
10894-        def _maybe_retry(f):
10895-            f.trap(NotEnoughSharesError)
10896-            # the download is worth retrying once. Make sure to use the
10897-            # old servermap, since it is what remembers the bad shares,
10898-            # but use MODE_WRITE to make it look for even more shares.
10899-            # TODO: consider allowing this to retry multiple times.. this
10900-            # approach will let us tolerate about 8 bad shares, I think.
10901-            return self._try_once_to_download_best_version(servermap,
10902-                                                           MODE_WRITE)
10903+        """
10904+        I am the serialized sibling of download_best_version.
10905+        """
10906+        d = self.get_best_readable_version()
10907+        d.addCallback(self._record_size)
10908+        d.addCallback(lambda version: version.download_to_data())
10909+
10910+        # It is possible that the download will fail because there
10911+        # aren't enough shares to be had. If so, we will try again after
10912+        # updating the servermap in MODE_WRITE, which may find more
10913+        # shares than updating in MODE_READ, as we just did. We can do
10914+        # this by getting the best mutable version and downloading from
10915+        # that -- the best mutable version will be a MutableFileVersion
10916+        # with a servermap that was last updated in MODE_WRITE, as we
10917+        # want. If this fails, then we give up.
10918+        def _maybe_retry(failure):
10919+            failure.trap(NotEnoughSharesError)
10920+
10921+            d = self.get_best_mutable_version()
10922+            d.addCallback(self._record_size)
10923+            d.addCallback(lambda version: version.download_to_data())
10924+            return d
10925+
10926         d.addErrback(_maybe_retry)
10927         return d
10928hunk ./src/allmydata/mutable/filenode.py 420
10929-    def _try_once_to_download_best_version(self, servermap, mode):
10930-        d = self._update_servermap(servermap, mode)
10931-        d.addCallback(self._once_updated_download_best_version, servermap)
10932-        return d
10933-    def _once_updated_download_best_version(self, ignored, servermap):
10934-        goal = servermap.best_recoverable_version()
10935-        if not goal:
10936-            raise UnrecoverableFileError("no recoverable versions")
10937-        return self._try_once_to_download_version(servermap, goal)
10938+
10939+
10940+    def _record_size(self, mfv):
10941+        """
10942+        I record the size of a mutable file version.
10943+        """
10944+        self._most_recent_size = mfv.get_size()
10945+        return mfv
10946+
10947 
10948     def get_size_of_best_version(self):
10949hunk ./src/allmydata/mutable/filenode.py 431
10950-        d = self.get_servermap(MODE_READ)
10951-        def _got_servermap(smap):
10952-            ver = smap.best_recoverable_version()
10953-            if not ver:
10954-                raise UnrecoverableFileError("no recoverable version")
10955-            return smap.size_of_version(ver)
10956-        d.addCallback(_got_servermap)
10957-        return d
10958+        """
10959+        I return the size of the best version of this mutable file.
10960+
10961+        This is equivalent to calling get_size() on the result of
10962+        get_best_readable_version().
10963+        """
10964+        d = self.get_best_readable_version()
10965+        return d.addCallback(lambda mfv: mfv.get_size())
10966+
10967+
10968+    #################################
10969+    # IMutableFileNode
10970+
10971+    def get_best_mutable_version(self, servermap=None):
10972+        """
10973+        I return a Deferred that fires with a MutableFileVersion
10974+        representing the best readable version of the file that I
10975+        represent. I am like get_best_readable_version, except that I
10976+        will try to make a writable version if I can.
10977+        """
10978+        return self.get_mutable_version(servermap=servermap)
10979+
10980+
10981+    def get_mutable_version(self, servermap=None, version=None):
10982+        """
10983+        I return a version of this mutable file. I return a Deferred
10984+        that fires with a MutableFileVersion
10985+
10986+        If version is provided, the Deferred will fire with a
10987+        MutableFileVersion initailized with that version. Otherwise, it
10988+        will fire with the best version that I can recover.
10989+
10990+        If servermap is provided, I will use that to find versions
10991+        instead of performing my own servermap update.
10992+        """
10993+        if self.is_readonly():
10994+            return self.get_readable_version(servermap=servermap,
10995+                                             version=version)
10996+
10997+        # get_mutable_version => write intent, so we require that the
10998+        # servermap is updated in MODE_WRITE
10999+        d = self._get_version_from_servermap(MODE_WRITE, servermap, version)
11000+        def _build_version((servermap, smap_version)):
11001+            # these should have been set by the servermap update.
11002+            assert self._secret_holder
11003+            assert self._writekey
11004+
11005+            mfv = MutableFileVersion(self,
11006+                                     servermap,
11007+                                     smap_version,
11008+                                     self._storage_index,
11009+                                     self._storage_broker,
11010+                                     self._readkey,
11011+                                     self._writekey,
11012+                                     self._secret_holder,
11013+                                     history=self._history)
11014+            assert not mfv.is_readonly()
11015+            return mfv
11016+
11017+        return d.addCallback(_build_version)
11018+
11019+
11020+    # XXX: I'm uncomfortable with the difference between upload and
11021+    #      overwrite, which, FWICT, is basically that you don't have to
11022+    #      do a servermap update before you overwrite. We split them up
11023+    #      that way anyway, so I guess there's no real difficulty in
11024+    #      offering both ways to callers, but it also makes the
11025+    #      public-facing API cluttery, and makes it hard to discern the
11026+    #      right way of doing things.
11027 
11028hunk ./src/allmydata/mutable/filenode.py 501
11029+    # In general, we leave it to callers to ensure that they aren't
11030+    # going to cause UncoordinatedWriteErrors when working with
11031+    # MutableFileVersions. We know that the next three operations
11032+    # (upload, overwrite, and modify) will all operate on the same
11033+    # version, so we say that only one of them can be going on at once,
11034+    # and serialize them to ensure that that actually happens, since as
11035+    # the caller in this situation it is our job to do that.
11036     def overwrite(self, new_contents):
11037hunk ./src/allmydata/mutable/filenode.py 509
11038+        """
11039+        I overwrite the contents of the best recoverable version of this
11040+        mutable file with new_contents. This is equivalent to calling
11041+        overwrite on the result of get_best_mutable_version with
11042+        new_contents as an argument. I return a Deferred that eventually
11043+        fires with the results of my replacement process.
11044+        """
11045         return self._do_serialized(self._overwrite, new_contents)
11046hunk ./src/allmydata/mutable/filenode.py 517
11047+
11048+
11049     def _overwrite(self, new_contents):
11050hunk ./src/allmydata/mutable/filenode.py 520
11051-        assert IMutableUploadable.providedBy(new_contents)
11052+        """
11053+        I am the serialized sibling of overwrite.
11054+        """
11055+        d = self.get_best_mutable_version()
11056+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
11057+
11058+
11059+
11060+    def upload(self, new_contents, servermap):
11061+        """
11062+        I overwrite the contents of the best recoverable version of this
11063+        mutable file with new_contents, using servermap instead of
11064+        creating/updating our own servermap. I return a Deferred that
11065+        fires with the results of my upload.
11066+        """
11067+        return self._do_serialized(self._upload, new_contents, servermap)
11068+
11069+
11070+    def _upload(self, new_contents, servermap):
11071+        """
11072+        I am the serialized sibling of upload.
11073+        """
11074+        d = self.get_best_mutable_version(servermap)
11075+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
11076+
11077+
11078+    def modify(self, modifier, backoffer=None):
11079+        """
11080+        I modify the contents of the best recoverable version of this
11081+        mutable file with the modifier. This is equivalent to calling
11082+        modify on the result of get_best_mutable_version. I return a
11083+        Deferred that eventually fires with an UploadResults instance
11084+        describing this process.
11085+        """
11086+        return self._do_serialized(self._modify, modifier, backoffer)
11087+
11088+
11089+    def _modify(self, modifier, backoffer):
11090+        """
11091+        I am the serialized sibling of modify.
11092+        """
11093+        d = self.get_best_mutable_version()
11094+        return d.addCallback(lambda mfv: mfv.modify(modifier, backoffer))
11095+
11096+
11097+    def download_version(self, servermap, version, fetch_privkey=False):
11098+        """
11099+        Download the specified version of this mutable file. I return a
11100+        Deferred that fires with the contents of the specified version
11101+        as a bytestring, or errbacks if the file is not recoverable.
11102+        """
11103+        d = self.get_readable_version(servermap, version)
11104+        return d.addCallback(lambda mfv: mfv.download_to_data(fetch_privkey))
11105+
11106+
11107+    def get_servermap(self, mode):
11108+        """
11109+        I return a servermap that has been updated in mode.
11110+
11111+        mode should be one of MODE_READ, MODE_WRITE, MODE_CHECK or
11112+        MODE_ANYTHING. See servermap.py for more on what these mean.
11113+        """
11114+        return self._do_serialized(self._get_servermap, mode)
11115+
11116 
11117hunk ./src/allmydata/mutable/filenode.py 585
11118+    def _get_servermap(self, mode):
11119+        """
11120+        I am a serialized twin to get_servermap.
11121+        """
11122         servermap = ServerMap()
11123hunk ./src/allmydata/mutable/filenode.py 590
11124-        d = self._update_servermap(servermap, mode=MODE_WRITE)
11125-        d.addCallback(lambda ignored: self._upload(new_contents, servermap))
11126+        return self._update_servermap(servermap, mode)
11127+
11128+
11129+    def _update_servermap(self, servermap, mode):
11130+        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11131+                             mode)
11132+        if self._history:
11133+            self._history.notify_mapupdate(u.get_status())
11134+        return u.update()
11135+
11136+
11137+    def set_version(self, version):
11138+        # I can be set in two ways:
11139+        #  1. When the node is created.
11140+        #  2. (for an existing share) when the Servermap is updated
11141+        #     before I am read.
11142+        assert version in (MDMF_VERSION, SDMF_VERSION)
11143+        self._protocol_version = version
11144+
11145+
11146+    def get_version(self):
11147+        return self._protocol_version
11148+
11149+
11150+    def _do_serialized(self, cb, *args, **kwargs):
11151+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11152+        # other serialized methods within this (or any other)
11153+        # MutableFileNode. The callable should be a bound method of this same
11154+        # MFN instance.
11155+        d = defer.Deferred()
11156+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11157+        # we need to put off d.callback until this Deferred is finished being
11158+        # processed. Otherwise the caller's subsequent activities (like,
11159+        # doing other things with this node) can cause reentrancy problems in
11160+        # the Deferred code itself
11161+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11162+        # add a log.err just in case something really weird happens, because
11163+        # self._serializer stays around forever, therefore we won't see the
11164+        # usual Unhandled Error in Deferred that would give us a hint.
11165+        self._serializer.addErrback(log.err)
11166         return d
11167 
11168 
11169hunk ./src/allmydata/mutable/filenode.py 633
11170+    def _upload(self, new_contents, servermap):
11171+        """
11172+        A MutableFileNode still has to have some way of getting
11173+        published initially, which is what I am here for. After that,
11174+        all publishing, updating, modifying and so on happens through
11175+        MutableFileVersions.
11176+        """
11177+        assert self._pubkey, "update_servermap must be called before publish"
11178+
11179+        p = Publish(self, self._storage_broker, servermap)
11180+        if self._history:
11181+            self._history.notify_publish(p.get_status(),
11182+                                         new_contents.get_size())
11183+        d = p.publish(new_contents)
11184+        d.addCallback(self._did_upload, new_contents.get_size())
11185+        return d
11186+
11187+
11188+    def _did_upload(self, res, size):
11189+        self._most_recent_size = size
11190+        return res
11191+
11192+
11193+class MutableFileVersion:
11194+    """
11195+    I represent a specific version (most likely the best version) of a
11196+    mutable file.
11197+
11198+    Since I implement IReadable, instances which hold a
11199+    reference to an instance of me are guaranteed the ability (absent
11200+    connection difficulties or unrecoverable versions) to read the file
11201+    that I represent. Depending on whether I was initialized with a
11202+    write capability or not, I may also provide callers the ability to
11203+    overwrite or modify the contents of the mutable file that I
11204+    reference.
11205+    """
11206+    implements(IMutableFileVersion)
11207+
11208+    def __init__(self,
11209+                 node,
11210+                 servermap,
11211+                 version,
11212+                 storage_index,
11213+                 storage_broker,
11214+                 readcap,
11215+                 writekey=None,
11216+                 write_secrets=None,
11217+                 history=None):
11218+
11219+        self._node = node
11220+        self._servermap = servermap
11221+        self._version = version
11222+        self._storage_index = storage_index
11223+        self._write_secrets = write_secrets
11224+        self._history = history
11225+        self._storage_broker = storage_broker
11226+
11227+        #assert isinstance(readcap, IURI)
11228+        self._readcap = readcap
11229+
11230+        self._writekey = writekey
11231+        self._serializer = defer.succeed(None)
11232+        self._size = None
11233+
11234+
11235+    def get_sequence_number(self):
11236+        """
11237+        Get the sequence number of the mutable version that I represent.
11238+        """
11239+        return 0
11240+
11241+
11242+    # TODO: Terminology?
11243+    def get_writekey(self):
11244+        """
11245+        I return a writekey or None if I don't have a writekey.
11246+        """
11247+        return self._writekey
11248+
11249+
11250+    def overwrite(self, new_contents):
11251+        """
11252+        I overwrite the contents of this mutable file version with the
11253+        data in new_contents.
11254+        """
11255+        assert not self.is_readonly()
11256+
11257+        return self._do_serialized(self._overwrite, new_contents)
11258+
11259+
11260+    def _overwrite(self, new_contents):
11261+        assert IMutableUploadable.providedBy(new_contents)
11262+        assert self._servermap.last_update_mode == MODE_WRITE
11263+
11264+        return self._upload(new_contents)
11265+
11266+
11267     def modify(self, modifier, backoffer=None):
11268         """I use a modifier callback to apply a change to the mutable file.
11269         I implement the following pseudocode::
11270hunk ./src/allmydata/mutable/filenode.py 770
11271         backoffer should not invoke any methods on this MutableFileNode
11272         instance, and it needs to be highly conscious of deadlock issues.
11273         """
11274+        assert not self.is_readonly()
11275+
11276         return self._do_serialized(self._modify, modifier, backoffer)
11277hunk ./src/allmydata/mutable/filenode.py 773
11278+
11279+
11280     def _modify(self, modifier, backoffer):
11281hunk ./src/allmydata/mutable/filenode.py 776
11282-        servermap = ServerMap()
11283         if backoffer is None:
11284             backoffer = BackoffAgent().delay
11285hunk ./src/allmydata/mutable/filenode.py 778
11286-        return self._modify_and_retry(servermap, modifier, backoffer, True)
11287-    def _modify_and_retry(self, servermap, modifier, backoffer, first_time):
11288-        d = self._modify_once(servermap, modifier, first_time)
11289+        return self._modify_and_retry(modifier, backoffer, True)
11290+
11291+
11292+    def _modify_and_retry(self, modifier, backoffer, first_time):
11293+        """
11294+        I try to apply modifier to the contents of this version of the
11295+        mutable file. If I succeed, I return an UploadResults instance
11296+        describing my success. If I fail, I try again after waiting for
11297+        a little bit.
11298+        """
11299+        log.msg("doing modify")
11300+        d = self._modify_once(modifier, first_time)
11301         def _retry(f):
11302             f.trap(UncoordinatedWriteError)
11303             d2 = defer.maybeDeferred(backoffer, self, f)
11304hunk ./src/allmydata/mutable/filenode.py 794
11305             d2.addCallback(lambda ignored:
11306-                           self._modify_and_retry(servermap, modifier,
11307+                           self._modify_and_retry(modifier,
11308                                                   backoffer, False))
11309             return d2
11310         d.addErrback(_retry)
11311hunk ./src/allmydata/mutable/filenode.py 799
11312         return d
11313-    def _modify_once(self, servermap, modifier, first_time):
11314-        d = self._update_servermap(servermap, MODE_WRITE)
11315-        d.addCallback(self._once_updated_download_best_version, servermap)
11316+
11317+
11318+    def _modify_once(self, modifier, first_time):
11319+        """
11320+        I attempt to apply a modifier to the contents of the mutable
11321+        file.
11322+        """
11323+        assert self._servermap.last_update_mode == MODE_WRITE
11324+
11325+        # download_to_data is serialized, so we have to call this to
11326+        # avoid deadlock.
11327+        d = self._try_to_download_data()
11328         def _apply(old_contents):
11329hunk ./src/allmydata/mutable/filenode.py 812
11330-            new_contents = modifier(old_contents, servermap, first_time)
11331+            new_contents = modifier(old_contents, self._servermap, first_time)
11332             if new_contents is None or new_contents == old_contents:
11333hunk ./src/allmydata/mutable/filenode.py 814
11334+                log.msg("no changes")
11335                 # no changes need to be made
11336                 if first_time:
11337                     return
11338hunk ./src/allmydata/mutable/filenode.py 822
11339                 # recovery when it observes UCWE, we need to do a second
11340                 # publish. See #551 for details. We'll basically loop until
11341                 # we managed an uncontested publish.
11342-                old_uploadable = MutableDataHandle(old_contents)
11343+                old_uploadable = MutableData(old_contents)
11344                 new_contents = old_uploadable
11345             precondition((IMutableUploadable.providedBy(new_contents) or
11346                           new_contents is None),
11347hunk ./src/allmydata/mutable/filenode.py 828
11348                          "Modifier function must return an IMutableUploadable "
11349                          "or None")
11350-            return self._upload(new_contents, servermap)
11351+            return self._upload(new_contents)
11352         d.addCallback(_apply)
11353         return d
11354 
11355hunk ./src/allmydata/mutable/filenode.py 832
11356-    def get_servermap(self, mode):
11357-        return self._do_serialized(self._get_servermap, mode)
11358-    def _get_servermap(self, mode):
11359-        servermap = ServerMap()
11360-        return self._update_servermap(servermap, mode)
11361-    def _update_servermap(self, servermap, mode):
11362-        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11363-                             mode)
11364-        if self._history:
11365-            self._history.notify_mapupdate(u.get_status())
11366-        return u.update()
11367 
11368hunk ./src/allmydata/mutable/filenode.py 833
11369-    def download_version(self, servermap, version, fetch_privkey=False):
11370-        return self._do_serialized(self._try_once_to_download_version,
11371-                                   servermap, version, fetch_privkey)
11372-    def _try_once_to_download_version(self, servermap, version,
11373-                                      fetch_privkey=False):
11374-        r = Retrieve(self, servermap, version, fetch_privkey)
11375+    def is_readonly(self):
11376+        """
11377+        I return True if this MutableFileVersion provides no write
11378+        access to the file that it encapsulates, and False if it
11379+        provides the ability to modify the file.
11380+        """
11381+        return self._writekey is None
11382+
11383+
11384+    def is_mutable(self):
11385+        """
11386+        I return True, since mutable files are always mutable by
11387+        somebody.
11388+        """
11389+        return True
11390+
11391+
11392+    def get_storage_index(self):
11393+        """
11394+        I return the storage index of the reference that I encapsulate.
11395+        """
11396+        return self._storage_index
11397+
11398+
11399+    def get_size(self):
11400+        """
11401+        I return the length, in bytes, of this readable object.
11402+        """
11403+        return self._servermap.size_of_version(self._version)
11404+
11405+
11406+    def download_to_data(self, fetch_privkey=False):
11407+        """
11408+        I return a Deferred that fires with the contents of this
11409+        readable object as a byte string.
11410+
11411+        """
11412+        c = consumer.MemoryConsumer()
11413+        d = self.read(c, fetch_privkey=fetch_privkey)
11414+        d.addCallback(lambda mc: "".join(mc.chunks))
11415+        return d
11416+
11417+
11418+    def _try_to_download_data(self):
11419+        """
11420+        I am an unserialized cousin of download_to_data; I am called
11421+        from the children of modify() to download the data associated
11422+        with this mutable version.
11423+        """
11424+        c = consumer.MemoryConsumer()
11425+        # modify will almost certainly write, so we need the privkey.
11426+        d = self._read(c, fetch_privkey=True)
11427+        d.addCallback(lambda mc: "".join(mc.chunks))
11428+        return d
11429+
11430+
11431+    def _update_servermap(self, mode=MODE_READ):
11432+        """
11433+        I update our Servermap according to my mode argument. I return a
11434+        Deferred that fires with None when this has finished. The
11435+        updated Servermap will be at self._servermap in that case.
11436+        """
11437+        d = self._node.get_servermap(mode)
11438+
11439+        def _got_servermap(servermap):
11440+            assert servermap.last_update_mode == mode
11441+
11442+            self._servermap = servermap
11443+        d.addCallback(_got_servermap)
11444+        return d
11445+
11446+
11447+    def read(self, consumer, offset=0, size=None, fetch_privkey=False):
11448+        """
11449+        I read a portion (possibly all) of the mutable file that I
11450+        reference into consumer.
11451+        """
11452+        return self._do_serialized(self._read, consumer, offset, size,
11453+                                   fetch_privkey)
11454+
11455+
11456+    def _read(self, consumer, offset=0, size=None, fetch_privkey=False):
11457+        """
11458+        I am the serialized companion of read.
11459+        """
11460+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
11461         if self._history:
11462             self._history.notify_retrieve(r.get_status())
11463hunk ./src/allmydata/mutable/filenode.py 921
11464-        d = r.download()
11465-        d.addCallback(self._downloaded_version)
11466+        d = r.download(consumer, offset, size)
11467+        return d
11468+
11469+
11470+    def _do_serialized(self, cb, *args, **kwargs):
11471+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11472+        # other serialized methods within this (or any other)
11473+        # MutableFileNode. The callable should be a bound method of this same
11474+        # MFN instance.
11475+        d = defer.Deferred()
11476+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11477+        # we need to put off d.callback until this Deferred is finished being
11478+        # processed. Otherwise the caller's subsequent activities (like,
11479+        # doing other things with this node) can cause reentrancy problems in
11480+        # the Deferred code itself
11481+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11482+        # add a log.err just in case something really weird happens, because
11483+        # self._serializer stays around forever, therefore we won't see the
11484+        # usual Unhandled Error in Deferred that would give us a hint.
11485+        self._serializer.addErrback(log.err)
11486         return d
11487hunk ./src/allmydata/mutable/filenode.py 942
11488-    def _downloaded_version(self, data):
11489-        self._most_recent_size = len(data)
11490-        return data
11491 
11492hunk ./src/allmydata/mutable/filenode.py 943
11493-    def upload(self, new_contents, servermap):
11494-        return self._do_serialized(self._upload, new_contents, servermap)
11495-    def _upload(self, new_contents, servermap):
11496-        assert self._pubkey, "update_servermap must be called before publish"
11497-        assert IMutableUploadable.providedBy(new_contents)
11498 
11499hunk ./src/allmydata/mutable/filenode.py 944
11500-        p = Publish(self, self._storage_broker, servermap)
11501+    def _upload(self, new_contents):
11502+        #assert self._pubkey, "update_servermap must be called before publish"
11503+        p = Publish(self._node, self._storage_broker, self._servermap)
11504         if self._history:
11505hunk ./src/allmydata/mutable/filenode.py 948
11506-            self._history.notify_publish(p.get_status(), new_contents.get_size())
11507+            self._history.notify_publish(p.get_status(),
11508+                                         new_contents.get_size())
11509         d = p.publish(new_contents)
11510         d.addCallback(self._did_upload, new_contents.get_size())
11511         return d
11512hunk ./src/allmydata/mutable/filenode.py 953
11513-    def _did_upload(self, res, size):
11514-        self._most_recent_size = size
11515-        return res
11516-
11517-
11518-    def set_version(self, version):
11519-        # I can be set in two ways:
11520-        #  1. When the node is created.
11521-        #  2. (for an existing share) when the Servermap is updated
11522-        #     before I am read.
11523-        assert version in (MDMF_VERSION, SDMF_VERSION)
11524-        self._protocol_version = version
11525 
11526 
11527hunk ./src/allmydata/mutable/filenode.py 955
11528-    def get_version(self):
11529-        return self._protocol_version
11530+    def _did_upload(self, res, size):
11531+        self._size = size
11532+        return res
11533}
11534[mutable/publish.py: enable segmented uploading for big files, change constants and wording, change MutableDataHandle to MutableData
11535Kevan Carstensen <kevan@isnotajoke.com>**20100717014549
11536 Ignore-this: f736c60c90ff09c98544af17146cf654
11537] {
11538hunk ./src/allmydata/mutable/publish.py 145
11539 
11540         self.data = newdata
11541         self.datalength = newdata.get_size()
11542+        if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
11543+            self._version = MDMF_VERSION
11544+        else:
11545+            self._version = SDMF_VERSION
11546 
11547         self.log("starting publish, datalen is %s" % self.datalength)
11548         self._status.set_size(self.datalength)
11549hunk ./src/allmydata/mutable/publish.py 1007
11550             old_position = self._filehandle.tell()
11551             # Seek to the end of the file by seeking 0 bytes from the
11552             # file's end
11553-            self._filehandle.seek(0, os.SEEK_END)
11554+            self._filehandle.seek(0, 2) # 2 == os.SEEK_END in 2.5+
11555             self._size = self._filehandle.tell()
11556             # Restore the previous position, in case this was called
11557             # after a read.
11558hunk ./src/allmydata/mutable/publish.py 1022
11559         """
11560         I return some data (up to length bytes) from my filehandle.
11561 
11562-        In most cases, I return length bytes. If I don't, it is because
11563-        length is longer than the distance between my current position
11564-        in the file that I represent and its end. In that case, I return
11565-        as many bytes as I can before going over the EOF.
11566+        In most cases, I return length bytes, but sometimes I won't --
11567+        for example, if I am asked to read beyond the end of a file, or
11568+        an error occurs.
11569         """
11570         return [self._filehandle.read(length)]
11571 
11572hunk ./src/allmydata/mutable/publish.py 1037
11573         self._filehandle.close()
11574 
11575 
11576-class MutableDataHandle(MutableFileHandle):
11577+class MutableData(MutableFileHandle):
11578     """
11579     I am a mutable uploadable built around a string, which I then cast
11580     into a StringIO and treat as a filehandle.
11581}
11582[immutable/filenode.py: fix broken implementation of #993 interfaces
11583Kevan Carstensen <kevan@isnotajoke.com>**20100717015049
11584 Ignore-this: 19ac8cf5d31ac88d4a1998ac342db004
11585] {
11586hunk ./src/allmydata/immutable/filenode.py 326
11587         immutable files can have only one version, we just return the
11588         current filenode.
11589         """
11590-        return self
11591+        return defer.succeed(self)
11592 
11593 
11594     def download_best_version(self):
11595hunk ./src/allmydata/immutable/filenode.py 412
11596 
11597     # IReadable, IFileNode, IFilesystemNode
11598     def get_best_readable_version(self):
11599-        return self
11600+        return defer.succeed(self)
11601 
11602 
11603     def download_best_version(self):
11604}
11605[mutable/retrieve.py: alter Retrieve so that it can download parts of mutable files
11606Kevan Carstensen <kevan@isnotajoke.com>**20100717015123
11607 Ignore-this: f49a6d3c05afc784aff0a4c63964a3e5
11608] {
11609hunk ./src/allmydata/mutable/retrieve.py 7
11610 from zope.interface import implements
11611 from twisted.internet import defer
11612 from twisted.python import failure
11613+from twisted.internet.interfaces import IPushProducer, IConsumer
11614 from foolscap.api import DeadReferenceError, eventually, fireEventually
11615 from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
11616                                  MDMF_VERSION, SDMF_VERSION
11617hunk ./src/allmydata/mutable/retrieve.py 86
11618     # times, and each will have a separate response chain. However the
11619     # Retrieve object will remain tied to a specific version of the file, and
11620     # will use a single ServerMap instance.
11621+    implements(IPushProducer)
11622 
11623     def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
11624                  verify=False):
11625hunk ./src/allmydata/mutable/retrieve.py 129
11626         # 3. When we are validating readers, we need to validate the
11627         #    signature on the prefix. Do we? We already do this in the
11628         #    servermap update?
11629-        #
11630-        # (just work on 1 and 2 for now, I guess)
11631         self._verify = False
11632         if verify:
11633             self._verify = True
11634hunk ./src/allmydata/mutable/retrieve.py 143
11635         self._status.set_size(datalength)
11636         self._status.set_encoding(k, N)
11637         self.readers = {}
11638+        self._paused = False
11639+        self._paused_deferred = None
11640+
11641 
11642     def get_status(self):
11643         return self._status
11644hunk ./src/allmydata/mutable/retrieve.py 157
11645             kwargs["facility"] = "tahoe.mutable.retrieve"
11646         return log.msg(*args, **kwargs)
11647 
11648-    def download(self):
11649+
11650+    ###################
11651+    # IPushProducer
11652+
11653+    def pauseProducing(self):
11654+        """
11655+        I am called by my download target if we have produced too much
11656+        data for it to handle. I make the downloader stop producing new
11657+        data until my resumeProducing method is called.
11658+        """
11659+        if self._paused:
11660+            return
11661+
11662+        # fired when the download is unpaused.
11663+        self._pause_deferred = defer.Deferred()
11664+        self._paused = True
11665+
11666+
11667+    def resumeProducing(self):
11668+        """
11669+        I am called by my download target once it is ready to begin
11670+        receiving data again.
11671+        """
11672+        if not self._paused:
11673+            return
11674+
11675+        self._paused = False
11676+        p = self._pause_deferred
11677+        self._pause_deferred = None
11678+        eventually(p.callback, None)
11679+
11680+
11681+    def _check_for_paused(self, res):
11682+        """
11683+        I am called just before a write to the consumer. I return a
11684+        Deferred that eventually fires with the data that is to be
11685+        written to the consumer. If the download has not been paused,
11686+        the Deferred fires immediately. Otherwise, the Deferred fires
11687+        when the downloader is unpaused.
11688+        """
11689+        if self._paused:
11690+            d = defer.Deferred()
11691+            self._pause_defered.addCallback(lambda ignored: d.callback(res))
11692+            return d
11693+        return defer.succeed(res)
11694+
11695+
11696+    def download(self, consumer=None, offset=0, size=None):
11697+        assert IConsumer.providedBy(consumer) or self._verify
11698+
11699+        if consumer:
11700+            self._consumer = consumer
11701+            # we provide IPushProducer, so streaming=True, per
11702+            # IConsumer.
11703+            self._consumer.registerProducer(self, streaming=True)
11704+
11705         self._done_deferred = defer.Deferred()
11706         self._started = time.time()
11707         self._status.set_status("Retrieving Shares")
11708hunk ./src/allmydata/mutable/retrieve.py 217
11709 
11710+        self._offset = offset
11711+        self._read_length = size
11712+
11713         # first, which servers can we use?
11714         versionmap = self.servermap.make_versionmap()
11715         shares = versionmap[self.verinfo]
11716hunk ./src/allmydata/mutable/retrieve.py 278
11717         assert len(self.remaining_sharemap) >= k
11718 
11719         self.log("starting download")
11720+        self._paused = False
11721         self._add_active_peers()
11722         # The download process beyond this is a state machine.
11723         # _add_active_peers will select the peers that we want to use
11724hunk ./src/allmydata/mutable/retrieve.py 324
11725 
11726         self._segment_decoder = codec.CRSDecoder()
11727         self._segment_decoder.set_params(segsize, k, n)
11728-        self._current_segment = 0
11729 
11730         if  not self._tail_data_size:
11731             self._tail_data_size = segsize
11732hunk ./src/allmydata/mutable/retrieve.py 349
11733             # So we don't have to do this later.
11734             self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
11735 
11736-        # If we have more than one segment, we are an SDMF file, which
11737-        # means that we need to validate the salts as we receive them.
11738-        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
11739-        self._salt_hash_tree[0] = IV # from the prefix.
11740+        # Our last task is to tell the downloader where to start and
11741+        # where to stop. We use three parameters for that:
11742+        #   - self._start_segment: the segment that we need to start
11743+        #     downloading from.
11744+        #   - self._current_segment: the next segment that we need to
11745+        #     download.
11746+        #   - self._last_segment: The last segment that we were asked to
11747+        #     download.
11748+        #
11749+        #  We say that the download is complete when
11750+        #  self._current_segment > self._last_segment. We use
11751+        #  self._start_segment and self._last_segment to know when to
11752+        #  strip things off of segments, and how much to strip.
11753+        if self._offset:
11754+            self.log("got offset: %d" % self._offset)
11755+            # our start segment is the first segment containing the
11756+            # offset we were given.
11757+            start = mathutil.div_ceil(self._offset,
11758+                                      self._segment_size)
11759+            # this gets us the first segment after self._offset. Then
11760+            # our start segment is the one before it.
11761+            start -= 1
11762+
11763+            assert start < self._num_segments
11764+            self._start_segment = start
11765+            self.log("got start segment: %d" % self._start_segment)
11766+        else:
11767+            self._start_segment = 0
11768+
11769+
11770+        if self._read_length:
11771+            # our end segment is the last segment containing part of the
11772+            # segment that we were asked to read.
11773+            self.log("got read length %d" % self._read_length)
11774+            end_data = self._offset + self._read_length
11775+            end = mathutil.div_ceil(end_data,
11776+                                    self._segment_size)
11777+            end -= 1
11778+            assert end < self._num_segments
11779+            self._last_segment = end
11780+            self.log("got end segment: %d" % self._last_segment)
11781+        else:
11782+            self._last_segment = self._num_segments - 1
11783 
11784hunk ./src/allmydata/mutable/retrieve.py 393
11785+        self._current_segment = self._start_segment
11786 
11787     def _add_active_peers(self):
11788         """
11789hunk ./src/allmydata/mutable/retrieve.py 637
11790         that this Retrieve is currently responsible for downloading.
11791         """
11792         assert len(self._active_readers) >= self._required_shares
11793-        if self._current_segment < self._num_segments:
11794+        if self._current_segment <= self._last_segment:
11795             d = self._process_segment(self._current_segment)
11796         else:
11797             d = defer.succeed(None)
11798hunk ./src/allmydata/mutable/retrieve.py 701
11799             d.addCallback(self._decrypt_segment)
11800             d.addErrback(self._validation_or_decoding_failed,
11801                          self._active_readers)
11802+            # check to see whether we've been paused before writing
11803+            # anything.
11804+            d.addCallback(self._check_for_paused)
11805             d.addCallback(self._set_segment)
11806             return d
11807         else:
11808hunk ./src/allmydata/mutable/retrieve.py 716
11809         target that is handling the file download.
11810         """
11811         self.log("got plaintext for segment %d" % self._current_segment)
11812-        self._plaintext += segment
11813+        if self._current_segment == self._start_segment:
11814+            # We're on the first segment. It's possible that we want
11815+            # only some part of the end of this segment, and that we
11816+            # just downloaded the whole thing to get that part. If so,
11817+            # we need to account for that and give the reader just the
11818+            # data that they want.
11819+            n = self._offset % self._segment_size
11820+            self.log("stripping %d bytes off of the first segment" % n)
11821+            self.log("original segment length: %d" % len(segment))
11822+            segment = segment[n:]
11823+            self.log("new segment length: %d" % len(segment))
11824+
11825+        if self._current_segment == self._last_segment and self._read_length is not None:
11826+            # We're on the last segment. It's possible that we only want
11827+            # part of the beginning of this segment, and that we
11828+            # downloaded the whole thing anyway. Make sure to give the
11829+            # caller only the portion of the segment that they want to
11830+            # receive.
11831+            extra = self._read_length
11832+            if self._start_segment != self._last_segment:
11833+                extra -= self._segment_size - \
11834+                            (self._offset % self._segment_size)
11835+            extra %= self._segment_size
11836+            self.log("original segment length: %d" % len(segment))
11837+            segment = segment[:extra]
11838+            self.log("new segment length: %d" % len(segment))
11839+            self.log("only taking %d bytes of the last segment" % extra)
11840+
11841+        if not self._verify:
11842+            self._consumer.write(segment)
11843+        else:
11844+            # we don't care about the plaintext if we are doing a verify.
11845+            segment = None
11846         self._current_segment += 1
11847 
11848 
11849hunk ./src/allmydata/mutable/retrieve.py 848
11850                                         reader.shnum,
11851                                         "corrupt hashes: %s" % e)
11852 
11853-        # TODO: Validate the salt, too.
11854         self.log('share %d is valid for segment %d' % (reader.shnum,
11855                                                        segnum))
11856         return {reader.shnum: (block, salt)}
11857hunk ./src/allmydata/mutable/retrieve.py 1014
11858               _done_deferred to errback.
11859         """
11860         self.log("checking for doneness")
11861-        if self._current_segment == self._num_segments:
11862+        if self._current_segment > self._last_segment:
11863             # No more segments to download, we're done.
11864             self.log("got plaintext, done")
11865             return self._done()
11866hunk ./src/allmydata/mutable/retrieve.py 1043
11867             ret = list(self._bad_shares)
11868             self.log("done verifying, found %d bad shares" % len(ret))
11869         else:
11870-            ret = self._plaintext
11871+            # TODO: upload status here?
11872+            ret = self._consumer
11873+            self._consumer.unregisterProducer()
11874         eventually(self._done_deferred.callback, ret)
11875 
11876 
11877hunk ./src/allmydata/mutable/retrieve.py 1066
11878                       "encoding %(k)d-of-%(n)d")
11879             args = {"have": self._current_segment,
11880                     "total": self._num_segments,
11881+                    "need": self._last_segment,
11882                     "k": self._required_shares,
11883                     "n": self._total_shares,
11884                     "bad": len(self._bad_shares)}
11885}
11886[change MutableDataHandle to MutableData in code.
11887Kevan Carstensen <kevan@isnotajoke.com>**20100717015210
11888 Ignore-this: f85ae425eabc21b47ad60bd6bf1f7dec
11889] {
11890hunk ./src/allmydata/dirnode.py 11
11891 from allmydata.mutable.common import NotWriteableError
11892 from allmydata.mutable.filenode import MutableFileNode
11893 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
11894-from allmydata.mutable.publish import MutableDataHandle
11895+from allmydata.mutable.publish import MutableData
11896 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
11897      IImmutableFileNode, IMutableFileNode, \
11898      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
11899hunk ./src/allmydata/dirnode.py 104
11900 
11901         del children[self.name]
11902         new_contents = self.node._pack_contents(children)
11903-        uploadable = MutableDataHandle(new_contents)
11904+        uploadable = MutableData(new_contents)
11905         return uploadable
11906 
11907 
11908hunk ./src/allmydata/dirnode.py 130
11909 
11910         children[name] = (child, metadata)
11911         new_contents = self.node._pack_contents(children)
11912-        uploadable = MutableDataHandle(new_contents)
11913+        uploadable = MutableData(new_contents)
11914         return uploadable
11915 
11916 
11917hunk ./src/allmydata/dirnode.py 175
11918 
11919             children[name] = (child, metadata)
11920         new_contents = self.node._pack_contents(children)
11921-        uploadable = MutableDataHandle(new_contents)
11922+        uploadable = MutableData(new_contents)
11923         return uploadable
11924 
11925 def _encrypt_rw_uri(writekey, rw_uri):
11926hunk ./src/allmydata/mutable/repairer.py 5
11927 from zope.interface import implements
11928 from twisted.internet import defer
11929 from allmydata.interfaces import IRepairResults, ICheckResults
11930-from allmydata.mutable.publish import MutableDataHandle
11931+from allmydata.mutable.publish import MutableData
11932 
11933 class RepairResults:
11934     implements(IRepairResults)
11935hunk ./src/allmydata/mutable/repairer.py 109
11936 
11937         d = self.node.download_version(smap, best_version, fetch_privkey=True)
11938         d.addCallback(lambda data:
11939-            MutableDataHandle(data))
11940+            MutableData(data))
11941         d.addCallback(self.node.upload, smap)
11942         d.addCallback(self.get_results, smap)
11943         return d
11944hunk ./src/allmydata/nodemaker.py 9
11945 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
11946 from allmydata.immutable.upload import Data
11947 from allmydata.mutable.filenode import MutableFileNode
11948-from allmydata.mutable.publish import MutableDataHandle
11949+from allmydata.mutable.publish import MutableData
11950 from allmydata.dirnode import DirectoryNode, pack_children
11951 from allmydata.unknown import UnknownNode
11952 from allmydata import uri
11953merger 0.0 (
11954merger 0.0 (
11955hunk ./src/allmydata/nodemaker.py 107
11956-                                     pack_children(n, initial_children),
11957+                                     MutableDataHandle(
11958+                                        pack_children(n, initial_children)),
11959merger 0.0 (
11960hunk ./src/allmydata/nodemaker.py 107
11961-                                     pack_children(n, initial_children))
11962+                                     pack_children(n, initial_children),
11963+                                     version)
11964hunk ./src/allmydata/nodemaker.py 107
11965-                                     pack_children(n, initial_children))
11966+                                     pack_children(initial_children, n.get_writekey()))
11967)
11968)
11969hunk ./src/allmydata/nodemaker.py 107
11970-                                     MutableDataHandle(
11971+                                     MutableData(
11972)
11973hunk ./src/allmydata/test/common.py 18
11974      DeepCheckResults, DeepCheckAndRepairResults
11975 from allmydata.mutable.common import CorruptShareError
11976 from allmydata.mutable.layout import unpack_header
11977-from allmydata.mutable.publish import MutableDataHandle
11978+from allmydata.mutable.publish import MutableData
11979 from allmydata.storage.server import storage_index_to_dir
11980 from allmydata.storage.mutable import MutableShareFile
11981 from allmydata.util import hashutil, log, fileutil, pollmixin
11982hunk ./src/allmydata/test/common.py 192
11983         return defer.succeed(self)
11984     def _get_initial_contents(self, contents):
11985         if contents is None:
11986-            return MutableDataHandle("")
11987+            return MutableData("")
11988 
11989         if IMutableUploadable.providedBy(contents):
11990             return contents
11991hunk ./src/allmydata/test/test_checker.py 11
11992 from allmydata.test.no_network import GridTestMixin
11993 from allmydata.immutable.upload import Data
11994 from allmydata.test.common_web import WebRenderingMixin
11995-from allmydata.mutable.publish import MutableDataHandle
11996+from allmydata.mutable.publish import MutableData
11997 
11998 class FakeClient:
11999     def get_storage_broker(self):
12000hunk ./src/allmydata/test/test_checker.py 292
12001             self.imm = c0.create_node_from_uri(ur.uri)
12002         d.addCallback(_stash_immutable)
12003         d.addCallback(lambda ign:
12004-            c0.create_mutable_file(MutableDataHandle("contents")))
12005+            c0.create_mutable_file(MutableData("contents")))
12006         def _stash_mutable(node):
12007             self.mut = node
12008         d.addCallback(_stash_mutable)
12009hunk ./src/allmydata/test/test_cli.py 12
12010 from allmydata.util import fileutil, hashutil, base32
12011 from allmydata import uri
12012 from allmydata.immutable import upload
12013-from allmydata.mutable.publish import MutableDataHandle
12014+from allmydata.mutable.publish import MutableData
12015 from allmydata.dirnode import normalize
12016 
12017 # Test that the scripts can be imported -- although the actual tests of their
12018hunk ./src/allmydata/test/test_cli.py 1975
12019         self.set_up_grid()
12020         c0 = self.g.clients[0]
12021         DATA = "data" * 100
12022-        DATA_uploadable = MutableDataHandle(DATA)
12023+        DATA_uploadable = MutableData(DATA)
12024         d = c0.create_mutable_file(DATA_uploadable)
12025         def _stash_uri(n):
12026             self.uri = n.get_uri()
12027hunk ./src/allmydata/test/test_cli.py 2078
12028                                                         convergence="")))
12029         d.addCallback(_stash_uri, "small")
12030         d.addCallback(lambda ign:
12031-            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
12032+            c0.create_mutable_file(MutableData(DATA+"1")))
12033         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
12034         d.addCallback(_stash_uri, "mutable")
12035 
12036hunk ./src/allmydata/test/test_deepcheck.py 9
12037 from twisted.internet import threads # CLI tests use deferToThread
12038 from allmydata.immutable import upload
12039 from allmydata.mutable.common import UnrecoverableFileError
12040-from allmydata.mutable.publish import MutableDataHandle
12041+from allmydata.mutable.publish import MutableData
12042 from allmydata.util import idlib
12043 from allmydata.util import base32
12044 from allmydata.scripts import runner
12045hunk ./src/allmydata/test/test_deepcheck.py 38
12046         self.basedir = "deepcheck/MutableChecker/good"
12047         self.set_up_grid()
12048         CONTENTS = "a little bit of data"
12049-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12050+        CONTENTS_uploadable = MutableData(CONTENTS)
12051         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12052         def _created(node):
12053             self.node = node
12054hunk ./src/allmydata/test/test_deepcheck.py 61
12055         self.basedir = "deepcheck/MutableChecker/corrupt"
12056         self.set_up_grid()
12057         CONTENTS = "a little bit of data"
12058-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12059+        CONTENTS_uploadable = MutableData(CONTENTS)
12060         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12061         def _stash_and_corrupt(node):
12062             self.node = node
12063hunk ./src/allmydata/test/test_deepcheck.py 99
12064         self.basedir = "deepcheck/MutableChecker/delete_share"
12065         self.set_up_grid()
12066         CONTENTS = "a little bit of data"
12067-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12068+        CONTENTS_uploadable = MutableData(CONTENTS)
12069         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12070         def _stash_and_delete(node):
12071             self.node = node
12072hunk ./src/allmydata/test/test_deepcheck.py 224
12073             self.root_uri = n.get_uri()
12074         d.addCallback(_created_root)
12075         d.addCallback(lambda ign:
12076-            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
12077+            c0.create_mutable_file(MutableData("mutable file contents")))
12078         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
12079         def _created_mutable(n):
12080             self.mutable = n
12081hunk ./src/allmydata/test/test_deepcheck.py 965
12082     def create_mangled(self, ignored, name):
12083         nodetype, mangletype = name.split("-", 1)
12084         if nodetype == "mutable":
12085-            mutable_uploadable = MutableDataHandle("mutable file contents")
12086+            mutable_uploadable = MutableData("mutable file contents")
12087             d = self.g.clients[0].create_mutable_file(mutable_uploadable)
12088             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
12089         elif nodetype == "large":
12090hunk ./src/allmydata/test/test_hung_server.py 10
12091 from allmydata.util.consumer import download_to_data
12092 from allmydata.immutable import upload
12093 from allmydata.mutable.common import UnrecoverableFileError
12094-from allmydata.mutable.publish import MutableDataHandle
12095+from allmydata.mutable.publish import MutableData
12096 from allmydata.storage.common import storage_index_to_dir
12097 from allmydata.test.no_network import GridTestMixin
12098 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
12099hunk ./src/allmydata/test/test_hung_server.py 96
12100         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
12101 
12102         if mutable:
12103-            uploadable = MutableDataHandle(mutable_plaintext)
12104+            uploadable = MutableData(mutable_plaintext)
12105             d = nm.create_mutable_file(uploadable)
12106             def _uploaded_mutable(node):
12107                 self.uri = node.get_uri()
12108hunk ./src/allmydata/test/test_mutable.py 27
12109      NotEnoughServersError, CorruptShareError
12110 from allmydata.mutable.retrieve import Retrieve
12111 from allmydata.mutable.publish import Publish, MutableFileHandle, \
12112-                                      MutableDataHandle
12113+                                      MutableData
12114 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
12115 from allmydata.mutable.layout import unpack_header, unpack_share, \
12116                                      MDMFSlotReadProxy
12117hunk ./src/allmydata/test/test_mutable.py 297
12118             d.addCallback(lambda smap: smap.dump(StringIO()))
12119             d.addCallback(lambda sio:
12120                           self.failUnless("3-of-10" in sio.getvalue()))
12121-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
12122+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
12123             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
12124             d.addCallback(lambda res: n.download_best_version())
12125             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12126hunk ./src/allmydata/test/test_mutable.py 304
12127             d.addCallback(lambda res: n.get_size_of_best_version())
12128             d.addCallback(lambda size:
12129                           self.failUnlessEqual(size, len("contents 1")))
12130-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12131+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12132             d.addCallback(lambda res: n.download_best_version())
12133             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12134             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12135hunk ./src/allmydata/test/test_mutable.py 308
12136-            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
12137+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
12138             d.addCallback(lambda res: n.download_best_version())
12139             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
12140             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
12141hunk ./src/allmydata/test/test_mutable.py 320
12142             # mapupdate-to-retrieve data caching (i.e. make the shares larger
12143             # than the default readsize, which is 2000 bytes). A 15kB file
12144             # will have 5kB shares.
12145-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
12146+            d.addCallback(lambda res: n.overwrite(MutableData("large size file" * 1000)))
12147             d.addCallback(lambda res: n.download_best_version())
12148             d.addCallback(lambda res:
12149                           self.failUnlessEqual(res, "large size file" * 1000))
12150hunk ./src/allmydata/test/test_mutable.py 343
12151             # to make them big enough to force the file to be uploaded
12152             # in more than one segment.
12153             big_contents = "contents1" * 100000 # about 900 KiB
12154-            big_contents_uploadable = MutableDataHandle(big_contents)
12155+            big_contents_uploadable = MutableData(big_contents)
12156             d.addCallback(lambda ignored:
12157                 n.overwrite(big_contents_uploadable))
12158             d.addCallback(lambda ignored:
12159hunk ./src/allmydata/test/test_mutable.py 355
12160             # segments, so that we make the downloader deal with
12161             # multiple segments.
12162             bigger_contents = "contents2" * 1000000 # about 9MiB
12163-            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
12164+            bigger_contents_uploadable = MutableData(bigger_contents)
12165             d.addCallback(lambda ignored:
12166                 n.overwrite(bigger_contents_uploadable))
12167             d.addCallback(lambda ignored:
12168hunk ./src/allmydata/test/test_mutable.py 368
12169 
12170 
12171     def test_create_with_initial_contents(self):
12172-        upload1 = MutableDataHandle("contents 1")
12173+        upload1 = MutableData("contents 1")
12174         d = self.nodemaker.create_mutable_file(upload1)
12175         def _created(n):
12176             d = n.download_best_version()
12177hunk ./src/allmydata/test/test_mutable.py 373
12178             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12179-            upload2 = MutableDataHandle("contents 2")
12180+            upload2 = MutableData("contents 2")
12181             d.addCallback(lambda res: n.overwrite(upload2))
12182             d.addCallback(lambda res: n.download_best_version())
12183             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12184hunk ./src/allmydata/test/test_mutable.py 385
12185 
12186     def test_create_mdmf_with_initial_contents(self):
12187         initial_contents = "foobarbaz" * 131072 # 900KiB
12188-        initial_contents_uploadable = MutableDataHandle(initial_contents)
12189+        initial_contents_uploadable = MutableData(initial_contents)
12190         d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
12191                                                version=MDMF_VERSION)
12192         def _created(n):
12193hunk ./src/allmydata/test/test_mutable.py 392
12194             d = n.download_best_version()
12195             d.addCallback(lambda data:
12196                 self.failUnlessEqual(data, initial_contents))
12197-            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
12198+            uploadable2 = MutableData(initial_contents + "foobarbaz")
12199             d.addCallback(lambda ignored:
12200                 n.overwrite(uploadable2))
12201             d.addCallback(lambda ignored:
12202hunk ./src/allmydata/test/test_mutable.py 413
12203             key = n.get_writekey()
12204             self.failUnless(isinstance(key, str), key)
12205             self.failUnlessEqual(len(key), 16) # AES key size
12206-            return MutableDataHandle(data)
12207+            return MutableData(data)
12208         d = self.nodemaker.create_mutable_file(_make_contents)
12209         def _created(n):
12210             return n.download_best_version()
12211hunk ./src/allmydata/test/test_mutable.py 429
12212             key = n.get_writekey()
12213             self.failUnless(isinstance(key, str), key)
12214             self.failUnlessEqual(len(key), 16)
12215-            return MutableDataHandle(data)
12216+            return MutableData(data)
12217         d = self.nodemaker.create_mutable_file(_make_contents,
12218                                                version=MDMF_VERSION)
12219         d.addCallback(lambda n:
12220hunk ./src/allmydata/test/test_mutable.py 441
12221 
12222     def test_create_with_too_large_contents(self):
12223         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
12224-        BIG_uploadable = MutableDataHandle(BIG)
12225+        BIG_uploadable = MutableData(BIG)
12226         d = self.nodemaker.create_mutable_file(BIG_uploadable)
12227         def _created(n):
12228hunk ./src/allmydata/test/test_mutable.py 444
12229-            other_BIG_uploadable = MutableDataHandle(BIG)
12230+            other_BIG_uploadable = MutableData(BIG)
12231             d = n.overwrite(other_BIG_uploadable)
12232             return d
12233         d.addCallback(_created)
12234hunk ./src/allmydata/test/test_mutable.py 460
12235     def test_modify(self):
12236         def _modifier(old_contents, servermap, first_time):
12237             new_contents = old_contents + "line2"
12238-            return MutableDataHandle(new_contents)
12239+            return MutableData(new_contents)
12240         def _non_modifier(old_contents, servermap, first_time):
12241hunk ./src/allmydata/test/test_mutable.py 462
12242-            return MutableDataHandle(old_contents)
12243+            return MutableData(old_contents)
12244         def _none_modifier(old_contents, servermap, first_time):
12245             return None
12246         def _error_modifier(old_contents, servermap, first_time):
12247hunk ./src/allmydata/test/test_mutable.py 469
12248             raise ValueError("oops")
12249         def _toobig_modifier(old_contents, servermap, first_time):
12250             new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
12251-            return MutableDataHandle(new_content)
12252+            return MutableData(new_content)
12253         calls = []
12254         def _ucw_error_modifier(old_contents, servermap, first_time):
12255             # simulate an UncoordinatedWriteError once
12256hunk ./src/allmydata/test/test_mutable.py 477
12257             if len(calls) <= 1:
12258                 raise UncoordinatedWriteError("simulated")
12259             new_contents = old_contents + "line3"
12260-            return MutableDataHandle(new_contents)
12261+            return MutableData(new_contents)
12262         def _ucw_error_non_modifier(old_contents, servermap, first_time):
12263             # simulate an UncoordinatedWriteError once, and don't actually
12264             # modify the contents on subsequent invocations
12265hunk ./src/allmydata/test/test_mutable.py 484
12266             calls.append(1)
12267             if len(calls) <= 1:
12268                 raise UncoordinatedWriteError("simulated")
12269-            return MutableDataHandle(old_contents)
12270+            return MutableData(old_contents)
12271 
12272         initial_contents = "line1"
12273hunk ./src/allmydata/test/test_mutable.py 487
12274-        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
12275+        d = self.nodemaker.create_mutable_file(MutableData(initial_contents))
12276         def _created(n):
12277             d = n.modify(_modifier)
12278             d.addCallback(lambda res: n.download_best_version())
12279hunk ./src/allmydata/test/test_mutable.py 548
12280 
12281     def test_modify_backoffer(self):
12282         def _modifier(old_contents, servermap, first_time):
12283-            return MutableDataHandle(old_contents + "line2")
12284+            return MutableData(old_contents + "line2")
12285         calls = []
12286         def _ucw_error_modifier(old_contents, servermap, first_time):
12287             # simulate an UncoordinatedWriteError once
12288hunk ./src/allmydata/test/test_mutable.py 555
12289             calls.append(1)
12290             if len(calls) <= 1:
12291                 raise UncoordinatedWriteError("simulated")
12292-            return MutableDataHandle(old_contents + "line3")
12293+            return MutableData(old_contents + "line3")
12294         def _always_ucw_error_modifier(old_contents, servermap, first_time):
12295             raise UncoordinatedWriteError("simulated")
12296         def _backoff_stopper(node, f):
12297hunk ./src/allmydata/test/test_mutable.py 570
12298         giveuper._delay = 0.1
12299         giveuper.factor = 1
12300 
12301-        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
12302+        d = self.nodemaker.create_mutable_file(MutableData("line1"))
12303         def _created(n):
12304             d = n.modify(_modifier)
12305             d.addCallback(lambda res: n.download_best_version())
12306hunk ./src/allmydata/test/test_mutable.py 620
12307             d.addCallback(lambda smap: smap.dump(StringIO()))
12308             d.addCallback(lambda sio:
12309                           self.failUnless("3-of-10" in sio.getvalue()))
12310-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
12311+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
12312             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
12313             d.addCallback(lambda res: n.download_best_version())
12314             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12315hunk ./src/allmydata/test/test_mutable.py 624
12316-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12317+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12318             d.addCallback(lambda res: n.download_best_version())
12319             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12320             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12321hunk ./src/allmydata/test/test_mutable.py 628
12322-            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
12323+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
12324             d.addCallback(lambda res: n.download_best_version())
12325             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
12326             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
12327hunk ./src/allmydata/test/test_mutable.py 646
12328         # publish a file and create shares, which can then be manipulated
12329         # later.
12330         self.CONTENTS = "New contents go here" * 1000
12331-        self.uploadable = MutableDataHandle(self.CONTENTS)
12332+        self.uploadable = MutableData(self.CONTENTS)
12333         self._storage = FakeStorage()
12334         self._nodemaker = make_nodemaker(self._storage)
12335         self._storage_broker = self._nodemaker.storage_broker
12336hunk ./src/allmydata/test/test_mutable.py 662
12337         # an MDMF file.
12338         # self.CONTENTS should have more than one segment.
12339         self.CONTENTS = "This is an MDMF file" * 100000
12340-        self.uploadable = MutableDataHandle(self.CONTENTS)
12341+        self.uploadable = MutableData(self.CONTENTS)
12342         self._storage = FakeStorage()
12343         self._nodemaker = make_nodemaker(self._storage)
12344         self._storage_broker = self._nodemaker.storage_broker
12345hunk ./src/allmydata/test/test_mutable.py 678
12346         # like publish_one, except that the result is guaranteed to be
12347         # an SDMF file
12348         self.CONTENTS = "This is an SDMF file" * 1000
12349-        self.uploadable = MutableDataHandle(self.CONTENTS)
12350+        self.uploadable = MutableData(self.CONTENTS)
12351         self._storage = FakeStorage()
12352         self._nodemaker = make_nodemaker(self._storage)
12353         self._storage_broker = self._nodemaker.storage_broker
12354hunk ./src/allmydata/test/test_mutable.py 696
12355                          "Contents 2",
12356                          "Contents 3a",
12357                          "Contents 3b"]
12358-        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
12359+        self.uploadables = [MutableData(d) for d in self.CONTENTS]
12360         self._copied_shares = {}
12361         self._storage = FakeStorage()
12362         self._nodemaker = make_nodemaker(self._storage)
12363hunk ./src/allmydata/test/test_mutable.py 826
12364         # create a new file, which is large enough to knock the privkey out
12365         # of the early part of the file
12366         LARGE = "These are Larger contents" * 200 # about 5KB
12367-        LARGE_uploadable = MutableDataHandle(LARGE)
12368+        LARGE_uploadable = MutableData(LARGE)
12369         d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
12370         def _created(large_fn):
12371             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
12372hunk ./src/allmydata/test/test_mutable.py 1842
12373 class MultipleEncodings(unittest.TestCase):
12374     def setUp(self):
12375         self.CONTENTS = "New contents go here"
12376-        self.uploadable = MutableDataHandle(self.CONTENTS)
12377+        self.uploadable = MutableData(self.CONTENTS)
12378         self._storage = FakeStorage()
12379         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
12380         self._storage_broker = self._nodemaker.storage_broker
12381hunk ./src/allmydata/test/test_mutable.py 1872
12382         s = self._storage
12383         s._peers = {} # clear existing storage
12384         p2 = Publish(fn2, self._storage_broker, None)
12385-        uploadable = MutableDataHandle(data)
12386+        uploadable = MutableData(data)
12387         d = p2.publish(uploadable)
12388         def _published(res):
12389             shares = s._peers
12390hunk ./src/allmydata/test/test_mutable.py 2049
12391         self._set_versions(target)
12392 
12393         def _modify(oldversion, servermap, first_time):
12394-            return MutableDataHandle(oldversion + " modified")
12395+            return MutableData(oldversion + " modified")
12396         d = self._fn.modify(_modify)
12397         d.addCallback(lambda res: self._fn.download_best_version())
12398         expected = self.CONTENTS[2] + " modified"
12399hunk ./src/allmydata/test/test_mutable.py 2175
12400         self.basedir = "mutable/Problems/test_publish_surprise"
12401         self.set_up_grid()
12402         nm = self.g.clients[0].nodemaker
12403-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12404+        d = nm.create_mutable_file(MutableData("contents 1"))
12405         def _created(n):
12406             d = defer.succeed(None)
12407             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12408hunk ./src/allmydata/test/test_mutable.py 2185
12409             d.addCallback(_got_smap1)
12410             # then modify the file, leaving the old map untouched
12411             d.addCallback(lambda res: log.msg("starting winning write"))
12412-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12413+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12414             # now attempt to modify the file with the old servermap. This
12415             # will look just like an uncoordinated write, in which every
12416             # single share got updated between our mapupdate and our publish
12417hunk ./src/allmydata/test/test_mutable.py 2194
12418                           self.shouldFail(UncoordinatedWriteError,
12419                                           "test_publish_surprise", None,
12420                                           n.upload,
12421-                                          MutableDataHandle("contents 2a"), self.old_map))
12422+                                          MutableData("contents 2a"), self.old_map))
12423             return d
12424         d.addCallback(_created)
12425         return d
12426hunk ./src/allmydata/test/test_mutable.py 2203
12427         self.basedir = "mutable/Problems/test_retrieve_surprise"
12428         self.set_up_grid()
12429         nm = self.g.clients[0].nodemaker
12430-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12431+        d = nm.create_mutable_file(MutableData("contents 1"))
12432         def _created(n):
12433             d = defer.succeed(None)
12434             d.addCallback(lambda res: n.get_servermap(MODE_READ))
12435hunk ./src/allmydata/test/test_mutable.py 2213
12436             d.addCallback(_got_smap1)
12437             # then modify the file, leaving the old map untouched
12438             d.addCallback(lambda res: log.msg("starting winning write"))
12439-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12440+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12441             # now attempt to retrieve the old version with the old servermap.
12442             # This will look like someone has changed the file since we
12443             # updated the servermap.
12444hunk ./src/allmydata/test/test_mutable.py 2241
12445         self.basedir = "mutable/Problems/test_unexpected_shares"
12446         self.set_up_grid()
12447         nm = self.g.clients[0].nodemaker
12448-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12449+        d = nm.create_mutable_file(MutableData("contents 1"))
12450         def _created(n):
12451             d = defer.succeed(None)
12452             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12453hunk ./src/allmydata/test/test_mutable.py 2253
12454                 self.g.remove_server(peer0)
12455                 # then modify the file, leaving the old map untouched
12456                 log.msg("starting winning write")
12457-                return n.overwrite(MutableDataHandle("contents 2"))
12458+                return n.overwrite(MutableData("contents 2"))
12459             d.addCallback(_got_smap1)
12460             # now attempt to modify the file with the old servermap. This
12461             # will look just like an uncoordinated write, in which every
12462hunk ./src/allmydata/test/test_mutable.py 2263
12463                           self.shouldFail(UncoordinatedWriteError,
12464                                           "test_surprise", None,
12465                                           n.upload,
12466-                                          MutableDataHandle("contents 2a"), self.old_map))
12467+                                          MutableData("contents 2a"), self.old_map))
12468             return d
12469         d.addCallback(_created)
12470         return d
12471hunk ./src/allmydata/test/test_mutable.py 2303
12472         d.addCallback(_break_peer0)
12473         # now "create" the file, using the pre-established key, and let the
12474         # initial publish finally happen
12475-        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
12476+        d.addCallback(lambda res: nm.create_mutable_file(MutableData("contents 1")))
12477         # that ought to work
12478         def _got_node(n):
12479             d = n.download_best_version()
12480hunk ./src/allmydata/test/test_mutable.py 2312
12481             def _break_peer1(res):
12482                 self.connection1.broken = True
12483             d.addCallback(_break_peer1)
12484-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12485+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12486             # that ought to work too
12487             d.addCallback(lambda res: n.download_best_version())
12488             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12489hunk ./src/allmydata/test/test_mutable.py 2344
12490         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
12491         self.g.break_server(peerids[0])
12492 
12493-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12494+        d = nm.create_mutable_file(MutableData("contents 1"))
12495         def _created(n):
12496             d = n.download_best_version()
12497             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12498hunk ./src/allmydata/test/test_mutable.py 2352
12499             def _break_second_server(res):
12500                 self.g.break_server(peerids[1])
12501             d.addCallback(_break_second_server)
12502-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12503+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12504             # that ought to work too
12505             d.addCallback(lambda res: n.download_best_version())
12506             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12507hunk ./src/allmydata/test/test_mutable.py 2371
12508         d = self.shouldFail(NotEnoughServersError,
12509                             "test_publish_all_servers_bad",
12510                             "Ran out of non-bad servers",
12511-                            nm.create_mutable_file, MutableDataHandle("contents"))
12512+                            nm.create_mutable_file, MutableData("contents"))
12513         return d
12514 
12515     def test_publish_no_servers(self):
12516hunk ./src/allmydata/test/test_mutable.py 2383
12517         d = self.shouldFail(NotEnoughServersError,
12518                             "test_publish_no_servers",
12519                             "Ran out of non-bad servers",
12520-                            nm.create_mutable_file, MutableDataHandle("contents"))
12521+                            nm.create_mutable_file, MutableData("contents"))
12522         return d
12523     test_publish_no_servers.timeout = 30
12524 
12525hunk ./src/allmydata/test/test_mutable.py 2401
12526         # we need some contents that are large enough to push the privkey out
12527         # of the early part of the file
12528         LARGE = "These are Larger contents" * 2000 # about 50KB
12529-        LARGE_uploadable = MutableDataHandle(LARGE)
12530+        LARGE_uploadable = MutableData(LARGE)
12531         d = nm.create_mutable_file(LARGE_uploadable)
12532         def _created(n):
12533             self.uri = n.get_uri()
12534hunk ./src/allmydata/test/test_mutable.py 2438
12535         self.set_up_grid(num_servers=20)
12536         nm = self.g.clients[0].nodemaker
12537         LARGE = "These are Larger contents" * 2000 # about 50KiB
12538-        LARGE_uploadable = MutableDataHandle(LARGE)
12539+        LARGE_uploadable = MutableData(LARGE)
12540         nm._node_cache = DevNullDictionary() # disable the nodecache
12541 
12542         d = nm.create_mutable_file(LARGE_uploadable)
12543hunk ./src/allmydata/test/test_mutable.py 2464
12544         self.set_up_grid(num_servers=20)
12545         nm = self.g.clients[0].nodemaker
12546         CONTENTS = "contents" * 2000
12547-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12548+        CONTENTS_uploadable = MutableData(CONTENTS)
12549         d = nm.create_mutable_file(CONTENTS_uploadable)
12550         def _created(node):
12551             self._node = node
12552hunk ./src/allmydata/test/test_mutable.py 2565
12553 class DataHandle(unittest.TestCase):
12554     def setUp(self):
12555         self.test_data = "Test Data" * 50000
12556-        self.uploadable = MutableDataHandle(self.test_data)
12557+        self.uploadable = MutableData(self.test_data)
12558 
12559 
12560     def test_datahandle_read(self):
12561hunk ./src/allmydata/test/test_sftp.py 84
12562         return d
12563 
12564     def _set_up_tree(self):
12565-        u = publish.MutableDataHandle("mutable file contents")
12566+        u = publish.MutableData("mutable file contents")
12567         d = self.client.create_mutable_file(u)
12568         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
12569         def _created_mutable(n):
12570hunk ./src/allmydata/test/test_system.py 22
12571 from allmydata.monitor import Monitor
12572 from allmydata.mutable.common import NotWriteableError
12573 from allmydata.mutable import layout as mutable_layout
12574-from allmydata.mutable.publish import MutableDataHandle
12575+from allmydata.mutable.publish import MutableData
12576 from foolscap.api import DeadReferenceError
12577 from twisted.python.failure import Failure
12578 from twisted.web.client import getPage
12579hunk ./src/allmydata/test/test_system.py 460
12580     def test_mutable(self):
12581         self.basedir = "system/SystemTest/test_mutable"
12582         DATA = "initial contents go here."  # 25 bytes % 3 != 0
12583-        DATA_uploadable = MutableDataHandle(DATA)
12584+        DATA_uploadable = MutableData(DATA)
12585         NEWDATA = "new contents yay"
12586hunk ./src/allmydata/test/test_system.py 462
12587-        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
12588+        NEWDATA_uploadable = MutableData(NEWDATA)
12589         NEWERDATA = "this is getting old"
12590hunk ./src/allmydata/test/test_system.py 464
12591-        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
12592+        NEWERDATA_uploadable = MutableData(NEWERDATA)
12593 
12594         d = self.set_up_nodes(use_key_generator=True)
12595 
12596hunk ./src/allmydata/test/test_system.py 642
12597         def _check_empty_file(res):
12598             # make sure we can create empty files, this usually screws up the
12599             # segsize math
12600-            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
12601+            d1 = self.clients[2].create_mutable_file(MutableData(""))
12602             d1.addCallback(lambda newnode: newnode.download_best_version())
12603             d1.addCallback(lambda res: self.failUnlessEqual("", res))
12604             return d1
12605hunk ./src/allmydata/test/test_system.py 674
12606 
12607         d.addCallback(check_kg_poolsize, 0)
12608         d.addCallback(lambda junk:
12609-            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
12610+            self.clients[3].create_mutable_file(MutableData('hello, world')))
12611         d.addCallback(check_kg_poolsize, -1)
12612         d.addCallback(lambda junk: self.clients[3].create_dirnode())
12613         d.addCallback(check_kg_poolsize, -2)
12614hunk ./src/allmydata/test/test_web.py 3184
12615             self.uris[which] = n.get_uri()
12616             assert isinstance(self.uris[which], str)
12617         d.addCallback(lambda ign:
12618-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
12619+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12620         d.addCallback(_stash_mutable_uri, "corrupt")
12621         d.addCallback(lambda ign:
12622                       c0.upload(upload.Data("literal", convergence="")))
12623hunk ./src/allmydata/test/test_web.py 3331
12624             self.uris[which] = n.get_uri()
12625             assert isinstance(self.uris[which], str)
12626         d.addCallback(lambda ign:
12627-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
12628+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12629         d.addCallback(_stash_mutable_uri, "corrupt")
12630 
12631         def _compute_fileurls(ignored):
12632hunk ./src/allmydata/test/test_web.py 3994
12633             self.uris[which] = n.get_uri()
12634             assert isinstance(self.uris[which], str)
12635         d.addCallback(lambda ign:
12636-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
12637+            c0.create_mutable_file(publish.MutableData(DATA+"2")))
12638         d.addCallback(_stash_mutable_uri, "mutable")
12639 
12640         def _compute_fileurls(ignored):
12641hunk ./src/allmydata/test/test_web.py 4094
12642         d.addCallback(_stash_uri, "small")
12643 
12644         d.addCallback(lambda ign:
12645-            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
12646+            c0.create_mutable_file(publish.MutableData("mutable")))
12647         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
12648         d.addCallback(_stash_uri, "mutable")
12649 
12650}
12651[tests: fix tests that were broken by #993
12652Kevan Carstensen <kevan@isnotajoke.com>**20100717015230
12653 Ignore-this: f0ace6538c6d824b7fe271c40a7ebf8d
12654] {
12655hunk ./src/allmydata/test/common.py 152
12656         consumer.write(data[start:end])
12657         return consumer
12658 
12659+
12660+    def get_best_readable_version(self):
12661+        return defer.succeed(self)
12662+
12663+
12664+    download_best_version = download_to_data
12665+
12666+
12667+    def download_to_data(self):
12668+        return download_to_data(self)
12669+
12670+
12671+    def get_size_of_best_version(self):
12672+        return defer.succeed(self.get_size)
12673+
12674+
12675 def make_chk_file_cap(size):
12676     return uri.CHKFileURI(key=os.urandom(16),
12677                           uri_extension_hash=os.urandom(32),
12678hunk ./src/allmydata/test/common.py 318
12679         return d
12680 
12681     def download_best_version(self):
12682+        return defer.succeed(self._download_best_version())
12683+
12684+
12685+    def _download_best_version(self, ignored=None):
12686         if isinstance(self.my_uri, uri.LiteralFileURI):
12687hunk ./src/allmydata/test/common.py 323
12688-            return defer.succeed(self.my_uri.data)
12689+            return self.my_uri.data
12690         if self.storage_index not in self.all_contents:
12691hunk ./src/allmydata/test/common.py 325
12692-            return defer.fail(NotEnoughSharesError(None, 0, 3))
12693-        return defer.succeed(self.all_contents[self.storage_index])
12694+            raise NotEnoughSharesError(None, 0, 3)
12695+        return self.all_contents[self.storage_index]
12696+
12697 
12698     def overwrite(self, new_contents):
12699         if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
12700hunk ./src/allmydata/test/common.py 352
12701         self.all_contents[self.storage_index] = new_data
12702         return None
12703 
12704+    # As actually implemented, MutableFilenode and MutableFileVersion
12705+    # are distinct. However, nothing in the webapi uses (yet) that
12706+    # distinction -- it just uses the unified download interface
12707+    # provided by get_best_readable_version and read. When we start
12708+    # doing cooler things like LDMF, we will want to revise this code to
12709+    # be less simplistic.
12710+    def get_best_readable_version(self):
12711+        return defer.succeed(self)
12712+
12713+
12714+    def read(self, consumer, offset=0, size=None):
12715+        data = self._download_best_version()
12716+        if size:
12717+            data = data[offset:offset+size]
12718+        consumer.write(data)
12719+        return defer.succeed(consumer)
12720+
12721+
12722 def make_mutable_file_cap():
12723     return uri.WriteableSSKFileURI(writekey=os.urandom(16),
12724                                    fingerprint=os.urandom(32))
12725hunk ./src/allmydata/test/test_filenode.py 98
12726         def _check_segment(res):
12727             self.failUnlessEqual(res, DATA[1:1+5])
12728         d.addCallback(_check_segment)
12729-        d.addCallback(lambda ignored:
12730-            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
12731+        d.addCallback(lambda ignored: fn1.get_best_readable_version())
12732+        d.addCallback(lambda fn2: self.failUnlessEqual(fn1, fn2))
12733         d.addCallback(lambda ignored:
12734             fn1.get_size_of_best_version())
12735         d.addCallback(lambda size:
12736hunk ./src/allmydata/test/test_immutable.py 168
12737 
12738 
12739     def test_get_best_readable_version(self):
12740-        n = self.n.get_best_readable_version()
12741-        self.failUnlessEqual(n, self.n)
12742+        d = self.n.get_best_readable_version()
12743+        d.addCallback(lambda n2:
12744+            self.failUnlessEqual(n2, self.n))
12745+        return d
12746 
12747     def test_get_size_of_best_version(self):
12748         d = self.n.get_size_of_best_version()
12749hunk ./src/allmydata/test/test_mutable.py 8
12750 from twisted.internet import defer, reactor
12751 from allmydata import uri, client
12752 from allmydata.nodemaker import NodeMaker
12753-from allmydata.util import base32
12754+from allmydata.util import base32, consumer
12755 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
12756      ssk_pubkey_fingerprint_hash
12757hunk ./src/allmydata/test/test_mutable.py 11
12758+from allmydata.util.deferredutil import gatherResults
12759 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
12760      NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
12761 from allmydata.monitor import Monitor
12762hunk ./src/allmydata/test/test_mutable.py 1000
12763         if version is None:
12764             version = servermap.best_recoverable_version()
12765         r = Retrieve(self._fn, servermap, version)
12766-        return r.download()
12767+        c = consumer.MemoryConsumer()
12768+        d = r.download(consumer=c)
12769+        d.addCallback(lambda mc: "".join(mc.chunks))
12770+        return d
12771+
12772 
12773     def test_basic(self):
12774         d = self.make_servermap()
12775hunk ./src/allmydata/test/test_mutable.py 1263
12776                             in str(servermap.problems[0]))
12777             ver = servermap.best_recoverable_version()
12778             r = Retrieve(self._fn, servermap, ver)
12779-            return r.download()
12780+            c = consumer.MemoryConsumer()
12781+            return r.download(c)
12782         d.addCallback(_do_retrieve)
12783hunk ./src/allmydata/test/test_mutable.py 1266
12784+        d.addCallback(lambda mc: "".join(mc.chunks))
12785         d.addCallback(lambda new_contents:
12786                       self.failUnlessEqual(new_contents, self.CONTENTS))
12787         return d
12788}
12789[test/test_immutable.py: add tests for #993-related modifications
12790Kevan Carstensen <kevan@isnotajoke.com>**20100717015402
12791 Ignore-this: d94ad98bd0d322ead85af2e0aa95be38
12792] hunk ./src/allmydata/test/test_mutable.py 2607
12793         start = chunk_size
12794         end = chunk_size * 2
12795         self.failUnlessEqual("".join(more_data), self.test_data[start:end])
12796+
12797+
12798+class Version(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
12799+    def setUp(self):
12800+        GridTestMixin.setUp(self)
12801+        self.basedir = self.mktemp()
12802+        self.set_up_grid()
12803+        self.c = self.g.clients[0]
12804+        self.nm = self.c.nodemaker
12805+        self.data = "test data" * 100000 # about 900 KiB; MDMF
12806+        self.small_data = "test data" * 10 # about 90 B; SDMF
12807+        return self.do_upload()
12808+
12809+
12810+    def do_upload(self):
12811+        d1 = self.nm.create_mutable_file(MutableData(self.data),
12812+                                         version=MDMF_VERSION)
12813+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
12814+        dl = gatherResults([d1, d2])
12815+        def _then((n1, n2)):
12816+            assert isinstance(n1, MutableFileNode)
12817+            assert isinstance(n2, MutableFileNode)
12818+
12819+            self.mdmf_node = n1
12820+            self.sdmf_node = n2
12821+        dl.addCallback(_then)
12822+        return dl
12823+
12824+
12825+    def test_get_readonly_mutable_version(self):
12826+        # Attempting to get a mutable version of a mutable file from a
12827+        # filenode initialized with a readcap should return a readonly
12828+        # version of that same node.
12829+        ro = self.mdmf_node.get_readonly()
12830+        d = ro.get_best_mutable_version()
12831+        d.addCallback(lambda version:
12832+            self.failUnless(version.is_readonly()))
12833+        d.addCallback(lambda ignored:
12834+            self.sdmf_node.get_readonly())
12835+        d.addCallback(lambda version:
12836+            self.failUnless(version.is_readonly()))
12837+        return d
12838+
12839+
12840+    def test_get_sequence_number(self):
12841+        d = self.mdmf_node.get_best_readable_version()
12842+        d.addCallback(lambda bv:
12843+            self.failUnlessEqual(bv.get_sequence_number(), 0))
12844+        d.addCallback(lambda ignored:
12845+            self.sdmf_node.get_best_readable_version())
12846+        d.addCallback(lambda bv:
12847+            self.failUnlessEqual(bv.get_sequence_number(), 0))
12848+        # Now update. The sequence number in both cases should be 1 in
12849+        # both cases.
12850+        def _do_update(ignored):
12851+            new_data = MutableData("foo bar baz" * 100000)
12852+            new_small_data = MutableData("foo bar baz" * 10)
12853+            d1 = self.mdmf_node.overwrite(new_data)
12854+            d2 = self.sdmf_node.overwrite(new_small_data)
12855+            dl = gatherResults([d1, d2])
12856+            return dl
12857+        d.addCallback(_do_update)
12858+        d.addCallback(lambda ignored:
12859+            self.mdmf_node.get_best_readable_version())
12860+        d.addCallback(lambda bv:
12861+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12862+        d.addCallback(lambda ignored:
12863+            self.sdmf_node.get_best_readable_version())
12864+        d.addCallback(lambda bv:
12865+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12866+        return d
12867+
12868+
12869+    def test_get_writekey(self):
12870+        d = self.mdmf_node.get_best_mutable_version()
12871+        d.addCallback(lambda bv:
12872+            self.failUnlessEqual(bv.get_writekey(),
12873+                                 self.mdmf_node.get_writekey()))
12874+        d.addCallback(lambda ignored:
12875+            self.sdmf_node.get_best_mutable_version())
12876+        d.addCallback(lambda bv:
12877+            self.failUnlessEqual(bv.get_writekey(),
12878+                                 self.sdmf_node.get_writekey()))
12879+        return d
12880+
12881+
12882+    def test_get_storage_index(self):
12883+        d = self.mdmf_node.get_best_mutable_version()
12884+        d.addCallback(lambda bv:
12885+            self.failUnlessEqual(bv.get_storage_index(),
12886+                                 self.mdmf_node.get_storage_index()))
12887+        d.addCallback(lambda ignored:
12888+            self.sdmf_node.get_best_mutable_version())
12889+        d.addCallback(lambda bv:
12890+            self.failUnlessEqual(bv.get_storage_index(),
12891+                                 self.sdmf_node.get_storage_index()))
12892+        return d
12893+
12894+
12895+    def test_get_readonly_version(self):
12896+        d = self.mdmf_node.get_best_readable_version()
12897+        d.addCallback(lambda bv:
12898+            self.failUnless(bv.is_readonly()))
12899+        d.addCallback(lambda ignored:
12900+            self.sdmf_node.get_best_readable_version())
12901+        d.addCallback(lambda bv:
12902+            self.failUnless(bv.is_readonly()))
12903+        return d
12904+
12905+
12906+    def test_get_mutable_version(self):
12907+        d = self.mdmf_node.get_best_mutable_version()
12908+        d.addCallback(lambda bv:
12909+            self.failIf(bv.is_readonly()))
12910+        d.addCallback(lambda ignored:
12911+            self.sdmf_node.get_best_mutable_version())
12912+        d.addCallback(lambda bv:
12913+            self.failIf(bv.is_readonly()))
12914+        return d
12915+
12916+
12917+    def test_toplevel_overwrite(self):
12918+        new_data = MutableData("foo bar baz" * 100000)
12919+        new_small_data = MutableData("foo bar baz" * 10)
12920+        d = self.mdmf_node.overwrite(new_data)
12921+        d.addCallback(lambda ignored:
12922+            self.mdmf_node.download_best_version())
12923+        d.addCallback(lambda data:
12924+            self.failUnlessEqual(data, "foo bar baz" * 100000))
12925+        d.addCallback(lambda ignored:
12926+            self.sdmf_node.overwrite(new_small_data))
12927+        d.addCallback(lambda ignored:
12928+            self.sdmf_node.download_best_version())
12929+        d.addCallback(lambda data:
12930+            self.failUnlessEqual(data, "foo bar baz" * 10))
12931+        return d
12932+
12933+
12934+    def test_toplevel_modify(self):
12935+        def modifier(old_contents, servermap, first_time):
12936+            return MutableData(old_contents + "modified")
12937+        d = self.mdmf_node.modify(modifier)
12938+        d.addCallback(lambda ignored:
12939+            self.mdmf_node.download_best_version())
12940+        d.addCallback(lambda data:
12941+            self.failUnlessIn("modified", data))
12942+        d.addCallback(lambda ignored:
12943+            self.sdmf_node.modify(modifier))
12944+        d.addCallback(lambda ignored:
12945+            self.sdmf_node.download_best_version())
12946+        d.addCallback(lambda data:
12947+            self.failUnlessIn("modified", data))
12948+        return d
12949+
12950+
12951+    def test_version_modify(self):
12952+        # TODO: When we can publish multiple versions, alter this test
12953+        # to modify a version other than the best usable version, then
12954+        # test to see that the best recoverable version is that.
12955+        def modifier(old_contents, servermap, first_time):
12956+            return MutableData(old_contents + "modified")
12957+        d = self.mdmf_node.modify(modifier)
12958+        d.addCallback(lambda ignored:
12959+            self.mdmf_node.download_best_version())
12960+        d.addCallback(lambda data:
12961+            self.failUnlessIn("modified", data))
12962+        d.addCallback(lambda ignored:
12963+            self.sdmf_node.modify(modifier))
12964+        d.addCallback(lambda ignored:
12965+            self.sdmf_node.download_best_version())
12966+        d.addCallback(lambda data:
12967+            self.failUnlessIn("modified", data))
12968+        return d
12969+
12970+
12971+    def test_download_version(self):
12972+        # This will only pass once we get the ability to publish
12973+        # multiple recoverable versions.
12974+        self.failUnless(False)
12975+
12976+
12977+    def test_partial_read(self):
12978+        # read only a few bytes at a time, and see that the results are
12979+        # what we expect.
12980+        d = self.mdmf_node.get_best_readable_version()
12981+        def _read_data(version):
12982+            c = consumer.MemoryConsumer()
12983+            d2 = defer.succeed(None)
12984+            for i in xrange(0, len(self.data), 10000):
12985+                d2.addCallback(lambda ignored, i=i: version.read(c, i, 10000))
12986+            d2.addCallback(lambda ignored:
12987+                self.failUnlessEqual(self.data, "".join(c.chunks)))
12988+            return d2
12989+        d.addCallback(_read_data)
12990+        return d
12991+
12992+
12993+    def test_read(self):
12994+        d = self.mdmf_node.get_best_readable_version()
12995+        def _read_data(version):
12996+            c = consumer.MemoryConsumer()
12997+            d2 = defer.succeed(None)
12998+            d2.addCallback(lambda ignored: version.read(c))
12999+            d2.addCallback(lambda ignored:
13000+                self.failUnlessEqual("".join(c.chunks), self.data))
13001+            return d2
13002+        d.addCallback(_read_data)
13003+        return d
13004+
13005+
13006+    def test_download_best_version(self):
13007+        d = self.mdmf_node.download_best_version()
13008+        d.addCallback(lambda data:
13009+            self.failUnlessEqual(data, self.data))
13010+        d.addCallback(lambda ignored:
13011+            self.sdmf_node.download_best_version())
13012+        d.addCallback(lambda data:
13013+            self.failUnlessEqual(data, self.small_data))
13014+        return d
13015[web/filenode.py: alter download code to use the new #993 interface.
13016Kevan Carstensen <kevan@isnotajoke.com>**20100717015426
13017 Ignore-this: 4e8b89f9b616755f1f644ecea4cda0bb
13018] {
13019hunk ./src/allmydata/web/filenode.py 167
13020             # properly. So we assume that at least the browser will agree
13021             # with itself, and echo back the same bytes that we were given.
13022             filename = get_arg(req, "filename", self.name) or "unknown"
13023-            if self.node.is_mutable():
13024-                # some day: d = self.node.get_best_version()
13025-                d = makeMutableDownloadable(self.node)
13026-            else:
13027-                d = defer.succeed(self.node)
13028+            d = self.node.get_best_readable_version()
13029             d.addCallback(lambda dn: FileDownloader(dn, filename))
13030             return d
13031         if t == "json":
13032hunk ./src/allmydata/web/filenode.py 191
13033         if t:
13034             raise WebError("GET file: bad t=%s" % t)
13035         filename = get_arg(req, "filename", self.name) or "unknown"
13036-        if self.node.is_mutable():
13037-            # some day: d = self.node.get_best_version()
13038-            d = makeMutableDownloadable(self.node)
13039-        else:
13040-            d = defer.succeed(self.node)
13041+        d = self.node.get_best_readable_version()
13042         d.addCallback(lambda dn: FileDownloader(dn, filename))
13043         return d
13044 
13045hunk ./src/allmydata/web/filenode.py 285
13046         d.addCallback(lambda res: self.node.get_uri())
13047         return d
13048 
13049-class MutableDownloadable:
13050-    #implements(IDownloadable)
13051-    def __init__(self, size, node):
13052-        self.size = size
13053-        self.node = node
13054-    def get_size(self):
13055-        return self.size
13056-    def is_mutable(self):
13057-        return True
13058-    def read(self, consumer, offset=0, size=None):
13059-        d = self.node.download_best_version()
13060-        d.addCallback(self._got_data, consumer, offset, size)
13061-        return d
13062-    def _got_data(self, contents, consumer, offset, size):
13063-        start = offset
13064-        if size is not None:
13065-            end = offset+size
13066-        else:
13067-            end = self.size
13068-        # SDMF: we can write the whole file in one big chunk
13069-        consumer.write(contents[start:end])
13070-        return consumer
13071-
13072-def makeMutableDownloadable(n):
13073-    d = defer.maybeDeferred(n.get_size_of_best_version)
13074-    d.addCallback(MutableDownloadable, n)
13075-    return d
13076 
13077 class FileDownloader(rend.Page):
13078     # since we override the rendering process (to let the tahoe Downloader
13079}
13080[test/common.py: remove FileTooLargeErrors that tested for an SDMF limitation that no longer exists
13081Kevan Carstensen <kevan@isnotajoke.com>**20100717015501
13082 Ignore-this: 8b17689d9391a4870a327c1d7c0b3225
13083] {
13084hunk ./src/allmydata/test/common.py 198
13085         self.init_from_cap(make_mutable_file_cap())
13086     def create(self, contents, key_generator=None, keysize=None):
13087         initial_contents = self._get_initial_contents(contents)
13088-        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
13089-            raise FileTooLargeError("SDMF is limited to one segment, and "
13090-                                    "%d > %d" % (initial_contents.get_size(),
13091-                                                 self.MUTABLE_SIZELIMIT))
13092         data = initial_contents.read(initial_contents.get_size())
13093         data = "".join(data)
13094         self.all_contents[self.storage_index] = data
13095hunk ./src/allmydata/test/common.py 326
13096 
13097 
13098     def overwrite(self, new_contents):
13099-        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
13100-            raise FileTooLargeError("SDMF is limited to one segment, and "
13101-                                    "%d > %d" % (new_contents.get_size(),
13102-                                                 self.MUTABLE_SIZELIMIT))
13103         assert not self.is_readonly()
13104         new_data = new_contents.read(new_contents.get_size())
13105         new_data = "".join(new_data)
13106}
13107[nodemaker.py: resolve a conflict introduced in one of the 1.7.1 patches
13108Kevan Carstensen <kevan@isnotajoke.com>**20100720213109
13109 Ignore-this: 4e7d4e611f4cdf04824e9040167aa11
13110] hunk ./src/allmydata/nodemaker.py 107
13111                          "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
13112             node.raise_error()
13113         d = self.create_mutable_file(lambda n:
13114-                                     pack_children(n, initial_children))
13115+                                     MutableData(pack_children(initial_children,
13116+                                                    n.get_writekey())),
13117+                                     version)
13118         d.addCallback(self._create_dirnode)
13119         return d
13120 
13121[frontends/sftpd.py: fix conflicts with trunk
13122Kevan Carstensen <kevan@isnotajoke.com>**20100727224651
13123 Ignore-this: 5636e7a27162bf3ca14d6c9dc07a015
13124] {
13125hunk ./src/allmydata/frontends/sftpd.py 664
13126         else:
13127             assert IFileNode.providedBy(filenode), filenode
13128 
13129-            # TODO: use download interface described in #993 when implemented.
13130-            if filenode.is_mutable():
13131-                self.async.addCallback(lambda ign: filenode.download_best_version())
13132-                def _downloaded(data):
13133-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
13134-                    self.consumer.write(data)
13135-                    self.consumer.finish()
13136-                    return None
13137-                self.async.addCallback(_downloaded)
13138-            else:
13139-                download_size = filenode.get_size()
13140-                assert download_size is not None, "download_size is None"
13141+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
13142+
13143+            def _read(version):
13144+                if noisy: self.log("_read", level=NOISY)
13145+                download_size = version.get_size()
13146+                assert download_size is not None
13147+
13148                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
13149 
13150hunk ./src/allmydata/frontends/sftpd.py 673
13151-                if noisy: self.log("_read", level=NOISY)
13152                 version.read(self.consumer, 0, None)
13153             self.async.addCallback(_read)
13154 
13155}
13156[interfaces.py: Create an IWritable interface
13157Kevan Carstensen <kevan@isnotajoke.com>**20100727224703
13158 Ignore-this: 3fd15da701c31c024963d7ee5c896124
13159] hunk ./src/allmydata/interfaces.py 633
13160         """
13161 
13162 
13163+class IWritable(Interface):
13164+    """
13165+    I define methods that callers can use to update SDMF and MDMF
13166+    mutable files on a Tahoe-LAFS grid.
13167+    """
13168+    # XXX: For the moment, we have only this. It is possible that we
13169+    #      want to move overwrite() and modify() in here too.
13170+    def update(data, offset):
13171+        """
13172+        I write the data from my data argument to the MDMF file,
13173+        starting at offset. I continue writing data until my data
13174+        argument is exhausted, appending data to the file as necessary.
13175+        """
13176+        # assert IMutableUploadable.providedBy(data)
13177+        # to append data: offset=node.get_size_of_best_version()
13178+        # do we want to support compacting MDMF?
13179+        # for an MDMF file, this can be done with O(data.get_size())
13180+        # memory. For an SDMF file, any modification takes
13181+        # O(node.get_size_of_best_version()).
13182+
13183+
13184 class IMutableFileVersion(IReadable):
13185     """I provide access to a particular version of a mutable file. The
13186     access is read/write if I was obtained from a filenode derived from
13187[mutable/layout.py: Alter MDMFSlotWriteProxy to perform all write operations in one actual write operation
13188Kevan Carstensen <kevan@isnotajoke.com>**20100727224725
13189 Ignore-this: 41d577e9d65eba9a38a4051c2a05d4be
13190] {
13191hunk ./src/allmydata/mutable/layout.py 814
13192         # last thing we write to the remote server.
13193         self._offsets = {}
13194         self._testvs = []
13195+        # This is a list of write vectors that will be sent to our
13196+        # remote server once we are directed to write things there.
13197+        self._writevs = []
13198         self._secrets = secrets
13199         # The segment size needs to be a multiple of the k parameter --
13200         # any padding should have been carried out by the publisher
13201hunk ./src/allmydata/mutable/layout.py 947
13202 
13203     def put_block(self, data, segnum, salt):
13204         """
13205-        Put the encrypted-and-encoded data segment in the slot, along
13206-        with the salt.
13207+        I queue a write vector for the data, salt, and segment number
13208+        provided to me. I return None, as I do not actually cause
13209+        anything to be written yet.
13210         """
13211         if segnum >= self._num_segments:
13212             raise LayoutInvalid("I won't overwrite the private key")
13213hunk ./src/allmydata/mutable/layout.py 967
13214         offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
13215         data = salt + data
13216 
13217-        datavs = [tuple([offset, data])]
13218-        return self._write(datavs)
13219+        self._writevs.append(tuple([offset, data]))
13220 
13221 
13222     def put_encprivkey(self, encprivkey):
13223hunk ./src/allmydata/mutable/layout.py 972
13224         """
13225-        Put the encrypted private key in the remote slot.
13226+        I queue a write vector for the encrypted private key provided to
13227+        me.
13228         """
13229         assert self._offsets
13230         assert self._offsets['enc_privkey']
13231hunk ./src/allmydata/mutable/layout.py 986
13232         if "share_hash_chain" in self._offsets:
13233             raise LayoutInvalid("You must write this before the block hash tree")
13234 
13235-        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
13236-        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
13237-        def _on_failure():
13238-            del(self._offsets['block_hash_tree'])
13239-        return self._write(datavs, on_failure=_on_failure)
13240+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + \
13241+            len(encprivkey)
13242+        self._writevs.append(tuple([self._offsets['enc_privkey'], encprivkey]))
13243 
13244 
13245     def put_blockhashes(self, blockhashes):
13246hunk ./src/allmydata/mutable/layout.py 993
13247         """
13248-        Put the block hash tree in the remote slot.
13249+        I queue a write vector to put the block hash tree in blockhashes
13250+        onto the remote server.
13251 
13252hunk ./src/allmydata/mutable/layout.py 996
13253-        The encrypted private key must be put before the block hash
13254+        The encrypted private key must be queued before the block hash
13255         tree, since we need to know how large it is to know where the
13256         block hash tree should go. The block hash tree must be put
13257         before the salt hash tree, since its size determines the
13258hunk ./src/allmydata/mutable/layout.py 1014
13259                                 "you put the share hash chain")
13260         blockhashes_s = "".join(blockhashes)
13261         self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
13262-        datavs = []
13263-        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
13264-        def _on_failure():
13265-            del(self._offsets['share_hash_chain'])
13266-        return self._write(datavs, on_failure=_on_failure)
13267+
13268+        self._writevs.append(tuple([self._offsets['block_hash_tree'],
13269+                                  blockhashes_s]))
13270 
13271 
13272     def put_sharehashes(self, sharehashes):
13273hunk ./src/allmydata/mutable/layout.py 1021
13274         """
13275-        Put the share hash chain in the remote slot.
13276+        I queue a write vector to put the share hash chain in my
13277+        argument onto the remote server.
13278 
13279hunk ./src/allmydata/mutable/layout.py 1024
13280-        The salt hash tree must be put before the share hash chain,
13281+        The salt hash tree must be queued before the share hash chain,
13282         since we need to know where the salt hash tree ends before we
13283         can know where the share hash chain starts. The share hash chain
13284         must be put before the signature, since the length of the packed
13285hunk ./src/allmydata/mutable/layout.py 1044
13286         if "verification_key" in self._offsets:
13287             raise LayoutInvalid("You must write the share hash chain "
13288                                 "before you write the signature")
13289-        datavs = []
13290         sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
13291                                   for i in sorted(sharehashes.keys())])
13292         self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
13293hunk ./src/allmydata/mutable/layout.py 1047
13294-        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
13295-        def _on_failure():
13296-            del(self._offsets['signature'])
13297-        return self._write(datavs, on_failure=_on_failure)
13298+        self._writevs.append(tuple([self._offsets['share_hash_chain'],
13299+                            sharehashes_s]))
13300 
13301 
13302     def put_root_hash(self, roothash):
13303hunk ./src/allmydata/mutable/layout.py 1069
13304         if len(roothash) != HASH_SIZE:
13305             raise LayoutInvalid("hashes and salts must be exactly %d bytes"
13306                                  % HASH_SIZE)
13307-        datavs = []
13308         self._root_hash = roothash
13309         # To write both of these values, we update the checkstring on
13310         # the remote server, which includes them
13311hunk ./src/allmydata/mutable/layout.py 1073
13312         checkstring = self.get_checkstring()
13313-        datavs.append(tuple([0, checkstring]))
13314+        self._writevs.append(tuple([0, checkstring]))
13315         # This write, if successful, changes the checkstring, so we need
13316         # to update our internal checkstring to be consistent with the
13317         # one on the server.
13318hunk ./src/allmydata/mutable/layout.py 1077
13319-        def _on_success():
13320-            self._testvs = [(0, len(checkstring), "eq", checkstring)]
13321-        def _on_failure():
13322-            self._root_hash = None
13323-        return self._write(datavs,
13324-                           on_success=_on_success,
13325-                           on_failure=_on_failure)
13326 
13327 
13328     def get_signable(self):
13329hunk ./src/allmydata/mutable/layout.py 1100
13330 
13331     def put_signature(self, signature):
13332         """
13333-        Put the signature field to the remote slot.
13334+        I queue a write vector for the signature of the MDMF share.
13335 
13336         I require that the root hash and share hash chain have been put
13337         to the grid before I will write the signature to the grid.
13338hunk ./src/allmydata/mutable/layout.py 1123
13339             raise LayoutInvalid("You must write the signature before the verification key")
13340 
13341         self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
13342-        datavs = []
13343-        datavs.append(tuple([self._offsets['signature'], signature]))
13344-        def _on_failure():
13345-            del(self._offsets['verification_key'])
13346-        return self._write(datavs, on_failure=_on_failure)
13347+        self._writevs.append(tuple([self._offsets['signature'], signature]))
13348 
13349 
13350     def put_verification_key(self, verification_key):
13351hunk ./src/allmydata/mutable/layout.py 1128
13352         """
13353-        Put the verification key into the remote slot.
13354+        I queue a write vector for the verification key.
13355 
13356         I require that the signature have been written to the storage
13357         server before I allow the verification key to be written to the
13358hunk ./src/allmydata/mutable/layout.py 1138
13359             raise LayoutInvalid("You must put the signature before you "
13360                                 "can put the verification key")
13361         self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
13362-        datavs = []
13363-        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
13364-        def _on_failure():
13365-            del(self._offsets['EOF'])
13366-        return self._write(datavs, on_failure=_on_failure)
13367+        self._writevs.append(tuple([self._offsets['verification_key'],
13368+                            verification_key]))
13369+
13370 
13371     def _get_offsets_tuple(self):
13372         return tuple([(key, value) for key, value in self._offsets.items()])
13373hunk ./src/allmydata/mutable/layout.py 1145
13374 
13375+
13376     def get_verinfo(self):
13377         return (self._seqnum,
13378                 self._root_hash,
13379hunk ./src/allmydata/mutable/layout.py 1159
13380 
13381     def finish_publishing(self):
13382         """
13383-        Write the offset table and encoding parameters to the remote
13384-        slot, since that's the only thing we have yet to publish at this
13385-        point.
13386+        I add a write vector for the offsets table, and then cause all
13387+        of the write vectors that I've dealt with so far to be published
13388+        to the remote server, ending the write process.
13389         """
13390         if "EOF" not in self._offsets:
13391             raise LayoutInvalid("You must put the verification key before "
13392hunk ./src/allmydata/mutable/layout.py 1174
13393                               self._offsets['signature'],
13394                               self._offsets['verification_key'],
13395                               self._offsets['EOF'])
13396-        datavs = []
13397-        datavs.append(tuple([offsets_offset, offsets]))
13398+        self._writevs.append(tuple([offsets_offset, offsets]))
13399         encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
13400         params = struct.pack(">BBQQ",
13401                              self._required_shares,
13402hunk ./src/allmydata/mutable/layout.py 1181
13403                              self._total_shares,
13404                              self._segment_size,
13405                              self._data_length)
13406-        datavs.append(tuple([encoding_parameters_offset, params]))
13407-        return self._write(datavs)
13408+        self._writevs.append(tuple([encoding_parameters_offset, params]))
13409+        return self._write(self._writevs)
13410 
13411 
13412     def _write(self, datavs, on_failure=None, on_success=None):
13413}
13414[test/test_mutable.py: test that write operations occur all at once
13415Kevan Carstensen <kevan@isnotajoke.com>**20100727224817
13416 Ignore-this: 44cb37c6887ee9baa3e67645ece9555d
13417] {
13418hunk ./src/allmydata/test/test_mutable.py 100
13419         self.storage = storage
13420         self.queries = 0
13421     def callRemote(self, methname, *args, **kwargs):
13422+        self.queries += 1
13423         def _call():
13424             meth = getattr(self, methname)
13425             return meth(*args, **kwargs)
13426hunk ./src/allmydata/test/test_mutable.py 109
13427         return d
13428 
13429     def callRemoteOnly(self, methname, *args, **kwargs):
13430+        self.queries += 1
13431         d = self.callRemote(methname, *args, **kwargs)
13432         d.addBoth(lambda ignore: None)
13433         pass
13434hunk ./src/allmydata/test/test_mutable.py 370
13435         return d
13436 
13437 
13438+    def test_mdmf_write_count(self):
13439+        # Publishing an MDMF file should only cause one write for each
13440+        # share that is to be published. Otherwise, we introduce
13441+        # undesirable semantics that are a regression from SDMF
13442+        upload = MutableData("MDMF" * 100000) # about 400 KiB
13443+        d = self.nodemaker.create_mutable_file(upload,
13444+                                               version=MDMF_VERSION)
13445+        def _check_server_write_counts(ignored):
13446+            sb = self.nodemaker.storage_broker
13447+            peers = sb.test_servers.values()
13448+            for peer in peers:
13449+                self.failUnlessEqual(peer.queries, 1)
13450+        d.addCallback(_check_server_write_counts)
13451+        return d
13452+
13453+
13454     def test_create_with_initial_contents(self):
13455         upload1 = MutableData("contents 1")
13456         d = self.nodemaker.create_mutable_file(upload1)
13457}
13458[test/test_storage.py: modify proxy tests to work with the new writing semantics
13459Kevan Carstensen <kevan@isnotajoke.com>**20100727224853
13460 Ignore-this: 2b6bdde6dc9d8e4e7f096cdb725b40cf
13461] {
13462hunk ./src/allmydata/test/test_storage.py 1681
13463         # diagnose the problem. This test ensures that the read vector
13464         # is working appropriately.
13465         mw = self._make_new_mw("si1", 0)
13466-        d = defer.succeed(None)
13467 
13468hunk ./src/allmydata/test/test_storage.py 1682
13469-        # Write one share. This should return a checkstring of nothing,
13470-        # since there is no data there.
13471-        d.addCallback(lambda ignored:
13472-            mw.put_block(self.block, 0, self.salt))
13473-        def _check_first_write(results):
13474-            result, readvs = results
13475-            self.failUnless(result)
13476-            self.failIf(readvs)
13477-        d.addCallback(_check_first_write)
13478-        # Now, there should be a different checkstring returned when
13479-        # we write other shares
13480-        d.addCallback(lambda ignored:
13481-            mw.put_block(self.block, 1, self.salt))
13482-        def _check_next_write(results):
13483-            result, readvs = results
13484+        for i in xrange(6):
13485+            mw.put_block(self.block, i, self.salt)
13486+        mw.put_encprivkey(self.encprivkey)
13487+        mw.put_blockhashes(self.block_hash_tree)
13488+        mw.put_sharehashes(self.share_hash_chain)
13489+        mw.put_root_hash(self.root_hash)
13490+        mw.put_signature(self.signature)
13491+        mw.put_verification_key(self.verification_key)
13492+        d = mw.finish_publishing()
13493+        def _then(results):
13494+            self.failUnless(len(results), 2)
13495+            result, readv = results
13496             self.failUnless(result)
13497hunk ./src/allmydata/test/test_storage.py 1695
13498-            self.expected_checkstring = mw.get_checkstring()
13499-            self.failUnlessIn(0, readvs)
13500-            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
13501-        d.addCallback(_check_next_write)
13502-        # Add the other four shares
13503-        for i in xrange(2, 6):
13504-            d.addCallback(lambda ignored, i=i:
13505-                mw.put_block(self.block, i, self.salt))
13506-            d.addCallback(_check_next_write)
13507-        # Add the encrypted private key
13508-        d.addCallback(lambda ignored:
13509-            mw.put_encprivkey(self.encprivkey))
13510-        d.addCallback(_check_next_write)
13511-        # Add the block hash tree and share hash tree
13512-        d.addCallback(lambda ignored:
13513-            mw.put_blockhashes(self.block_hash_tree))
13514-        d.addCallback(_check_next_write)
13515-        d.addCallback(lambda ignored:
13516-            mw.put_sharehashes(self.share_hash_chain))
13517-        d.addCallback(_check_next_write)
13518-        # Add the root hash and the salt hash. This should change the
13519-        # checkstring, but not in a way that we'll be able to see right
13520-        # now, since the read vectors are applied before the write
13521-        # vectors.
13522+            self.failIf(readv)
13523+            self.old_checkstring = mw.get_checkstring()
13524+            mw.set_checkstring("")
13525+        d.addCallback(_then)
13526         d.addCallback(lambda ignored:
13527hunk ./src/allmydata/test/test_storage.py 1700
13528-            mw.put_root_hash(self.root_hash))
13529-        def _check_old_testv_after_new_one_is_written(results):
13530+            mw.finish_publishing())
13531+        def _then_again(results):
13532+            self.failUnlessEqual(len(results), 2)
13533             result, readvs = results
13534hunk ./src/allmydata/test/test_storage.py 1704
13535-            self.failUnless(result)
13536+            self.failIf(result)
13537             self.failUnlessIn(0, readvs)
13538hunk ./src/allmydata/test/test_storage.py 1706
13539-            self.failUnlessEqual(self.expected_checkstring,
13540-                                 readvs[0][0])
13541-            new_checkstring = mw.get_checkstring()
13542-            self.failIfEqual(new_checkstring,
13543-                             readvs[0][0])
13544-        d.addCallback(_check_old_testv_after_new_one_is_written)
13545-        # Now add the signature. This should succeed, meaning that the
13546-        # data gets written and the read vector matches what the writer
13547-        # thinks should be there.
13548-        d.addCallback(lambda ignored:
13549-            mw.put_signature(self.signature))
13550-        d.addCallback(_check_next_write)
13551+            readv = readvs[0][0]
13552+            self.failUnlessEqual(readv, self.old_checkstring)
13553+        d.addCallback(_then_again)
13554         # The checkstring remains the same for the rest of the process.
13555         return d
13556 
13557hunk ./src/allmydata/test/test_storage.py 1811
13558         # same share.
13559         mw1 = self._make_new_mw("si1", 0)
13560         mw2 = self._make_new_mw("si1", 0)
13561-        d = defer.succeed(None)
13562+
13563         def _check_success(results):
13564             result, readvs = results
13565             self.failUnless(result)
13566hunk ./src/allmydata/test/test_storage.py 1820
13567             result, readvs = results
13568             self.failIf(result)
13569 
13570-        d.addCallback(lambda ignored:
13571-            mw1.put_block(self.block, 0, self.salt))
13572+        def _write_share(mw):
13573+            for i in xrange(6):
13574+                mw.put_block(self.block, i, self.salt)
13575+            mw.put_encprivkey(self.encprivkey)
13576+            mw.put_blockhashes(self.block_hash_tree)
13577+            mw.put_sharehashes(self.share_hash_chain)
13578+            mw.put_root_hash(self.root_hash)
13579+            mw.put_signature(self.signature)
13580+            mw.put_verification_key(self.verification_key)
13581+            return mw.finish_publishing()
13582+        d = _write_share(mw1)
13583         d.addCallback(_check_success)
13584         d.addCallback(lambda ignored:
13585hunk ./src/allmydata/test/test_storage.py 1833
13586-            mw2.put_block(self.block, 0, self.salt))
13587+            _write_share(mw2))
13588         d.addCallback(_check_failure)
13589         return d
13590 
13591hunk ./src/allmydata/test/test_storage.py 1859
13592 
13593     def test_write_test_vectors(self):
13594         # If we give the write proxy a bogus test vector at
13595-        # any point during the process, it should fail to write.
13596+        # any point during the process, it should fail to write when we
13597+        # tell it to write.
13598+        def _check_failure(results):
13599+            self.failUnlessEqual(len(results), 2)
13600+            res, d = results
13601+            self.failIf(res)
13602+
13603+        def _check_success(results):
13604+            self.failUnlessEqual(len(results), 2)
13605+            res, d = results
13606+            self.failUnless(results)
13607+
13608         mw = self._make_new_mw("si1", 0)
13609         mw.set_checkstring("this is a lie")
13610hunk ./src/allmydata/test/test_storage.py 1873
13611-        # The initial write should be expecting to find the improbable
13612-        # checkstring above in place; finding nothing, it should fail.
13613-        d = defer.succeed(None)
13614-        d.addCallback(lambda ignored:
13615-            mw.put_block(self.block, 0, self.salt))
13616-        def _check_failure(results):
13617-            result, readv = results
13618-            self.failIf(result)
13619+        for i in xrange(6):
13620+            mw.put_block(self.block, i, self.salt)
13621+        mw.put_encprivkey(self.encprivkey)
13622+        mw.put_blockhashes(self.block_hash_tree)
13623+        mw.put_sharehashes(self.share_hash_chain)
13624+        mw.put_root_hash(self.root_hash)
13625+        mw.put_signature(self.signature)
13626+        mw.put_verification_key(self.verification_key)
13627+        d = mw.finish_publishing()
13628         d.addCallback(_check_failure)
13629hunk ./src/allmydata/test/test_storage.py 1883
13630-        # Now set the checkstring to the empty string, which
13631-        # indicates that no share is there.
13632         d.addCallback(lambda ignored:
13633             mw.set_checkstring(""))
13634         d.addCallback(lambda ignored:
13635hunk ./src/allmydata/test/test_storage.py 1886
13636-            mw.put_block(self.block, 0, self.salt))
13637-        def _check_success(results):
13638-            result, readv = results
13639-            self.failUnless(result)
13640-        d.addCallback(_check_success)
13641-        # Now set the checkstring to something wrong
13642-        d.addCallback(lambda ignored:
13643-            mw.set_checkstring("something wrong"))
13644-        # This should fail to do anything
13645-        d.addCallback(lambda ignored:
13646-            mw.put_block(self.block, 1, self.salt))
13647-        d.addCallback(_check_failure)
13648-        # Now set it back to what it should be.
13649-        d.addCallback(lambda ignored:
13650-            mw.set_checkstring(mw.get_checkstring()))
13651-        for i in xrange(1, 6):
13652-            d.addCallback(lambda ignored, i=i:
13653-                mw.put_block(self.block, i, self.salt))
13654-            d.addCallback(_check_success)
13655-        d.addCallback(lambda ignored:
13656-            mw.put_encprivkey(self.encprivkey))
13657-        d.addCallback(_check_success)
13658-        d.addCallback(lambda ignored:
13659-            mw.put_blockhashes(self.block_hash_tree))
13660-        d.addCallback(_check_success)
13661-        d.addCallback(lambda ignored:
13662-            mw.put_sharehashes(self.share_hash_chain))
13663-        d.addCallback(_check_success)
13664-        def _keep_old_checkstring(ignored):
13665-            self.old_checkstring = mw.get_checkstring()
13666-            mw.set_checkstring("foobarbaz")
13667-        d.addCallback(_keep_old_checkstring)
13668-        d.addCallback(lambda ignored:
13669-            mw.put_root_hash(self.root_hash))
13670-        d.addCallback(_check_failure)
13671-        d.addCallback(lambda ignored:
13672-            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
13673-        def _restore_old_checkstring(ignored):
13674-            mw.set_checkstring(self.old_checkstring)
13675-        d.addCallback(_restore_old_checkstring)
13676-        d.addCallback(lambda ignored:
13677-            mw.put_root_hash(self.root_hash))
13678-        d.addCallback(_check_success)
13679-        # The checkstring should have been set appropriately for us on
13680-        # the last write; if we try to change it to something else,
13681-        # that change should cause the verification key step to fail.
13682-        d.addCallback(lambda ignored:
13683-            mw.set_checkstring("something else"))
13684-        d.addCallback(lambda ignored:
13685-            mw.put_signature(self.signature))
13686-        d.addCallback(_check_failure)
13687-        d.addCallback(lambda ignored:
13688-            mw.set_checkstring(mw.get_checkstring()))
13689-        d.addCallback(lambda ignored:
13690-            mw.put_signature(self.signature))
13691-        d.addCallback(_check_success)
13692-        d.addCallback(lambda ignored:
13693-            mw.put_verification_key(self.verification_key))
13694+            mw.finish_publishing())
13695         d.addCallback(_check_success)
13696         return d
13697 
13698hunk ./src/allmydata/test/test_storage.py 1891
13699 
13700-    def test_offset_only_set_on_success(self):
13701-        # The write proxy should be smart enough to detect when a write
13702-        # has failed, and to temper its definition of progress based on
13703-        # that.
13704-        mw = self._make_new_mw("si1", 0)
13705-        d = defer.succeed(None)
13706-        for i in xrange(1, 6):
13707-            d.addCallback(lambda ignored, i=i:
13708-                mw.put_block(self.block, i, self.salt))
13709-        def _break_checkstring(ignored):
13710-            self._old_checkstring = mw.get_checkstring()
13711-            mw.set_checkstring("foobarbaz")
13712-
13713-        def _fix_checkstring(ignored):
13714-            mw.set_checkstring(self._old_checkstring)
13715-
13716-        d.addCallback(_break_checkstring)
13717-
13718-        # Setting the encrypted private key shouldn't work now, which is
13719-        # to be expected and is tested elsewhere. We also want to make
13720-        # sure that we can't add the block hash tree after a failed
13721-        # write of this sort.
13722-        d.addCallback(lambda ignored:
13723-            mw.put_encprivkey(self.encprivkey))
13724-        d.addCallback(lambda ignored:
13725-            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
13726-                            None,
13727-                            mw.put_blockhashes, self.block_hash_tree))
13728-        d.addCallback(_fix_checkstring)
13729-        d.addCallback(lambda ignored:
13730-            mw.put_encprivkey(self.encprivkey))
13731-        d.addCallback(_break_checkstring)
13732-        d.addCallback(lambda ignored:
13733-            mw.put_blockhashes(self.block_hash_tree))
13734-        d.addCallback(lambda ignored:
13735-            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
13736-                            None,
13737-                            mw.put_sharehashes, self.share_hash_chain))
13738-        d.addCallback(_fix_checkstring)
13739-        d.addCallback(lambda ignored:
13740-            mw.put_blockhashes(self.block_hash_tree))
13741-        d.addCallback(_break_checkstring)
13742-        d.addCallback(lambda ignored:
13743-            mw.put_sharehashes(self.share_hash_chain))
13744-        d.addCallback(lambda ignored:
13745-            self.shouldFail(LayoutInvalid, "out-of-order root hash",
13746-                            None,
13747-                            mw.put_root_hash, self.root_hash))
13748-        d.addCallback(_fix_checkstring)
13749-        d.addCallback(lambda ignored:
13750-            mw.put_sharehashes(self.share_hash_chain))
13751-        d.addCallback(_break_checkstring)
13752-        d.addCallback(lambda ignored:
13753-            mw.put_root_hash(self.root_hash))
13754-        d.addCallback(lambda ignored:
13755-            self.shouldFail(LayoutInvalid, "out-of-order signature",
13756-                            None,
13757-                            mw.put_signature, self.signature))
13758-        d.addCallback(_fix_checkstring)
13759-        d.addCallback(lambda ignored:
13760-            mw.put_root_hash(self.root_hash))
13761-        d.addCallback(_break_checkstring)
13762-        d.addCallback(lambda ignored:
13763-            mw.put_signature(self.signature))
13764-        d.addCallback(lambda ignored:
13765-            self.shouldFail(LayoutInvalid, "out-of-order verification key",
13766-                            None,
13767-                            mw.put_verification_key,
13768-                            self.verification_key))
13769-        d.addCallback(_fix_checkstring)
13770-        d.addCallback(lambda ignored:
13771-            mw.put_signature(self.signature))
13772-        d.addCallback(_break_checkstring)
13773-        d.addCallback(lambda ignored:
13774-            mw.put_verification_key(self.verification_key))
13775-        d.addCallback(lambda ignored:
13776-            self.shouldFail(LayoutInvalid, "out-of-order finish",
13777-                            None,
13778-                            mw.finish_publishing))
13779-        return d
13780-
13781-
13782     def serialize_blockhashes(self, blockhashes):
13783         return "".join(blockhashes)
13784 
13785hunk ./src/allmydata/test/test_storage.py 1905
13786         # This translates to a file with 6 6-byte segments, and with 2-byte
13787         # blocks.
13788         mw = self._make_new_mw("si1", 0)
13789-        mw2 = self._make_new_mw("si1", 1)
13790         # Test writing some blocks.
13791         read = self.ss.remote_slot_readv
13792         expected_sharedata_offset = struct.calcsize(MDMFHEADER)
13793hunk ./src/allmydata/test/test_storage.py 1910
13794         written_block_size = 2 + len(self.salt)
13795         written_block = self.block + self.salt
13796-        def _check_block_write(i, share):
13797-            self.failUnlessEqual(read("si1", [share], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
13798-                                {share: [written_block]})
13799-        d = defer.succeed(None)
13800         for i in xrange(6):
13801hunk ./src/allmydata/test/test_storage.py 1911
13802-            d.addCallback(lambda ignored, i=i:
13803-                mw.put_block(self.block, i, self.salt))
13804-            d.addCallback(lambda ignored, i=i:
13805-                _check_block_write(i, 0))
13806-        # Now try the same thing, but with share 1 instead of share 0.
13807-        for i in xrange(6):
13808-            d.addCallback(lambda ignored, i=i:
13809-                mw2.put_block(self.block, i, self.salt))
13810-            d.addCallback(lambda ignored, i=i:
13811-                _check_block_write(i, 1))
13812+            mw.put_block(self.block, i, self.salt)
13813 
13814hunk ./src/allmydata/test/test_storage.py 1913
13815-        # Next, we make a fake encrypted private key, and put it onto the
13816-        # storage server.
13817-        d.addCallback(lambda ignored:
13818-            mw.put_encprivkey(self.encprivkey))
13819-        expected_private_key_offset = expected_sharedata_offset + \
13820+        mw.put_encprivkey(self.encprivkey)
13821+        mw.put_blockhashes(self.block_hash_tree)
13822+        mw.put_sharehashes(self.share_hash_chain)
13823+        mw.put_root_hash(self.root_hash)
13824+        mw.put_signature(self.signature)
13825+        mw.put_verification_key(self.verification_key)
13826+        d = mw.finish_publishing()
13827+        def _check_publish(results):
13828+            self.failUnlessEqual(len(results), 2)
13829+            result, ign = results
13830+            self.failUnless(result, "publish failed")
13831+            for i in xrange(6):
13832+                self.failUnlessEqual(read("si1", [0], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
13833+                                {0: [written_block]})
13834+
13835+            expected_private_key_offset = expected_sharedata_offset + \
13836                                       len(written_block) * 6
13837hunk ./src/allmydata/test/test_storage.py 1930
13838-        self.failUnlessEqual(len(self.encprivkey), 7)
13839-        d.addCallback(lambda ignored:
13840+            self.failUnlessEqual(len(self.encprivkey), 7)
13841             self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
13842hunk ./src/allmydata/test/test_storage.py 1932
13843-                                 {0: [self.encprivkey]}))
13844+                                 {0: [self.encprivkey]})
13845 
13846hunk ./src/allmydata/test/test_storage.py 1934
13847-        # Next, we put a fake block hash tree.
13848-        d.addCallback(lambda ignored:
13849-            mw.put_blockhashes(self.block_hash_tree))
13850-        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
13851-        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
13852-        d.addCallback(lambda ignored:
13853+            expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
13854+            self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
13855             self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
13856hunk ./src/allmydata/test/test_storage.py 1937
13857-                                 {0: [self.block_hash_tree_s]}))
13858+                                 {0: [self.block_hash_tree_s]})
13859 
13860hunk ./src/allmydata/test/test_storage.py 1939
13861-        # Next, put a fake share hash chain
13862-        d.addCallback(lambda ignored:
13863-            mw.put_sharehashes(self.share_hash_chain))
13864-        expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
13865-        d.addCallback(lambda ignored:
13866+            expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
13867             self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
13868hunk ./src/allmydata/test/test_storage.py 1941
13869-                                 {0: [self.share_hash_chain_s]}))
13870+                                 {0: [self.share_hash_chain_s]})
13871 
13872hunk ./src/allmydata/test/test_storage.py 1943
13873-        # Next, we put what is supposed to be the root hash of
13874-        # our share hash tree but isn't       
13875-        d.addCallback(lambda ignored:
13876-            mw.put_root_hash(self.root_hash))
13877-        # The root hash gets inserted at byte 9 (its position is in the header,
13878-        # and is fixed).
13879-        def _check(ignored):
13880             self.failUnlessEqual(read("si1", [0], [(9, 32)]),
13881                                  {0: [self.root_hash]})
13882hunk ./src/allmydata/test/test_storage.py 1945
13883-        d.addCallback(_check)
13884-
13885-        # Next, we put a signature of the header block.
13886-        d.addCallback(lambda ignored:
13887-            mw.put_signature(self.signature))
13888-        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
13889-        self.failUnlessEqual(len(self.signature), 9)
13890-        d.addCallback(lambda ignored:
13891+            expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
13892+            self.failUnlessEqual(len(self.signature), 9)
13893             self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
13894hunk ./src/allmydata/test/test_storage.py 1948
13895-                                 {0: [self.signature]}))
13896+                                 {0: [self.signature]})
13897 
13898hunk ./src/allmydata/test/test_storage.py 1950
13899-        # Next, we put the verification key
13900-        d.addCallback(lambda ignored:
13901-            mw.put_verification_key(self.verification_key))
13902-        expected_verification_key_offset = expected_signature_offset + len(self.signature)
13903-        self.failUnlessEqual(len(self.verification_key), 6)
13904-        d.addCallback(lambda ignored:
13905+            expected_verification_key_offset = expected_signature_offset + len(self.signature)
13906+            self.failUnlessEqual(len(self.verification_key), 6)
13907             self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
13908hunk ./src/allmydata/test/test_storage.py 1953
13909-                                 {0: [self.verification_key]}))
13910+                                 {0: [self.verification_key]})
13911 
13912hunk ./src/allmydata/test/test_storage.py 1955
13913-        def _check_signable(ignored):
13914-            # Make sure that the signable is what we think it should be.
13915             signable = mw.get_signable()
13916             verno, seq, roothash, k, n, segsize, datalen = \
13917                                             struct.unpack(">BQ32sBBQQ",
13918hunk ./src/allmydata/test/test_storage.py 1966
13919             self.failUnlessEqual(n, 10)
13920             self.failUnlessEqual(segsize, 6)
13921             self.failUnlessEqual(datalen, 36)
13922-        d.addCallback(_check_signable)
13923-        # Next, we cause the offset table to be published.
13924-        d.addCallback(lambda ignored:
13925-            mw.finish_publishing())
13926-        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
13927+            expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
13928 
13929hunk ./src/allmydata/test/test_storage.py 1968
13930-        def _check_offsets(ignored):
13931             # Check the version number to make sure that it is correct.
13932             expected_version_number = struct.pack(">B", 1)
13933             self.failUnlessEqual(read("si1", [0], [(0, 1)]),
13934hunk ./src/allmydata/test/test_storage.py 2008
13935             expected_offset = struct.pack(">Q", expected_eof_offset)
13936             self.failUnlessEqual(read("si1", [0], [(99, 8)]),
13937                                  {0: [expected_offset]})
13938-        d.addCallback(_check_offsets)
13939+        d.addCallback(_check_publish)
13940         return d
13941 
13942     def _make_new_mw(self, si, share, datalength=36):
13943}
13944[mutable/publish.py: alter mutable publisher to work with new writing semantics
13945Kevan Carstensen <kevan@isnotajoke.com>**20100727225001
13946 Ignore-this: a6b4628e749e09bfcddf3309271b5831
13947] {
13948hunk ./src/allmydata/mutable/publish.py 389
13949         if self._state == PUSHING_BLOCKS_STATE:
13950             return self.push_segment(self._current_segment)
13951 
13952-        # XXX: Do we want more granularity in states? Is that useful at
13953-        #      all?
13954-        #      Yes -- quicker reaction to UCW.
13955         elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
13956             return self.push_everything_else()
13957 
13958hunk ./src/allmydata/mutable/publish.py 490
13959         results, salt = encoded_and_salt
13960         shares, shareids = results
13961         started = time.time()
13962-        dl = []
13963         for i in xrange(len(shares)):
13964             sharedata = shares[i]
13965             shareid = shareids[i]
13966hunk ./src/allmydata/mutable/publish.py 502
13967 
13968             # find the writer for this share
13969             writer = self.writers[shareid]
13970-            d = writer.put_block(sharedata, segnum, salt)
13971-            d.addCallback(self._got_write_answer, writer, started)
13972-            d.addErrback(self._connection_problem, writer)
13973-            dl.append(d)
13974-        return defer.DeferredList(dl)
13975+            writer.put_block(sharedata, segnum, salt)
13976 
13977 
13978     def push_everything_else(self):
13979hunk ./src/allmydata/mutable/publish.py 510
13980         I put everything else associated with a share.
13981         """
13982         encprivkey = self._encprivkey
13983-        d = self.push_encprivkey()
13984-        d.addCallback(self.push_blockhashes)
13985-        d.addCallback(self.push_sharehashes)
13986-        d.addCallback(self.push_toplevel_hashes_and_signature)
13987-        d.addCallback(self.finish_publishing)
13988+        self.push_encprivkey()
13989+        self.push_blockhashes()
13990+        self.push_sharehashes()
13991+        self.push_toplevel_hashes_and_signature()
13992+        d = self.finish_publishing()
13993         def _change_state(ignored):
13994             self._state = DONE_STATE
13995         d.addCallback(_change_state)
13996hunk ./src/allmydata/mutable/publish.py 525
13997     def push_encprivkey(self):
13998         started = time.time()
13999         encprivkey = self._encprivkey
14000-        dl = []
14001         for writer in self.writers.itervalues():
14002hunk ./src/allmydata/mutable/publish.py 526
14003-            d = writer.put_encprivkey(encprivkey)
14004-            d.addCallback(self._got_write_answer, writer, started)
14005-            d.addErrback(self._connection_problem, writer)
14006-            dl.append(d)
14007-        d = defer.DeferredList(dl)
14008-        return d
14009+            writer.put_encprivkey(encprivkey)
14010 
14011 
14012hunk ./src/allmydata/mutable/publish.py 529
14013-    def push_blockhashes(self, ignored):
14014+    def push_blockhashes(self):
14015         started = time.time()
14016hunk ./src/allmydata/mutable/publish.py 531
14017-        dl = []
14018         self.sharehash_leaves = [None] * len(self.blockhashes)
14019         for shnum, blockhashes in self.blockhashes.iteritems():
14020             t = hashtree.HashTree(blockhashes)
14021hunk ./src/allmydata/mutable/publish.py 538
14022             # set the leaf for future use.
14023             self.sharehash_leaves[shnum] = t[0]
14024             writer = self.writers[shnum]
14025-            d = writer.put_blockhashes(self.blockhashes[shnum])
14026-            d.addCallback(self._got_write_answer, writer, started)
14027-            d.addErrback(self._connection_problem, self.writers[shnum])
14028-            dl.append(d)
14029-        d = defer.DeferredList(dl)
14030-        return d
14031+            writer.put_blockhashes(self.blockhashes[shnum])
14032 
14033 
14034hunk ./src/allmydata/mutable/publish.py 541
14035-    def push_sharehashes(self, ignored):
14036+    def push_sharehashes(self):
14037         started = time.time()
14038         share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
14039         share_hash_chain = {}
14040hunk ./src/allmydata/mutable/publish.py 545
14041-        ds = []
14042         for shnum in xrange(len(self.sharehash_leaves)):
14043             needed_indices = share_hash_tree.needed_hashes(shnum)
14044             self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
14045hunk ./src/allmydata/mutable/publish.py 550
14046                                              for i in needed_indices] )
14047             writer = self.writers[shnum]
14048-            d = writer.put_sharehashes(self.sharehashes[shnum])
14049-            d.addCallback(self._got_write_answer, writer, started)
14050-            d.addErrback(self._connection_problem, writer)
14051-            ds.append(d)
14052+            writer.put_sharehashes(self.sharehashes[shnum])
14053         self.root_hash = share_hash_tree[0]
14054hunk ./src/allmydata/mutable/publish.py 552
14055-        d = defer.DeferredList(ds)
14056-        return d
14057 
14058 
14059hunk ./src/allmydata/mutable/publish.py 554
14060-    def push_toplevel_hashes_and_signature(self, ignored):
14061+    def push_toplevel_hashes_and_signature(self):
14062         # We need to to three things here:
14063         #   - Push the root hash and salt hash
14064         #   - Get the checkstring of the resulting layout; sign that.
14065hunk ./src/allmydata/mutable/publish.py 560
14066         #   - Push the signature
14067         started = time.time()
14068-        ds = []
14069         for shnum in xrange(self.total_shares):
14070             writer = self.writers[shnum]
14071hunk ./src/allmydata/mutable/publish.py 562
14072-            d = writer.put_root_hash(self.root_hash)
14073-            d.addCallback(self._got_write_answer, writer, started)
14074-            ds.append(d)
14075-        d = defer.DeferredList(ds)
14076-        d.addCallback(self._update_checkstring)
14077-        d.addCallback(self._make_and_place_signature)
14078-        return d
14079+            writer.put_root_hash(self.root_hash)
14080+        self._update_checkstring()
14081+        self._make_and_place_signature()
14082 
14083 
14084hunk ./src/allmydata/mutable/publish.py 567
14085-    def _update_checkstring(self, ignored):
14086+    def _update_checkstring(self):
14087         """
14088         After putting the root hash, MDMF files will have the
14089         checkstring written to the storage server. This means that we
14090hunk ./src/allmydata/mutable/publish.py 578
14091         self._checkstring = self.writers.values()[0].get_checkstring()
14092 
14093 
14094-    def _make_and_place_signature(self, ignored):
14095+    def _make_and_place_signature(self):
14096         """
14097         I create and place the signature.
14098         """
14099hunk ./src/allmydata/mutable/publish.py 586
14100         signable = self.writers[0].get_signable()
14101         self.signature = self._privkey.sign(signable)
14102 
14103-        ds = []
14104         for (shnum, writer) in self.writers.iteritems():
14105hunk ./src/allmydata/mutable/publish.py 587
14106-            d = writer.put_signature(self.signature)
14107-            d.addCallback(self._got_write_answer, writer, started)
14108-            d.addErrback(self._connection_problem, writer)
14109-            ds.append(d)
14110-        return defer.DeferredList(ds)
14111+            writer.put_signature(self.signature)
14112 
14113 
14114hunk ./src/allmydata/mutable/publish.py 590
14115-    def finish_publishing(self, ignored):
14116+    def finish_publishing(self):
14117         # We're almost done -- we just need to put the verification key
14118         # and the offsets
14119         started = time.time()
14120hunk ./src/allmydata/mutable/publish.py 601
14121         # TODO: Bad, since we remove from this same dict. We need to
14122         # make a copy, or just use a non-iterated value.
14123         for (shnum, writer) in self.writers.iteritems():
14124-            d = writer.put_verification_key(verification_key)
14125-            d.addCallback(self._got_write_answer, writer, started)
14126-            d.addCallback(self._record_verinfo)
14127-            d.addCallback(lambda ignored, writer=writer:
14128-                writer.finish_publishing())
14129+            writer.put_verification_key(verification_key)
14130+            d = writer.finish_publishing()
14131             d.addCallback(self._got_write_answer, writer, started)
14132             d.addErrback(self._connection_problem, writer)
14133             ds.append(d)
14134hunk ./src/allmydata/mutable/publish.py 606
14135+        self._record_verinfo()
14136         return defer.DeferredList(ds)
14137 
14138 
14139hunk ./src/allmydata/mutable/publish.py 610
14140-    def _record_verinfo(self, ignored):
14141+    def _record_verinfo(self):
14142         self.versioninfo = self.writers.values()[0].get_verinfo()
14143 
14144 
14145}
14146[test/test_mutable.py: Add tests for new servermap behavior
14147Kevan Carstensen <kevan@isnotajoke.com>**20100728232434
14148 Ignore-this: aa6da7dbc9f86eb8840c8f0e779e644d
14149] {
14150hunk ./src/allmydata/test/test_mutable.py 773
14151     def setUp(self):
14152         return self.publish_one()
14153 
14154-    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None):
14155+    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None,
14156+                       update_range=None):
14157         if fn is None:
14158             fn = self._fn
14159         if sb is None:
14160hunk ./src/allmydata/test/test_mutable.py 780
14161             sb = self._storage_broker
14162         smu = ServermapUpdater(fn, sb, Monitor(),
14163-                               ServerMap(), mode)
14164+                               ServerMap(), mode, update_range=update_range)
14165         d = smu.update()
14166         return d
14167 
14168hunk ./src/allmydata/test/test_mutable.py 855
14169         d.addCallback(lambda sm: self.failUnlessOneRecoverable(sm, 10))
14170         return d
14171 
14172+
14173     def test_mark_bad(self):
14174         d = defer.succeed(None)
14175         ms = self.make_servermap
14176hunk ./src/allmydata/test/test_mutable.py 970
14177         return d
14178 
14179 
14180+    def test_fetch_update(self):
14181+        d = defer.succeed(None)
14182+        d.addCallback(lambda ignored:
14183+            self.publish_mdmf())
14184+        d.addCallback(lambda ignored:
14185+            self.make_servermap(mode=MODE_WRITE, update_range=(1, 2)))
14186+        def _check_servermap(sm):
14187+            # 10 shares
14188+            self.failUnlessEqual(len(sm.update_data), 10)
14189+            # one version
14190+            for data in sm.update_data.itervalues():
14191+                self.failUnlessEqual(len(data), 1)
14192+        d.addCallback(_check_servermap)
14193+        return d
14194+
14195+
14196     def test_servermapupdater_finds_sdmf_files(self):
14197         d = defer.succeed(None)
14198         d.addCallback(lambda ignored:
14199hunk ./src/allmydata/test/test_mutable.py 1756
14200 
14201     def test_mdmf_repairable_5shares(self):
14202         d = self.publish_mdmf()
14203-        def _delete_all_shares(ign):
14204+        def _delete_some_shares(ign):
14205             shares = self._storage._peers
14206             for peerid in shares:
14207                 for shnum in list(shares[peerid]):
14208hunk ./src/allmydata/test/test_mutable.py 1762
14209                     if shnum > 5:
14210                         del shares[peerid][shnum]
14211-        d.addCallback(_delete_all_shares)
14212+        d.addCallback(_delete_some_shares)
14213         d.addCallback(lambda ign: self._fn.check(Monitor()))
14214hunk ./src/allmydata/test/test_mutable.py 1764
14215+        def _check(cr):
14216+            self.failIf(cr.is_healthy())
14217+            self.failUnless(cr.is_recoverable())
14218+            return cr
14219+        d.addCallback(_check)
14220         d.addCallback(lambda check_results: self._fn.repair(check_results))
14221hunk ./src/allmydata/test/test_mutable.py 1770
14222-        def _check(crr):
14223+        def _check1(crr):
14224             self.failUnlessEqual(crr.get_successful(), True)
14225hunk ./src/allmydata/test/test_mutable.py 1772
14226-        d.addCallback(_check)
14227+        d.addCallback(_check1)
14228         return d
14229 
14230 
14231}
14232[mutable/filenode.py: add an update method.
14233Kevan Carstensen <kevan@isnotajoke.com>**20100730234029
14234 Ignore-this: 3ed4dcfb8a247812ed357216913334e7
14235] {
14236hunk ./src/allmydata/mutable/filenode.py 9
14237 from foolscap.api import eventually
14238 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
14239      NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
14240-     IMutableFileVersion
14241-from allmydata.util import hashutil, log, consumer
14242+     IMutableFileVersion, IWritable
14243+from allmydata.util import hashutil, log, consumer, deferredutil
14244 from allmydata.util.assertutil import precondition
14245 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
14246 from allmydata.monitor import Monitor
14247hunk ./src/allmydata/mutable/filenode.py 17
14248 from pycryptopp.cipher.aes import AES
14249 
14250 from allmydata.mutable.publish import Publish, MutableFileHandle, \
14251-                                      MutableData
14252+                                      MutableData,\
14253+                                      DEFAULT_MAX_SEGMENT_SIZE, \
14254+                                      TransformingUploadable
14255 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
14256      ResponseCache, UncoordinatedWriteError
14257 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
14258hunk ./src/allmydata/mutable/filenode.py 671
14259     overwrite or modify the contents of the mutable file that I
14260     reference.
14261     """
14262-    implements(IMutableFileVersion)
14263+    implements(IMutableFileVersion, IWritable)
14264 
14265     def __init__(self,
14266                  node,
14267hunk ./src/allmydata/mutable/filenode.py 960
14268     def _did_upload(self, res, size):
14269         self._size = size
14270         return res
14271+
14272+    def _update(self, data, offset):
14273+        """
14274+        Do an update of this mutable file version by inserting data at
14275+        offset within the file. If offset is the EOF, this is an append
14276+        operation. I return a Deferred that fires with the results of
14277+        the update operation when it has completed.
14278+
14279+        In cases where update does not append any data, or where it does
14280+        not append so many blocks that the block count crosses a
14281+        power-of-two boundary, this operation will use roughly
14282+        O(data.get_size()) memory/bandwidth/CPU to perform the update.
14283+        Otherwise, it must download, re-encode, and upload the entire
14284+        file again, which will use O(filesize) resources.
14285+        """
14286+        return self._do_serialized(self._update, data, offset)
14287+
14288+
14289+    def _update(self, data, offset):
14290+        """
14291+        I update the mutable file version represented by this particular
14292+        IMutableVersion by inserting the data in data at the offset
14293+        offset. I return a Deferred that fires when this has been
14294+        completed.
14295+        """
14296+        d = self._do_update_update(data, offset)
14297+        d.addCallback(self._decode_and_decrypt_segments)
14298+        d.addCallback(self._build_uploadable_and_finish)
14299+        return d
14300+
14301+
14302+    def _do_update_update(self, data, offset):
14303+        """
14304+        I start the Servermap update that gets us the data we need to
14305+        continue the update process. I return a Deferred that fires when
14306+        the servermap update is done.
14307+        """
14308+        assert IMutableUploadable.providedBy(data)
14309+        assert self.is_mutable()
14310+        # offset == self.get_size() is valid and means that we are
14311+        # appending data to the file.
14312+        assert offset <= self.get_size()
14313+
14314+        datasize = data.get_size()
14315+        # We'll need the segment that the data starts in, regardless of
14316+        # what we'll do later.
14317+        start_segment = mathutil.div_ceil(offset, DEFAULT_MAX_SEGMENT_SIZE)
14318+        start_segment -= 1
14319+
14320+        # We only need the end segment if the data we append does not go
14321+        # beyond the current end-of-file.
14322+        end_segment = start_segment
14323+        if offset + data.get_size() < self.get_size():
14324+            end_data = offset + data.get_size()
14325+            end_segment = mathutil.div_ceil(end_data, DEFAULT_MAX_SEGMENT_SIZE)
14326+            end_segment -= 1
14327+
14328+        # Now ask for the servermap to be updated in MODE_WRITE with
14329+        # this update range.
14330+        u = ServermapUpdater(self, self._storage_broker, Monitor(),
14331+                             self._servermap,
14332+                             mode=MODE_WRITE,
14333+                             update_range=(start_segment, end_segment))
14334+        return u.update()
14335+
14336+
14337+    def _decode_and_decrypt_segments(self, ignored, data, offset):
14338+        """
14339+        After the servermap update, I take the encrypted and encoded
14340+        data that the servermap fetched while doing its update and
14341+        transform it into decoded-and-decrypted plaintext that can be
14342+        used by the new uploadable. I return a Deferred that fires with
14343+        the segments.
14344+        """
14345+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
14346+        # decode: takes in our blocks and salts from the servermap,
14347+        # returns a Deferred that fires with the corresponding plaintext
14348+        # segments. Does not download -- simply takes advantage of
14349+        # existing infrastructure within the Retrieve class to avoid
14350+        # duplicating code.
14351+        sm = self._servermap
14352+        # XXX: If the methods in the servermap don't work as
14353+        # abstractions, you should rewrite them instead of going around
14354+        # them.
14355+        data = sm.update_data
14356+        data = [data[1] for i in update_data if i[0] == self._verinfo]
14357+        start_seg_data = [d[0] for d in data]
14358+        end_seg_data = [d[1] for d in data]
14359+        d1 = r.decode(start_seg_data)
14360+        d2 = r.decode(end_seg_data)
14361+        return deferredutil.gatherResults([d1, d2])
14362+
14363+
14364+    def _build_uploadable_and_finish(self, segments, data, offset):
14365+        """
14366+        After the process has the plaintext segments, I build the
14367+        TransformingUploadable that the publisher will eventually
14368+        re-upload to the grid. I then invoke the publisher with that
14369+        uploadable, and return a Deferred when the publish operation has
14370+        completed without issue.
14371+        """
14372+        u = TransformingUploadable(data, offset,
14373+                                   DEFAULT_MAX_SEGMENT_SIZE,
14374+                                   segments[0],
14375+                                   segments[1])
14376+        p = Publish(self._node, self._storage_broker, self._servermap)
14377+        return p.update(data, offset, blockhashes)
14378}
14379[mutable/publish.py: learn how to update as well as publish files.
14380Kevan Carstensen <kevan@isnotajoke.com>**20100730234056
14381 Ignore-this: 4c04857280da970f5c1c6c75466f1cd9
14382] {
14383hunk ./src/allmydata/mutable/publish.py 132
14384             kwargs["facility"] = "tahoe.mutable.publish"
14385         return log.msg(*args, **kwargs)
14386 
14387+
14388+    def update(self, data, offset, blockhashes):
14389+        """
14390+        I replace the contents of this file with the contents of data,
14391+        starting at offset. I return a Deferred that fires with None
14392+        when the replacement has been completed, or with an error if
14393+        something went wrong during the process.
14394+
14395+        Note that this process will not upload new shares. If the file
14396+        being updated is in need of repair, callers will have to repair
14397+        it on their own.
14398+        """
14399+        # How this works:
14400+        # 1: Make peer assignments. We'll assign each share that we know
14401+        # about on the grid to that peer that currently holds that
14402+        # share, and will not place any new shares.
14403+        # 2: Setup encoding parameters. Most of these will stay the same
14404+        # -- datalength will change, as will some of the offsets.
14405+        # 3. Upload the new segments.
14406+        # 4. Be done.
14407+        assert IMutableUploadable.providedBy(data)
14408+
14409+        self.data = data
14410+
14411+        # XXX: Use the MutableFileVersion instead.
14412+        self.datalength = self._node.get_size()
14413+        if offset + data.get_size() > self.datalength:
14414+            self.datalength = offset + data.get_size()
14415+
14416+        if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
14417+            self._version = MDMF_VERSION
14418+        else:
14419+            self._version = SDMF_VERSION
14420+
14421+        self.log("starting update")
14422+        self.log("adding new data of length %d at offset %d" % \
14423+                    data.get_size(), offset)
14424+        self.log("new data length is %d" % self.datalength)
14425+        self._status.set_size(self.datalength)
14426+        self._status.set_status("Started")
14427+        self._started = time.time()
14428+
14429+        self.done_deferred = defer.Deferred()
14430+
14431+        self._writekey = self._node.get_writekey()
14432+        assert self._writekey, "need write capability to publish"
14433+
14434+        # first, which servers will we publish to? We require that the
14435+        # servermap was updated in MODE_WRITE, so we can depend upon the
14436+        # peerlist computed by that process instead of computing our own.
14437+        assert self._servermap
14438+        assert self._servermap.last_update_mode in (MODE_WRITE, MODE_CHECK)
14439+        # we will push a version that is one larger than anything present
14440+        # in the grid, according to the servermap.
14441+        self._new_seqnum = self._servermap.highest_seqnum() + 1
14442+        self._status.set_servermap(self._servermap)
14443+
14444+        self.log(format="new seqnum will be %(seqnum)d",
14445+                 seqnum=self._new_seqnum, level=log.NOISY)
14446+
14447+        # We're updating an existing file, so all of the following
14448+        # should be available.
14449+        self.readkey = self._node.get_readkey()
14450+        self.required_shares = self._node.get_required_shares()
14451+        assert self.required_shares is not None
14452+        self.total_shares = self._node.get_total_shares()
14453+        assert self.total_shares is not None
14454+        self._status.set_encoding(self.required_shares, self.total_shares)
14455+
14456+        self._pubkey = self._node.get_pubkey()
14457+        assert self._pubkey
14458+        self._privkey = self._node.get_privkey()
14459+        assert self._privkey
14460+        self._encprivkey = self._node.get_encprivkey()
14461+
14462+        sb = self._storage_broker
14463+        full_peerlist = sb.get_servers_for_index(self._storage_index)
14464+        self.full_peerlist = full_peerlist # for use later, immutable
14465+        self.bad_peers = set() # peerids who have errbacked/refused requests
14466+
14467+        # This will set self.segment_size, self.num_segments, and
14468+        # self.fec. TODO: Does it know how to do the offset? Probably
14469+        # not. So do that part next.
14470+        self.setup_encoding_parameters(offset=offset)
14471+
14472+        # if we experience any surprises (writes which were rejected because
14473+        # our test vector did not match, or shares which we didn't expect to
14474+        # see), we set this flag and report an UncoordinatedWriteError at the
14475+        # end of the publish process.
14476+        self.surprised = False
14477+
14478+        # as a failsafe, refuse to iterate through self.loop more than a
14479+        # thousand times.
14480+        self.looplimit = 1000
14481+
14482+        # we keep track of three tables. The first is our goal: which share
14483+        # we want to see on which servers. This is initially populated by the
14484+        # existing servermap.
14485+        self.goal = set() # pairs of (peerid, shnum) tuples
14486+
14487+        # the second table is our list of outstanding queries: those which
14488+        # are in flight and may or may not be delivered, accepted, or
14489+        # acknowledged. Items are added to this table when the request is
14490+        # sent, and removed when the response returns (or errbacks).
14491+        self.outstanding = set() # (peerid, shnum) tuples
14492+
14493+        # the third is a table of successes: share which have actually been
14494+        # placed. These are populated when responses come back with success.
14495+        # When self.placed == self.goal, we're done.
14496+        self.placed = set() # (peerid, shnum) tuples
14497+
14498+        # we also keep a mapping from peerid to RemoteReference. Each time we
14499+        # pull a connection out of the full peerlist, we add it to this for
14500+        # use later.
14501+        self.connections = {}
14502+
14503+        self.bad_share_checkstrings = {}
14504+
14505+        # This is set at the last step of the publishing process.
14506+        self.versioninfo = ""
14507+
14508+        # we use the servermap to populate the initial goal: this way we will
14509+        # try to update each existing share in place. Since we're
14510+        # updating, we ignore damaged and missing shares -- callers must
14511+        # do a repair to repair and recreate these.
14512+        for (peerid, shnum) in self._servermap.servermap:
14513+            self.goal.add( (peerid, shnum) )
14514+            self.connections[peerid] = self._servermap.connections[peerid]
14515+        self.writers = {}
14516+        if self._version == MDMF_VERSION:
14517+            writer_class = MDMFSlotWriteProxy
14518+        else:
14519+            writer_class = SDMFSlotWriteProxy
14520+
14521+        # For each (peerid, shnum) in self.goal, we make a
14522+        # write proxy for that peer. We'll use this to write
14523+        # shares to the peer.
14524+        for key in self.goal:
14525+            peerid, shnum = key
14526+            write_enabler = self._node.get_write_enabler(peerid)
14527+            renew_secret = self._node.get_renewal_secret(peerid)
14528+            cancel_secret = self._node.get_cancel_secret(peerid)
14529+            secrets = (write_enabler, renew_secret, cancel_secret)
14530+
14531+            self.writers[shnum] =  writer_class(shnum,
14532+                                                self.connections[peerid],
14533+                                                self._storage_index,
14534+                                                secrets,
14535+                                                self._new_seqnum,
14536+                                                self.required_shares,
14537+                                                self.total_shares,
14538+                                                self.segment_size,
14539+                                                self.datalength)
14540+            self.writers[shnum].peerid = peerid
14541+            assert (peerid, shnum) in self._servermap.servermap
14542+            old_versionid, old_timestamp = self._servermap.servermap[key]
14543+            (old_seqnum, old_root_hash, old_salt, old_segsize,
14544+             old_datalength, old_k, old_N, old_prefix,
14545+             old_offsets_tuple) = old_versionid
14546+            self.writers[shnum].set_checkstring(old_seqnum,
14547+                                                old_root_hash,
14548+                                                old_salt)
14549+
14550+        # Our remote shares will not have a complete checkstring until
14551+        # after we are done writing share data and have started to write
14552+        # blocks. In the meantime, we need to know what to look for when
14553+        # writing, so that we can detect UncoordinatedWriteErrors.
14554+        self._checkstring = self.writers.values()[0].get_checkstring()
14555+
14556+        # Now, we start pushing shares.
14557+        self._status.timings["setup"] = time.time() - self._started
14558+        # First, we encrypt, encode, and publish the shares that we need
14559+        # to encrypt, encode, and publish.
14560+
14561+        # Our update process fetched these for us. We need to update
14562+        # them in place as publishing happens.
14563+        self.blockhashes = {} # (shnum, [blochashes])
14564+        for (i, bht) in blockhashes.iteritems():
14565+            self.blockhashes[i] = bht
14566+
14567+        # These are filled in later, after we've modified the block hash
14568+        # tree suitably.
14569+        self.sharehash_leaves = None # eventually [sharehashes]
14570+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
14571+                              # validate the share]
14572+
14573+        d = defer.succeed(None)
14574+        self.log("Starting push")
14575+
14576+        self._state = PUSHING_BLOCKS_STATE
14577+        self._push()
14578+
14579+        return self.done_deferred
14580+
14581+
14582     def publish(self, newdata):
14583         """Publish the filenode's current contents.  Returns a Deferred that
14584         fires (with None) when the publish has done as much work as it's ever
14585hunk ./src/allmydata/mutable/publish.py 502
14586         # that we publish. We define it this way so that empty publishes
14587         # will still have something to write to the remote slot.
14588         self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
14589+        for i in xrange(self.total_shares):
14590+            blocks = self.blockhashes[i]
14591+            for j in xrange(self.num_segments):
14592+                blocks.append(None)
14593         self.sharehash_leaves = None # eventually [sharehashes]
14594         self.sharehashes = {} # shnum -> [sharehash leaves necessary to
14595                               # validate the share]
14596hunk ./src/allmydata/mutable/publish.py 519
14597         return self.done_deferred
14598 
14599 
14600-    def setup_encoding_parameters(self):
14601+    def setup_encoding_parameters(self, offset=0):
14602         if self._version == MDMF_VERSION:
14603             segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
14604         else:
14605hunk ./src/allmydata/mutable/publish.py 528
14606         segment_size = mathutil.next_multiple(segment_size,
14607                                               self.required_shares)
14608         self.segment_size = segment_size
14609+
14610+        # Calculate the starting segment for the upload.
14611         if segment_size:
14612             self.num_segments = mathutil.div_ceil(self.datalength,
14613                                                   segment_size)
14614hunk ./src/allmydata/mutable/publish.py 533
14615+            self.starting_segment = mathutil.div_ceil(offset,
14616+                                                      segment_size)
14617+            if offset == 0:
14618+                self.starting_segment = 0
14619+
14620         else:
14621             self.num_segments = 0
14622hunk ./src/allmydata/mutable/publish.py 540
14623+            self.starting_segment = 0
14624+
14625 
14626         self.log("building encoding parameters for file")
14627         self.log("got segsize %d" % self.segment_size)
14628hunk ./src/allmydata/mutable/publish.py 576
14629                                 self.total_shares)
14630             self.tail_fec = tail_fec
14631 
14632-        self._current_segment = 0
14633+        self._current_segment = self.starting_segment
14634+        self.end_segment = self.num_segments - 1
14635+        # Now figure out where the last segment should be.
14636+        if self.data.get_size() != self.datalength:
14637+            end = offset + self.data.get_size()
14638+            self.end_segment = mathutil.div_ceil(end,
14639+                                                 segment_size)
14640+            self.end_segment -= 1
14641+        self.log("got start segment %d" % self.starting_segment)
14642+        self.log("got end segment %d" % self.end_segment)
14643 
14644 
14645     def _push(self, ignored=None):
14646hunk ./src/allmydata/mutable/publish.py 618
14647         if self.num_segments == 0 and self._version == SDMF_VERSION:
14648             self._add_dummy_salts()
14649 
14650-        if segnum == self.num_segments:
14651+        if segnum > self.end_segment:
14652             # We don't have any more segments to push.
14653             self._state = PUSHING_EVERYTHING_ELSE_STATE
14654             return self._push()
14655hunk ./src/allmydata/mutable/publish.py 715
14656             else:
14657                 hashed = sharedata
14658             block_hash = hashutil.block_hash(hashed)
14659-            self.blockhashes[shareid].append(block_hash)
14660+            self.blockhashes[shareid][segnum] = block_hash
14661 
14662             # find the writer for this share
14663             writer = self.writers[shareid]
14664hunk ./src/allmydata/mutable/publish.py 1227
14665         assert isinstance(s, str)
14666 
14667         MutableFileHandle.__init__(self, StringIO(s))
14668+
14669+
14670+class TransformingUploadable:
14671+    """
14672+    I am an IMutableUploadable that wraps another IMutableUploadable,
14673+    and some segments that are already on the grid. When I am called to
14674+    read, I handle merging of boundary segments.
14675+    """
14676+    implements(IMutableUploadable)
14677+
14678+
14679+    def __init__(self, data, offset, segment_size, start, end):
14680+        assert IMutableUploadable.providedBy(data)
14681+        # offset == data.get_size() means that we're appending.
14682+        assert offset <= data.get_size()
14683+
14684+        self._newdata = data
14685+        self._offset = offset
14686+        self._segment_size = segment_size
14687+        self._start = start
14688+        self._end = end
14689+
14690+        self._read_marker = 0
14691+        self._first_segment_offset = offset % segment_size
14692+
14693+
14694+    def get_size(self):
14695+        # TODO
14696+        pass
14697+
14698+
14699+    def read(self, length):
14700+        # We can be in three states here:
14701+        #   0. In the first segment of data. In this segment, we need to
14702+        #      return the original data until we get to the new data.
14703+        #      This is so that replacing data in the middle of an
14704+        #      existing segment works as expected.
14705+        #   1. After the first segment, before the last segment. In this
14706+        #      state, we delegate all reads to the underlying
14707+        #      IMutableUploadable.
14708+        #   2. Reading the last segment of data. If our replacement ends
14709+        #      in the middle of an existing segment, we need to pad the
14710+        #      replacement data with enough data from the end of segment
14711+        #      so that the replacement can happen.
14712+
14713+        # are we in state 0?
14714+        if self._read_marker < self._first_segment_offset:
14715+            # We need to read at least some data from the first segment
14716+            # to satisfy this read.
14717+            old_data_length = self._first_segment_offset - self._read_marker
14718+            if old_data_length > length:
14719+                old_data_length = length
14720+
14721+            new_data_length = length - old_data_length
14722+            old_data_end = old_data_length + self._read_marker
14723+            old_data = self._start[self._read_marker:old_data_end]
14724+            new_data = self._newdata.read(new_data_length)
14725+            new_data = "".join(new_data)
14726+            data = old_data + new_data
14727+
14728+        # are we in state 3?
14729+        elif self._read_marker + self._length > \
14730+                self._offset + self._newdata.get_size():
14731+            # We need to pad this read (for the last segment) with an
14732+            # appropriate amount of data from the old segment.
14733+            new_data_length = self._newdata.get_size() - self._read_marker
14734+            new_data = self._newdata.read(new_data_length)
14735+            new_data = "".join(data)
14736+            old_data_length = length - new_data_length
14737+            old_data_offset = new_data_length
14738+            old_data = self._end[old_data_offset:old_data_offset +
14739+                                 old_data_length]
14740+            data = new_data + old_data
14741+        else:
14742+            data = self._newdata.read(length)
14743+            data = "".join(data)
14744+
14745+        assert len(data) == length
14746+        self._read_marker += length
14747+        return data
14748+
14749+
14750+    def close(self):
14751+        pass
14752}
14753[mutable/retrieve.py: expose decoding and decrypting methods to callers
14754Kevan Carstensen <kevan@isnotajoke.com>**20100730234141
14755 Ignore-this: db66ee9019755f49388fe74049f26982
14756] hunk ./src/allmydata/mutable/retrieve.py 291
14757         return self._done_deferred
14758 
14759 
14760+    def decode(blocks_and_salts, segnum):
14761+        """
14762+        I am a helper method that the mutable file update process uses
14763+        as a shortcut to decode and decrypt the segments that it needs
14764+        to fetch in order to perform a file update. I take in a
14765+        collection of blocks and salts, and pick some of those to make a
14766+        segment with. I return the plaintext associated with that
14767+        segment.
14768+        """
14769+        self._setup_encoding_parameters()
14770+        d = self._decode_blocks(blocks_and_salts, segnum)
14771+        d.addCallback(self._decrypt_segment)
14772+        return d
14773+
14774+
14775     def _setup_encoding_parameters(self):
14776         """
14777         I set up the encoding parameters, including k, n, the number
14778[mutable/servermap.py: lay some groundwork for IWritable
14779Kevan Carstensen <kevan@isnotajoke.com>**20100730234229
14780 Ignore-this: c9ef2cace79db1e15937d030bcad20d9
14781] {
14782hunk ./src/allmydata/mutable/servermap.py 9
14783 from twisted.python import failure
14784 from foolscap.api import DeadReferenceError, RemoteException, eventually, \
14785                          fireEventually
14786-from allmydata.util import base32, hashutil, idlib, log
14787+from allmydata.util import base32, hashutil, idlib, log, deferredutil
14788 from allmydata.storage.server import si_b2a
14789 from allmydata.interfaces import IServermapUpdaterStatus
14790 from pycryptopp.publickey import rsa
14791hunk ./src/allmydata/mutable/servermap.py 124
14792         self.bad_shares = {} # maps (peerid,shnum) to old checkstring
14793         self.last_update_mode = None
14794         self.last_update_time = 0
14795+        self.update_data = {} # (verinfo,shnum) => data
14796 
14797     def copy(self):
14798         s = ServerMap()
14799hunk ./src/allmydata/mutable/servermap.py 340
14800         return False
14801 
14802 
14803+    def get_update_data_for_share_and_verinfo(self, shnum, verinfo):
14804+        """
14805+        I return the update data for the given shnum
14806+        """
14807+        update_data = self.update_data[shnum]
14808+        update_datum = [i[1] for i in update_data if i[0] == verinfo][0]
14809+        return update_datum
14810+
14811+
14812+    def set_update_data_for_share_and_verinfo(self, shnum, verinfo, data):
14813+        """
14814+        I record the block hash tree for the given shnum.
14815+        """
14816+        self.update_data.setdefault(shnum , []).append((verinfo, data))
14817+
14818+
14819 class ServermapUpdater:
14820     def __init__(self, filenode, storage_broker, monitor, servermap,
14821hunk ./src/allmydata/mutable/servermap.py 358
14822-                 mode=MODE_READ, add_lease=False):
14823+                 mode=MODE_READ, add_lease=False, update_range=None):
14824         """I update a servermap, locating a sufficient number of useful
14825         shares and remembering where they are located.
14826 
14827hunk ./src/allmydata/mutable/servermap.py 405
14828             # we use unpack_prefix_and_signature, so we need 1k
14829             self._read_size = 1000
14830         self._need_privkey = False
14831+
14832         if mode == MODE_WRITE and not self._node.get_privkey():
14833             self._need_privkey = True
14834         # check+repair: repair requires the privkey, so if we didn't happen
14835hunk ./src/allmydata/mutable/servermap.py 412
14836         # to ask for it during the check, we'll have problems doing the
14837         # publish.
14838 
14839+        self.fetch_update_data = False
14840+        if mode == MODE_WRITE and update_range:
14841+            # We're updating the servermap in preparation for an
14842+            # in-place file update, so we need to fetch some additional
14843+            # data from each share that we find.
14844+            assert len(update_range) == 2
14845+
14846+            self.start_segment = update_range[0]
14847+            self.end_segment = update_range[1]
14848+            self.fetch_update_data = True
14849+
14850         prefix = si_b2a(self._storage_index)[:5]
14851         self._log_number = log.msg(format="SharemapUpdater(%(si)s): starting (%(mode)s)",
14852                                    si=prefix, mode=mode)
14853hunk ./src/allmydata/mutable/servermap.py 643
14854                       level=log.NOISY)
14855         now = time.time()
14856         elapsed = now - started
14857-        self._queries_outstanding.discard(peerid)
14858-        self._servermap.reachable_peers.add(peerid)
14859-        self._must_query.discard(peerid)
14860-        self._queries_completed += 1
14861+        def _done_processing(ignored=None):
14862+            self._queries_outstanding.discard(peerid)
14863+            self._servermap.reachable_peers.add(peerid)
14864+            self._must_query.discard(peerid)
14865+            self._queries_completed += 1
14866         if not self._running:
14867             self.log("but we're not running, so we'll ignore it", parent=lp,
14868                      level=log.NOISY)
14869hunk ./src/allmydata/mutable/servermap.py 651
14870+            _done_processing()
14871             self._status.add_per_server_time(peerid, "late", started, elapsed)
14872             return
14873         self._status.add_per_server_time(peerid, "query", started, elapsed)
14874hunk ./src/allmydata/mutable/servermap.py 679
14875             #     public key. We use this to validate the signature.
14876             if not self._node.get_pubkey():
14877                 # fetch and set the public key.
14878-                d = reader.get_verification_key()
14879+                d = reader.get_verification_key(queue=True)
14880                 d.addCallback(lambda results, shnum=shnum, peerid=peerid:
14881                     self._try_to_set_pubkey(results, peerid, shnum, lp))
14882                 # XXX: Make self._pubkey_query_failed?
14883hunk ./src/allmydata/mutable/servermap.py 688
14884             else:
14885                 # we already have the public key.
14886                 d = defer.succeed(None)
14887+
14888             # Neither of these two branches return anything of
14889             # consequence, so the first entry in our deferredlist will
14890             # be None.
14891hunk ./src/allmydata/mutable/servermap.py 705
14892             #   to get the version information. In MDMF, this lives at
14893             #   the end of the share, so unless the file is quite small,
14894             #   we'll need to do a remote fetch to get it.
14895-            d3 = reader.get_signature()
14896+            d3 = reader.get_signature(queue=True)
14897             d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
14898                 self._got_corrupt_share(error, shnum, peerid, data, lp))
14899             #  Once we have all three of these responses, we can move on
14900hunk ./src/allmydata/mutable/servermap.py 714
14901             # Does the node already have a privkey? If not, we'll try to
14902             # fetch it here.
14903             if self._need_privkey:
14904-                d4 = reader.get_encprivkey()
14905+                d4 = reader.get_encprivkey(queue=True)
14906                 d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
14907                     self._try_to_validate_privkey(results, peerid, shnum, lp))
14908                 d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
14909hunk ./src/allmydata/mutable/servermap.py 722
14910             else:
14911                 d4 = defer.succeed(None)
14912 
14913-            dl = defer.DeferredList([d, d2, d3, d4])
14914+
14915+            if self.fetch_update_data:
14916+                # fetch the block hash tree and first + last segment, as
14917+                # configured earlier.
14918+                # Then set them in wherever we happen to want to set
14919+                # them.
14920+                ds = []
14921+                # XXX: We do this above, too. Is there a good way to
14922+                # make the two routines share the value without
14923+                # introducing more roundtrips?
14924+                ds.append(reader.get_verinfo())
14925+                ds.append(reader.get_blockhashes(queue=True))
14926+                ds.append(reader.get_block_and_salt(self.start_segment,
14927+                                                    queue=True))
14928+                ds.append(reader.get_block_and_salt(self.end_segment,
14929+                                                    queue=True))
14930+                d5 = deferredutil.gatherResults(ds)
14931+                d5.addCallback(self._got_update_results_one_share, shnum)
14932+            else:
14933+                d5 = defer.succeed(None)
14934+
14935+            dl = defer.DeferredList([d, d2, d3, d4, d5])
14936+            reader.flush()
14937             dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
14938                 self._got_signature_one_share(results, shnum, peerid, lp))
14939             dl.addErrback(lambda error, shnum=shnum, data=data:
14940hunk ./src/allmydata/mutable/servermap.py 764
14941         # that we returned to our caller to fire, which tells them that
14942         # they have a complete servermap, and that we won't be touching
14943         # the servermap anymore.
14944+        dl.addCallback(_done_processing)
14945         dl.addCallback(self._check_for_done)
14946         dl.addErrback(self._fatal_error)
14947         # all done!
14948hunk ./src/allmydata/mutable/servermap.py 799
14949                  peerid=idlib.shortnodeid_b2a(peerid),
14950                  level=log.NOISY,
14951                  parent=lp)
14952-        _, verinfo, signature, __ = results
14953+        _, verinfo, signature, __, ___ = results
14954         (seqnum,
14955          root_hash,
14956          saltish,
14957hunk ./src/allmydata/mutable/servermap.py 864
14958         return verinfo
14959 
14960 
14961+    def _got_update_results_one_share(self, results, share):
14962+        """
14963+        I record the update results in results.
14964+        """
14965+        assert len(results) == 4
14966+        verinfo, blockhashes, start, end = results
14967+        update_data = (blockhashes, start, end)
14968+        self._servermap.set_update_data_for_share_and_verinfo(share,
14969+                                                              verinfo,
14970+                                                              update_data)
14971+
14972+
14973     def _deserialize_pubkey(self, pubkey_s):
14974         verifier = rsa.create_verifying_key_from_string(pubkey_s)
14975         return verifier
14976}
14977
14978Context:
14979
14980[misc/build_helpers/run-with-pythonpath.py: fix stale comment, and remove 'trial' example that is not the right way to run trial.
14981david-sarah@jacaranda.org**20100726225729
14982 Ignore-this: a61f55557ad69a1633bfb2b8172cce97
14983] 
14984[docs/specifications/dirnodes.txt: 'mesh'->'grid'.
14985david-sarah@jacaranda.org**20100723061616
14986 Ignore-this: 887bcf921ef00afba8e05e9239035bca
14987] 
14988[docs/specifications/dirnodes.txt: bring layer terminology up-to-date with architecture.txt, and a few other updates (e.g. note that the MAC is no longer verified, and that URIs can be unknown). Also 'Tahoe'->'Tahoe-LAFS'.
14989david-sarah@jacaranda.org**20100723054703
14990 Ignore-this: f3b98183e7d0a0f391225b8b93ac6c37
14991] 
14992[docs: use current cap to Zooko's wiki page in example text
14993zooko@zooko.com**20100721010543
14994 Ignore-this: 4f36f36758f9fdbaf9eb73eac23b6652
14995 fixes #1134
14996] 
14997[__init__.py: silence DeprecationWarning about BaseException.message globally. fixes #1129
14998david-sarah@jacaranda.org**20100720011939
14999 Ignore-this: 38808986ba79cb2786b010504a22f89
15000] 
15001[test_runner: test that 'tahoe --version' outputs no noise (e.g. DeprecationWarnings).
15002david-sarah@jacaranda.org**20100720011345
15003 Ignore-this: dd358b7b2e5d57282cbe133e8069702e
15004] 
15005[TAG allmydata-tahoe-1.7.1
15006zooko@zooko.com**20100719131352
15007 Ignore-this: 6942056548433dc653a746703819ad8c
15008] 
15009[relnotes.txt: updated for v1.7.1 release!
15010zooko@zooko.com**20100719083059
15011 Ignore-this: 9f10eb19b65a39d652b546c57481da45
15012] 
15013[immutable: add test case of #1128, fix test case of #1118
15014zooko@zooko.com**20100719081612
15015 Ignore-this: 8f9f742e7dac2bd9b49c19bd10f1c204
15016] 
15017[NEWS: add #1118 and reflow
15018zooko@zooko.com**20100719081248
15019 Ignore-this: 37a2e39d58c7b584b3c7f193bc1b30df
15020] 
15021[immutable: fix bug in which preexisting_shares and merged were shallowly referencing the same sets
15022zooko@zooko.com**20100719075426
15023 Ignore-this: 90827f8ce7ff0fc0c3c7f819399b8cf0
15024 This bug had the effect of making uploads sometimes (rarely) appear to succeed when they had actually not distributed the shares well enough to achieve the desired servers-of-happiness level.
15025] 
15026[upload.py: fix #1118 by aborting newly-homeless buckets when reassignment runs. This makes a previously failing assert correct. This version refactors 'abort' into two methods, rather than using a default argument.
15027david-sarah@jacaranda.org**20100719044655
15028 Ignore-this: 142d182c0739986812140bb8387077d5
15029] 
15030[docs/known_issues.txt: update release version and date.
15031david-sarah@jacaranda.org**20100718235940
15032 Ignore-this: dbbb42dbfa6c0d205a0b8e6e58eee9c7
15033] 
15034[relnotes.txt, docs/quickstart.html: prepare for 1.7.1 release. Don't claim to work on Cygwin (this might work but is untested).
15035david-sarah@jacaranda.org**20100718235437
15036 Ignore-this: dfc7334ee4bb76c04ee19304a7f1024b
15037] 
15038[immutable: extend the tests to check that the shares that got uploaded really do make a sufficiently Happy distribution
15039zooko@zooko.com**20100719045047
15040 Ignore-this: 89c33a7b795e23018667351045a8d5d0
15041 This patch also renames some instances of "find_shares()" to "find_all_shares()" and other instances to "find_uri_shares()" as appropriate -- the conflation between those names confused me at first when writing these tests.
15042] 
15043[immutable: test for #1118
15044zooko@zooko.com**20100718221537
15045 Ignore-this: 8882aabe2aaec6a0148c87e735d817ad
15046] 
15047[immutable: test for #1124
15048zooko@zooko.com**20100718222907
15049 Ignore-this: 1766e3cbab92ea2a9e246f40eb6e770b
15050] 
15051[docs/logging.txt: document that _trial_temp/test.log does not receive messages below level=OPERATIONAL, due to <http://foolscap.lothar.com/trac/ticket/154>.
15052david-sarah@jacaranda.org**20100718230420
15053 Ignore-this: aef40f2e74ddeabee5e122e8d80893a1
15054] 
15055[trivial: fix unused import (sorry about that, pyflakes)
15056zooko@zooko.com**20100718215133
15057 Ignore-this: c2414e443405072b51d552295f2c0e8c
15058] 
15059[tests, NEWS, CREDITS re: #1117
15060zooko@zooko.com**20100718203225
15061 Ignore-this: 1f08be2c692fb72cc0dd023259f11354
15062 Give Brian and Kevan promotions, move release date in NEWS to the 18th, commit Brian's test for #1117.
15063 fixes #1117
15064] 
15065[test/test_upload.py: test to see that aborted buckets are ignored by the storage server
15066Kevan Carstensen <kevan@isnotajoke.com>**20100716001046
15067 Ignore-this: cc075c24b1c86d737f3199af894cc780
15068] 
15069[test/test_storage.py: test for the new remote_abort semantics.
15070Kevan Carstensen <kevan@isnotajoke.com>**20100715232148
15071 Ignore-this: d3d6491f17bf670e770ca4b385007515
15072] 
15073[storage/immutable.py: make remote_abort btell the storage server about aborted buckets.
15074Kevan Carstensen <kevan@isnotajoke.com>**20100715232105
15075 Ignore-this: 16ab0090676355abdd5600ed44ff19c9
15076] 
15077[test/test_upload.py: changes to test plumbing for #1117 tests
15078Kevan Carstensen <kevan@isnotajoke.com>**20100715231820
15079 Ignore-this: 78a6d359d7bf8529d283e2815bf1e2de
15080 
15081     - Add a callRemoteOnly method to FakeBucketWriter.
15082     - Change the abort method in FakeBucketWriter to not return a
15083       RuntimeError.
15084] 
15085[immutable/upload.py: abort buckets if peer selection fails
15086Kevan Carstensen <kevan@isnotajoke.com>**20100715231714
15087 Ignore-this: 2a0b643a22284df292d8ed9d91b1fd37
15088] 
15089[test_encodingutil: correct an error in the previous patch to StdlibUnicode.test_open_representable.
15090david-sarah@jacaranda.org**20100718151420
15091 Ignore-this: af050955f623fbc0e4d78e15a0a8a144
15092] 
15093[NEWS: Forward-compatibility improvements for non-ASCII caps (#1051).
15094david-sarah@jacaranda.org**20100718143622
15095 Ignore-this: 1edfebc4bd38a3b5c35e75c99588153f
15096] 
15097[test_dirnode and test_web: don't use failUnlessReallyEqual in cases where the return type from simplejson.loads can vary between unicode and str. Use to_str when comparing URIs parsed from JSON.
15098david-sarah@jacaranda.org**20100718142915
15099 Ignore-this: c4e78ef4b1478dd400da71cf077ffa4a
15100] 
15101[test_encodingutil: StdlibUnicode.test_open_representable no longer uses a mock.
15102david-sarah@jacaranda.org**20100718125412
15103 Ignore-this: 4bf373a5e2dfe4209e5e364124af29a3
15104] 
15105[docs: add comment clarifying #1051
15106zooko@zooko.com**20100718053250
15107 Ignore-this: 6cfc0930434cbdbbc262dabb58f1505d
15108] 
15109[docs: update NEWS
15110zooko@zooko.com**20100718053225
15111 Ignore-this: 63d5c782ef84812e6d010f0590866831
15112] 
15113[Add tests of caps from the future that have non-ASCII characters in them (encoded as UTF-8). The changes to test_uri.py, test_client.py, and test_dirnode.py add tests of non-ASCII future caps in addition to the current tests. The changes to test_web.py just replace the tests of all-ASCII future caps with tests of non-ASCII future caps. We also change uses of failUnlessEqual to failUnlessReallyEqual, in order to catch cases where the type of a string is not as expected.
15114david-sarah@jacaranda.org**20100711200252
15115 Ignore-this: c2f193352369d32e06865f8f3e951894
15116] 
15117[Debian documentation update
15118jacob@appelbaum.net**20100305003004] 
15119[debian-docs-patch-final
15120jacob@appelbaum.net**20100304085955] 
15121[M-x whitespace-cleanup
15122zooko@zooko.com**20100718032739
15123 Ignore-this: babfd4af6ad2fc885c957fd5c8b10c3f
15124] 
15125[docs: tidy up NEWS a little
15126zooko@zooko.com**20100718032434
15127 Ignore-this: 54f2820fd1a37c8967609f6bfc4e5e18
15128] 
15129[benchmarking: update bench_dirnode.py to reflect the new directory interfaces
15130zooko@zooko.com**20100718031710
15131 Ignore-this: 368ba523dd3de80d9da29cd58afbe827
15132] 
15133[test_encodingutil: fix test_open_representable, which is only valid when run on a platform for which we know an unrepresentable filename.
15134david-sarah@jacaranda.org**20100718030333
15135 Ignore-this: c114d92c17714a5d4ae005c15267d60c
15136] 
15137[iputil.py: Add support for FreeBSD 7,8 and 9
15138francois@ctrlaltdel.ch**20100718022832
15139 Ignore-this: 1829b4cf4b91107f4cf87841e6167e99
15140 committed by: zooko@zooko.com
15141 date: 2010-07-17
15142 and I also patched: NEWS and CREDITS
15143] 
15144[NEWS: add snippet about #1083
15145zooko@zooko.com**20100718020653
15146 Ignore-this: d353a9d93cbc5a5e6ba4671f78d1e22b
15147] 
15148[fileutil: docstrings for non-obvious usage restrictions on methods of EncryptedTemporaryFile.
15149david-sarah@jacaranda.org**20100717054647
15150 Ignore-this: 46d8fc10782fa8ec2b6c5b168c841943
15151] 
15152[Move EncryptedTemporaryFile from SFTP frontend to allmydata.util.fileutil, and make the FTP frontend also use it (fixing #1083).
15153david-sarah@jacaranda.org**20100711213721
15154 Ignore-this: e452e8ca66391aa2a1a49afe0114f317
15155] 
15156[NEWS: reorder NEWS snippets to be in descending order of interestingness
15157zooko@zooko.com**20100718015929
15158 Ignore-this: 146c42e88a9555a868a04a69dd0e5326
15159] 
15160[Correct stringutils->encodingutil patch to be the newer version, rather than the old version that was committed in error.
15161david-sarah@jacaranda.org**20100718013435
15162 Ignore-this: c8940c4e1aa2e9acc80cd4fe54753cd8
15163] 
15164[test_cli.py: fix error that crept in when rebasing the patch for #1072.
15165david-sarah@jacaranda.org**20100718000123
15166 Ignore-this: 3e8f6cc3a27b747c708221dd581934f4
15167] 
15168[stringutils: add test for when sys.stdout has no encoding attribute (fixes #1099).
15169david-sarah@jacaranda.org**20100717045816
15170 Ignore-this: f28dce6940e909f12f354086d17db54f
15171] 
15172[CLI: add 'tahoe unlink' as an alias to 'tahoe rm', for forward-compatibility.
15173david-sarah@jacaranda.org**20100717220411
15174 Ignore-this: 3ecdde7f2d0498514cef32e118e0b855
15175] 
15176[minor code clean-up in dirnode.py
15177zooko@zooko.com**20100714060255
15178 Ignore-this: bb0ab2783203e605024b3e2f798256a1
15179 Impose micro-POLA by passing only the writekey instead of the whole node object to {{{_encrypt_rw_uri()}}}. Remove DummyImmutableFileNode in nodemaker.py, which is obviated by this. Add micro-optimization by precomputing the netstring of the empty string and branching on whether the writekey is present or not outside of {{{_encrypt_rw_uri()}}}. Add doc about writekey to docstring.
15180 fixes #967
15181] 
15182[Rename stringutils to encodingutil, and drop listdir_unicode and open_unicode (since the Python stdlib functions work fine with Unicode paths). Also move some utility functions to fileutil.
15183david-sarah@jacaranda.org**20100712003015
15184 Ignore-this: 103b809d180df17a7283077c3104c7be
15185] 
15186[Allow URIs passed in the initial JSON for t=mkdir-with-children, t=mkdir-immutable to be Unicode. Also pass the name of each child into nodemaker.create_from_cap for error reporting.
15187david-sarah@jacaranda.org**20100711195525
15188 Ignore-this: deac32d8b91ba26ede18905d3f7d2b93
15189] 
15190[docs: CREDITS and NEWS
15191zooko@zooko.com**20100714060150
15192 Ignore-this: dc83e612f77d69e50ee975f07f6b16fe
15193] 
15194[CREDITS: more creds for Kevan, plus utf-8 BOM
15195zooko@zooko.com**20100619045503
15196 Ignore-this: 72d02bdd7a0f324f1cee8cd399c7c6de
15197] 
15198[cli.py: make command descriptions consistently end with a full stop.
15199david-sarah@jacaranda.org**20100714014538
15200 Ignore-this: 9ee7fa29ca2d1631db4049c2a389a97a
15201] 
15202[SFTP: address some of the comments in zooko's review (#1106).
15203david-sarah@jacaranda.org**20100712025537
15204 Ignore-this: c3921638a2d4f1de2a776ae78e4dc37e
15205] 
15206[docs/logging.txt: note that setting flogging vars might affect tests with race conditions.
15207david-sarah@jacaranda.org**20100712050721
15208 Ignore-this: fc1609d215fcd5561a57fd1226206f27
15209] 
15210[test_storage.py: potential fix for failures when logging is enabled.
15211david-sarah@jacaranda.org**19700713040546
15212 Ignore-this: 5815693a0df3e64c52c3c6b7be2846c7
15213] 
15214[upcase_since_on_welcome
15215terrellrussell@gmail.com**20100708193903] 
15216[server_version_on_welcome_page.dpatch.txt
15217freestorm77@gmail.com**20100605191721
15218 Ignore-this: b450c76dc875f5ac8cca229a666cbd0a
15219 
15220 
15221 - The storage server version is 0 for all storage nodes in the Welcome Page
15222 
15223 
15224] 
15225[NEWS: add NEWS snippets about two recent patches
15226zooko@zooko.com**20100708162058
15227 Ignore-this: 6c9da6a0ad7351a960bdd60f81532899
15228] 
15229[directory_html_top_banner.dpatch
15230freestorm77@gmail.com**20100622205301
15231 Ignore-this: 1d770d975e0c414c996564774f049bca
15232 
15233 The div tag with the link "Return to Welcome page" on the directory.xhtml page is not correct
15234 
15235] 
15236[tahoe_css_toolbar.dpatch
15237freestorm77@gmail.com**20100622210046
15238 Ignore-this: 5b3ebb2e0f52bbba718a932f80c246c0
15239 
15240 CSS modification to be correctly diplayed with Internet Explorer 8
15241 
15242 The links on the top of page directory.xhtml are not diplayed in the same line as display with Firefox.
15243 
15244] 
15245[runnin_test_tahoe_css.dpatch
15246freestorm77@gmail.com**20100622214714
15247 Ignore-this: e0db73d68740aad09a7b9ae60a08c05c
15248 
15249 Runnin test for changes in tahoe.css file
15250 
15251] 
15252[runnin_test_directory_xhtml.dpatch
15253freestorm77@gmail.com**20100622201403
15254 Ignore-this: f8962463fce50b9466405cb59fe11d43
15255 
15256 Runnin test for diretory.xhtml top banner
15257 
15258] 
15259[stringutils.py: tolerate sys.stdout having no 'encoding' attribute.
15260david-sarah@jacaranda.org**20100626040817
15261 Ignore-this: f42cad81cef645ee38ac1df4660cc850
15262] 
15263[quickstart.html: python 2.5 -> 2.6 as recommended version
15264david-sarah@jacaranda.org**20100705175858
15265 Ignore-this: bc3a14645ea1d5435002966ae903199f
15266] 
15267[SFTP: don't call .stopProducing on the producer registered with OverwriteableFileConsumer (which breaks with warner's new downloader).
15268david-sarah@jacaranda.org**20100628231926
15269 Ignore-this: 131b7a5787bc85a9a356b5740d9d996f
15270] 
15271[docs/how_to_make_a_tahoe-lafs_release.txt: trivial correction, install.html should now be quickstart.html.
15272david-sarah@jacaranda.org**20100625223929
15273 Ignore-this: 99a5459cac51bd867cc11ad06927ff30
15274] 
15275[setup: in the Makefile, refuse to upload tarballs unless someone has passed the environment variable "BB_BRANCH" with value "trunk"
15276zooko@zooko.com**20100619034928
15277 Ignore-this: 276ddf9b6ad7ec79e27474862e0f7d6
15278] 
15279[trivial: tiny update to in-line comment
15280zooko@zooko.com**20100614045715
15281 Ignore-this: 10851b0ed2abfed542c97749e5d280bc
15282 (I'm actually committing this patch as a test of the new eager-annotation-computation of trac-darcs.)
15283] 
15284[docs: about.html link to home page early on, and be decentralized storage instead of cloud storage this time around
15285zooko@zooko.com**20100619065318
15286 Ignore-this: dc6db03f696e5b6d2848699e754d8053
15287] 
15288[docs: update about.html, especially to have a non-broken link to quickstart.html, and also to comment out the broken links to "for Paranoids" and "for Corporates"
15289zooko@zooko.com**20100619065124
15290 Ignore-this: e292c7f51c337a84ebfeb366fbd24d6c
15291] 
15292[TAG allmydata-tahoe-1.7.0
15293zooko@zooko.com**20100619052631
15294 Ignore-this: d21e27afe6d85e2e3ba6a3292ba2be1
15295] 
15296Patch bundle hash:
15297703267dd05f0c32d2c11b54bc50bf653d04999c2