Ticket #393: 393status24.dpatch

File 393status24.dpatch, 663.8 KB (added by kevan, at 2010-08-02T23:19:27Z)
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
183Mon Aug  2 15:48:01 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
184  * test/test_mutable.py: add tests for updating behavior
185
186Mon Aug  2 15:48:21 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
187  * mutable: fix bugs that prevented the update tests from working
188
189New patches:
190
191[Misc. changes to support the work I'm doing
192Kevan Carstensen <kevan@isnotajoke.com>**20100624234637
193 Ignore-this: fdd18fa8cc05f4b4b15ff53ee24a1819
194 
195     - Add a notion of file version number to interfaces.py
196     - Alter mutable file node interfaces to have a notion of version,
197       though this may be changed later.
198     - Alter mutable/filenode.py to conform to these changes.
199     - Add a salt hasher to util/hashutil.py
200] {
201hunk ./src/allmydata/interfaces.py 7
202      ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
203 
204 HASH_SIZE=32
205+SALT_SIZE=16
206+
207+SDMF_VERSION=0
208+MDMF_VERSION=1
209 
210 Hash = StringConstraint(maxLength=HASH_SIZE,
211                         minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
212hunk ./src/allmydata/interfaces.py 811
213         writer-visible data using this writekey.
214         """
215 
216+    def set_version(version):
217+        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
218+        we upload in SDMF for reasons of compatibility. If you want to
219+        change this, set_version will let you do that.
220+
221+        To say that this file should be uploaded in SDMF, pass in a 0. To
222+        say that the file should be uploaded as MDMF, pass in a 1.
223+        """
224+
225+    def get_version():
226+        """Returns the mutable file protocol version."""
227+
228 class NotEnoughSharesError(Exception):
229     """Download was unable to get enough shares"""
230 
231hunk ./src/allmydata/mutable/filenode.py 8
232 from twisted.internet import defer, reactor
233 from foolscap.api import eventually
234 from allmydata.interfaces import IMutableFileNode, \
235-     ICheckable, ICheckResults, NotEnoughSharesError
236+     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
237 from allmydata.util import hashutil, log
238 from allmydata.util.assertutil import precondition
239 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
240hunk ./src/allmydata/mutable/filenode.py 67
241         self._sharemap = {} # known shares, shnum-to-[nodeids]
242         self._cache = ResponseCache()
243         self._most_recent_size = None
244+        # filled in after __init__ if we're being created for the first time;
245+        # filled in by the servermap updater before publishing, otherwise.
246+        # set to this default value in case neither of those things happen,
247+        # or in case the servermap can't find any shares to tell us what
248+        # to publish as.
249+        # TODO: Set this back to None, and find out why the tests fail
250+        #       with it set to None.
251+        self._protocol_version = SDMF_VERSION
252 
253         # all users of this MutableFileNode go through the serializer. This
254         # takes advantage of the fact that Deferreds discard the callbacks
255hunk ./src/allmydata/mutable/filenode.py 472
256     def _did_upload(self, res, size):
257         self._most_recent_size = size
258         return res
259+
260+
261+    def set_version(self, version):
262+        # I can be set in two ways:
263+        #  1. When the node is created.
264+        #  2. (for an existing share) when the Servermap is updated
265+        #     before I am read.
266+        assert version in (MDMF_VERSION, SDMF_VERSION)
267+        self._protocol_version = version
268+
269+
270+    def get_version(self):
271+        return self._protocol_version
272hunk ./src/allmydata/util/hashutil.py 90
273 MUTABLE_READKEY_TAG = "allmydata_mutable_writekey_to_readkey_v1"
274 MUTABLE_DATAKEY_TAG = "allmydata_mutable_readkey_to_datakey_v1"
275 MUTABLE_STORAGEINDEX_TAG = "allmydata_mutable_readkey_to_storage_index_v1"
276+MUTABLE_SALT_TAG = "allmydata_mutable_segment_salt_v1"
277 
278 # dirnodes
279 DIRNODE_CHILD_WRITECAP_TAG = "allmydata_mutable_writekey_and_salt_to_dirnode_child_capkey_v1"
280hunk ./src/allmydata/util/hashutil.py 134
281 def plaintext_segment_hasher():
282     return tagged_hasher(PLAINTEXT_SEGMENT_TAG)
283 
284+def mutable_salt_hash(data):
285+    return tagged_hash(MUTABLE_SALT_TAG, data)
286+def mutable_salt_hasher():
287+    return tagged_hasher(MUTABLE_SALT_TAG)
288+
289 KEYLEN = 16
290 IVLEN = 16
291 
292}
293[nodemaker.py: create MDMF files when asked to
294Kevan Carstensen <kevan@isnotajoke.com>**20100624234833
295 Ignore-this: 26c16aaca9ddab7a7ce37a4530bc970
296] {
297hunk ./src/allmydata/nodemaker.py 3
298 import weakref
299 from zope.interface import implements
300-from allmydata.interfaces import INodeMaker
301+from allmydata.util.assertutil import precondition
302+from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError, \
303+                                 SDMF_VERSION, MDMF_VERSION
304 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
305 from allmydata.immutable.upload import Data
306 from allmydata.mutable.filenode import MutableFileNode
307hunk ./src/allmydata/nodemaker.py 88
308             return self._create_dirnode(filenode)
309         return None
310 
311-    def create_mutable_file(self, contents=None, keysize=None):
312+    def create_mutable_file(self, contents=None, keysize=None,
313+                            version=SDMF_VERSION):
314         n = MutableFileNode(self.storage_broker, self.secret_holder,
315                             self.default_encoding_parameters, self.history)
316hunk ./src/allmydata/nodemaker.py 92
317+        n.set_version(version)
318         d = self.key_generator.generate(keysize)
319         d.addCallback(n.create_with_keys, contents)
320         d.addCallback(lambda res: n)
321hunk ./src/allmydata/nodemaker.py 98
322         return d
323 
324-    def create_new_mutable_directory(self, initial_children={}):
325+    def create_new_mutable_directory(self, initial_children={},
326+                                     version=SDMF_VERSION):
327+        # initial_children must have metadata (i.e. {} instead of None)
328+        for (name, (node, metadata)) in initial_children.iteritems():
329+            precondition(isinstance(metadata, dict),
330+                         "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
331+            node.raise_error()
332         d = self.create_mutable_file(lambda n:
333                                      pack_children(initial_children, n.get_writekey()))
334         d.addCallback(self._create_dirnode)
335merger 0.0 (
336hunk ./src/allmydata/nodemaker.py 106
337-                                     pack_children(n, initial_children))
338+                                     pack_children(initial_children, n.get_writekey()))
339hunk ./src/allmydata/nodemaker.py 106
340-                                     pack_children(n, initial_children))
341+                                     pack_children(n, initial_children),
342+                                     version)
343)
344}
345[storage/server.py: minor code cleanup
346Kevan Carstensen <kevan@isnotajoke.com>**20100624234905
347 Ignore-this: 2358c531c39e48d3c8e56b62b5768228
348] {
349hunk ./src/allmydata/storage/server.py 569
350                                          self)
351         return share
352 
353-    def remote_slot_readv(self, storage_index, shares, readv):
354+    def remote_slot_readv(self, storage_index, shares, readvs):
355         start = time.time()
356         self.count("readv")
357         si_s = si_b2a(storage_index)
358hunk ./src/allmydata/storage/server.py 590
359             if sharenum in shares or not shares:
360                 filename = os.path.join(bucketdir, sharenum_s)
361                 msf = MutableShareFile(filename, self)
362-                datavs[sharenum] = msf.readv(readv)
363+                datavs[sharenum] = msf.readv(readvs)
364         log.msg("returning shares %s" % (datavs.keys(),),
365                 facility="tahoe.storage", level=log.NOISY, parent=lp)
366         self.add_latency("readv", time.time() - start)
367}
368[test/test_mutable.py: alter some tests that were failing due to MDMF; minor code cleanup.
369Kevan Carstensen <kevan@isnotajoke.com>**20100624234924
370 Ignore-this: afb86ec1fbdbfe1a5ef6f46f350273c0
371] {
372hunk ./src/allmydata/test/test_mutable.py 151
373             chr(ord(original[byte_offset]) ^ 0x01) +
374             original[byte_offset+1:])
375 
376+def add_two(original, byte_offset):
377+    # It isn't enough to simply flip the bit for the version number,
378+    # because 1 is a valid version number. So we add two instead.
379+    return (original[:byte_offset] +
380+            chr(ord(original[byte_offset]) ^ 0x02) +
381+            original[byte_offset+1:])
382+
383 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
384     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
385     # list of shnums to corrupt.
386hunk ./src/allmydata/test/test_mutable.py 187
387                 real_offset = offset1
388             real_offset = int(real_offset) + offset2 + offset_offset
389             assert isinstance(real_offset, int), offset
390-            shares[shnum] = flip_bit(data, real_offset)
391+            if offset1 == 0: # verbyte
392+                f = add_two
393+            else:
394+                f = flip_bit
395+            shares[shnum] = f(data, real_offset)
396     return res
397 
398 def make_storagebroker(s=None, num_peers=10):
399hunk ./src/allmydata/test/test_mutable.py 423
400         d.addCallback(_created)
401         return d
402 
403+
404     def test_modify_backoffer(self):
405         def _modifier(old_contents, servermap, first_time):
406             return old_contents + "line2"
407hunk ./src/allmydata/test/test_mutable.py 658
408         d.addCallback(_created)
409         return d
410 
411+
412     def _copy_shares(self, ignored, index):
413         shares = self._storage._peers
414         # we need a deep copy
415}
416[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
417Kevan Carstensen <kevan@isnotajoke.com>**20100626003520
418 Ignore-this: 836e59e2fde0535f6b4bea3468dc8244
419] {
420hunk ./src/allmydata/test/test_mutable.py 168
421                 and shnum not in shnums_to_corrupt):
422                 continue
423             data = shares[shnum]
424-            (version,
425-             seqnum,
426-             root_hash,
427-             IV,
428-             k, N, segsize, datalen,
429-             o) = unpack_header(data)
430-            if isinstance(offset, tuple):
431-                offset1, offset2 = offset
432-            else:
433-                offset1 = offset
434-                offset2 = 0
435-            if offset1 == "pubkey":
436-                real_offset = 107
437-            elif offset1 in o:
438-                real_offset = o[offset1]
439-            else:
440-                real_offset = offset1
441-            real_offset = int(real_offset) + offset2 + offset_offset
442-            assert isinstance(real_offset, int), offset
443-            if offset1 == 0: # verbyte
444-                f = add_two
445-            else:
446-                f = flip_bit
447-            shares[shnum] = f(data, real_offset)
448-    return res
449+            # We're feeding the reader all of the share data, so it
450+            # won't need to use the rref that we didn't provide, nor the
451+            # storage index that we didn't provide. We do this because
452+            # the reader will work for both MDMF and SDMF.
453+            reader = MDMFSlotReadProxy(None, None, shnum, data)
454+            # We need to get the offsets for the next part.
455+            d = reader.get_verinfo()
456+            def _do_corruption(verinfo, data, shnum):
457+                (seqnum,
458+                 root_hash,
459+                 IV,
460+                 segsize,
461+                 datalen,
462+                 k, n, prefix, o) = verinfo
463+                if isinstance(offset, tuple):
464+                    offset1, offset2 = offset
465+                else:
466+                    offset1 = offset
467+                    offset2 = 0
468+                if offset1 == "pubkey":
469+                    real_offset = 107
470+                elif offset1 in o:
471+                    real_offset = o[offset1]
472+                else:
473+                    real_offset = offset1
474+                real_offset = int(real_offset) + offset2 + offset_offset
475+                assert isinstance(real_offset, int), offset
476+                if offset1 == 0: # verbyte
477+                    f = add_two
478+                else:
479+                    f = flip_bit
480+                shares[shnum] = f(data, real_offset)
481+            d.addCallback(_do_corruption, data, shnum)
482+            ds.append(d)
483+    dl = defer.DeferredList(ds)
484+    dl.addCallback(lambda ignored: res)
485+    return dl
486 
487 def make_storagebroker(s=None, num_peers=10):
488     if not s:
489hunk ./src/allmydata/test/test_mutable.py 1177
490         return d
491 
492     def test_download_fails(self):
493-        corrupt(None, self._storage, "signature")
494-        d = self.shouldFail(UnrecoverableFileError, "test_download_anyway",
495+        d = corrupt(None, self._storage, "signature")
496+        d.addCallback(lambda ignored:
497+            self.shouldFail(UnrecoverableFileError, "test_download_anyway",
498                             "no recoverable versions",
499                             self._fn.download_best_version)
500         return d
501hunk ./src/allmydata/test/test_mutable.py 1232
502         return d
503 
504     def test_check_all_bad_sig(self):
505-        corrupt(None, self._storage, 1) # bad sig
506-        d = self._fn.check(Monitor())
507+        d = corrupt(None, self._storage, 1) # bad sig
508+        d.addCallback(lambda ignored:
509+            self._fn.check(Monitor()))
510         d.addCallback(self.check_bad, "test_check_all_bad_sig")
511         return d
512 
513hunk ./src/allmydata/test/test_mutable.py 1239
514     def test_check_all_bad_blocks(self):
515-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
516+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
517         # the Checker won't notice this.. it doesn't look at actual data
518hunk ./src/allmydata/test/test_mutable.py 1241
519-        d = self._fn.check(Monitor())
520+        d.addCallback(lambda ignored:
521+            self._fn.check(Monitor()))
522         d.addCallback(self.check_good, "test_check_all_bad_blocks")
523         return d
524 
525hunk ./src/allmydata/test/test_mutable.py 1252
526         return d
527 
528     def test_verify_all_bad_sig(self):
529-        corrupt(None, self._storage, 1) # bad sig
530-        d = self._fn.check(Monitor(), verify=True)
531+        d = corrupt(None, self._storage, 1) # bad sig
532+        d.addCallback(lambda ignored:
533+            self._fn.check(Monitor(), verify=True))
534         d.addCallback(self.check_bad, "test_verify_all_bad_sig")
535         return d
536 
537hunk ./src/allmydata/test/test_mutable.py 1259
538     def test_verify_one_bad_sig(self):
539-        corrupt(None, self._storage, 1, [9]) # bad sig
540-        d = self._fn.check(Monitor(), verify=True)
541+        d = corrupt(None, self._storage, 1, [9]) # bad sig
542+        d.addCallback(lambda ignored:
543+            self._fn.check(Monitor(), verify=True))
544         d.addCallback(self.check_bad, "test_verify_one_bad_sig")
545         return d
546 
547hunk ./src/allmydata/test/test_mutable.py 1266
548     def test_verify_one_bad_block(self):
549-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
550+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
551         # the Verifier *will* notice this, since it examines every byte
552hunk ./src/allmydata/test/test_mutable.py 1268
553-        d = self._fn.check(Monitor(), verify=True)
554+        d.addCallback(lambda ignored:
555+            self._fn.check(Monitor(), verify=True))
556         d.addCallback(self.check_bad, "test_verify_one_bad_block")
557         d.addCallback(self.check_expected_failure,
558                       CorruptShareError, "block hash tree failure",
559hunk ./src/allmydata/test/test_mutable.py 1277
560         return d
561 
562     def test_verify_one_bad_sharehash(self):
563-        corrupt(None, self._storage, "share_hash_chain", [9], 5)
564-        d = self._fn.check(Monitor(), verify=True)
565+        d = corrupt(None, self._storage, "share_hash_chain", [9], 5)
566+        d.addCallback(lambda ignored:
567+            self._fn.check(Monitor(), verify=True))
568         d.addCallback(self.check_bad, "test_verify_one_bad_sharehash")
569         d.addCallback(self.check_expected_failure,
570                       CorruptShareError, "corrupt hashes",
571hunk ./src/allmydata/test/test_mutable.py 1287
572         return d
573 
574     def test_verify_one_bad_encprivkey(self):
575-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
576-        d = self._fn.check(Monitor(), verify=True)
577+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
578+        d.addCallback(lambda ignored:
579+            self._fn.check(Monitor(), verify=True))
580         d.addCallback(self.check_bad, "test_verify_one_bad_encprivkey")
581         d.addCallback(self.check_expected_failure,
582                       CorruptShareError, "invalid privkey",
583hunk ./src/allmydata/test/test_mutable.py 1297
584         return d
585 
586     def test_verify_one_bad_encprivkey_uncheckable(self):
587-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
588+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
589         readonly_fn = self._fn.get_readonly()
590         # a read-only node has no way to validate the privkey
591hunk ./src/allmydata/test/test_mutable.py 1300
592-        d = readonly_fn.check(Monitor(), verify=True)
593+        d.addCallback(lambda ignored:
594+            readonly_fn.check(Monitor(), verify=True))
595         d.addCallback(self.check_good,
596                       "test_verify_one_bad_encprivkey_uncheckable")
597         return d
598}
599[Alter the ServermapUpdater to find MDMF files
600Kevan Carstensen <kevan@isnotajoke.com>**20100626234118
601 Ignore-this: 25f6278209c2983ba8f307cfe0fde0
602 
603 The servermapupdater should find MDMF files on a grid in the same way
604 that it finds SDMF files. This patch makes it do that.
605] {
606hunk ./src/allmydata/mutable/servermap.py 7
607 from itertools import count
608 from twisted.internet import defer
609 from twisted.python import failure
610-from foolscap.api import DeadReferenceError, RemoteException, eventually
611+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
612+                         fireEventually
613 from allmydata.util import base32, hashutil, idlib, log
614 from allmydata.storage.server import si_b2a
615 from allmydata.interfaces import IServermapUpdaterStatus
616hunk ./src/allmydata/mutable/servermap.py 17
617 from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \
618      DictOfSets, CorruptShareError, NeedMoreDataError
619 from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
620-     SIGNED_PREFIX_LENGTH
621+     SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
622 
623 class UpdateStatus:
624     implements(IServermapUpdaterStatus)
625hunk ./src/allmydata/mutable/servermap.py 254
626         """Return a set of versionids, one for each version that is currently
627         recoverable."""
628         versionmap = self.make_versionmap()
629-
630         recoverable_versions = set()
631         for (verinfo, shares) in versionmap.items():
632             (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
633hunk ./src/allmydata/mutable/servermap.py 366
634         self._servers_responded = set()
635 
636         # how much data should we read?
637+        # SDMF:
638         #  * if we only need the checkstring, then [0:75]
639         #  * if we need to validate the checkstring sig, then [543ish:799ish]
640         #  * if we need the verification key, then [107:436ish]
641hunk ./src/allmydata/mutable/servermap.py 374
642         #  * if we need the encrypted private key, we want [-1216ish:]
643         #   * but we can't read from negative offsets
644         #   * the offset table tells us the 'ish', also the positive offset
645-        # A future version of the SMDF slot format should consider using
646-        # fixed-size slots so we can retrieve less data. For now, we'll just
647-        # read 2000 bytes, which also happens to read enough actual data to
648-        # pre-fetch a 9-entry dirnode.
649+        # MDMF:
650+        #  * Checkstring? [0:72]
651+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
652+        #    the offset table will tell us for sure.
653+        #  * If we need the verification key, we have to consult the offset
654+        #    table as well.
655+        # At this point, we don't know which we are. Our filenode can
656+        # tell us, but it might be lying -- in some cases, we're
657+        # responsible for telling it which kind of file it is.
658         self._read_size = 4000
659         if mode == MODE_CHECK:
660             # we use unpack_prefix_and_signature, so we need 1k
661hunk ./src/allmydata/mutable/servermap.py 432
662         self._queries_completed = 0
663 
664         sb = self._storage_broker
665+        # All of the peers, permuted by the storage index, as usual.
666         full_peerlist = sb.get_servers_for_index(self._storage_index)
667         self.full_peerlist = full_peerlist # for use later, immutable
668         self.extra_peers = full_peerlist[:] # peers are removed as we use them
669hunk ./src/allmydata/mutable/servermap.py 439
670         self._good_peers = set() # peers who had some shares
671         self._empty_peers = set() # peers who don't have any shares
672         self._bad_peers = set() # peers to whom our queries failed
673+        self._readers = {} # peerid -> dict(sharewriters), filled in
674+                           # after responses come in.
675 
676         k = self._node.get_required_shares()
677hunk ./src/allmydata/mutable/servermap.py 443
678+        # For what cases can these conditions work?
679         if k is None:
680             # make a guess
681             k = 3
682hunk ./src/allmydata/mutable/servermap.py 456
683         self.num_peers_to_query = k + self.EPSILON
684 
685         if self.mode == MODE_CHECK:
686+            # We want to query all of the peers.
687             initial_peers_to_query = dict(full_peerlist)
688             must_query = set(initial_peers_to_query.keys())
689             self.extra_peers = []
690hunk ./src/allmydata/mutable/servermap.py 464
691             # we're planning to replace all the shares, so we want a good
692             # chance of finding them all. We will keep searching until we've
693             # seen epsilon that don't have a share.
694+            # We don't query all of the peers because that could take a while.
695             self.num_peers_to_query = N + self.EPSILON
696             initial_peers_to_query, must_query = self._build_initial_querylist()
697             self.required_num_empty_peers = self.EPSILON
698hunk ./src/allmydata/mutable/servermap.py 474
699             # might also avoid the round trip required to read the encrypted
700             # private key.
701 
702-        else:
703+        else: # MODE_READ, MODE_ANYTHING
704+            # 2k peers is good enough.
705             initial_peers_to_query, must_query = self._build_initial_querylist()
706 
707         # this is a set of peers that we are required to get responses from:
708hunk ./src/allmydata/mutable/servermap.py 490
709         # before we can consider ourselves finished, and self.extra_peers
710         # contains the overflow (peers that we should tap if we don't get
711         # enough responses)
712+        # I guess that self._must_query is a subset of
713+        # initial_peers_to_query?
714+        assert set(must_query).issubset(set(initial_peers_to_query))
715 
716         self._send_initial_requests(initial_peers_to_query)
717         self._status.timings["initial_queries"] = time.time() - self._started
718hunk ./src/allmydata/mutable/servermap.py 549
719         # errors that aren't handled by _query_failed (and errors caused by
720         # _query_failed) get logged, but we still want to check for doneness.
721         d.addErrback(log.err)
722-        d.addBoth(self._check_for_done)
723         d.addErrback(self._fatal_error)
724hunk ./src/allmydata/mutable/servermap.py 550
725+        d.addCallback(self._check_for_done)
726         return d
727 
728     def _do_read(self, ss, peerid, storage_index, shnums, readv):
729hunk ./src/allmydata/mutable/servermap.py 569
730         d = ss.callRemote("slot_readv", storage_index, shnums, readv)
731         return d
732 
733+
734+    def _got_corrupt_share(self, e, shnum, peerid, data, lp):
735+        """
736+        I am called when a remote server returns a corrupt share in
737+        response to one of our queries. By corrupt, I mean a share
738+        without a valid signature. I then record the failure, notify the
739+        server of the corruption, and record the share as bad.
740+        """
741+        f = failure.Failure(e)
742+        self.log(format="bad share: %(f_value)s", f_value=str(f),
743+                 failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
744+        # Notify the server that its share is corrupt.
745+        self.notify_server_corruption(peerid, shnum, str(e))
746+        # By flagging this as a bad peer, we won't count any of
747+        # the other shares on that peer as valid, though if we
748+        # happen to find a valid version string amongst those
749+        # shares, we'll keep track of it so that we don't need
750+        # to validate the signature on those again.
751+        self._bad_peers.add(peerid)
752+        self._last_failure = f
753+        # XXX: Use the reader for this?
754+        checkstring = data[:SIGNED_PREFIX_LENGTH]
755+        self._servermap.mark_bad_share(peerid, shnum, checkstring)
756+        self._servermap.problems.append(f)
757+
758+
759+    def _cache_good_sharedata(self, verinfo, shnum, now, data):
760+        """
761+        If one of my queries returns successfully (which means that we
762+        were able to and successfully did validate the signature), I
763+        cache the data that we initially fetched from the storage
764+        server. This will help reduce the number of roundtrips that need
765+        to occur when the file is downloaded, or when the file is
766+        updated.
767+        """
768+        self._node._add_to_cache(verinfo, shnum, 0, data, now)
769+
770+
771     def _got_results(self, datavs, peerid, readsize, stuff, started):
772         lp = self.log(format="got result from [%(peerid)s], %(numshares)d shares",
773                       peerid=idlib.shortnodeid_b2a(peerid),
774hunk ./src/allmydata/mutable/servermap.py 630
775         else:
776             self._empty_peers.add(peerid)
777 
778-        last_verinfo = None
779-        last_shnum = None
780+        ss, storage_index = stuff
781+        ds = []
782+
783         for shnum,datav in datavs.items():
784             data = datav[0]
785hunk ./src/allmydata/mutable/servermap.py 635
786-            try:
787-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
788-                last_verinfo = verinfo
789-                last_shnum = shnum
790-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
791-            except CorruptShareError, e:
792-                # log it and give the other shares a chance to be processed
793-                f = failure.Failure()
794-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
795-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
796-                self.notify_server_corruption(peerid, shnum, str(e))
797-                self._bad_peers.add(peerid)
798-                self._last_failure = f
799-                checkstring = data[:SIGNED_PREFIX_LENGTH]
800-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
801-                self._servermap.problems.append(f)
802-                pass
803-
804-        self._status.timings["cumulative_verify"] += (time.time() - now)
805+            reader = MDMFSlotReadProxy(ss,
806+                                       storage_index,
807+                                       shnum,
808+                                       data)
809+            self._readers.setdefault(peerid, dict())[shnum] = reader
810+            # our goal, with each response, is to validate the version
811+            # information and share data as best we can at this point --
812+            # we do this by validating the signature. To do this, we
813+            # need to do the following:
814+            #   - If we don't already have the public key, fetch the
815+            #     public key. We use this to validate the signature.
816+            if not self._node.get_pubkey():
817+                # fetch and set the public key.
818+                d = reader.get_verification_key()
819+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
820+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
821+                # XXX: Make self._pubkey_query_failed?
822+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
823+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
824+            else:
825+                # we already have the public key.
826+                d = defer.succeed(None)
827+            # Neither of these two branches return anything of
828+            # consequence, so the first entry in our deferredlist will
829+            # be None.
830 
831hunk ./src/allmydata/mutable/servermap.py 661
832-        if self._need_privkey and last_verinfo:
833-            # send them a request for the privkey. We send one request per
834-            # server.
835-            lp2 = self.log("sending privkey request",
836-                           parent=lp, level=log.NOISY)
837-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
838-             offsets_tuple) = last_verinfo
839-            o = dict(offsets_tuple)
840+            # - Next, we need the version information. We almost
841+            #   certainly got this by reading the first thousand or so
842+            #   bytes of the share on the storage server, so we
843+            #   shouldn't need to fetch anything at this step.
844+            d2 = reader.get_verinfo()
845+            d2.addErrback(lambda error, shnum=shnum, peerid=peerid:
846+                self._got_corrupt_share(error, shnum, peerid, data, lp))
847+            # - Next, we need the signature. For an SDMF share, it is
848+            #   likely that we fetched this when doing our initial fetch
849+            #   to get the version information. In MDMF, this lives at
850+            #   the end of the share, so unless the file is quite small,
851+            #   we'll need to do a remote fetch to get it.
852+            d3 = reader.get_signature()
853+            d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
854+                self._got_corrupt_share(error, shnum, peerid, data, lp))
855+            #  Once we have all three of these responses, we can move on
856+            #  to validating the signature
857 
858hunk ./src/allmydata/mutable/servermap.py 679
859-            self._queries_outstanding.add(peerid)
860-            readv = [ (o['enc_privkey'], (o['EOF'] - o['enc_privkey'])) ]
861-            ss = self._servermap.connections[peerid]
862-            privkey_started = time.time()
863-            d = self._do_read(ss, peerid, self._storage_index,
864-                              [last_shnum], readv)
865-            d.addCallback(self._got_privkey_results, peerid, last_shnum,
866-                          privkey_started, lp2)
867-            d.addErrback(self._privkey_query_failed, peerid, last_shnum, lp2)
868-            d.addErrback(log.err)
869-            d.addCallback(self._check_for_done)
870-            d.addErrback(self._fatal_error)
871+            # Does the node already have a privkey? If not, we'll try to
872+            # fetch it here.
873+            if self._need_privkey:
874+                d4 = reader.get_encprivkey()
875+                d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
876+                    self._try_to_validate_privkey(results, peerid, shnum, lp))
877+                d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
878+                    self._privkey_query_failed(error, shnum, data, lp))
879+            else:
880+                d4 = defer.succeed(None)
881 
882hunk ./src/allmydata/mutable/servermap.py 690
883+            dl = defer.DeferredList([d, d2, d3, d4])
884+            dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
885+                self._got_signature_one_share(results, shnum, peerid, lp))
886+            dl.addErrback(lambda error, shnum=shnum, data=data:
887+               self._got_corrupt_share(error, shnum, peerid, data, lp))
888+            dl.addCallback(lambda verinfo, shnum=shnum, peerid=peerid, data=data:
889+                self._cache_good_sharedata(verinfo, shnum, now, data))
890+            ds.append(dl)
891+        # dl is a deferred list that will fire when all of the shares
892+        # that we found on this peer are done processing. When dl fires,
893+        # we know that processing is done, so we can decrement the
894+        # semaphore-like thing that we incremented earlier.
895+        dl = defer.DeferredList(ds, fireOnOneErrback=True)
896+        # Are we done? Done means that there are no more queries to
897+        # send, that there are no outstanding queries, and that we
898+        # haven't received any queries that are still processing. If we
899+        # are done, self._check_for_done will cause the done deferred
900+        # that we returned to our caller to fire, which tells them that
901+        # they have a complete servermap, and that we won't be touching
902+        # the servermap anymore.
903+        dl.addCallback(self._check_for_done)
904+        dl.addErrback(self._fatal_error)
905         # all done!
906         self.log("_got_results done", parent=lp, level=log.NOISY)
907hunk ./src/allmydata/mutable/servermap.py 714
908+        return dl
909+
910+
911+    def _try_to_set_pubkey(self, pubkey_s, peerid, shnum, lp):
912+        if self._node.get_pubkey():
913+            return # don't go through this again if we don't have to
914+        fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
915+        assert len(fingerprint) == 32
916+        if fingerprint != self._node.get_fingerprint():
917+            raise CorruptShareError(peerid, shnum,
918+                                "pubkey doesn't match fingerprint")
919+        self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
920+        assert self._node.get_pubkey()
921+
922 
923     def notify_server_corruption(self, peerid, shnum, reason):
924         ss = self._servermap.connections[peerid]
925hunk ./src/allmydata/mutable/servermap.py 734
926         ss.callRemoteOnly("advise_corrupt_share",
927                           "mutable", self._storage_index, shnum, reason)
928 
929-    def _got_results_one_share(self, shnum, data, peerid, lp):
930+
931+    def _got_signature_one_share(self, results, shnum, peerid, lp):
932+        # It is our job to give versioninfo to our caller. We need to
933+        # raise CorruptShareError if the share is corrupt for any
934+        # reason, something that our caller will handle.
935         self.log(format="_got_results: got shnum #%(shnum)d from peerid %(peerid)s",
936                  shnum=shnum,
937                  peerid=idlib.shortnodeid_b2a(peerid),
938hunk ./src/allmydata/mutable/servermap.py 744
939                  level=log.NOISY,
940                  parent=lp)
941-
942-        # this might raise NeedMoreDataError, if the pubkey and signature
943-        # live at some weird offset. That shouldn't happen, so I'm going to
944-        # treat it as a bad share.
945-        (seqnum, root_hash, IV, k, N, segsize, datalength,
946-         pubkey_s, signature, prefix) = unpack_prefix_and_signature(data)
947-
948-        if not self._node.get_pubkey():
949-            fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
950-            assert len(fingerprint) == 32
951-            if fingerprint != self._node.get_fingerprint():
952-                raise CorruptShareError(peerid, shnum,
953-                                        "pubkey doesn't match fingerprint")
954-            self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
955-
956-        if self._need_privkey:
957-            self._try_to_extract_privkey(data, peerid, shnum, lp)
958-
959-        (ig_version, ig_seqnum, ig_root_hash, ig_IV, ig_k, ig_N,
960-         ig_segsize, ig_datalen, offsets) = unpack_header(data)
961+        _, verinfo, signature, __ = results
962+        (seqnum,
963+         root_hash,
964+         saltish,
965+         segsize,
966+         datalen,
967+         k,
968+         n,
969+         prefix,
970+         offsets) = verinfo[1]
971         offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
972 
973hunk ./src/allmydata/mutable/servermap.py 756
974-        verinfo = (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
975+        # XXX: This should be done for us in the method, so
976+        # presumably you can go in there and fix it.
977+        verinfo = (seqnum,
978+                   root_hash,
979+                   saltish,
980+                   segsize,
981+                   datalen,
982+                   k,
983+                   n,
984+                   prefix,
985                    offsets_tuple)
986hunk ./src/allmydata/mutable/servermap.py 767
987+        # This tuple uniquely identifies a share on the grid; we use it
988+        # to keep track of the ones that we've already seen.
989 
990         if verinfo not in self._valid_versions:
991hunk ./src/allmydata/mutable/servermap.py 771
992-            # it's a new pair. Verify the signature.
993-            valid = self._node.get_pubkey().verify(prefix, signature)
994+            # This is a new version tuple, and we need to validate it
995+            # against the public key before keeping track of it.
996+            assert self._node.get_pubkey()
997+            valid = self._node.get_pubkey().verify(prefix, signature[1])
998             if not valid:
999hunk ./src/allmydata/mutable/servermap.py 776
1000-                raise CorruptShareError(peerid, shnum, "signature is invalid")
1001+                raise CorruptShareError(peerid, shnum,
1002+                                        "signature is invalid")
1003 
1004hunk ./src/allmydata/mutable/servermap.py 779
1005-            # ok, it's a valid verinfo. Add it to the list of validated
1006-            # versions.
1007-            self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
1008-                     % (seqnum, base32.b2a(root_hash)[:4],
1009-                        idlib.shortnodeid_b2a(peerid), shnum,
1010-                        k, N, segsize, datalength),
1011-                     parent=lp)
1012-            self._valid_versions.add(verinfo)
1013-        # We now know that this is a valid candidate verinfo.
1014+        # ok, it's a valid verinfo. Add it to the list of validated
1015+        # versions.
1016+        self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
1017+                 % (seqnum, base32.b2a(root_hash)[:4],
1018+                    idlib.shortnodeid_b2a(peerid), shnum,
1019+                    k, n, segsize, datalen),
1020+                    parent=lp)
1021+        self._valid_versions.add(verinfo)
1022+        # We now know that this is a valid candidate verinfo. Whether or
1023+        # not this instance of it is valid is a matter for the next
1024+        # statement; at this point, we just know that if we see this
1025+        # version info again, that its signature checks out and that
1026+        # we're okay to skip the signature-checking step.
1027 
1028hunk ./src/allmydata/mutable/servermap.py 793
1029+        # (peerid, shnum) are bound in the method invocation.
1030         if (peerid, shnum) in self._servermap.bad_shares:
1031             # we've been told that the rest of the data in this share is
1032             # unusable, so don't add it to the servermap.
1033hunk ./src/allmydata/mutable/servermap.py 808
1034         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
1035         return verinfo
1036 
1037+
1038     def _deserialize_pubkey(self, pubkey_s):
1039         verifier = rsa.create_verifying_key_from_string(pubkey_s)
1040         return verifier
1041hunk ./src/allmydata/mutable/servermap.py 813
1042 
1043-    def _try_to_extract_privkey(self, data, peerid, shnum, lp):
1044-        try:
1045-            r = unpack_share(data)
1046-        except NeedMoreDataError, e:
1047-            # this share won't help us. oh well.
1048-            offset = e.encprivkey_offset
1049-            length = e.encprivkey_length
1050-            self.log("shnum %d on peerid %s: share was too short (%dB) "
1051-                     "to get the encprivkey; [%d:%d] ought to hold it" %
1052-                     (shnum, idlib.shortnodeid_b2a(peerid), len(data),
1053-                      offset, offset+length),
1054-                     parent=lp)
1055-            # NOTE: if uncoordinated writes are taking place, someone might
1056-            # change the share (and most probably move the encprivkey) before
1057-            # we get a chance to do one of these reads and fetch it. This
1058-            # will cause us to see a NotEnoughSharesError(unable to fetch
1059-            # privkey) instead of an UncoordinatedWriteError . This is a
1060-            # nuisance, but it will go away when we move to DSA-based mutable
1061-            # files (since the privkey will be small enough to fit in the
1062-            # write cap).
1063-
1064-            return
1065-
1066-        (seqnum, root_hash, IV, k, N, segsize, datalen,
1067-         pubkey, signature, share_hash_chain, block_hash_tree,
1068-         share_data, enc_privkey) = r
1069-
1070-        return self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1071 
1072     def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
1073hunk ./src/allmydata/mutable/servermap.py 815
1074-
1075+        """
1076+        Given a writekey from a remote server, I validate it against the
1077+        writekey stored in my node. If it is valid, then I set the
1078+        privkey and encprivkey properties of the node.
1079+        """
1080         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
1081         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
1082         if alleged_writekey != self._node.get_writekey():
1083hunk ./src/allmydata/mutable/servermap.py 892
1084         self._queries_completed += 1
1085         self._last_failure = f
1086 
1087-    def _got_privkey_results(self, datavs, peerid, shnum, started, lp):
1088-        now = time.time()
1089-        elapsed = now - started
1090-        self._status.add_per_server_time(peerid, "privkey", started, elapsed)
1091-        self._queries_outstanding.discard(peerid)
1092-        if not self._need_privkey:
1093-            return
1094-        if shnum not in datavs:
1095-            self.log("privkey wasn't there when we asked it",
1096-                     level=log.WEIRD, umid="VA9uDQ")
1097-            return
1098-        datav = datavs[shnum]
1099-        enc_privkey = datav[0]
1100-        self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1101 
1102     def _privkey_query_failed(self, f, peerid, shnum, lp):
1103         self._queries_outstanding.discard(peerid)
1104hunk ./src/allmydata/mutable/servermap.py 906
1105         self._servermap.problems.append(f)
1106         self._last_failure = f
1107 
1108+
1109     def _check_for_done(self, res):
1110         # exit paths:
1111         #  return self._send_more_queries(outstanding) : send some more queries
1112hunk ./src/allmydata/mutable/servermap.py 912
1113         #  return self._done() : all done
1114         #  return : keep waiting, no new queries
1115-
1116         lp = self.log(format=("_check_for_done, mode is '%(mode)s', "
1117                               "%(outstanding)d queries outstanding, "
1118                               "%(extra)d extra peers available, "
1119hunk ./src/allmydata/mutable/servermap.py 1117
1120         self._servermap.last_update_time = self._started
1121         # the servermap will not be touched after this
1122         self.log("servermap: %s" % self._servermap.summarize_versions())
1123+
1124         eventually(self._done_deferred.callback, self._servermap)
1125 
1126     def _fatal_error(self, f):
1127hunk ./src/allmydata/test/test_mutable.py 637
1128         d.addCallback(_created)
1129         return d
1130 
1131-    def publish_multiple(self):
1132+    def publish_mdmf(self):
1133+        # like publish_one, except that the result is guaranteed to be
1134+        # an MDMF file.
1135+        # self.CONTENTS should have more than one segment.
1136+        self.CONTENTS = "This is an MDMF file" * 100000
1137+        self._storage = FakeStorage()
1138+        self._nodemaker = make_nodemaker(self._storage)
1139+        self._storage_broker = self._nodemaker.storage_broker
1140+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
1141+        def _created(node):
1142+            self._fn = node
1143+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1144+        d.addCallback(_created)
1145+        return d
1146+
1147+
1148+    def publish_sdmf(self):
1149+        # like publish_one, except that the result is guaranteed to be
1150+        # an SDMF file
1151+        self.CONTENTS = "This is an SDMF file" * 1000
1152+        self._storage = FakeStorage()
1153+        self._nodemaker = make_nodemaker(self._storage)
1154+        self._storage_broker = self._nodemaker.storage_broker
1155+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
1156+        def _created(node):
1157+            self._fn = node
1158+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1159+        d.addCallback(_created)
1160+        return d
1161+
1162+
1163+    def publish_multiple(self, version=0):
1164         self.CONTENTS = ["Contents 0",
1165                          "Contents 1",
1166                          "Contents 2",
1167hunk ./src/allmydata/test/test_mutable.py 677
1168         self._copied_shares = {}
1169         self._storage = FakeStorage()
1170         self._nodemaker = make_nodemaker(self._storage)
1171-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0]) # seqnum=1
1172+        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
1173         def _created(node):
1174             self._fn = node
1175             # now create multiple versions of the same file, and accumulate
1176hunk ./src/allmydata/test/test_mutable.py 906
1177         return d
1178 
1179 
1180+    def test_servermapupdater_finds_mdmf_files(self):
1181+        # setUp already published an MDMF file for us. We just need to
1182+        # make sure that when we run the ServermapUpdater, the file is
1183+        # reported to have one recoverable version.
1184+        d = defer.succeed(None)
1185+        d.addCallback(lambda ignored:
1186+            self.publish_mdmf())
1187+        d.addCallback(lambda ignored:
1188+            self.make_servermap(mode=MODE_CHECK))
1189+        # Calling make_servermap also updates the servermap in the mode
1190+        # that we specify, so we just need to see what it says.
1191+        def _check_servermap(sm):
1192+            self.failUnlessEqual(len(sm.recoverable_versions()), 1)
1193+        d.addCallback(_check_servermap)
1194+        return d
1195+
1196+
1197+    def test_servermapupdater_finds_sdmf_files(self):
1198+        d = defer.succeed(None)
1199+        d.addCallback(lambda ignored:
1200+            self.publish_sdmf())
1201+        d.addCallback(lambda ignored:
1202+            self.make_servermap(mode=MODE_CHECK))
1203+        d.addCallback(lambda servermap:
1204+            self.failUnlessEqual(len(servermap.recoverable_versions()), 1))
1205+        return d
1206+
1207 
1208 class Roundtrip(unittest.TestCase, testutil.ShouldFailMixin, PublishMixin):
1209     def setUp(self):
1210hunk ./src/allmydata/test/test_mutable.py 1050
1211         return d
1212     test_no_servers_download.timeout = 15
1213 
1214+
1215     def _test_corrupt_all(self, offset, substring,
1216                           should_succeed=False, corrupt_early=True,
1217                           failure_checker=None):
1218}
1219[Make a segmented mutable uploader
1220Kevan Carstensen <kevan@isnotajoke.com>**20100626234204
1221 Ignore-this: d199af8ab0bc64d8ed2bc19c5437bfba
1222 
1223 The mutable file uploader should be able to publish files with one
1224 segment and files with multiple segments. This patch makes it do that.
1225 This is still incomplete, and rather ugly -- I need to flesh out error
1226 handling, I need to write tests, and I need to remove some of the uglier
1227 kludges in the process before I can call this done.
1228] {
1229hunk ./src/allmydata/mutable/publish.py 8
1230 from zope.interface import implements
1231 from twisted.internet import defer
1232 from twisted.python import failure
1233-from allmydata.interfaces import IPublishStatus
1234+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
1235 from allmydata.util import base32, hashutil, mathutil, idlib, log
1236 from allmydata import hashtree, codec
1237 from allmydata.storage.server import si_b2a
1238hunk ./src/allmydata/mutable/publish.py 19
1239      UncoordinatedWriteError, NotEnoughServersError
1240 from allmydata.mutable.servermap import ServerMap
1241 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
1242-     unpack_checkstring, SIGNED_PREFIX
1243+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
1244+
1245+KiB = 1024
1246+DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
1247 
1248 class PublishStatus:
1249     implements(IPublishStatus)
1250hunk ./src/allmydata/mutable/publish.py 112
1251         self._status.set_helper(False)
1252         self._status.set_progress(0.0)
1253         self._status.set_active(True)
1254+        # We use this to control how the file is written.
1255+        version = self._node.get_version()
1256+        assert version in (SDMF_VERSION, MDMF_VERSION)
1257+        self._version = version
1258 
1259     def get_status(self):
1260         return self._status
1261hunk ./src/allmydata/mutable/publish.py 134
1262         simultaneous write.
1263         """
1264 
1265-        # 1: generate shares (SDMF: files are small, so we can do it in RAM)
1266-        # 2: perform peer selection, get candidate servers
1267-        #  2a: send queries to n+epsilon servers, to determine current shares
1268-        #  2b: based upon responses, create target map
1269-        # 3: send slot_testv_and_readv_and_writev messages
1270-        # 4: as responses return, update share-dispatch table
1271-        # 4a: may need to run recovery algorithm
1272-        # 5: when enough responses are back, we're done
1273+        # 0. Setup encoding parameters, encoder, and other such things.
1274+        # 1. Encrypt, encode, and publish segments.
1275 
1276         self.log("starting publish, datalen is %s" % len(newdata))
1277         self._status.set_size(len(newdata))
1278hunk ./src/allmydata/mutable/publish.py 187
1279         self.bad_peers = set() # peerids who have errbacked/refused requests
1280 
1281         self.newdata = newdata
1282-        self.salt = os.urandom(16)
1283 
1284hunk ./src/allmydata/mutable/publish.py 188
1285+        # This will set self.segment_size, self.num_segments, and
1286+        # self.fec.
1287         self.setup_encoding_parameters()
1288 
1289         # if we experience any surprises (writes which were rejected because
1290hunk ./src/allmydata/mutable/publish.py 238
1291             self.bad_share_checkstrings[key] = old_checkstring
1292             self.connections[peerid] = self._servermap.connections[peerid]
1293 
1294-        # create the shares. We'll discard these as they are delivered. SDMF:
1295-        # we're allowed to hold everything in memory.
1296+        # Now, the process dovetails -- if this is an SDMF file, we need
1297+        # to write an SDMF file. Otherwise, we need to write an MDMF
1298+        # file.
1299+        if self._version == MDMF_VERSION:
1300+            return self._publish_mdmf()
1301+        else:
1302+            return self._publish_sdmf()
1303+        #return self.done_deferred
1304+
1305+    def _publish_mdmf(self):
1306+        # Next, we find homes for all of the shares that we don't have
1307+        # homes for yet.
1308+        # TODO: Make this part do peer selection.
1309+        self.update_goal()
1310+        self.writers = {}
1311+        # For each (peerid, shnum) in self.goal, we make an
1312+        # MDMFSlotWriteProxy for that peer. We'll use this to write
1313+        # shares to the peer.
1314+        for key in self.goal:
1315+            peerid, shnum = key
1316+            write_enabler = self._node.get_write_enabler(peerid)
1317+            renew_secret = self._node.get_renewal_secret(peerid)
1318+            cancel_secret = self._node.get_cancel_secret(peerid)
1319+            secrets = (write_enabler, renew_secret, cancel_secret)
1320+
1321+            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
1322+                                                      self.connections[peerid],
1323+                                                      self._storage_index,
1324+                                                      secrets,
1325+                                                      self._new_seqnum,
1326+                                                      self.required_shares,
1327+                                                      self.total_shares,
1328+                                                      self.segment_size,
1329+                                                      len(self.newdata))
1330+            if (peerid, shnum) in self._servermap.servermap:
1331+                old_versionid, old_timestamp = self._servermap.servermap[key]
1332+                (old_seqnum, old_root_hash, old_salt, old_segsize,
1333+                 old_datalength, old_k, old_N, old_prefix,
1334+                 old_offsets_tuple) = old_versionid
1335+                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
1336+
1337+        # Now, we start pushing shares.
1338+        self._status.timings["setup"] = time.time() - self._started
1339+        def _start_pushing(res):
1340+            self._started_pushing = time.time()
1341+            return res
1342+
1343+        # First, we encrypt, encode, and publish the shares that we need
1344+        # to encrypt, encode, and publish.
1345+
1346+        # This will eventually hold the block hash chain for each share
1347+        # that we publish. We define it this way so that empty publishes
1348+        # will still have something to write to the remote slot.
1349+        self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
1350+        self.sharehash_leaves = None # eventually [sharehashes]
1351+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
1352+                              # validate the share]
1353 
1354hunk ./src/allmydata/mutable/publish.py 296
1355+        d = defer.succeed(None)
1356+        self.log("Starting push")
1357+        for i in xrange(self.num_segments - 1):
1358+            d.addCallback(lambda ignored, i=i:
1359+                self.push_segment(i))
1360+            d.addCallback(self._turn_barrier)
1361+        # We have at least one segment, so we will have a tail segment
1362+        if self.num_segments > 0:
1363+            d.addCallback(lambda ignored:
1364+                self.push_tail_segment())
1365+
1366+        d.addCallback(lambda ignored:
1367+            self.push_encprivkey())
1368+        d.addCallback(lambda ignored:
1369+            self.push_blockhashes())
1370+        d.addCallback(lambda ignored:
1371+            self.push_sharehashes())
1372+        d.addCallback(lambda ignored:
1373+            self.push_toplevel_hashes_and_signature())
1374+        d.addCallback(lambda ignored:
1375+            self.finish_publishing())
1376+        return d
1377+
1378+
1379+    def _publish_sdmf(self):
1380         self._status.timings["setup"] = time.time() - self._started
1381hunk ./src/allmydata/mutable/publish.py 322
1382+        self.salt = os.urandom(16)
1383+
1384         d = self._encrypt_and_encode()
1385         d.addCallback(self._generate_shares)
1386         def _start_pushing(res):
1387hunk ./src/allmydata/mutable/publish.py 335
1388 
1389         return self.done_deferred
1390 
1391+
1392     def setup_encoding_parameters(self):
1393hunk ./src/allmydata/mutable/publish.py 337
1394-        segment_size = len(self.newdata)
1395+        if self._version == MDMF_VERSION:
1396+            segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
1397+        else:
1398+            segment_size = len(self.newdata) # SDMF is only one segment
1399         # this must be a multiple of self.required_shares
1400         segment_size = mathutil.next_multiple(segment_size,
1401                                               self.required_shares)
1402hunk ./src/allmydata/mutable/publish.py 350
1403                                                   segment_size)
1404         else:
1405             self.num_segments = 0
1406-        assert self.num_segments in [0, 1,] # SDMF restrictions
1407+        if self._version == SDMF_VERSION:
1408+            assert self.num_segments in (0, 1) # SDMF
1409+            return
1410+        # calculate the tail segment size.
1411+        self.tail_segment_size = len(self.newdata) % segment_size
1412+
1413+        if self.tail_segment_size == 0:
1414+            # The tail segment is the same size as the other segments.
1415+            self.tail_segment_size = segment_size
1416+
1417+        # We'll make an encoder ahead-of-time for the normal-sized
1418+        # segments (defined as any segment of segment_size size.
1419+        # (the part of the code that puts the tail segment will make its
1420+        #  own encoder for that part)
1421+        fec = codec.CRSEncoder()
1422+        fec.set_params(self.segment_size,
1423+                       self.required_shares, self.total_shares)
1424+        self.piece_size = fec.get_block_size()
1425+        self.fec = fec
1426+
1427+
1428+    def push_segment(self, segnum):
1429+        started = time.time()
1430+        segsize = self.segment_size
1431+        self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
1432+        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
1433+        assert len(data) == segsize
1434+
1435+        salt = os.urandom(16)
1436+
1437+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1438+        enc = AES(key)
1439+        crypttext = enc.process(data)
1440+        assert len(crypttext) == len(data)
1441+
1442+        now = time.time()
1443+        self._status.timings["encrypt"] = now - started
1444+        started = now
1445+
1446+        # now apply FEC
1447+
1448+        self._status.set_status("Encoding")
1449+        crypttext_pieces = [None] * self.required_shares
1450+        piece_size = self.piece_size
1451+        for i in range(len(crypttext_pieces)):
1452+            offset = i * piece_size
1453+            piece = crypttext[offset:offset+piece_size]
1454+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1455+            crypttext_pieces[i] = piece
1456+            assert len(piece) == piece_size
1457+        d = self.fec.encode(crypttext_pieces)
1458+        def _done_encoding(res):
1459+            elapsed = time.time() - started
1460+            self._status.timings["encode"] = elapsed
1461+            return res
1462+        d.addCallback(_done_encoding)
1463+
1464+        def _push_shares_and_salt(results):
1465+            shares, shareids = results
1466+            dl = []
1467+            for i in xrange(len(shares)):
1468+                sharedata = shares[i]
1469+                shareid = shareids[i]
1470+                block_hash = hashutil.block_hash(salt + sharedata)
1471+                self.blockhashes[shareid].append(block_hash)
1472+
1473+                # find the writer for this share
1474+                d = self.writers[shareid].put_block(sharedata, segnum, salt)
1475+                dl.append(d)
1476+            # TODO: Naturally, we need to check on the results of these.
1477+            return defer.DeferredList(dl)
1478+        d.addCallback(_push_shares_and_salt)
1479+        return d
1480+
1481+
1482+    def push_tail_segment(self):
1483+        # This is essentially the same as push_segment, except that we
1484+        # don't use the cached encoder that we use elsewhere.
1485+        self.log("Pushing tail segment")
1486+        started = time.time()
1487+        segsize = self.segment_size
1488+        data = self.newdata[segsize * (self.num_segments-1):]
1489+        assert len(data) == self.tail_segment_size
1490+        salt = os.urandom(16)
1491+
1492+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1493+        enc = AES(key)
1494+        crypttext = enc.process(data)
1495+        assert len(crypttext) == len(data)
1496+
1497+        now = time.time()
1498+        self._status.timings['encrypt'] = now - started
1499+        started = now
1500+
1501+        self._status.set_status("Encoding")
1502+        tail_fec = codec.CRSEncoder()
1503+        tail_fec.set_params(self.tail_segment_size,
1504+                            self.required_shares,
1505+                            self.total_shares)
1506+
1507+        crypttext_pieces = [None] * self.required_shares
1508+        piece_size = tail_fec.get_block_size()
1509+        for i in range(len(crypttext_pieces)):
1510+            offset = i * piece_size
1511+            piece = crypttext[offset:offset+piece_size]
1512+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1513+            crypttext_pieces[i] = piece
1514+            assert len(piece) == piece_size
1515+        d = tail_fec.encode(crypttext_pieces)
1516+        def _push_shares_and_salt(results):
1517+            shares, shareids = results
1518+            dl = []
1519+            for i in xrange(len(shares)):
1520+                sharedata = shares[i]
1521+                shareid = shareids[i]
1522+                block_hash = hashutil.block_hash(salt + sharedata)
1523+                self.blockhashes[shareid].append(block_hash)
1524+                # find the writer for this share
1525+                d = self.writers[shareid].put_block(sharedata,
1526+                                                    self.num_segments - 1,
1527+                                                    salt)
1528+                dl.append(d)
1529+            # TODO: Naturally, we need to check on the results of these.
1530+            return defer.DeferredList(dl)
1531+        d.addCallback(_push_shares_and_salt)
1532+        return d
1533+
1534+
1535+    def push_encprivkey(self):
1536+        started = time.time()
1537+        encprivkey = self._encprivkey
1538+        dl = []
1539+        def _spy_on_writer(results):
1540+            print results
1541+            return results
1542+        for shnum, writer in self.writers.iteritems():
1543+            d = writer.put_encprivkey(encprivkey)
1544+            dl.append(d)
1545+        d = defer.DeferredList(dl)
1546+        return d
1547+
1548+
1549+    def push_blockhashes(self):
1550+        started = time.time()
1551+        dl = []
1552+        def _spy_on_results(results):
1553+            print results
1554+            return results
1555+        self.sharehash_leaves = [None] * len(self.blockhashes)
1556+        for shnum, blockhashes in self.blockhashes.iteritems():
1557+            t = hashtree.HashTree(blockhashes)
1558+            self.blockhashes[shnum] = list(t)
1559+            # set the leaf for future use.
1560+            self.sharehash_leaves[shnum] = t[0]
1561+            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
1562+            dl.append(d)
1563+        d = defer.DeferredList(dl)
1564+        return d
1565+
1566+
1567+    def push_sharehashes(self):
1568+        share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
1569+        share_hash_chain = {}
1570+        ds = []
1571+        def _spy_on_results(results):
1572+            print results
1573+            return results
1574+        for shnum in xrange(len(self.sharehash_leaves)):
1575+            needed_indices = share_hash_tree.needed_hashes(shnum)
1576+            self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
1577+                                             for i in needed_indices] )
1578+            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
1579+            ds.append(d)
1580+        self.root_hash = share_hash_tree[0]
1581+        d = defer.DeferredList(ds)
1582+        return d
1583+
1584+
1585+    def push_toplevel_hashes_and_signature(self):
1586+        # We need to to three things here:
1587+        #   - Push the root hash and salt hash
1588+        #   - Get the checkstring of the resulting layout; sign that.
1589+        #   - Push the signature
1590+        ds = []
1591+        def _spy_on_results(results):
1592+            print results
1593+            return results
1594+        for shnum in xrange(self.total_shares):
1595+            d = self.writers[shnum].put_root_hash(self.root_hash)
1596+            ds.append(d)
1597+        d = defer.DeferredList(ds)
1598+        def _make_and_place_signature(ignored):
1599+            signable = self.writers[0].get_signable()
1600+            self.signature = self._privkey.sign(signable)
1601+
1602+            ds = []
1603+            for (shnum, writer) in self.writers.iteritems():
1604+                d = writer.put_signature(self.signature)
1605+                ds.append(d)
1606+            return defer.DeferredList(ds)
1607+        d.addCallback(_make_and_place_signature)
1608+        return d
1609+
1610+
1611+    def finish_publishing(self):
1612+        # We're almost done -- we just need to put the verification key
1613+        # and the offsets
1614+        ds = []
1615+        verification_key = self._pubkey.serialize()
1616+
1617+        def _spy_on_results(results):
1618+            print results
1619+            return results
1620+        for (shnum, writer) in self.writers.iteritems():
1621+            d = writer.put_verification_key(verification_key)
1622+            d.addCallback(lambda ignored, writer=writer:
1623+                writer.finish_publishing())
1624+            ds.append(d)
1625+        return defer.DeferredList(ds)
1626+
1627+
1628+    def _turn_barrier(self, res):
1629+        # putting this method in a Deferred chain imposes a guaranteed
1630+        # reactor turn between the pre- and post- portions of that chain.
1631+        # This can be useful to limit memory consumption: since Deferreds do
1632+        # not do tail recursion, code which uses defer.succeed(result) for
1633+        # consistency will cause objects to live for longer than you might
1634+        # normally expect.
1635+        return fireEventually(res)
1636+
1637 
1638     def _fatal_error(self, f):
1639         self.log("error during loop", failure=f, level=log.UNUSUAL)
1640hunk ./src/allmydata/mutable/publish.py 716
1641             self.log_goal(self.goal, "after update: ")
1642 
1643 
1644-
1645     def _encrypt_and_encode(self):
1646         # this returns a Deferred that fires with a list of (sharedata,
1647         # sharenum) tuples. TODO: cache the ciphertext, only produce the
1648hunk ./src/allmydata/mutable/publish.py 757
1649         d.addCallback(_done_encoding)
1650         return d
1651 
1652+
1653     def _generate_shares(self, shares_and_shareids):
1654         # this sets self.shares and self.root_hash
1655         self.log("_generate_shares")
1656hunk ./src/allmydata/mutable/publish.py 1145
1657             self._status.set_progress(1.0)
1658         eventually(self.done_deferred.callback, res)
1659 
1660-
1661hunk ./src/allmydata/test/test_mutable.py 248
1662         d.addCallback(_created)
1663         return d
1664 
1665+
1666+    def test_create_mdmf(self):
1667+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
1668+        def _created(n):
1669+            self.failUnless(isinstance(n, MutableFileNode))
1670+            self.failUnlessEqual(n.get_storage_index(), n._storage_index)
1671+            sb = self.nodemaker.storage_broker
1672+            peer0 = sorted(sb.get_all_serverids())[0]
1673+            shnums = self._storage._peers[peer0].keys()
1674+            self.failUnlessEqual(len(shnums), 1)
1675+        d.addCallback(_created)
1676+        return d
1677+
1678+
1679     def test_serialize(self):
1680         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
1681         calls = []
1682hunk ./src/allmydata/test/test_mutable.py 334
1683         d.addCallback(_created)
1684         return d
1685 
1686+
1687+    def test_create_mdmf_with_initial_contents(self):
1688+        initial_contents = "foobarbaz" * 131072 # 900KiB
1689+        d = self.nodemaker.create_mutable_file(initial_contents,
1690+                                               version=MDMF_VERSION)
1691+        def _created(n):
1692+            d = n.download_best_version()
1693+            d.addCallback(lambda data:
1694+                self.failUnlessEqual(data, initial_contents))
1695+            d.addCallback(lambda ignored:
1696+                n.overwrite(initial_contents + "foobarbaz"))
1697+            d.addCallback(lambda ignored:
1698+                n.download_best_version())
1699+            d.addCallback(lambda data:
1700+                self.failUnlessEqual(data, initial_contents +
1701+                                           "foobarbaz"))
1702+            return d
1703+        d.addCallback(_created)
1704+        return d
1705+
1706+
1707     def test_create_with_initial_contents_function(self):
1708         data = "initial contents"
1709         def _make_contents(n):
1710hunk ./src/allmydata/test/test_mutable.py 370
1711         d.addCallback(lambda data2: self.failUnlessEqual(data2, data))
1712         return d
1713 
1714+
1715+    def test_create_mdmf_with_initial_contents_function(self):
1716+        data = "initial contents" * 100000
1717+        def _make_contents(n):
1718+            self.failUnless(isinstance(n, MutableFileNode))
1719+            key = n.get_writekey()
1720+            self.failUnless(isinstance(key, str), key)
1721+            self.failUnlessEqual(len(key), 16)
1722+            return data
1723+        d = self.nodemaker.create_mutable_file(_make_contents,
1724+                                               version=MDMF_VERSION)
1725+        d.addCallback(lambda n:
1726+            n.download_best_version())
1727+        d.addCallback(lambda data2:
1728+            self.failUnlessEqual(data2, data))
1729+        return d
1730+
1731+
1732     def test_create_with_too_large_contents(self):
1733         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
1734         d = self.nodemaker.create_mutable_file(BIG)
1735}
1736[Write a segmented mutable downloader
1737Kevan Carstensen <kevan@isnotajoke.com>**20100626234314
1738 Ignore-this: d2bef531cde1b5c38f2eb28afdd4b17c
1739 
1740 The segmented mutable downloader can deal with MDMF files (files with
1741 one or more segments in MDMF format) and SDMF files (files with one
1742 segment in SDMF format). It is backwards compatible with the old
1743 file format.
1744 
1745 This patch also contains tests for the segmented mutable downloader.
1746] {
1747hunk ./src/allmydata/mutable/retrieve.py 8
1748 from twisted.internet import defer
1749 from twisted.python import failure
1750 from foolscap.api import DeadReferenceError, eventually, fireEventually
1751-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
1752-from allmydata.util import hashutil, idlib, log
1753+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
1754+                                 MDMF_VERSION, SDMF_VERSION
1755+from allmydata.util import hashutil, idlib, log, mathutil
1756 from allmydata import hashtree, codec
1757 from allmydata.storage.server import si_b2a
1758 from pycryptopp.cipher.aes import AES
1759hunk ./src/allmydata/mutable/retrieve.py 17
1760 from pycryptopp.publickey import rsa
1761 
1762 from allmydata.mutable.common import DictOfSets, CorruptShareError, UncoordinatedWriteError
1763-from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data
1764+from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data, \
1765+                                     MDMFSlotReadProxy
1766 
1767 class RetrieveStatus:
1768     implements(IRetrieveStatus)
1769hunk ./src/allmydata/mutable/retrieve.py 104
1770         self.verinfo = verinfo
1771         # during repair, we may be called upon to grab the private key, since
1772         # it wasn't picked up during a verify=False checker run, and we'll
1773-        # need it for repair to generate the a new version.
1774+        # need it for repair to generate a new version.
1775         self._need_privkey = fetch_privkey
1776         if self._node.get_privkey():
1777             self._need_privkey = False
1778hunk ./src/allmydata/mutable/retrieve.py 109
1779 
1780+        if self._need_privkey:
1781+            # TODO: Evaluate the need for this. We'll use it if we want
1782+            # to limit how many queries are on the wire for the privkey
1783+            # at once.
1784+            self._privkey_query_markers = [] # one Marker for each time we've
1785+                                             # tried to get the privkey.
1786+
1787         self._status = RetrieveStatus()
1788         self._status.set_storage_index(self._storage_index)
1789         self._status.set_helper(False)
1790hunk ./src/allmydata/mutable/retrieve.py 125
1791          offsets_tuple) = self.verinfo
1792         self._status.set_size(datalength)
1793         self._status.set_encoding(k, N)
1794+        self.readers = {}
1795 
1796     def get_status(self):
1797         return self._status
1798hunk ./src/allmydata/mutable/retrieve.py 149
1799         self.remaining_sharemap = DictOfSets()
1800         for (shnum, peerid, timestamp) in shares:
1801             self.remaining_sharemap.add(shnum, peerid)
1802+            # If the servermap update fetched anything, it fetched at least 1
1803+            # KiB, so we ask for that much.
1804+            # TODO: Change the cache methods to allow us to fetch all of the
1805+            # data that they have, then change this method to do that.
1806+            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
1807+                                                               shnum,
1808+                                                               0,
1809+                                                               1000)
1810+            ss = self.servermap.connections[peerid]
1811+            reader = MDMFSlotReadProxy(ss,
1812+                                       self._storage_index,
1813+                                       shnum,
1814+                                       any_cache)
1815+            reader.peerid = peerid
1816+            self.readers[shnum] = reader
1817+
1818 
1819         self.shares = {} # maps shnum to validated blocks
1820hunk ./src/allmydata/mutable/retrieve.py 167
1821+        self._active_readers = [] # list of active readers for this dl.
1822+        self._validated_readers = set() # set of readers that we have
1823+                                        # validated the prefix of
1824+        self._block_hash_trees = {} # shnum => hashtree
1825+        # TODO: Make this into a file-backed consumer or something to
1826+        # conserve memory.
1827+        self._plaintext = ""
1828 
1829         # how many shares do we need?
1830hunk ./src/allmydata/mutable/retrieve.py 176
1831-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1832+        (seqnum,
1833+         root_hash,
1834+         IV,
1835+         segsize,
1836+         datalength,
1837+         k,
1838+         N,
1839+         prefix,
1840          offsets_tuple) = self.verinfo
1841hunk ./src/allmydata/mutable/retrieve.py 185
1842-        assert len(self.remaining_sharemap) >= k
1843-        # we start with the lowest shnums we have available, since FEC is
1844-        # faster if we're using "primary shares"
1845-        self.active_shnums = set(sorted(self.remaining_sharemap.keys())[:k])
1846-        for shnum in self.active_shnums:
1847-            # we use an arbitrary peer who has the share. If shares are
1848-            # doubled up (more than one share per peer), we could make this
1849-            # run faster by spreading the load among multiple peers. But the
1850-            # algorithm to do that is more complicated than I want to write
1851-            # right now, and a well-provisioned grid shouldn't have multiple
1852-            # shares per peer.
1853-            peerid = list(self.remaining_sharemap[shnum])[0]
1854-            self.get_data(shnum, peerid)
1855 
1856hunk ./src/allmydata/mutable/retrieve.py 186
1857-        # control flow beyond this point: state machine. Receiving responses
1858-        # from queries is the input. We might send out more queries, or we
1859-        # might produce a result.
1860 
1861hunk ./src/allmydata/mutable/retrieve.py 187
1862+        # We need one share hash tree for the entire file; its leaves
1863+        # are the roots of the block hash trees for the shares that
1864+        # comprise it, and its root is in the verinfo.
1865+        self.share_hash_tree = hashtree.IncompleteHashTree(N)
1866+        self.share_hash_tree.set_hashes({0: root_hash})
1867+
1868+        # This will set up both the segment decoder and the tail segment
1869+        # decoder, as well as a variety of other instance variables that
1870+        # the download process will use.
1871+        self._setup_encoding_parameters()
1872+        assert len(self.remaining_sharemap) >= k
1873+
1874+        self.log("starting download")
1875+        self._add_active_peers()
1876+        # The download process beyond this is a state machine.
1877+        # _add_active_peers will select the peers that we want to use
1878+        # for the download, and then attempt to start downloading. After
1879+        # each segment, it will check for doneness, reacting to broken
1880+        # peers and corrupt shares as necessary. If it runs out of good
1881+        # peers before downloading all of the segments, _done_deferred
1882+        # will errback.  Otherwise, it will eventually callback with the
1883+        # contents of the mutable file.
1884         return self._done_deferred
1885 
1886hunk ./src/allmydata/mutable/retrieve.py 211
1887-    def get_data(self, shnum, peerid):
1888-        self.log(format="sending sh#%(shnum)d request to [%(peerid)s]",
1889-                 shnum=shnum,
1890-                 peerid=idlib.shortnodeid_b2a(peerid),
1891-                 level=log.NOISY)
1892-        ss = self.servermap.connections[peerid]
1893-        started = time.time()
1894-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1895+
1896+    def _setup_encoding_parameters(self):
1897+        """
1898+        I set up the encoding parameters, including k, n, the number
1899+        of segments associated with this file, and the segment decoder.
1900+        """
1901+        (seqnum,
1902+         root_hash,
1903+         IV,
1904+         segsize,
1905+         datalength,
1906+         k,
1907+         n,
1908+         known_prefix,
1909          offsets_tuple) = self.verinfo
1910hunk ./src/allmydata/mutable/retrieve.py 226
1911-        offsets = dict(offsets_tuple)
1912+        self._required_shares = k
1913+        self._total_shares = n
1914+        self._segment_size = segsize
1915+        self._data_length = datalength
1916+
1917+        if not IV:
1918+            self._version = MDMF_VERSION
1919+        else:
1920+            self._version = SDMF_VERSION
1921+
1922+        if datalength and segsize:
1923+            self._num_segments = mathutil.div_ceil(datalength, segsize)
1924+            self._tail_data_size = datalength % segsize
1925+        else:
1926+            self._num_segments = 0
1927+            self._tail_data_size = 0
1928 
1929hunk ./src/allmydata/mutable/retrieve.py 243
1930-        # we read the checkstring, to make sure that the data we grab is from
1931-        # the right version.
1932-        readv = [ (0, struct.calcsize(SIGNED_PREFIX)) ]
1933+        self._segment_decoder = codec.CRSDecoder()
1934+        self._segment_decoder.set_params(segsize, k, n)
1935+        self._current_segment = 0
1936 
1937hunk ./src/allmydata/mutable/retrieve.py 247
1938-        # We also read the data, and the hashes necessary to validate them
1939-        # (share_hash_chain, block_hash_tree, share_data). We don't read the
1940-        # signature or the pubkey, since that was handled during the
1941-        # servermap phase, and we'll be comparing the share hash chain
1942-        # against the roothash that was validated back then.
1943+        if  not self._tail_data_size:
1944+            self._tail_data_size = segsize
1945 
1946hunk ./src/allmydata/mutable/retrieve.py 250
1947-        readv.append( (offsets['share_hash_chain'],
1948-                       offsets['enc_privkey'] - offsets['share_hash_chain'] ) )
1949+        self._tail_segment_size = mathutil.next_multiple(self._tail_data_size,
1950+                                                         self._required_shares)
1951+        if self._tail_segment_size == self._segment_size:
1952+            self._tail_decoder = self._segment_decoder
1953+        else:
1954+            self._tail_decoder = codec.CRSDecoder()
1955+            self._tail_decoder.set_params(self._tail_segment_size,
1956+                                          self._required_shares,
1957+                                          self._total_shares)
1958 
1959hunk ./src/allmydata/mutable/retrieve.py 260
1960-        # if we need the private key (for repair), we also fetch that
1961-        if self._need_privkey:
1962-            readv.append( (offsets['enc_privkey'],
1963-                           offsets['EOF'] - offsets['enc_privkey']) )
1964+        self.log("got encoding parameters: "
1965+                 "k: %d "
1966+                 "n: %d "
1967+                 "%d segments of %d bytes each (%d byte tail segment)" % \
1968+                 (k, n, self._num_segments, self._segment_size,
1969+                  self._tail_segment_size))
1970 
1971hunk ./src/allmydata/mutable/retrieve.py 267
1972-        m = Marker()
1973-        self._outstanding_queries[m] = (peerid, shnum, started)
1974+        for i in xrange(self._total_shares):
1975+            # So we don't have to do this later.
1976+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
1977 
1978hunk ./src/allmydata/mutable/retrieve.py 271
1979-        # ask the cache first
1980-        got_from_cache = False
1981-        datavs = []
1982-        for (offset, length) in readv:
1983-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
1984-                                                            offset, length)
1985-            if data is not None:
1986-                datavs.append(data)
1987-        if len(datavs) == len(readv):
1988-            self.log("got data from cache")
1989-            got_from_cache = True
1990-            d = fireEventually({shnum: datavs})
1991-            # datavs is a dict mapping shnum to a pair of strings
1992-        else:
1993-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
1994-        self.remaining_sharemap.discard(shnum, peerid)
1995+        # If we have more than one segment, we are an SDMF file, which
1996+        # means that we need to validate the salts as we receive them.
1997+        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
1998+        self._salt_hash_tree[0] = IV # from the prefix.
1999 
2000hunk ./src/allmydata/mutable/retrieve.py 276
2001-        d.addCallback(self._got_results, m, peerid, started, got_from_cache)
2002-        d.addErrback(self._query_failed, m, peerid)
2003-        # errors that aren't handled by _query_failed (and errors caused by
2004-        # _query_failed) get logged, but we still want to check for doneness.
2005-        def _oops(f):
2006-            self.log(format="problem in _query_failed for sh#%(shnum)d to %(peerid)s",
2007-                     shnum=shnum,
2008-                     peerid=idlib.shortnodeid_b2a(peerid),
2009-                     failure=f,
2010-                     level=log.WEIRD, umid="W0xnQA")
2011-        d.addErrback(_oops)
2012-        d.addBoth(self._check_for_done)
2013-        # any error during _check_for_done means the download fails. If the
2014-        # download is successful, _check_for_done will fire _done by itself.
2015-        d.addErrback(self._done)
2016-        d.addErrback(log.err)
2017-        return d # purely for testing convenience
2018 
2019hunk ./src/allmydata/mutable/retrieve.py 277
2020-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
2021-        # isolate the callRemote to a separate method, so tests can subclass
2022-        # Publish and override it
2023-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
2024-        return d
2025+    def _add_active_peers(self):
2026+        """
2027+        I populate self._active_readers with enough active readers to
2028+        retrieve the contents of this mutable file. I am called before
2029+        downloading starts, and (eventually) after each validation
2030+        error, connection error, or other problem in the download.
2031+        """
2032+        # TODO: It would be cool to investigate other heuristics for
2033+        # reader selection. For instance, the cost (in time the user
2034+        # spends waiting for their file) of selecting a really slow peer
2035+        # that happens to have a primary share is probably more than
2036+        # selecting a really fast peer that doesn't have a primary
2037+        # share. Maybe the servermap could be extended to provide this
2038+        # information; it could keep track of latency information while
2039+        # it gathers more important data, and then this routine could
2040+        # use that to select active readers.
2041+        #
2042+        # (these and other questions would be easier to answer with a
2043+        #  robust, configurable tahoe-lafs simulator, which modeled node
2044+        #  failures, differences in node speed, and other characteristics
2045+        #  that we expect storage servers to have.  You could have
2046+        #  presets for really stable grids (like allmydata.com),
2047+        #  friendnets, make it easy to configure your own settings, and
2048+        #  then simulate the effect of big changes on these use cases
2049+        #  instead of just reasoning about what the effect might be. Out
2050+        #  of scope for MDMF, though.)
2051 
2052hunk ./src/allmydata/mutable/retrieve.py 304
2053-    def remove_peer(self, peerid):
2054-        for shnum in list(self.remaining_sharemap.keys()):
2055-            self.remaining_sharemap.discard(shnum, peerid)
2056+        # We need at least self._required_shares readers to download a
2057+        # segment.
2058+        needed = self._required_shares - len(self._active_readers)
2059+        # XXX: Why don't format= log messages work here?
2060+        self.log("adding %d peers to the active peers list" % needed)
2061 
2062hunk ./src/allmydata/mutable/retrieve.py 310
2063-    def _got_results(self, datavs, marker, peerid, started, got_from_cache):
2064-        now = time.time()
2065-        elapsed = now - started
2066-        if not got_from_cache:
2067-            self._status.add_fetch_timing(peerid, elapsed)
2068-        self.log(format="got results (%(shares)d shares) from [%(peerid)s]",
2069-                 shares=len(datavs),
2070-                 peerid=idlib.shortnodeid_b2a(peerid),
2071-                 level=log.NOISY)
2072-        self._outstanding_queries.pop(marker, None)
2073-        if not self._running:
2074-            return
2075+        # We favor lower numbered shares, since FEC is faster with
2076+        # primary shares than with other shares, and lower-numbered
2077+        # shares are more likely to be primary than higher numbered
2078+        # shares.
2079+        active_shnums = set(sorted(self.remaining_sharemap.keys()))
2080+        # We shouldn't consider adding shares that we already have; this
2081+        # will cause problems later.
2082+        active_shnums -= set([reader.shnum for reader in self._active_readers])
2083+        active_shnums = list(active_shnums)[:needed]
2084+        if len(active_shnums) < needed:
2085+            # We don't have enough readers to retrieve the file; fail.
2086+            return self._failed()
2087 
2088hunk ./src/allmydata/mutable/retrieve.py 323
2089-        # note that we only ask for a single share per query, so we only
2090-        # expect a single share back. On the other hand, we use the extra
2091-        # shares if we get them.. seems better than an assert().
2092+        for shnum in active_shnums:
2093+            self._active_readers.append(self.readers[shnum])
2094+            self.log("added reader for share %d" % shnum)
2095+        assert len(self._active_readers) == self._required_shares
2096+        # Conceptually, this is part of the _add_active_peers step. It
2097+        # validates the prefixes of newly added readers to make sure
2098+        # that they match what we are expecting for self.verinfo. If
2099+        # validation is successful, _validate_active_prefixes will call
2100+        # _download_current_segment for us. If validation is
2101+        # unsuccessful, then _validate_prefixes will remove the peer and
2102+        # call _add_active_peers again, where we will attempt to rectify
2103+        # the problem by choosing another peer.
2104+        return self._validate_active_prefixes()
2105 
2106hunk ./src/allmydata/mutable/retrieve.py 337
2107-        for shnum,datav in datavs.items():
2108-            (prefix, hash_and_data) = datav[:2]
2109-            try:
2110-                self._got_results_one_share(shnum, peerid,
2111-                                            prefix, hash_and_data)
2112-            except CorruptShareError, e:
2113-                # log it and give the other shares a chance to be processed
2114-                f = failure.Failure()
2115-                self.log(format="bad share: %(f_value)s",
2116-                         f_value=str(f.value), failure=f,
2117-                         level=log.WEIRD, umid="7fzWZw")
2118-                self.notify_server_corruption(peerid, shnum, str(e))
2119-                self.remove_peer(peerid)
2120-                self.servermap.mark_bad_share(peerid, shnum, prefix)
2121-                self._bad_shares.add( (peerid, shnum) )
2122-                self._status.problems[peerid] = f
2123-                self._last_failure = f
2124-                pass
2125-            if self._need_privkey and len(datav) > 2:
2126-                lp = None
2127-                self._try_to_validate_privkey(datav[2], peerid, shnum, lp)
2128-        # all done!
2129 
2130hunk ./src/allmydata/mutable/retrieve.py 338
2131-    def notify_server_corruption(self, peerid, shnum, reason):
2132-        ss = self.servermap.connections[peerid]
2133-        ss.callRemoteOnly("advise_corrupt_share",
2134-                          "mutable", self._storage_index, shnum, reason)
2135+    def _validate_active_prefixes(self):
2136+        """
2137+        I check to make sure that the prefixes on the peers that I am
2138+        currently reading from match the prefix that we want to see, as
2139+        said in self.verinfo.
2140 
2141hunk ./src/allmydata/mutable/retrieve.py 344
2142-    def _got_results_one_share(self, shnum, peerid,
2143-                               got_prefix, got_hash_and_data):
2144-        self.log("_got_results: got shnum #%d from peerid %s"
2145-                 % (shnum, idlib.shortnodeid_b2a(peerid)))
2146-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2147+        If I find that all of the active peers have acceptable prefixes,
2148+        I pass control to _download_current_segment, which will use
2149+        those peers to do cool things. If I find that some of the active
2150+        peers have unacceptable prefixes, I will remove them from active
2151+        peers (and from further consideration) and call
2152+        _add_active_peers to attempt to rectify the situation. I keep
2153+        track of which peers I have already validated so that I don't
2154+        need to do so again.
2155+        """
2156+        assert self._active_readers, "No more active readers"
2157+
2158+        ds = []
2159+        new_readers = set(self._active_readers) - self._validated_readers
2160+        self.log('validating %d newly-added active readers' % len(new_readers))
2161+
2162+        for reader in new_readers:
2163+            # We force a remote read here -- otherwise, we are relying
2164+            # on cached data that we already verified as valid, and we
2165+            # won't detect an uncoordinated write that has occurred
2166+            # since the last servermap update.
2167+            d = reader.get_prefix(force_remote=True)
2168+            d.addCallback(self._try_to_validate_prefix, reader)
2169+            ds.append(d)
2170+        dl = defer.DeferredList(ds, consumeErrors=True)
2171+        def _check_results(results):
2172+            # Each result in results will be of the form (success, msg).
2173+            # We don't care about msg, but success will tell us whether
2174+            # or not the checkstring validated. If it didn't, we need to
2175+            # remove the offending (peer,share) from our active readers,
2176+            # and ensure that active readers is again populated.
2177+            bad_readers = []
2178+            for i, result in enumerate(results):
2179+                if not result[0]:
2180+                    reader = self._active_readers[i]
2181+                    f = result[1]
2182+                    assert isinstance(f, failure.Failure)
2183+
2184+                    self.log("The reader %s failed to "
2185+                             "properly validate: %s" % \
2186+                             (reader, str(f.value)))
2187+                    bad_readers.append((reader, f))
2188+                else:
2189+                    reader = self._active_readers[i]
2190+                    self.log("the reader %s checks out, so we'll use it" % \
2191+                             reader)
2192+                    self._validated_readers.add(reader)
2193+                    # Each time we validate a reader, we check to see if
2194+                    # we need the private key. If we do, we politely ask
2195+                    # for it and then continue computing. If we find
2196+                    # that we haven't gotten it at the end of
2197+                    # segment decoding, then we'll take more drastic
2198+                    # measures.
2199+                    if self._need_privkey:
2200+                        d = reader.get_encprivkey()
2201+                        d.addCallback(self._try_to_validate_privkey, reader)
2202+            if bad_readers:
2203+                # We do them all at once, or else we screw up list indexing.
2204+                for (reader, f) in bad_readers:
2205+                    self._mark_bad_share(reader, f)
2206+                return self._add_active_peers()
2207+            else:
2208+                return self._download_current_segment()
2209+            # The next step will assert that it has enough active
2210+            # readers to fetch shares; we just need to remove it.
2211+        dl.addCallback(_check_results)
2212+        return dl
2213+
2214+
2215+    def _try_to_validate_prefix(self, prefix, reader):
2216+        """
2217+        I check that the prefix returned by a candidate server for
2218+        retrieval matches the prefix that the servermap knows about
2219+        (and, hence, the prefix that was validated earlier). If it does,
2220+        I return True, which means that I approve of the use of the
2221+        candidate server for segment retrieval. If it doesn't, I return
2222+        False, which means that another server must be chosen.
2223+        """
2224+        (seqnum,
2225+         root_hash,
2226+         IV,
2227+         segsize,
2228+         datalength,
2229+         k,
2230+         N,
2231+         known_prefix,
2232          offsets_tuple) = self.verinfo
2233hunk ./src/allmydata/mutable/retrieve.py 430
2234-        assert len(got_prefix) == len(prefix), (len(got_prefix), len(prefix))
2235-        if got_prefix != prefix:
2236-            msg = "someone wrote to the data since we read the servermap: prefix changed"
2237-            raise UncoordinatedWriteError(msg)
2238-        (share_hash_chain, block_hash_tree,
2239-         share_data) = unpack_share_data(self.verinfo, got_hash_and_data)
2240+        if known_prefix != prefix:
2241+            self.log("prefix from share %d doesn't match" % reader.shnum)
2242+            raise UncoordinatedWriteError("Mismatched prefix -- this could "
2243+                                          "indicate an uncoordinated write")
2244+        # Otherwise, we're okay -- no issues.
2245 
2246hunk ./src/allmydata/mutable/retrieve.py 436
2247-        assert isinstance(share_data, str)
2248-        # build the block hash tree. SDMF has only one leaf.
2249-        leaves = [hashutil.block_hash(share_data)]
2250-        t = hashtree.HashTree(leaves)
2251-        if list(t) != block_hash_tree:
2252-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
2253-        share_hash_leaf = t[0]
2254-        t2 = hashtree.IncompleteHashTree(N)
2255-        # root_hash was checked by the signature
2256-        t2.set_hashes({0: root_hash})
2257-        try:
2258-            t2.set_hashes(hashes=share_hash_chain,
2259-                          leaves={shnum: share_hash_leaf})
2260-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
2261-                IndexError), e:
2262-            msg = "corrupt hashes: %s" % (e,)
2263-            raise CorruptShareError(peerid, shnum, msg)
2264-        self.log(" data valid! len=%d" % len(share_data))
2265-        # each query comes down to this: placing validated share data into
2266-        # self.shares
2267-        self.shares[shnum] = share_data
2268 
2269hunk ./src/allmydata/mutable/retrieve.py 437
2270-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
2271+    def _remove_reader(self, reader):
2272+        """
2273+        At various points, we will wish to remove a peer from
2274+        consideration and/or use. These include, but are not necessarily
2275+        limited to:
2276 
2277hunk ./src/allmydata/mutable/retrieve.py 443
2278-        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2279-        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2280-        if alleged_writekey != self._node.get_writekey():
2281-            self.log("invalid privkey from %s shnum %d" %
2282-                     (idlib.nodeid_b2a(peerid)[:8], shnum),
2283-                     parent=lp, level=log.WEIRD, umid="YIw4tA")
2284-            return
2285+            - A connection error.
2286+            - A mismatched prefix (that is, a prefix that does not match
2287+              our conception of the version information string).
2288+            - A failing block hash, salt hash, or share hash, which can
2289+              indicate disk failure/bit flips, or network trouble.
2290 
2291hunk ./src/allmydata/mutable/retrieve.py 449
2292-        # it's good
2293-        self.log("got valid privkey from shnum %d on peerid %s" %
2294-                 (shnum, idlib.shortnodeid_b2a(peerid)),
2295-                 parent=lp)
2296-        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2297-        self._node._populate_encprivkey(enc_privkey)
2298-        self._node._populate_privkey(privkey)
2299-        self._need_privkey = False
2300+        This method will do that. I will make sure that the
2301+        (shnum,reader) combination represented by my reader argument is
2302+        not used for anything else during this download. I will not
2303+        advise the reader of any corruption, something that my callers
2304+        may wish to do on their own.
2305+        """
2306+        # TODO: When you're done writing this, see if this is ever
2307+        # actually used for something that _mark_bad_share isn't. I have
2308+        # a feeling that they will be used for very similar things, and
2309+        # that having them both here is just going to be an epic amount
2310+        # of code duplication.
2311+        #
2312+        # (well, okay, not epic, but meaningful)
2313+        self.log("removing reader %s" % reader)
2314+        # Remove the reader from _active_readers
2315+        self._active_readers.remove(reader)
2316+        # TODO: self.readers.remove(reader)?
2317+        for shnum in list(self.remaining_sharemap.keys()):
2318+            self.remaining_sharemap.discard(shnum, reader.peerid)
2319 
2320hunk ./src/allmydata/mutable/retrieve.py 469
2321-    def _query_failed(self, f, marker, peerid):
2322-        self.log(format="query to [%(peerid)s] failed",
2323-                 peerid=idlib.shortnodeid_b2a(peerid),
2324-                 level=log.NOISY)
2325-        self._status.problems[peerid] = f
2326-        self._outstanding_queries.pop(marker, None)
2327-        if not self._running:
2328-            return
2329-        self._last_failure = f
2330-        self.remove_peer(peerid)
2331-        level = log.WEIRD
2332-        if f.check(DeadReferenceError):
2333-            level = log.UNUSUAL
2334-        self.log(format="error during query: %(f_value)s",
2335-                 f_value=str(f.value), failure=f, level=level, umid="gOJB5g")
2336 
2337hunk ./src/allmydata/mutable/retrieve.py 470
2338-    def _check_for_done(self, res):
2339-        # exit paths:
2340-        #  return : keep waiting, no new queries
2341-        #  return self._send_more_queries(outstanding) : send some more queries
2342-        #  fire self._done(plaintext) : download successful
2343-        #  raise exception : download fails
2344+    def _mark_bad_share(self, reader, f):
2345+        """
2346+        I mark the (peerid, shnum) encapsulated by my reader argument as
2347+        a bad share, which means that it will not be used anywhere else.
2348 
2349hunk ./src/allmydata/mutable/retrieve.py 475
2350-        self.log(format="_check_for_done: running=%(running)s, decoding=%(decoding)s",
2351-                 running=self._running, decoding=self._decoding,
2352-                 level=log.NOISY)
2353-        if not self._running:
2354-            return
2355-        if self._decoding:
2356-            return
2357-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2358-         offsets_tuple) = self.verinfo
2359+        There are several reasons to want to mark something as a bad
2360+        share. These include:
2361 
2362hunk ./src/allmydata/mutable/retrieve.py 478
2363-        if len(self.shares) < k:
2364-            # we don't have enough shares yet
2365-            return self._maybe_send_more_queries(k)
2366-        if self._need_privkey:
2367-            # we got k shares, but none of them had a valid privkey. TODO:
2368-            # look further. Adding code to do this is a bit complicated, and
2369-            # I want to avoid that complication, and this should be pretty
2370-            # rare (k shares with bitflips in the enc_privkey but not in the
2371-            # data blocks). If we actually do get here, the subsequent repair
2372-            # will fail for lack of a privkey.
2373-            self.log("got k shares but still need_privkey, bummer",
2374-                     level=log.WEIRD, umid="MdRHPA")
2375+            - A connection error to the peer.
2376+            - A mismatched prefix (that is, a prefix that does not match
2377+              our local conception of the version information string).
2378+            - A failing block hash, salt hash, share hash, or other
2379+              integrity check.
2380 
2381hunk ./src/allmydata/mutable/retrieve.py 484
2382-        # we have enough to finish. All the shares have had their hashes
2383-        # checked, so if something fails at this point, we don't know how
2384-        # to fix it, so the download will fail.
2385+        This method will ensure that readers that we wish to mark bad
2386+        (for these reasons or other reasons) are not used for the rest
2387+        of the download. Additionally, it will attempt to tell the
2388+        remote peer (with no guarantee of success) that its share is
2389+        corrupt.
2390+        """
2391+        self.log("marking share %d on server %s as bad" % \
2392+                 (reader.shnum, reader))
2393+        self._remove_reader(reader)
2394+        self._bad_shares.add((reader.peerid, reader.shnum))
2395+        self._status.problems[reader.peerid] = f
2396+        self._last_failure = f
2397+        self.notify_server_corruption(reader.peerid, reader.shnum,
2398+                                      str(f.value))
2399 
2400hunk ./src/allmydata/mutable/retrieve.py 499
2401-        self._decoding = True # avoid reentrancy
2402-        self._status.set_status("decoding")
2403-        now = time.time()
2404-        elapsed = now - self._started
2405-        self._status.timings["fetch"] = elapsed
2406 
2407hunk ./src/allmydata/mutable/retrieve.py 500
2408-        d = defer.maybeDeferred(self._decode)
2409-        d.addCallback(self._decrypt, IV, self._node.get_readkey())
2410-        d.addBoth(self._done)
2411-        return d # purely for test convenience
2412+    def _download_current_segment(self):
2413+        """
2414+        I download, validate, decode, decrypt, and assemble the segment
2415+        that this Retrieve is currently responsible for downloading.
2416+        """
2417+        assert len(self._active_readers) >= self._required_shares
2418+        if self._current_segment < self._num_segments:
2419+            d = self._process_segment(self._current_segment)
2420+        else:
2421+            d = defer.succeed(None)
2422+        d.addCallback(self._check_for_done)
2423+        return d
2424 
2425hunk ./src/allmydata/mutable/retrieve.py 513
2426-    def _maybe_send_more_queries(self, k):
2427-        # we don't have enough shares yet. Should we send out more queries?
2428-        # There are some number of queries outstanding, each for a single
2429-        # share. If we can generate 'needed_shares' additional queries, we do
2430-        # so. If we can't, then we know this file is a goner, and we raise
2431-        # NotEnoughSharesError.
2432-        self.log(format=("_maybe_send_more_queries, have=%(have)d, k=%(k)d, "
2433-                         "outstanding=%(outstanding)d"),
2434-                 have=len(self.shares), k=k,
2435-                 outstanding=len(self._outstanding_queries),
2436-                 level=log.NOISY)
2437 
2438hunk ./src/allmydata/mutable/retrieve.py 514
2439-        remaining_shares = k - len(self.shares)
2440-        needed = remaining_shares - len(self._outstanding_queries)
2441-        if not needed:
2442-            # we have enough queries in flight already
2443+    def _process_segment(self, segnum):
2444+        """
2445+        I download, validate, decode, and decrypt one segment of the
2446+        file that this Retrieve is retrieving. This means coordinating
2447+        the process of getting k blocks of that file, validating them,
2448+        assembling them into one segment with the decoder, and then
2449+        decrypting them.
2450+        """
2451+        self.log("processing segment %d" % segnum)
2452 
2453hunk ./src/allmydata/mutable/retrieve.py 524
2454-            # TODO: but if they've been in flight for a long time, and we
2455-            # have reason to believe that new queries might respond faster
2456-            # (i.e. we've seen other queries come back faster, then consider
2457-            # sending out new queries. This could help with peers which have
2458-            # silently gone away since the servermap was updated, for which
2459-            # we're still waiting for the 15-minute TCP disconnect to happen.
2460-            self.log("enough queries are in flight, no more are needed",
2461-                     level=log.NOISY)
2462-            return
2463+        # TODO: The old code uses a marker. Should this code do that
2464+        # too? What did the Marker do?
2465+        assert len(self._active_readers) >= self._required_shares
2466+
2467+        # We need to ask each of our active readers for its block and
2468+        # salt. We will then validate those. If validation is
2469+        # successful, we will assemble the results into plaintext.
2470+        ds = []
2471+        for reader in self._active_readers:
2472+            d = reader.get_block_and_salt(segnum, queue=True)
2473+            d2 = self._get_needed_hashes(reader, segnum)
2474+            dl = defer.DeferredList([d, d2], consumeErrors=True)
2475+            dl.addCallback(self._validate_block, segnum, reader)
2476+            dl.addErrback(self._validation_or_decoding_failed, [reader])
2477+            ds.append(dl)
2478+            reader.flush()
2479+        dl = defer.DeferredList(ds)
2480+        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
2481+        return dl
2482 
2483hunk ./src/allmydata/mutable/retrieve.py 544
2484-        outstanding_shnums = set([shnum
2485-                                  for (peerid, shnum, started)
2486-                                  in self._outstanding_queries.values()])
2487-        # prefer low-numbered shares, they are more likely to be primary
2488-        available_shnums = sorted(self.remaining_sharemap.keys())
2489-        for shnum in available_shnums:
2490-            if shnum in outstanding_shnums:
2491-                # skip ones that are already in transit
2492-                continue
2493-            if shnum not in self.remaining_sharemap:
2494-                # no servers for that shnum. note that DictOfSets removes
2495-                # empty sets from the dict for us.
2496-                continue
2497-            peerid = list(self.remaining_sharemap[shnum])[0]
2498-            # get_data will remove that peerid from the sharemap, and add the
2499-            # query to self._outstanding_queries
2500-            self._status.set_status("Retrieving More Shares")
2501-            self.get_data(shnum, peerid)
2502-            needed -= 1
2503-            if not needed:
2504+
2505+    def _maybe_decode_and_decrypt_segment(self, blocks_and_salts, segnum):
2506+        """
2507+        I take the results of fetching and validating the blocks from a
2508+        callback chain in another method. If the results are such that
2509+        they tell me that validation and fetching succeeded without
2510+        incident, I will proceed with decoding and decryption.
2511+        Otherwise, I will do nothing.
2512+        """
2513+        self.log("trying to decode and decrypt segment %d" % segnum)
2514+        failures = False
2515+        for block_and_salt in blocks_and_salts:
2516+            if not block_and_salt[0] or block_and_salt[1] == None:
2517+                self.log("some validation operations failed; not proceeding")
2518+                failures = True
2519                 break
2520hunk ./src/allmydata/mutable/retrieve.py 560
2521+        if not failures:
2522+            self.log("everything looks ok, building segment %d" % segnum)
2523+            d = self._decode_blocks(blocks_and_salts, segnum)
2524+            d.addCallback(self._decrypt_segment)
2525+            d.addErrback(self._validation_or_decoding_failed,
2526+                         self._active_readers)
2527+            d.addCallback(self._set_segment)
2528+            return d
2529+        else:
2530+            return defer.succeed(None)
2531+
2532+
2533+    def _set_segment(self, segment):
2534+        """
2535+        Given a plaintext segment, I register that segment with the
2536+        target that is handling the file download.
2537+        """
2538+        self.log("got plaintext for segment %d" % self._current_segment)
2539+        self._plaintext += segment
2540+        self._current_segment += 1
2541 
2542hunk ./src/allmydata/mutable/retrieve.py 581
2543-        # at this point, we have as many outstanding queries as we can. If
2544-        # needed!=0 then we might not have enough to recover the file.
2545-        if needed:
2546-            format = ("ran out of peers: "
2547-                      "have %(have)d shares (k=%(k)d), "
2548-                      "%(outstanding)d queries in flight, "
2549-                      "need %(need)d more, "
2550-                      "found %(bad)d bad shares")
2551-            args = {"have": len(self.shares),
2552-                    "k": k,
2553-                    "outstanding": len(self._outstanding_queries),
2554-                    "need": needed,
2555-                    "bad": len(self._bad_shares),
2556-                    }
2557-            self.log(format=format,
2558-                     level=log.WEIRD, umid="ezTfjw", **args)
2559-            err = NotEnoughSharesError("%s, last failure: %s" %
2560-                                      (format % args, self._last_failure))
2561-            if self._bad_shares:
2562-                self.log("We found some bad shares this pass. You should "
2563-                         "update the servermap and try again to check "
2564-                         "more peers",
2565-                         level=log.WEIRD, umid="EFkOlA")
2566-                err.servermap = self.servermap
2567-            raise err
2568 
2569hunk ./src/allmydata/mutable/retrieve.py 582
2570+    def _validation_or_decoding_failed(self, f, readers):
2571+        """
2572+        I am called when a block or a salt fails to correctly validate, or when
2573+        the decryption or decoding operation fails for some reason.  I react to
2574+        this failure by notifying the remote server of corruption, and then
2575+        removing the remote peer from further activity.
2576+        """
2577+        assert isinstance(readers, list)
2578+        bad_shnums = [reader.shnum for reader in readers]
2579+
2580+        self.log("validation or decoding failed on share(s) %s, peer(s) %s "
2581+                 ", segment %d: %s" % \
2582+                 (bad_shnums, readers, self._current_segment, str(f)))
2583+        for reader in readers:
2584+            self._mark_bad_share(reader, f)
2585         return
2586 
2587hunk ./src/allmydata/mutable/retrieve.py 599
2588-    def _decode(self):
2589-        started = time.time()
2590-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2591-         offsets_tuple) = self.verinfo
2592 
2593hunk ./src/allmydata/mutable/retrieve.py 600
2594-        # shares_dict is a dict mapping shnum to share data, but the codec
2595-        # wants two lists.
2596-        shareids = []; shares = []
2597-        for shareid, share in self.shares.items():
2598+    def _validate_block(self, results, segnum, reader):
2599+        """
2600+        I validate a block from one share on a remote server.
2601+        """
2602+        # Grab the part of the block hash tree that is necessary to
2603+        # validate this block, then generate the block hash root.
2604+        self.log("validating share %d for segment %d" % (reader.shnum,
2605+                                                             segnum))
2606+        # Did we fail to fetch either of the things that we were
2607+        # supposed to? Fail if so.
2608+        if not results[0][0] and results[1][0]:
2609+            # handled by the errback handler.
2610+
2611+            # These all get batched into one query, so the resulting
2612+            # failure should be the same for all of them, so we can just
2613+            # use the first one.
2614+            assert isinstance(results[0][1], failure.Failure)
2615+
2616+            f = results[0][1]
2617+            raise CorruptShareError(reader.peerid,
2618+                                    reader.shnum,
2619+                                    "Connection error: %s" % str(f))
2620+
2621+        block_and_salt, block_and_sharehashes = results
2622+        block, salt = block_and_salt[1]
2623+        blockhashes, sharehashes = block_and_sharehashes[1]
2624+
2625+        blockhashes = dict(enumerate(blockhashes[1]))
2626+        self.log("the reader gave me the following blockhashes: %s" % \
2627+                 blockhashes.keys())
2628+        self.log("the reader gave me the following sharehashes: %s" % \
2629+                 sharehashes[1].keys())
2630+        bht = self._block_hash_trees[reader.shnum]
2631+
2632+        if bht.needed_hashes(segnum, include_leaf=True):
2633+            try:
2634+                bht.set_hashes(blockhashes)
2635+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2636+                    IndexError), e:
2637+                raise CorruptShareError(reader.peerid,
2638+                                        reader.shnum,
2639+                                        "block hash tree failure: %s" % e)
2640+
2641+        if self._version == MDMF_VERSION:
2642+            blockhash = hashutil.block_hash(salt + block)
2643+        else:
2644+            blockhash = hashutil.block_hash(block)
2645+        # If this works without an error, then validation is
2646+        # successful.
2647+        try:
2648+           bht.set_hashes(leaves={segnum: blockhash})
2649+        except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2650+                IndexError), e:
2651+            raise CorruptShareError(reader.peerid,
2652+                                    reader.shnum,
2653+                                    "block hash tree failure: %s" % e)
2654+
2655+        # Reaching this point means that we know that this segment
2656+        # is correct. Now we need to check to see whether the share
2657+        # hash chain is also correct.
2658+        # SDMF wrote share hash chains that didn't contain the
2659+        # leaves, which would be produced from the block hash tree.
2660+        # So we need to validate the block hash tree first. If
2661+        # successful, then bht[0] will contain the root for the
2662+        # shnum, which will be a leaf in the share hash tree, which
2663+        # will allow us to validate the rest of the tree.
2664+        if self.share_hash_tree.needed_hashes(reader.shnum,
2665+                                               include_leaf=True):
2666+            try:
2667+                self.share_hash_tree.set_hashes(hashes=sharehashes[1],
2668+                                            leaves={reader.shnum: bht[0]})
2669+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2670+                    IndexError), e:
2671+                raise CorruptShareError(reader.peerid,
2672+                                        reader.shnum,
2673+                                        "corrupt hashes: %s" % e)
2674+
2675+        # TODO: Validate the salt, too.
2676+        self.log('share %d is valid for segment %d' % (reader.shnum,
2677+                                                       segnum))
2678+        return {reader.shnum: (block, salt)}
2679+
2680+
2681+    def _get_needed_hashes(self, reader, segnum):
2682+        """
2683+        I get the hashes needed to validate segnum from the reader, then return
2684+        to my caller when this is done.
2685+        """
2686+        bht = self._block_hash_trees[reader.shnum]
2687+        needed = bht.needed_hashes(segnum, include_leaf=True)
2688+        # The root of the block hash tree is also a leaf in the share
2689+        # hash tree. So we don't need to fetch it from the remote
2690+        # server. In the case of files with one segment, this means that
2691+        # we won't fetch any block hash tree from the remote server,
2692+        # since the hash of each share of the file is the entire block
2693+        # hash tree, and is a leaf in the share hash tree. This is fine,
2694+        # since any share corruption will be detected in the share hash
2695+        # tree.
2696+        #needed.discard(0)
2697+        self.log("getting blockhashes for segment %d, share %d: %s" % \
2698+                 (segnum, reader.shnum, str(needed)))
2699+        d1 = reader.get_blockhashes(needed, queue=True, force_remote=True)
2700+        if self.share_hash_tree.needed_hashes(reader.shnum):
2701+            need = self.share_hash_tree.needed_hashes(reader.shnum)
2702+            self.log("also need sharehashes for share %d: %s" % (reader.shnum,
2703+                                                                 str(need)))
2704+            d2 = reader.get_sharehashes(need, queue=True, force_remote=True)
2705+        else:
2706+            d2 = defer.succeed({}) # the logic in the next method
2707+                                   # expects a dict
2708+        dl = defer.DeferredList([d1, d2], consumeErrors=True)
2709+        return dl
2710+
2711+
2712+    def _decode_blocks(self, blocks_and_salts, segnum):
2713+        """
2714+        I take a list of k blocks and salts, and decode that into a
2715+        single encrypted segment.
2716+        """
2717+        d = {}
2718+        # We want to merge our dictionaries to the form
2719+        # {shnum: blocks_and_salts}
2720+        #
2721+        # The dictionaries come from validate block that way, so we just
2722+        # need to merge them.
2723+        for block_and_salt in blocks_and_salts:
2724+            d.update(block_and_salt[1])
2725+
2726+        # All of these blocks should have the same salt; in SDMF, it is
2727+        # the file-wide IV, while in MDMF it is the per-segment salt. In
2728+        # either case, we just need to get one of them and use it.
2729+        #
2730+        # d.items()[0] is like (shnum, (block, salt))
2731+        # d.items()[0][1] is like (block, salt)
2732+        # d.items()[0][1][1] is the salt.
2733+        salt = d.items()[0][1][1]
2734+        # Next, extract just the blocks from the dict. We'll use the
2735+        # salt in the next step.
2736+        share_and_shareids = [(k, v[0]) for k, v in d.items()]
2737+        d2 = dict(share_and_shareids)
2738+        shareids = []
2739+        shares = []
2740+        for shareid, share in d2.items():
2741             shareids.append(shareid)
2742             shares.append(share)
2743 
2744hunk ./src/allmydata/mutable/retrieve.py 746
2745-        assert len(shareids) >= k, len(shareids)
2746+        assert len(shareids) >= self._required_shares, len(shareids)
2747         # zfec really doesn't want extra shares
2748hunk ./src/allmydata/mutable/retrieve.py 748
2749-        shareids = shareids[:k]
2750-        shares = shares[:k]
2751-
2752-        fec = codec.CRSDecoder()
2753-        fec.set_params(segsize, k, N)
2754-
2755-        self.log("params %s, we have %d shares" % ((segsize, k, N), len(shares)))
2756-        self.log("about to decode, shareids=%s" % (shareids,))
2757-        d = defer.maybeDeferred(fec.decode, shares, shareids)
2758-        def _done(buffers):
2759-            self._status.timings["decode"] = time.time() - started
2760-            self.log(" decode done, %d buffers" % len(buffers))
2761+        shareids = shareids[:self._required_shares]
2762+        shares = shares[:self._required_shares]
2763+        self.log("decoding segment %d" % segnum)
2764+        if segnum == self._num_segments - 1:
2765+            d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids)
2766+        else:
2767+            d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids)
2768+        def _process(buffers):
2769             segment = "".join(buffers)
2770hunk ./src/allmydata/mutable/retrieve.py 757
2771+            self.log(format="now decoding segment %(segnum)s of %(numsegs)s",
2772+                     segnum=segnum,
2773+                     numsegs=self._num_segments,
2774+                     level=log.NOISY)
2775             self.log(" joined length %d, datalength %d" %
2776hunk ./src/allmydata/mutable/retrieve.py 762
2777-                     (len(segment), datalength))
2778-            segment = segment[:datalength]
2779+                     (len(segment), self._data_length))
2780+            if segnum == self._num_segments - 1:
2781+                size_to_use = self._tail_data_size
2782+            else:
2783+                size_to_use = self._segment_size
2784+            segment = segment[:size_to_use]
2785             self.log(" segment len=%d" % len(segment))
2786hunk ./src/allmydata/mutable/retrieve.py 769
2787-            return segment
2788-        def _err(f):
2789-            self.log(" decode failed: %s" % f)
2790-            return f
2791-        d.addCallback(_done)
2792-        d.addErrback(_err)
2793+            return segment, salt
2794+        d.addCallback(_process)
2795         return d
2796 
2797hunk ./src/allmydata/mutable/retrieve.py 773
2798-    def _decrypt(self, crypttext, IV, readkey):
2799+
2800+    def _decrypt_segment(self, segment_and_salt):
2801+        """
2802+        I take a single segment and its salt, and decrypt it. I return
2803+        the plaintext of the segment that is in my argument.
2804+        """
2805+        segment, salt = segment_and_salt
2806         self._status.set_status("decrypting")
2807hunk ./src/allmydata/mutable/retrieve.py 781
2808+        self.log("decrypting segment %d" % self._current_segment)
2809         started = time.time()
2810hunk ./src/allmydata/mutable/retrieve.py 783
2811-        key = hashutil.ssk_readkey_data_hash(IV, readkey)
2812+        key = hashutil.ssk_readkey_data_hash(salt, self._node.get_readkey())
2813         decryptor = AES(key)
2814hunk ./src/allmydata/mutable/retrieve.py 785
2815-        plaintext = decryptor.process(crypttext)
2816+        plaintext = decryptor.process(segment)
2817         self._status.timings["decrypt"] = time.time() - started
2818         return plaintext
2819 
2820hunk ./src/allmydata/mutable/retrieve.py 789
2821-    def _done(self, res):
2822-        if not self._running:
2823+
2824+    def notify_server_corruption(self, peerid, shnum, reason):
2825+        ss = self.servermap.connections[peerid]
2826+        ss.callRemoteOnly("advise_corrupt_share",
2827+                          "mutable", self._storage_index, shnum, reason)
2828+
2829+
2830+    def _try_to_validate_privkey(self, enc_privkey, reader):
2831+
2832+        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2833+        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2834+        if alleged_writekey != self._node.get_writekey():
2835+            self.log("invalid privkey from %s shnum %d" %
2836+                     (reader, reader.shnum),
2837+                     level=log.WEIRD, umid="YIw4tA")
2838             return
2839hunk ./src/allmydata/mutable/retrieve.py 805
2840-        self._running = False
2841-        self._status.set_active(False)
2842-        self._status.timings["total"] = time.time() - self._started
2843-        # res is either the new contents, or a Failure
2844-        if isinstance(res, failure.Failure):
2845-            self.log("Retrieve done, with failure", failure=res,
2846-                     level=log.UNUSUAL)
2847-            self._status.set_status("Failed")
2848-        else:
2849-            self.log("Retrieve done, success!")
2850-            self._status.set_status("Finished")
2851-            self._status.set_progress(1.0)
2852-            # remember the encoding parameters, use them again next time
2853-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2854-             offsets_tuple) = self.verinfo
2855-            self._node._populate_required_shares(k)
2856-            self._node._populate_total_shares(N)
2857-        eventually(self._done_deferred.callback, res)
2858 
2859hunk ./src/allmydata/mutable/retrieve.py 806
2860+        # it's good
2861+        self.log("got valid privkey from shnum %d on reader %s" %
2862+                 (reader.shnum, reader))
2863+        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2864+        self._node._populate_encprivkey(enc_privkey)
2865+        self._node._populate_privkey(privkey)
2866+        self._need_privkey = False
2867+
2868+
2869+    def _check_for_done(self, res):
2870+        """
2871+        I check to see if this Retrieve object has successfully finished
2872+        its work.
2873+
2874+        I can exit in the following ways:
2875+            - If there are no more segments to download, then I exit by
2876+              causing self._done_deferred to fire with the plaintext
2877+              content requested by the caller.
2878+            - If there are still segments to be downloaded, and there
2879+              are enough active readers (readers which have not broken
2880+              and have not given us corrupt data) to continue
2881+              downloading, I send control back to
2882+              _download_current_segment.
2883+            - If there are still segments to be downloaded but there are
2884+              not enough active peers to download them, I ask
2885+              _add_active_peers to add more peers. If it is successful,
2886+              it will call _download_current_segment. If there are not
2887+              enough peers to retrieve the file, then that will cause
2888+              _done_deferred to errback.
2889+        """
2890+        self.log("checking for doneness")
2891+        if self._current_segment == self._num_segments:
2892+            # No more segments to download, we're done.
2893+            self.log("got plaintext, done")
2894+            return self._done()
2895+
2896+        if len(self._active_readers) >= self._required_shares:
2897+            # More segments to download, but we have enough good peers
2898+            # in self._active_readers that we can do that without issue,
2899+            # so go nab the next segment.
2900+            self.log("not done yet: on segment %d of %d" % \
2901+                     (self._current_segment + 1, self._num_segments))
2902+            return self._download_current_segment()
2903+
2904+        self.log("not done yet: on segment %d of %d, need to add peers" % \
2905+                 (self._current_segment + 1, self._num_segments))
2906+        return self._add_active_peers()
2907+
2908+
2909+    def _done(self):
2910+        """
2911+        I am called by _check_for_done when the download process has
2912+        finished successfully. After making some useful logging
2913+        statements, I return the decrypted contents to the owner of this
2914+        Retrieve object through self._done_deferred.
2915+        """
2916+        eventually(self._done_deferred.callback, self._plaintext)
2917+
2918+
2919+    def _failed(self):
2920+        """
2921+        I am called by _add_active_peers when there are not enough
2922+        active peers left to complete the download. After making some
2923+        useful logging statements, I return an exception to that effect
2924+        to the caller of this Retrieve object through
2925+        self._done_deferred.
2926+        """
2927+        format = ("ran out of peers: "
2928+                  "have %(have)d of %(total)d segments "
2929+                  "found %(bad)d bad shares "
2930+                  "encoding %(k)d-of-%(n)d")
2931+        args = {"have": self._current_segment,
2932+                "total": self._num_segments,
2933+                "k": self._required_shares,
2934+                "n": self._total_shares,
2935+                "bad": len(self._bad_shares)}
2936+        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
2937+                                                        str(self._last_failure)))
2938+        f = failure.Failure(e)
2939+        eventually(self._done_deferred.callback, f)
2940hunk ./src/allmydata/test/test_mutable.py 12
2941 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
2942      ssk_pubkey_fingerprint_hash
2943 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
2944-     NotEnoughSharesError
2945+     NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
2946 from allmydata.monitor import Monitor
2947 from allmydata.test.common import ShouldFailMixin
2948 from allmydata.test.no_network import GridTestMixin
2949hunk ./src/allmydata/test/test_mutable.py 28
2950 from allmydata.mutable.retrieve import Retrieve
2951 from allmydata.mutable.publish import Publish
2952 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
2953-from allmydata.mutable.layout import unpack_header, unpack_share
2954+from allmydata.mutable.layout import unpack_header, unpack_share, \
2955+                                     MDMFSlotReadProxy
2956 from allmydata.mutable.repairer import MustForceRepairError
2957 
2958 import allmydata.test.common_util as testutil
2959hunk ./src/allmydata/test/test_mutable.py 104
2960         d = fireEventually()
2961         d.addCallback(lambda res: _call())
2962         return d
2963+
2964     def callRemoteOnly(self, methname, *args, **kwargs):
2965         d = self.callRemote(methname, *args, **kwargs)
2966         d.addBoth(lambda ignore: None)
2967hunk ./src/allmydata/test/test_mutable.py 163
2968 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
2969     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
2970     # list of shnums to corrupt.
2971+    ds = []
2972     for peerid in s._peers:
2973         shares = s._peers[peerid]
2974         for shnum in shares:
2975hunk ./src/allmydata/test/test_mutable.py 190
2976                 else:
2977                     offset1 = offset
2978                     offset2 = 0
2979-                if offset1 == "pubkey":
2980+                if offset1 == "pubkey" and IV:
2981                     real_offset = 107
2982hunk ./src/allmydata/test/test_mutable.py 192
2983+                elif offset1 == "share_data" and not IV:
2984+                    real_offset = 104
2985                 elif offset1 in o:
2986                     real_offset = o[offset1]
2987                 else:
2988hunk ./src/allmydata/test/test_mutable.py 327
2989         d.addCallback(_created)
2990         return d
2991 
2992+
2993+    def test_upload_and_download_mdmf(self):
2994+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
2995+        def _created(n):
2996+            d = defer.succeed(None)
2997+            d.addCallback(lambda ignored:
2998+                n.get_servermap(MODE_READ))
2999+            def _then(servermap):
3000+                dumped = servermap.dump(StringIO())
3001+                self.failUnlessIn("3-of-10", dumped.getvalue())
3002+            d.addCallback(_then)
3003+            # Now overwrite the contents with some new contents. We want
3004+            # to make them big enough to force the file to be uploaded
3005+            # in more than one segment.
3006+            big_contents = "contents1" * 100000 # about 900 KiB
3007+            d.addCallback(lambda ignored:
3008+                n.overwrite(big_contents))
3009+            d.addCallback(lambda ignored:
3010+                n.download_best_version())
3011+            d.addCallback(lambda data:
3012+                self.failUnlessEqual(data, big_contents))
3013+            # Overwrite the contents again with some new contents. As
3014+            # before, they need to be big enough to force multiple
3015+            # segments, so that we make the downloader deal with
3016+            # multiple segments.
3017+            bigger_contents = "contents2" * 1000000 # about 9MiB
3018+            d.addCallback(lambda ignored:
3019+                n.overwrite(bigger_contents))
3020+            d.addCallback(lambda ignored:
3021+                n.download_best_version())
3022+            d.addCallback(lambda data:
3023+                self.failUnlessEqual(data, bigger_contents))
3024+            return d
3025+        d.addCallback(_created)
3026+        return d
3027+
3028+
3029     def test_create_with_initial_contents(self):
3030         d = self.nodemaker.create_mutable_file("contents 1")
3031         def _created(n):
3032hunk ./src/allmydata/test/test_mutable.py 1147
3033 
3034 
3035     def _test_corrupt_all(self, offset, substring,
3036-                          should_succeed=False, corrupt_early=True,
3037-                          failure_checker=None):
3038+                          should_succeed=False,
3039+                          corrupt_early=True,
3040+                          failure_checker=None,
3041+                          fetch_privkey=False):
3042         d = defer.succeed(None)
3043         if corrupt_early:
3044             d.addCallback(corrupt, self._storage, offset)
3045hunk ./src/allmydata/test/test_mutable.py 1167
3046                     self.failUnlessIn(substring, "".join(allproblems))
3047                 return servermap
3048             if should_succeed:
3049-                d1 = self._fn.download_version(servermap, ver)
3050+                d1 = self._fn.download_version(servermap, ver,
3051+                                               fetch_privkey)
3052                 d1.addCallback(lambda new_contents:
3053                                self.failUnlessEqual(new_contents, self.CONTENTS))
3054             else:
3055hunk ./src/allmydata/test/test_mutable.py 1175
3056                 d1 = self.shouldFail(NotEnoughSharesError,
3057                                      "_corrupt_all(offset=%s)" % (offset,),
3058                                      substring,
3059-                                     self._fn.download_version, servermap, ver)
3060+                                     self._fn.download_version, servermap,
3061+                                                                ver,
3062+                                                                fetch_privkey)
3063             if failure_checker:
3064                 d1.addCallback(failure_checker)
3065             d1.addCallback(lambda res: servermap)
3066hunk ./src/allmydata/test/test_mutable.py 1186
3067         return d
3068 
3069     def test_corrupt_all_verbyte(self):
3070-        # when the version byte is not 0, we hit an UnknownVersionError error
3071-        # in unpack_share().
3072+        # when the version byte is not 0 or 1, we hit an UnknownVersionError
3073+        # error in unpack_share().
3074         d = self._test_corrupt_all(0, "UnknownVersionError")
3075         def _check_servermap(servermap):
3076             # and the dump should mention the problems
3077hunk ./src/allmydata/test/test_mutable.py 1193
3078             s = StringIO()
3079             dump = servermap.dump(s).getvalue()
3080-            self.failUnless("10 PROBLEMS" in dump, dump)
3081+            self.failUnless("30 PROBLEMS" in dump, dump)
3082         d.addCallback(_check_servermap)
3083         return d
3084 
3085hunk ./src/allmydata/test/test_mutable.py 1263
3086         return self._test_corrupt_all("enc_privkey", None, should_succeed=True)
3087 
3088 
3089+    def test_corrupt_all_encprivkey_late(self):
3090+        # this should work for the same reason as above, but we corrupt
3091+        # after the servermap update to exercise the error handling
3092+        # code.
3093+        # We need to remove the privkey from the node, or the retrieve
3094+        # process won't know to update it.
3095+        self._fn._privkey = None
3096+        return self._test_corrupt_all("enc_privkey",
3097+                                      None, # this shouldn't fail
3098+                                      should_succeed=True,
3099+                                      corrupt_early=False,
3100+                                      fetch_privkey=True)
3101+
3102+
3103     def test_corrupt_all_seqnum_late(self):
3104         # corrupting the seqnum between mapupdate and retrieve should result
3105         # in NotEnoughSharesError, since each share will look invalid
3106hunk ./src/allmydata/test/test_mutable.py 1283
3107         def _check(res):
3108             f = res[0]
3109             self.failUnless(f.check(NotEnoughSharesError))
3110-            self.failUnless("someone wrote to the data since we read the servermap" in str(f))
3111+            self.failUnless("uncoordinated write" in str(f))
3112         return self._test_corrupt_all(1, "ran out of peers",
3113                                       corrupt_early=False,
3114                                       failure_checker=_check)
3115hunk ./src/allmydata/test/test_mutable.py 1333
3116                       self.failUnlessEqual(new_contents, self.CONTENTS))
3117         return d
3118 
3119-    def test_corrupt_some(self):
3120-        # corrupt the data of first five shares (so the servermap thinks
3121-        # they're good but retrieve marks them as bad), so that the
3122-        # MODE_READ set of 6 will be insufficient, forcing node.download to
3123-        # retry with more servers.
3124-        corrupt(None, self._storage, "share_data", range(5))
3125-        d = self.make_servermap()
3126+
3127+    def _test_corrupt_some(self, offset, mdmf=False):
3128+        if mdmf:
3129+            d = self.publish_mdmf()
3130+        else:
3131+            d = defer.succeed(None)
3132+        d.addCallback(lambda ignored:
3133+            corrupt(None, self._storage, offset, range(5)))
3134+        d.addCallback(lambda ignored:
3135+            self.make_servermap())
3136         def _do_retrieve(servermap):
3137             ver = servermap.best_recoverable_version()
3138             self.failUnless(ver)
3139hunk ./src/allmydata/test/test_mutable.py 1349
3140             return self._fn.download_best_version()
3141         d.addCallback(_do_retrieve)
3142         d.addCallback(lambda new_contents:
3143-                      self.failUnlessEqual(new_contents, self.CONTENTS))
3144+            self.failUnlessEqual(new_contents, self.CONTENTS))
3145         return d
3146 
3147hunk ./src/allmydata/test/test_mutable.py 1352
3148+
3149+    def test_corrupt_some(self):
3150+        # corrupt the data of first five shares (so the servermap thinks
3151+        # they're good but retrieve marks them as bad), so that the
3152+        # MODE_READ set of 6 will be insufficient, forcing node.download to
3153+        # retry with more servers.
3154+        return self._test_corrupt_some("share_data")
3155+
3156+
3157     def test_download_fails(self):
3158         d = corrupt(None, self._storage, "signature")
3159         d.addCallback(lambda ignored:
3160hunk ./src/allmydata/test/test_mutable.py 1366
3161             self.shouldFail(UnrecoverableFileError, "test_download_anyway",
3162                             "no recoverable versions",
3163-                            self._fn.download_best_version)
3164+                            self._fn.download_best_version))
3165         return d
3166 
3167 
3168hunk ./src/allmydata/test/test_mutable.py 1370
3169+
3170+    def test_corrupt_mdmf_block_hash_tree(self):
3171+        d = self.publish_mdmf()
3172+        d.addCallback(lambda ignored:
3173+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3174+                                   "block hash tree failure",
3175+                                   corrupt_early=False,
3176+                                   should_succeed=False))
3177+        return d
3178+
3179+
3180+    def test_corrupt_mdmf_block_hash_tree_late(self):
3181+        d = self.publish_mdmf()
3182+        d.addCallback(lambda ignored:
3183+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3184+                                   "block hash tree failure",
3185+                                   corrupt_early=True,
3186+                                   should_succeed=False))
3187+        return d
3188+
3189+
3190+    def test_corrupt_mdmf_share_data(self):
3191+        d = self.publish_mdmf()
3192+        d.addCallback(lambda ignored:
3193+            # TODO: Find out what the block size is and corrupt a
3194+            # specific block, rather than just guessing.
3195+            self._test_corrupt_all(("share_data", 12 * 40),
3196+                                    "block hash tree failure",
3197+                                    corrupt_early=True,
3198+                                    should_succeed=False))
3199+        return d
3200+
3201+
3202+    def test_corrupt_some_mdmf(self):
3203+        return self._test_corrupt_some(("share_data", 12 * 40),
3204+                                       mdmf=True)
3205+
3206+
3207 class CheckerMixin:
3208     def check_good(self, r, where):
3209         self.failUnless(r.is_healthy(), where)
3210hunk ./src/allmydata/test/test_mutable.py 2116
3211             d.addCallback(lambda res:
3212                           self.shouldFail(NotEnoughSharesError,
3213                                           "test_retrieve_surprise",
3214-                                          "ran out of peers: have 0 shares (k=3)",
3215+                                          "ran out of peers: have 0 of 1",
3216                                           n.download_version,
3217                                           self.old_map,
3218                                           self.old_map.best_recoverable_version(),
3219hunk ./src/allmydata/test/test_mutable.py 2125
3220         d.addCallback(_created)
3221         return d
3222 
3223+
3224     def test_unexpected_shares(self):
3225         # upload the file, take a servermap, shut down one of the servers,
3226         # upload it again (causing shares to appear on a new server), then
3227hunk ./src/allmydata/test/test_mutable.py 2329
3228         self.basedir = "mutable/Problems/test_privkey_query_missing"
3229         self.set_up_grid(num_servers=20)
3230         nm = self.g.clients[0].nodemaker
3231-        LARGE = "These are Larger contents" * 2000 # about 50KB
3232+        LARGE = "These are Larger contents" * 2000 # about 50KiB
3233         nm._node_cache = DevNullDictionary() # disable the nodecache
3234 
3235         d = nm.create_mutable_file(LARGE)
3236hunk ./src/allmydata/test/test_mutable.py 2342
3237         d.addCallback(_created)
3238         d.addCallback(lambda res: self.n2.get_servermap(MODE_WRITE))
3239         return d
3240+
3241+
3242+    def test_block_and_hash_query_error(self):
3243+        # This tests for what happens when a query to a remote server
3244+        # fails in either the hash validation step or the block getting
3245+        # step (because of batching, this is the same actual query).
3246+        # We need to have the storage server persist up until the point
3247+        # that its prefix is validated, then suddenly die. This
3248+        # exercises some exception handling code in Retrieve.
3249+        self.basedir = "mutable/Problems/test_block_and_hash_query_error"
3250+        self.set_up_grid(num_servers=20)
3251+        nm = self.g.clients[0].nodemaker
3252+        CONTENTS = "contents" * 2000
3253+        d = nm.create_mutable_file(CONTENTS)
3254+        def _created(node):
3255+            self._node = node
3256+        d.addCallback(_created)
3257+        d.addCallback(lambda ignored:
3258+            self._node.get_servermap(MODE_READ))
3259+        def _then(servermap):
3260+            # we have our servermap. Now we set up the servers like the
3261+            # tests above -- the first one that gets a read call should
3262+            # start throwing errors, but only after returning its prefix
3263+            # for validation. Since we'll download without fetching the
3264+            # private key, the next query to the remote server will be
3265+            # for either a block and salt or for hashes, either of which
3266+            # will exercise the error handling code.
3267+            killer = FirstServerGetsKilled()
3268+            for (serverid, ss) in nm.storage_broker.get_all_servers():
3269+                ss.post_call_notifier = killer.notify
3270+            ver = servermap.best_recoverable_version()
3271+            assert ver
3272+            return self._node.download_version(servermap, ver)
3273+        d.addCallback(_then)
3274+        d.addCallback(lambda data:
3275+            self.failUnlessEqual(data, CONTENTS))
3276+        return d
3277}
3278[mutable/checker.py: check MDMF files
3279Kevan Carstensen <kevan@isnotajoke.com>**20100628225048
3280 Ignore-this: fb697b36285d60552df6ca5ac6a37629
3281 
3282 This patch adapts the mutable file checker and verifier to check and
3283 verify MDMF files. It does this by using the new segmented downloader,
3284 which is trained to perform verification operations on request. This
3285 removes some code duplication.
3286] {
3287hunk ./src/allmydata/mutable/checker.py 12
3288 from allmydata.mutable.common import MODE_CHECK, CorruptShareError
3289 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
3290 from allmydata.mutable.layout import unpack_share, SIGNED_PREFIX_LENGTH
3291+from allmydata.mutable.retrieve import Retrieve # for verifying
3292 
3293 class MutableChecker:
3294 
3295hunk ./src/allmydata/mutable/checker.py 29
3296 
3297     def check(self, verify=False, add_lease=False):
3298         servermap = ServerMap()
3299+        # Updating the servermap in MODE_CHECK will stand a good chance
3300+        # of finding all of the shares, and getting a good idea of
3301+        # recoverability, etc, without verifying.
3302         u = ServermapUpdater(self._node, self._storage_broker, self._monitor,
3303                              servermap, MODE_CHECK, add_lease=add_lease)
3304         if self._history:
3305hunk ./src/allmydata/mutable/checker.py 55
3306         if num_recoverable:
3307             self.best_version = servermap.best_recoverable_version()
3308 
3309+        # The file is unhealthy and needs to be repaired if:
3310+        # - There are unrecoverable versions.
3311         if servermap.unrecoverable_versions():
3312             self.need_repair = True
3313hunk ./src/allmydata/mutable/checker.py 59
3314+        # - There isn't a recoverable version.
3315         if num_recoverable != 1:
3316             self.need_repair = True
3317hunk ./src/allmydata/mutable/checker.py 62
3318+        # - The best recoverable version is missing some shares.
3319         if self.best_version:
3320             available_shares = servermap.shares_available()
3321             (num_distinct_shares, k, N) = available_shares[self.best_version]
3322hunk ./src/allmydata/mutable/checker.py 73
3323 
3324     def _verify_all_shares(self, servermap):
3325         # read every byte of each share
3326+        #
3327+        # This logic is going to be very nearly the same as the
3328+        # downloader. I bet we could pass the downloader a flag that
3329+        # makes it do this, and piggyback onto that instead of
3330+        # duplicating a bunch of code.
3331+        #
3332+        # Like:
3333+        #  r = Retrieve(blah, blah, blah, verify=True)
3334+        #  d = r.download()
3335+        #  (wait, wait, wait, d.callback)
3336+        # 
3337+        #  Then, when it has finished, we can check the servermap (which
3338+        #  we provided to Retrieve) to figure out which shares are bad,
3339+        #  since the Retrieve process will have updated the servermap as
3340+        #  it went along.
3341+        #
3342+        #  By passing the verify=True flag to the constructor, we are
3343+        #  telling the downloader a few things.
3344+        #
3345+        #  1. It needs to download all N shares, not just K shares.
3346+        #  2. It doesn't need to decrypt or decode the shares, only
3347+        #     verify them.
3348         if not self.best_version:
3349             return
3350hunk ./src/allmydata/mutable/checker.py 97
3351-        versionmap = servermap.make_versionmap()
3352-        shares = versionmap[self.best_version]
3353-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3354-         offsets_tuple) = self.best_version
3355-        offsets = dict(offsets_tuple)
3356-        readv = [ (0, offsets["EOF"]) ]
3357-        dl = []
3358-        for (shnum, peerid, timestamp) in shares:
3359-            ss = servermap.connections[peerid]
3360-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
3361-            d.addCallback(self._got_answer, peerid, servermap)
3362-            dl.append(d)
3363-        return defer.DeferredList(dl, fireOnOneErrback=True, consumeErrors=True)
3364 
3365hunk ./src/allmydata/mutable/checker.py 98
3366-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
3367-        # isolate the callRemote to a separate method, so tests can subclass
3368-        # Publish and override it
3369-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
3370+        r = Retrieve(self._node, servermap, self.best_version, verify=True)
3371+        d = r.download()
3372+        d.addCallback(self._process_bad_shares)
3373         return d
3374 
3375hunk ./src/allmydata/mutable/checker.py 103
3376-    def _got_answer(self, datavs, peerid, servermap):
3377-        for shnum,datav in datavs.items():
3378-            data = datav[0]
3379-            try:
3380-                self._got_results_one_share(shnum, peerid, data)
3381-            except CorruptShareError:
3382-                f = failure.Failure()
3383-                self.need_repair = True
3384-                self.bad_shares.append( (peerid, shnum, f) )
3385-                prefix = data[:SIGNED_PREFIX_LENGTH]
3386-                servermap.mark_bad_share(peerid, shnum, prefix)
3387-                ss = servermap.connections[peerid]
3388-                self.notify_server_corruption(ss, shnum, str(f.value))
3389-
3390-    def check_prefix(self, peerid, shnum, data):
3391-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3392-         offsets_tuple) = self.best_version
3393-        got_prefix = data[:SIGNED_PREFIX_LENGTH]
3394-        if got_prefix != prefix:
3395-            raise CorruptShareError(peerid, shnum,
3396-                                    "prefix mismatch: share changed while we were reading it")
3397-
3398-    def _got_results_one_share(self, shnum, peerid, data):
3399-        self.check_prefix(peerid, shnum, data)
3400-
3401-        # the [seqnum:signature] pieces are validated by _compare_prefix,
3402-        # which checks their signature against the pubkey known to be
3403-        # associated with this file.
3404 
3405hunk ./src/allmydata/mutable/checker.py 104
3406-        (seqnum, root_hash, IV, k, N, segsize, datalen, pubkey, signature,
3407-         share_hash_chain, block_hash_tree, share_data,
3408-         enc_privkey) = unpack_share(data)
3409-
3410-        # validate [share_hash_chain,block_hash_tree,share_data]
3411-
3412-        leaves = [hashutil.block_hash(share_data)]
3413-        t = hashtree.HashTree(leaves)
3414-        if list(t) != block_hash_tree:
3415-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
3416-        share_hash_leaf = t[0]
3417-        t2 = hashtree.IncompleteHashTree(N)
3418-        # root_hash was checked by the signature
3419-        t2.set_hashes({0: root_hash})
3420-        try:
3421-            t2.set_hashes(hashes=share_hash_chain,
3422-                          leaves={shnum: share_hash_leaf})
3423-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
3424-                IndexError), e:
3425-            msg = "corrupt hashes: %s" % (e,)
3426-            raise CorruptShareError(peerid, shnum, msg)
3427-
3428-        # validate enc_privkey: only possible if we have a write-cap
3429-        if not self._node.is_readonly():
3430-            alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3431-            alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3432-            if alleged_writekey != self._node.get_writekey():
3433-                raise CorruptShareError(peerid, shnum, "invalid privkey")
3434+    def _process_bad_shares(self, bad_shares):
3435+        if bad_shares:
3436+            self.need_repair = True
3437+        self.bad_shares = bad_shares
3438 
3439hunk ./src/allmydata/mutable/checker.py 109
3440-    def notify_server_corruption(self, ss, shnum, reason):
3441-        ss.callRemoteOnly("advise_corrupt_share",
3442-                          "mutable", self._storage_index, shnum, reason)
3443 
3444     def _count_shares(self, smap, version):
3445         available_shares = smap.shares_available()
3446hunk ./src/allmydata/test/test_mutable.py 193
3447                 if offset1 == "pubkey" and IV:
3448                     real_offset = 107
3449                 elif offset1 == "share_data" and not IV:
3450-                    real_offset = 104
3451+                    real_offset = 107
3452                 elif offset1 in o:
3453                     real_offset = o[offset1]
3454                 else:
3455hunk ./src/allmydata/test/test_mutable.py 395
3456             return d
3457         d.addCallback(_created)
3458         return d
3459+    test_create_mdmf_with_initial_contents.timeout = 20
3460 
3461 
3462     def test_create_with_initial_contents_function(self):
3463hunk ./src/allmydata/test/test_mutable.py 700
3464                                            k, N, segsize, datalen)
3465                 self.failUnless(p._pubkey.verify(sig_material, signature))
3466                 #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
3467-                self.failUnless(isinstance(share_hash_chain, dict))
3468-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3469+                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3470                 for shnum,share_hash in share_hash_chain.items():
3471                     self.failUnless(isinstance(shnum, int))
3472                     self.failUnless(isinstance(share_hash, str))
3473hunk ./src/allmydata/test/test_mutable.py 820
3474                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
3475 
3476 
3477+
3478+
3479 class Servermap(unittest.TestCase, PublishMixin):
3480     def setUp(self):
3481         return self.publish_one()
3482hunk ./src/allmydata/test/test_mutable.py 951
3483         self._storage._peers = {} # delete all shares
3484         ms = self.make_servermap
3485         d = defer.succeed(None)
3486-
3487+#
3488         d.addCallback(lambda res: ms(mode=MODE_CHECK))
3489         d.addCallback(lambda sm: self.failUnlessNoneRecoverable(sm))
3490 
3491hunk ./src/allmydata/test/test_mutable.py 1440
3492         d.addCallback(self.check_good, "test_check_good")
3493         return d
3494 
3495+    def test_check_mdmf_good(self):
3496+        d = self.publish_mdmf()
3497+        d.addCallback(lambda ignored:
3498+            self._fn.check(Monitor()))
3499+        d.addCallback(self.check_good, "test_check_mdmf_good")
3500+        return d
3501+
3502     def test_check_no_shares(self):
3503         for shares in self._storage._peers.values():
3504             shares.clear()
3505hunk ./src/allmydata/test/test_mutable.py 1454
3506         d.addCallback(self.check_bad, "test_check_no_shares")
3507         return d
3508 
3509+    def test_check_mdmf_no_shares(self):
3510+        d = self.publish_mdmf()
3511+        def _then(ignored):
3512+            for share in self._storage._peers.values():
3513+                share.clear()
3514+        d.addCallback(_then)
3515+        d.addCallback(lambda ignored:
3516+            self._fn.check(Monitor()))
3517+        d.addCallback(self.check_bad, "test_check_mdmf_no_shares")
3518+        return d
3519+
3520     def test_check_not_enough_shares(self):
3521         for shares in self._storage._peers.values():
3522             for shnum in shares.keys():
3523hunk ./src/allmydata/test/test_mutable.py 1474
3524         d.addCallback(self.check_bad, "test_check_not_enough_shares")
3525         return d
3526 
3527+    def test_check_mdmf_not_enough_shares(self):
3528+        d = self.publish_mdmf()
3529+        def _then(ignored):
3530+            for shares in self._storage._peers.values():
3531+                for shnum in shares.keys():
3532+                    if shnum > 0:
3533+                        del shares[shnum]
3534+        d.addCallback(_then)
3535+        d.addCallback(lambda ignored:
3536+            self._fn.check(Monitor()))
3537+        d.addCallback(self.check_bad, "test_check_mdmf_not_enougH_shares")
3538+        return d
3539+
3540+
3541     def test_check_all_bad_sig(self):
3542         d = corrupt(None, self._storage, 1) # bad sig
3543         d.addCallback(lambda ignored:
3544hunk ./src/allmydata/test/test_mutable.py 1495
3545         d.addCallback(self.check_bad, "test_check_all_bad_sig")
3546         return d
3547 
3548+    def test_check_mdmf_all_bad_sig(self):
3549+        d = self.publish_mdmf()
3550+        d.addCallback(lambda ignored:
3551+            corrupt(None, self._storage, 1))
3552+        d.addCallback(lambda ignored:
3553+            self._fn.check(Monitor()))
3554+        d.addCallback(self.check_bad, "test_check_mdmf_all_bad_sig")
3555+        return d
3556+
3557     def test_check_all_bad_blocks(self):
3558         d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
3559         # the Checker won't notice this.. it doesn't look at actual data
3560hunk ./src/allmydata/test/test_mutable.py 1512
3561         d.addCallback(self.check_good, "test_check_all_bad_blocks")
3562         return d
3563 
3564+
3565+    def test_check_mdmf_all_bad_blocks(self):
3566+        d = self.publish_mdmf()
3567+        d.addCallback(lambda ignored:
3568+            corrupt(None, self._storage, "share_data"))
3569+        d.addCallback(lambda ignored:
3570+            self._fn.check(Monitor()))
3571+        d.addCallback(self.check_good, "test_check_mdmf_all_bad_blocks")
3572+        return d
3573+
3574     def test_verify_good(self):
3575         d = self._fn.check(Monitor(), verify=True)
3576         d.addCallback(self.check_good, "test_verify_good")
3577hunk ./src/allmydata/test/test_mutable.py 1582
3578                       "test_verify_one_bad_encprivkey_uncheckable")
3579         return d
3580 
3581+
3582+    def test_verify_mdmf_good(self):
3583+        d = self.publish_mdmf()
3584+        d.addCallback(lambda ignored:
3585+            self._fn.check(Monitor(), verify=True))
3586+        d.addCallback(self.check_good, "test_verify_mdmf_good")
3587+        return d
3588+
3589+
3590+    def test_verify_mdmf_one_bad_block(self):
3591+        d = self.publish_mdmf()
3592+        d.addCallback(lambda ignored:
3593+            corrupt(None, self._storage, "share_data", [1]))
3594+        d.addCallback(lambda ignored:
3595+            self._fn.check(Monitor(), verify=True))
3596+        # We should find one bad block here
3597+        d.addCallback(self.check_bad, "test_verify_mdmf_one_bad_block")
3598+        d.addCallback(self.check_expected_failure,
3599+                      CorruptShareError, "block hash tree failure",
3600+                      "test_verify_mdmf_one_bad_block")
3601+        return d
3602+
3603+
3604+    def test_verify_mdmf_bad_encprivkey(self):
3605+        d = self.publish_mdmf()
3606+        d.addCallback(lambda ignored:
3607+            corrupt(None, self._storage, "enc_privkey", [1]))
3608+        d.addCallback(lambda ignored:
3609+            self._fn.check(Monitor(), verify=True))
3610+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
3611+        d.addCallback(self.check_expected_failure,
3612+                      CorruptShareError, "privkey",
3613+                      "test_verify_mdmf_bad_encprivkey")
3614+        return d
3615+
3616+
3617+    def test_verify_mdmf_bad_sig(self):
3618+        d = self.publish_mdmf()
3619+        d.addCallback(lambda ignored:
3620+            corrupt(None, self._storage, 1, [1]))
3621+        d.addCallback(lambda ignored:
3622+            self._fn.check(Monitor(), verify=True))
3623+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_sig")
3624+        return d
3625+
3626+
3627+    def test_verify_mdmf_bad_encprivkey_uncheckable(self):
3628+        d = self.publish_mdmf()
3629+        d.addCallback(lambda ignored:
3630+            corrupt(None, self._storage, "enc_privkey", [1]))
3631+        d.addCallback(lambda ignored:
3632+            self._fn.get_readonly())
3633+        d.addCallback(lambda fn:
3634+            fn.check(Monitor(), verify=True))
3635+        d.addCallback(self.check_good,
3636+                      "test_verify_mdmf_bad_encprivkey_uncheckable")
3637+        return d
3638+
3639+
3640 class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
3641 
3642     def get_shares(self, s):
3643hunk ./src/allmydata/test/test_mutable.py 1706
3644         current_shares = self.old_shares[-1]
3645         self.failUnlessEqual(old_shares, current_shares)
3646 
3647+
3648     def test_unrepairable_0shares(self):
3649         d = self.publish_one()
3650         def _delete_all_shares(ign):
3651hunk ./src/allmydata/test/test_mutable.py 1721
3652         d.addCallback(_check)
3653         return d
3654 
3655+    def test_mdmf_unrepairable_0shares(self):
3656+        d = self.publish_mdmf()
3657+        def _delete_all_shares(ign):
3658+            shares = self._storage._peers
3659+            for peerid in shares:
3660+                shares[peerid] = {}
3661+        d.addCallback(_delete_all_shares)
3662+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3663+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3664+        d.addCallback(lambda crr: self.failIf(crr.get_successful()))
3665+        return d
3666+
3667+
3668     def test_unrepairable_1share(self):
3669         d = self.publish_one()
3670         def _delete_all_shares(ign):
3671hunk ./src/allmydata/test/test_mutable.py 1750
3672         d.addCallback(_check)
3673         return d
3674 
3675+    def test_mdmf_unrepairable_1share(self):
3676+        d = self.publish_mdmf()
3677+        def _delete_all_shares(ign):
3678+            shares = self._storage._peers
3679+            for peerid in shares:
3680+                for shnum in list(shares[peerid]):
3681+                    if shnum > 0:
3682+                        del shares[peerid][shnum]
3683+        d.addCallback(_delete_all_shares)
3684+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3685+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3686+        def _check(crr):
3687+            self.failUnlessEqual(crr.get_successful(), False)
3688+        d.addCallback(_check)
3689+        return d
3690+
3691+    def test_repairable_5shares(self):
3692+        d = self.publish_mdmf()
3693+        def _delete_all_shares(ign):
3694+            shares = self._storage._peers
3695+            for peerid in shares:
3696+                for shnum in list(shares[peerid]):
3697+                    if shnum > 4:
3698+                        del shares[peerid][shnum]
3699+        d.addCallback(_delete_all_shares)
3700+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3701+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3702+        def _check(crr):
3703+            self.failUnlessEqual(crr.get_successful(), True)
3704+        d.addCallback(_check)
3705+        return d
3706+
3707+    def test_mdmf_repairable_5shares(self):
3708+        d = self.publish_mdmf()
3709+        def _delete_all_shares(ign):
3710+            shares = self._storage._peers
3711+            for peerid in shares:
3712+                for shnum in list(shares[peerid]):
3713+                    if shnum > 5:
3714+                        del shares[peerid][shnum]
3715+        d.addCallback(_delete_all_shares)
3716+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3717+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3718+        def _check(crr):
3719+            self.failUnlessEqual(crr.get_successful(), True)
3720+        d.addCallback(_check)
3721+        return d
3722+
3723+
3724     def test_merge(self):
3725         self.old_shares = []
3726         d = self.publish_multiple()
3727}
3728[mutable/retrieve.py: learn how to verify mutable files
3729Kevan Carstensen <kevan@isnotajoke.com>**20100628225201
3730 Ignore-this: 989af7800c47589620918461ec989483
3731] {
3732hunk ./src/allmydata/mutable/retrieve.py 86
3733     # Retrieve object will remain tied to a specific version of the file, and
3734     # will use a single ServerMap instance.
3735 
3736-    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False):
3737+    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
3738+                 verify=False):
3739         self._node = filenode
3740         assert self._node.get_pubkey()
3741         self._storage_index = filenode.get_storage_index()
3742hunk ./src/allmydata/mutable/retrieve.py 106
3743         # during repair, we may be called upon to grab the private key, since
3744         # it wasn't picked up during a verify=False checker run, and we'll
3745         # need it for repair to generate a new version.
3746-        self._need_privkey = fetch_privkey
3747-        if self._node.get_privkey():
3748+        self._need_privkey = fetch_privkey or verify
3749+        if self._node.get_privkey() and not verify:
3750             self._need_privkey = False
3751 
3752         if self._need_privkey:
3753hunk ./src/allmydata/mutable/retrieve.py 117
3754             self._privkey_query_markers = [] # one Marker for each time we've
3755                                              # tried to get the privkey.
3756 
3757+        # verify means that we are using the downloader logic to verify all
3758+        # of our shares. This tells the downloader a few things.
3759+        #
3760+        # 1. We need to download all of the shares.
3761+        # 2. We don't need to decode or decrypt the shares, since our
3762+        #    caller doesn't care about the plaintext, only the
3763+        #    information about which shares are or are not valid.
3764+        # 3. When we are validating readers, we need to validate the
3765+        #    signature on the prefix. Do we? We already do this in the
3766+        #    servermap update?
3767+        #
3768+        # (just work on 1 and 2 for now, I guess)
3769+        self._verify = False
3770+        if verify:
3771+            self._verify = True
3772+
3773         self._status = RetrieveStatus()
3774         self._status.set_storage_index(self._storage_index)
3775         self._status.set_helper(False)
3776hunk ./src/allmydata/mutable/retrieve.py 323
3777 
3778         # We need at least self._required_shares readers to download a
3779         # segment.
3780-        needed = self._required_shares - len(self._active_readers)
3781+        if self._verify:
3782+            needed = self._total_shares
3783+        else:
3784+            needed = self._required_shares - len(self._active_readers)
3785         # XXX: Why don't format= log messages work here?
3786         self.log("adding %d peers to the active peers list" % needed)
3787 
3788hunk ./src/allmydata/mutable/retrieve.py 339
3789         # will cause problems later.
3790         active_shnums -= set([reader.shnum for reader in self._active_readers])
3791         active_shnums = list(active_shnums)[:needed]
3792-        if len(active_shnums) < needed:
3793+        if len(active_shnums) < needed and not self._verify:
3794             # We don't have enough readers to retrieve the file; fail.
3795             return self._failed()
3796 
3797hunk ./src/allmydata/mutable/retrieve.py 346
3798         for shnum in active_shnums:
3799             self._active_readers.append(self.readers[shnum])
3800             self.log("added reader for share %d" % shnum)
3801-        assert len(self._active_readers) == self._required_shares
3802+        assert len(self._active_readers) >= self._required_shares
3803         # Conceptually, this is part of the _add_active_peers step. It
3804         # validates the prefixes of newly added readers to make sure
3805         # that they match what we are expecting for self.verinfo. If
3806hunk ./src/allmydata/mutable/retrieve.py 416
3807                     # that we haven't gotten it at the end of
3808                     # segment decoding, then we'll take more drastic
3809                     # measures.
3810-                    if self._need_privkey:
3811+                    if self._need_privkey and not self._node.is_readonly():
3812                         d = reader.get_encprivkey()
3813                         d.addCallback(self._try_to_validate_privkey, reader)
3814             if bad_readers:
3815hunk ./src/allmydata/mutable/retrieve.py 423
3816                 # We do them all at once, or else we screw up list indexing.
3817                 for (reader, f) in bad_readers:
3818                     self._mark_bad_share(reader, f)
3819-                return self._add_active_peers()
3820+                if self._verify:
3821+                    if len(self._active_readers) >= self._required_shares:
3822+                        return self._download_current_segment()
3823+                    else:
3824+                        return self._failed()
3825+                else:
3826+                    return self._add_active_peers()
3827             else:
3828                 return self._download_current_segment()
3829             # The next step will assert that it has enough active
3830hunk ./src/allmydata/mutable/retrieve.py 518
3831         """
3832         self.log("marking share %d on server %s as bad" % \
3833                  (reader.shnum, reader))
3834+        prefix = self.verinfo[-2]
3835+        self.servermap.mark_bad_share(reader.peerid,
3836+                                      reader.shnum,
3837+                                      prefix)
3838         self._remove_reader(reader)
3839hunk ./src/allmydata/mutable/retrieve.py 523
3840-        self._bad_shares.add((reader.peerid, reader.shnum))
3841+        self._bad_shares.add((reader.peerid, reader.shnum, f))
3842         self._status.problems[reader.peerid] = f
3843         self._last_failure = f
3844         self.notify_server_corruption(reader.peerid, reader.shnum,
3845hunk ./src/allmydata/mutable/retrieve.py 571
3846             ds.append(dl)
3847             reader.flush()
3848         dl = defer.DeferredList(ds)
3849-        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3850+        if self._verify:
3851+            dl.addCallback(lambda ignored: "")
3852+            dl.addCallback(self._set_segment)
3853+        else:
3854+            dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3855         return dl
3856 
3857 
3858hunk ./src/allmydata/mutable/retrieve.py 701
3859         # shnum, which will be a leaf in the share hash tree, which
3860         # will allow us to validate the rest of the tree.
3861         if self.share_hash_tree.needed_hashes(reader.shnum,
3862-                                               include_leaf=True):
3863+                                              include_leaf=True) or \
3864+                                              self._verify:
3865             try:
3866                 self.share_hash_tree.set_hashes(hashes=sharehashes[1],
3867                                             leaves={reader.shnum: bht[0]})
3868hunk ./src/allmydata/mutable/retrieve.py 832
3869 
3870 
3871     def _try_to_validate_privkey(self, enc_privkey, reader):
3872-
3873         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3874         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3875         if alleged_writekey != self._node.get_writekey():
3876hunk ./src/allmydata/mutable/retrieve.py 838
3877             self.log("invalid privkey from %s shnum %d" %
3878                      (reader, reader.shnum),
3879                      level=log.WEIRD, umid="YIw4tA")
3880+            if self._verify:
3881+                self.servermap.mark_bad_share(reader.peerid, reader.shnum,
3882+                                              self.verinfo[-2])
3883+                e = CorruptShareError(reader.peerid,
3884+                                      reader.shnum,
3885+                                      "invalid privkey")
3886+                f = failure.Failure(e)
3887+                self._bad_shares.add((reader.peerid, reader.shnum, f))
3888             return
3889 
3890         # it's good
3891hunk ./src/allmydata/mutable/retrieve.py 904
3892         statements, I return the decrypted contents to the owner of this
3893         Retrieve object through self._done_deferred.
3894         """
3895-        eventually(self._done_deferred.callback, self._plaintext)
3896+        if self._verify:
3897+            ret = list(self._bad_shares)
3898+            self.log("done verifying, found %d bad shares" % len(ret))
3899+        else:
3900+            ret = self._plaintext
3901+        eventually(self._done_deferred.callback, ret)
3902 
3903 
3904     def _failed(self):
3905hunk ./src/allmydata/mutable/retrieve.py 920
3906         to the caller of this Retrieve object through
3907         self._done_deferred.
3908         """
3909-        format = ("ran out of peers: "
3910-                  "have %(have)d of %(total)d segments "
3911-                  "found %(bad)d bad shares "
3912-                  "encoding %(k)d-of-%(n)d")
3913-        args = {"have": self._current_segment,
3914-                "total": self._num_segments,
3915-                "k": self._required_shares,
3916-                "n": self._total_shares,
3917-                "bad": len(self._bad_shares)}
3918-        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
3919-                                                        str(self._last_failure)))
3920-        f = failure.Failure(e)
3921-        eventually(self._done_deferred.callback, f)
3922+        if self._verify:
3923+            ret = list(self._bad_shares)
3924+        else:
3925+            format = ("ran out of peers: "
3926+                      "have %(have)d of %(total)d segments "
3927+                      "found %(bad)d bad shares "
3928+                      "encoding %(k)d-of-%(n)d")
3929+            args = {"have": self._current_segment,
3930+                    "total": self._num_segments,
3931+                    "k": self._required_shares,
3932+                    "n": self._total_shares,
3933+                    "bad": len(self._bad_shares)}
3934+            e = NotEnoughSharesError("%s, last failure: %s" % \
3935+                                     (format % args, str(self._last_failure)))
3936+            f = failure.Failure(e)
3937+            ret = f
3938+        eventually(self._done_deferred.callback, ret)
3939}
3940[interfaces.py: add IMutableSlotWriter
3941Kevan Carstensen <kevan@isnotajoke.com>**20100630183305
3942 Ignore-this: ff9dca96ef1a009ae85485682f81ea5
3943] hunk ./src/allmydata/interfaces.py 418
3944         """
3945 
3946 
3947+class IMutableSlotWriter(Interface):
3948+    """
3949+    The interface for a writer around a mutable slot on a remote server.
3950+    """
3951+    def set_checkstring(checkstring, *args):
3952+        """
3953+        Set the checkstring that I will pass to the remote server when
3954+        writing.
3955+
3956+            @param checkstring A packed checkstring to use.
3957+
3958+        Note that implementations can differ in which semantics they
3959+        wish to support for set_checkstring -- they can, for example,
3960+        build the checkstring themselves from its constituents, or
3961+        some other thing.
3962+        """
3963+
3964+    def get_checkstring():
3965+        """
3966+        Get the checkstring that I think currently exists on the remote
3967+        server.
3968+        """
3969+
3970+    def put_block(data, segnum, salt):
3971+        """
3972+        Add a block and salt to the share.
3973+        """
3974+
3975+    def put_encprivey(encprivkey):
3976+        """
3977+        Add the encrypted private key to the share.
3978+        """
3979+
3980+    def put_blockhashes(blockhashes=list):
3981+        """
3982+        Add the block hash tree to the share.
3983+        """
3984+
3985+    def put_sharehashes(sharehashes=dict):
3986+        """
3987+        Add the share hash chain to the share.
3988+        """
3989+
3990+    def get_signable():
3991+        """
3992+        Return the part of the share that needs to be signed.
3993+        """
3994+
3995+    def put_signature(signature):
3996+        """
3997+        Add the signature to the share.
3998+        """
3999+
4000+    def put_verification_key(verification_key):
4001+        """
4002+        Add the verification key to the share.
4003+        """
4004+
4005+    def finish_publishing():
4006+        """
4007+        Do anything necessary to finish writing the share to a remote
4008+        server. I require that no further publishing needs to take place
4009+        after this method has been called.
4010+        """
4011+
4012+
4013 class IURI(Interface):
4014     def init_from_string(uri):
4015         """Accept a string (as created by my to_string() method) and populate
4016[test/test_mutable.py: temporarily disable two tests that are now irrelevant
4017Kevan Carstensen <kevan@isnotajoke.com>**20100701232806
4018 Ignore-this: 701e143567f3954812ca6960af1d6ac7
4019] {
4020hunk ./src/allmydata/test/test_mutable.py 651
4021             self.failUnlessEqual(len(share_ids), 10)
4022         d.addCallback(_done)
4023         return d
4024+    test_encrypt.todo = "Write an equivalent of this for the new uploader"
4025 
4026     def test_generate(self):
4027         nm = make_nodemaker()
4028hunk ./src/allmydata/test/test_mutable.py 713
4029                 self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
4030         d.addCallback(_generated)
4031         return d
4032+    test_generate.todo = "Write an equivalent of this for the new uploader"
4033 
4034     # TODO: when we publish to 20 peers, we should get one share per peer on 10
4035     # when we publish to 3 peers, we should get either 3 or 4 shares per peer
4036}
4037[Add MDMF reader and writer, and SDMF writer
4038Kevan Carstensen <kevan@isnotajoke.com>**20100702225531
4039 Ignore-this: bf6276a91d27dcb4e779b0eb82ea1843
4040 
4041 The MDMF/SDMF reader MDMF writer, and SDMF writer are similar to the
4042 object proxies that exist for immutable files. They abstract away
4043 details of connection, state, and caching from their callers (in this
4044 case, the download, servermap updater, and uploader), and expose methods
4045 to get and set information on the remote server.
4046 
4047 MDMFSlotReadProxy reads a mutable file from the server, doing the right
4048 thing (in most cases) regardless of whether the file is MDMF or SDMF. It
4049 allows callers to tell it how to batch and flush reads.
4050 
4051 MDMFSlotWriteProxy writes an MDMF mutable file to a server.
4052 
4053 SDMFSlotWriteProxy writes an SDMF mutable file to a server.
4054 
4055 This patch also includes tests for MDMFSlotReadProxy,
4056 SDMFSlotWriteProxy, and MDMFSlotWriteProxy.
4057] {
4058hunk ./src/allmydata/mutable/layout.py 4
4059 
4060 import struct
4061 from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError
4062+from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \
4063+                                 MDMF_VERSION, IMutableSlotWriter
4064+from allmydata.util import mathutil, observer
4065+from twisted.python import failure
4066+from twisted.internet import defer
4067+from zope.interface import implements
4068+
4069+
4070+# These strings describe the format of the packed structs they help process
4071+# Here's what they mean:
4072+#
4073+#  PREFIX:
4074+#    >: Big-endian byte order; the most significant byte is first (leftmost).
4075+#    B: The version information; an 8 bit version identifier. Stored as
4076+#       an unsigned char. This is currently 00 00 00 00; our modifications
4077+#       will turn it into 00 00 00 01.
4078+#    Q: The sequence number; this is sort of like a revision history for
4079+#       mutable files; they start at 1 and increase as they are changed after
4080+#       being uploaded. Stored as an unsigned long long, which is 8 bytes in
4081+#       length.
4082+#  32s: The root hash of the share hash tree. We use sha-256d, so we use 32
4083+#       characters = 32 bytes to store the value.
4084+#  16s: The salt for the readkey. This is a 16-byte random value, stored as
4085+#       16 characters.
4086+#
4087+#  SIGNED_PREFIX additions, things that are covered by the signature:
4088+#    B: The "k" encoding parameter. We store this as an 8-bit character,
4089+#       which is convenient because our erasure coding scheme cannot
4090+#       encode if you ask for more than 255 pieces.
4091+#    B: The "N" encoding parameter. Stored as an 8-bit character for the
4092+#       same reasons as above.
4093+#    Q: The segment size of the uploaded file. This will essentially be the
4094+#       length of the file in SDMF. An unsigned long long, so we can store
4095+#       files of quite large size.
4096+#    Q: The data length of the uploaded file. Modulo padding, this will be
4097+#       the same of the data length field. Like the data length field, it is
4098+#       an unsigned long long and can be quite large.
4099+#
4100+#   HEADER additions:
4101+#     L: The offset of the signature of this. An unsigned long.
4102+#     L: The offset of the share hash chain. An unsigned long.
4103+#     L: The offset of the block hash tree. An unsigned long.
4104+#     L: The offset of the share data. An unsigned long.
4105+#     Q: The offset of the encrypted private key. An unsigned long long, to
4106+#        account for the possibility of a lot of share data.
4107+#     Q: The offset of the EOF. An unsigned long long, to account for the
4108+#        possibility of a lot of share data.
4109+#
4110+#  After all of these, we have the following:
4111+#    - The verification key: Occupies the space between the end of the header
4112+#      and the start of the signature (i.e.: data[HEADER_LENGTH:o['signature']].
4113+#    - The signature, which goes from the signature offset to the share hash
4114+#      chain offset.
4115+#    - The share hash chain, which goes from the share hash chain offset to
4116+#      the block hash tree offset.
4117+#    - The share data, which goes from the share data offset to the encrypted
4118+#      private key offset.
4119+#    - The encrypted private key offset, which goes until the end of the file.
4120+#
4121+#  The block hash tree in this encoding has only one share, so the offset of
4122+#  the share data will be 32 bits more than the offset of the block hash tree.
4123+#  Given this, we may need to check to see how many bytes a reasonably sized
4124+#  block hash tree will take up.
4125 
4126 PREFIX = ">BQ32s16s" # each version has a different prefix
4127 SIGNED_PREFIX = ">BQ32s16s BBQQ" # this is covered by the signature
4128hunk ./src/allmydata/mutable/layout.py 73
4129 SIGNED_PREFIX_LENGTH = struct.calcsize(SIGNED_PREFIX)
4130 HEADER = ">BQ32s16s BBQQ LLLLQQ" # includes offsets
4131 HEADER_LENGTH = struct.calcsize(HEADER)
4132+OFFSETS = ">LLLLQQ"
4133+OFFSETS_LENGTH = struct.calcsize(OFFSETS)
4134 
4135 def unpack_header(data):
4136     o = {}
4137hunk ./src/allmydata/mutable/layout.py 194
4138     return (share_hash_chain, block_hash_tree, share_data)
4139 
4140 
4141-def pack_checkstring(seqnum, root_hash, IV):
4142+def pack_checkstring(seqnum, root_hash, IV, version=0):
4143     return struct.pack(PREFIX,
4144hunk ./src/allmydata/mutable/layout.py 196
4145-                       0, # version,
4146+                       version,
4147                        seqnum,
4148                        root_hash,
4149                        IV)
4150hunk ./src/allmydata/mutable/layout.py 269
4151                            encprivkey])
4152     return final_share
4153 
4154+def pack_prefix(seqnum, root_hash, IV,
4155+                required_shares, total_shares,
4156+                segment_size, data_length):
4157+    prefix = struct.pack(SIGNED_PREFIX,
4158+                         0, # version,
4159+                         seqnum,
4160+                         root_hash,
4161+                         IV,
4162+                         required_shares,
4163+                         total_shares,
4164+                         segment_size,
4165+                         data_length,
4166+                         )
4167+    return prefix
4168+
4169+
4170+class SDMFSlotWriteProxy:
4171+    implements(IMutableSlotWriter)
4172+    """
4173+    I represent a remote write slot for an SDMF mutable file. I build a
4174+    share in memory, and then write it in one piece to the remote
4175+    server. This mimics how SDMF shares were built before MDMF (and the
4176+    new MDMF uploader), but provides that functionality in a way that
4177+    allows the MDMF uploader to be built without much special-casing for
4178+    file format, which makes the uploader code more readable.
4179+    """
4180+    def __init__(self,
4181+                 shnum,
4182+                 rref, # a remote reference to a storage server
4183+                 storage_index,
4184+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4185+                 seqnum, # the sequence number of the mutable file
4186+                 required_shares,
4187+                 total_shares,
4188+                 segment_size,
4189+                 data_length): # the length of the original file
4190+        self.shnum = shnum
4191+        self._rref = rref
4192+        self._storage_index = storage_index
4193+        self._secrets = secrets
4194+        self._seqnum = seqnum
4195+        self._required_shares = required_shares
4196+        self._total_shares = total_shares
4197+        self._segment_size = segment_size
4198+        self._data_length = data_length
4199+
4200+        # This is an SDMF file, so it should have only one segment, so,
4201+        # modulo padding of the data length, the segment size and the
4202+        # data length should be the same.
4203+        expected_segment_size = mathutil.next_multiple(data_length,
4204+                                                       self._required_shares)
4205+        assert expected_segment_size == segment_size
4206+
4207+        self._block_size = self._segment_size / self._required_shares
4208+
4209+        # This is meant to mimic how SDMF files were built before MDMF
4210+        # entered the picture: we generate each share in its entirety,
4211+        # then push it off to the storage server in one write. When
4212+        # callers call set_*, they are just populating this dict.
4213+        # finish_publishing will stitch these pieces together into a
4214+        # coherent share, and then write the coherent share to the
4215+        # storage server.
4216+        self._share_pieces = {}
4217+
4218+        # This tells the write logic what checkstring to use when
4219+        # writing remote shares.
4220+        self._testvs = []
4221+
4222+        self._readvs = [(0, struct.calcsize(PREFIX))]
4223+
4224+
4225+    def set_checkstring(self, checkstring_or_seqnum,
4226+                              root_hash=None,
4227+                              salt=None):
4228+        """
4229+        Set the checkstring that I will pass to the remote server when
4230+        writing.
4231+
4232+            @param checkstring_or_seqnum: A packed checkstring to use,
4233+                   or a sequence number. I will treat this as a checkstr
4234+
4235+        Note that implementations can differ in which semantics they
4236+        wish to support for set_checkstring -- they can, for example,
4237+        build the checkstring themselves from its constituents, or
4238+        some other thing.
4239+        """
4240+        if root_hash and salt:
4241+            checkstring = struct.pack(PREFIX,
4242+                                      0,
4243+                                      checkstring_or_seqnum,
4244+                                      root_hash,
4245+                                      salt)
4246+        else:
4247+            checkstring = checkstring_or_seqnum
4248+        self._testvs = [(0, len(checkstring), "eq", checkstring)]
4249+
4250+
4251+    def get_checkstring(self):
4252+        """
4253+        Get the checkstring that I think currently exists on the remote
4254+        server.
4255+        """
4256+        if self._testvs:
4257+            return self._testvs[0][3]
4258+        return ""
4259+
4260+
4261+    def put_block(self, data, segnum, salt):
4262+        """
4263+        Add a block and salt to the share.
4264+        """
4265+        # SDMF files have only one segment
4266+        assert segnum == 0
4267+        assert len(data) == self._block_size
4268+        assert len(salt) == SALT_SIZE
4269+
4270+        self._share_pieces['sharedata'] = data
4271+        self._share_pieces['salt'] = salt
4272+
4273+        # TODO: Figure out something intelligent to return.
4274+        return defer.succeed(None)
4275+
4276+
4277+    def put_encprivkey(self, encprivkey):
4278+        """
4279+        Add the encrypted private key to the share.
4280+        """
4281+        self._share_pieces['encprivkey'] = encprivkey
4282+
4283+        return defer.succeed(None)
4284+
4285+
4286+    def put_blockhashes(self, blockhashes):
4287+        """
4288+        Add the block hash tree to the share.
4289+        """
4290+        assert isinstance(blockhashes, list)
4291+        for h in blockhashes:
4292+            assert len(h) == HASH_SIZE
4293+
4294+        # serialize the blockhashes, then set them.
4295+        blockhashes_s = "".join(blockhashes)
4296+        self._share_pieces['block_hash_tree'] = blockhashes_s
4297+
4298+        return defer.succeed(None)
4299+
4300+
4301+    def put_sharehashes(self, sharehashes):
4302+        """
4303+        Add the share hash chain to the share.
4304+        """
4305+        assert isinstance(sharehashes, dict)
4306+        for h in sharehashes.itervalues():
4307+            assert len(h) == HASH_SIZE
4308+
4309+        # serialize the sharehashes, then set them.
4310+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4311+                                 for i in sorted(sharehashes.keys())])
4312+        self._share_pieces['share_hash_chain'] = sharehashes_s
4313+
4314+        return defer.succeed(None)
4315+
4316+
4317+    def put_root_hash(self, root_hash):
4318+        """
4319+        Add the root hash to the share.
4320+        """
4321+        assert len(root_hash) == HASH_SIZE
4322+
4323+        self._share_pieces['root_hash'] = root_hash
4324+
4325+        return defer.succeed(None)
4326+
4327+
4328+    def put_salt(self, salt):
4329+        """
4330+        Add a salt to an empty SDMF file.
4331+        """
4332+        assert len(salt) == SALT_SIZE
4333+
4334+        self._share_pieces['salt'] = salt
4335+        self._share_pieces['sharedata'] = ""
4336+
4337+
4338+    def get_signable(self):
4339+        """
4340+        Return the part of the share that needs to be signed.
4341+
4342+        SDMF writers need to sign the packed representation of the
4343+        first eight fields of the remote share, that is:
4344+            - version number (0)
4345+            - sequence number
4346+            - root of the share hash tree
4347+            - salt
4348+            - k
4349+            - n
4350+            - segsize
4351+            - datalen
4352+
4353+        This method is responsible for returning that to callers.
4354+        """
4355+        return struct.pack(SIGNED_PREFIX,
4356+                           0,
4357+                           self._seqnum,
4358+                           self._share_pieces['root_hash'],
4359+                           self._share_pieces['salt'],
4360+                           self._required_shares,
4361+                           self._total_shares,
4362+                           self._segment_size,
4363+                           self._data_length)
4364+
4365+
4366+    def put_signature(self, signature):
4367+        """
4368+        Add the signature to the share.
4369+        """
4370+        self._share_pieces['signature'] = signature
4371+
4372+        return defer.succeed(None)
4373+
4374+
4375+    def put_verification_key(self, verification_key):
4376+        """
4377+        Add the verification key to the share.
4378+        """
4379+        self._share_pieces['verification_key'] = verification_key
4380+
4381+        return defer.succeed(None)
4382+
4383+
4384+    def get_verinfo(self):
4385+        """
4386+        I return my verinfo tuple. This is used by the ServermapUpdater
4387+        to keep track of versions of mutable files.
4388+
4389+        The verinfo tuple for MDMF files contains:
4390+            - seqnum
4391+            - root hash
4392+            - a blank (nothing)
4393+            - segsize
4394+            - datalen
4395+            - k
4396+            - n
4397+            - prefix (the thing that you sign)
4398+            - a tuple of offsets
4399+
4400+        We include the nonce in MDMF to simplify processing of version
4401+        information tuples.
4402+
4403+        The verinfo tuple for SDMF files is the same, but contains a
4404+        16-byte IV instead of a hash of salts.
4405+        """
4406+        return (self._seqnum,
4407+                self._share_pieces['root_hash'],
4408+                self._share_pieces['salt'],
4409+                self._segment_size,
4410+                self._data_length,
4411+                self._required_shares,
4412+                self._total_shares,
4413+                self.get_signable(),
4414+                self._get_offsets_tuple())
4415+
4416+    def _get_offsets_dict(self):
4417+        post_offset = HEADER_LENGTH
4418+        offsets = {}
4419+
4420+        verification_key_length = len(self._share_pieces['verification_key'])
4421+        o1 = offsets['signature'] = post_offset + verification_key_length
4422+
4423+        signature_length = len(self._share_pieces['signature'])
4424+        o2 = offsets['share_hash_chain'] = o1 + signature_length
4425+
4426+        share_hash_chain_length = len(self._share_pieces['share_hash_chain'])
4427+        o3 = offsets['block_hash_tree'] = o2 + share_hash_chain_length
4428+
4429+        block_hash_tree_length = len(self._share_pieces['block_hash_tree'])
4430+        o4 = offsets['share_data'] = o3 + block_hash_tree_length
4431+
4432+        share_data_length = len(self._share_pieces['sharedata'])
4433+        o5 = offsets['enc_privkey'] = o4 + share_data_length
4434+
4435+        encprivkey_length = len(self._share_pieces['encprivkey'])
4436+        offsets['EOF'] = o5 + encprivkey_length
4437+        return offsets
4438+
4439+
4440+    def _get_offsets_tuple(self):
4441+        offsets = self._get_offsets_dict()
4442+        return tuple([(key, value) for key, value in offsets.items()])
4443+
4444+
4445+    def _pack_offsets(self):
4446+        offsets = self._get_offsets_dict()
4447+        return struct.pack(">LLLLQQ",
4448+                           offsets['signature'],
4449+                           offsets['share_hash_chain'],
4450+                           offsets['block_hash_tree'],
4451+                           offsets['share_data'],
4452+                           offsets['enc_privkey'],
4453+                           offsets['EOF'])
4454+
4455+
4456+    def finish_publishing(self):
4457+        """
4458+        Do anything necessary to finish writing the share to a remote
4459+        server. I require that no further publishing needs to take place
4460+        after this method has been called.
4461+        """
4462+        for k in ["sharedata", "encprivkey", "signature", "verification_key",
4463+                  "share_hash_chain", "block_hash_tree"]:
4464+            assert k in self._share_pieces
4465+        # This is the only method that actually writes something to the
4466+        # remote server.
4467+        # First, we need to pack the share into data that we can write
4468+        # to the remote server in one write.
4469+        offsets = self._pack_offsets()
4470+        prefix = self.get_signable()
4471+        final_share = "".join([prefix,
4472+                               offsets,
4473+                               self._share_pieces['verification_key'],
4474+                               self._share_pieces['signature'],
4475+                               self._share_pieces['share_hash_chain'],
4476+                               self._share_pieces['block_hash_tree'],
4477+                               self._share_pieces['sharedata'],
4478+                               self._share_pieces['encprivkey']])
4479+
4480+        # Our only data vector is going to be writing the final share,
4481+        # in its entirely.
4482+        datavs = [(0, final_share)]
4483+
4484+        if not self._testvs:
4485+            # Our caller has not provided us with another checkstring
4486+            # yet, so we assume that we are writing a new share, and set
4487+            # a test vector that will allow a new share to be written.
4488+            self._testvs = []
4489+            self._testvs.append(tuple([0, 1, "eq", ""]))
4490+            new_share = True
4491+
4492+        tw_vectors = {}
4493+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
4494+        return self._rref.callRemote("slot_testv_and_readv_and_writev",
4495+                                     self._storage_index,
4496+                                     self._secrets,
4497+                                     tw_vectors,
4498+                                     # TODO is it useful to read something?
4499+                                     self._readvs)
4500+
4501+
4502+MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
4503+MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
4504+MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
4505+MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
4506+MDMFCHECKSTRING = ">BQ32s"
4507+MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
4508+MDMFOFFSETS = ">QQQQQQ"
4509+MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
4510+
4511+class MDMFSlotWriteProxy:
4512+    implements(IMutableSlotWriter)
4513+
4514+    """
4515+    I represent a remote write slot for an MDMF mutable file.
4516+
4517+    I abstract away from my caller the details of block and salt
4518+    management, and the implementation of the on-disk format for MDMF
4519+    shares.
4520+    """
4521+    # Expected layout, MDMF:
4522+    # offset:     size:       name:
4523+    #-- signed part --
4524+    # 0           1           version number (01)
4525+    # 1           8           sequence number
4526+    # 9           32          share tree root hash
4527+    # 41          1           The "k" encoding parameter
4528+    # 42          1           The "N" encoding parameter
4529+    # 43          8           The segment size of the uploaded file
4530+    # 51          8           The data length of the original plaintext
4531+    #-- end signed part --
4532+    # 59          8           The offset of the encrypted private key
4533+    # 67          8           The offset of the block hash tree
4534+    # 75          8           The offset of the share hash chain
4535+    # 83          8           The offset of the signature
4536+    # 91          8           The offset of the verification key
4537+    # 99          8           The offset of the EOF
4538+    #
4539+    # followed by salts and share data, the encrypted private key, the
4540+    # block hash tree, the salt hash tree, the share hash chain, a
4541+    # signature over the first eight fields, and a verification key.
4542+    #
4543+    # The checkstring is the first three fields -- the version number,
4544+    # sequence number, root hash and root salt hash. This is consistent
4545+    # in meaning to what we have with SDMF files, except now instead of
4546+    # using the literal salt, we use a value derived from all of the
4547+    # salts -- the share hash root.
4548+    #
4549+    # The salt is stored before the block for each segment. The block
4550+    # hash tree is computed over the combination of block and salt for
4551+    # each segment. In this way, we get integrity checking for both
4552+    # block and salt with the current block hash tree arrangement.
4553+    #
4554+    # The ordering of the offsets is different to reflect the dependencies
4555+    # that we'll run into with an MDMF file. The expected write flow is
4556+    # something like this:
4557+    #
4558+    #   0: Initialize with the sequence number, encoding parameters and
4559+    #      data length. From this, we can deduce the number of segments,
4560+    #      and where they should go.. We can also figure out where the
4561+    #      encrypted private key should go, because we can figure out how
4562+    #      big the share data will be.
4563+    #
4564+    #   1: Encrypt, encode, and upload the file in chunks. Do something
4565+    #      like
4566+    #
4567+    #       put_block(data, segnum, salt)
4568+    #
4569+    #      to write a block and a salt to the disk. We can do both of
4570+    #      these operations now because we have enough of the offsets to
4571+    #      know where to put them.
4572+    #
4573+    #   2: Put the encrypted private key. Use:
4574+    #
4575+    #        put_encprivkey(encprivkey)
4576+    #
4577+    #      Now that we know the length of the private key, we can fill
4578+    #      in the offset for the block hash tree.
4579+    #
4580+    #   3: We're now in a position to upload the block hash tree for
4581+    #      a share. Put that using something like:
4582+    #       
4583+    #        put_blockhashes(block_hash_tree)
4584+    #
4585+    #      Note that block_hash_tree is a list of hashes -- we'll take
4586+    #      care of the details of serializing that appropriately. When
4587+    #      we get the block hash tree, we are also in a position to
4588+    #      calculate the offset for the share hash chain, and fill that
4589+    #      into the offsets table.
4590+    #
4591+    #   4: At the same time, we're in a position to upload the salt hash
4592+    #      tree. This is a Merkle tree over all of the salts. We use a
4593+    #      Merkle tree so that we can validate each block,salt pair as
4594+    #      we download them later. We do this using
4595+    #
4596+    #        put_salthashes(salt_hash_tree)
4597+    #
4598+    #      When you do this, I automatically put the root of the tree
4599+    #      (the hash at index 0 of the list) in its appropriate slot in
4600+    #      the signed prefix of the share.
4601+    #
4602+    #   5: We're now in a position to upload the share hash chain for
4603+    #      a share. Do that with something like:
4604+    #     
4605+    #        put_sharehashes(share_hash_chain)
4606+    #
4607+    #      share_hash_chain should be a dictionary mapping shnums to
4608+    #      32-byte hashes -- the wrapper handles serialization.
4609+    #      We'll know where to put the signature at this point, also.
4610+    #      The root of this tree will be put explicitly in the next
4611+    #      step.
4612+    #
4613+    #      TODO: Why? Why not just include it in the tree here?
4614+    #
4615+    #   6: Before putting the signature, we must first put the
4616+    #      root_hash. Do this with:
4617+    #
4618+    #        put_root_hash(root_hash).
4619+    #     
4620+    #      In terms of knowing where to put this value, it was always
4621+    #      possible to place it, but it makes sense semantically to
4622+    #      place it after the share hash tree, so that's why you do it
4623+    #      in this order.
4624+    #
4625+    #   6: With the root hash put, we can now sign the header. Use:
4626+    #
4627+    #        get_signable()
4628+    #
4629+    #      to get the part of the header that you want to sign, and use:
4630+    #       
4631+    #        put_signature(signature)
4632+    #
4633+    #      to write your signature to the remote server.
4634+    #
4635+    #   6: Add the verification key, and finish. Do:
4636+    #
4637+    #        put_verification_key(key)
4638+    #
4639+    #      and
4640+    #
4641+    #        finish_publish()
4642+    #
4643+    # Checkstring management:
4644+    #
4645+    # To write to a mutable slot, we have to provide test vectors to ensure
4646+    # that we are writing to the same data that we think we are. These
4647+    # vectors allow us to detect uncoordinated writes; that is, writes
4648+    # where both we and some other shareholder are writing to the
4649+    # mutable slot, and to report those back to the parts of the program
4650+    # doing the writing.
4651+    #
4652+    # With SDMF, this was easy -- all of the share data was written in
4653+    # one go, so it was easy to detect uncoordinated writes, and we only
4654+    # had to do it once. With MDMF, not all of the file is written at
4655+    # once.
4656+    #
4657+    # If a share is new, we write out as much of the header as we can
4658+    # before writing out anything else. This gives other writers a
4659+    # canary that they can use to detect uncoordinated writes, and, if
4660+    # they do the same thing, gives us the same canary. We them update
4661+    # the share. We won't be able to write out two fields of the header
4662+    # -- the share tree hash and the salt hash -- until we finish
4663+    # writing out the share. We only require the writer to provide the
4664+    # initial checkstring, and keep track of what it should be after
4665+    # updates ourselves.
4666+    #
4667+    # If we haven't written anything yet, then on the first write (which
4668+    # will probably be a block + salt of a share), we'll also write out
4669+    # the header. On subsequent passes, we'll expect to see the header.
4670+    # This changes in two places:
4671+    #
4672+    #   - When we write out the salt hash
4673+    #   - When we write out the root of the share hash tree
4674+    #
4675+    # since these values will change the header. It is possible that we
4676+    # can just make those be written in one operation to minimize
4677+    # disruption.
4678+    def __init__(self,
4679+                 shnum,
4680+                 rref, # a remote reference to a storage server
4681+                 storage_index,
4682+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4683+                 seqnum, # the sequence number of the mutable file
4684+                 required_shares,
4685+                 total_shares,
4686+                 segment_size,
4687+                 data_length): # the length of the original file
4688+        self.shnum = shnum
4689+        self._rref = rref
4690+        self._storage_index = storage_index
4691+        self._seqnum = seqnum
4692+        self._required_shares = required_shares
4693+        assert self.shnum >= 0 and self.shnum < total_shares
4694+        self._total_shares = total_shares
4695+        # We build up the offset table as we write things. It is the
4696+        # last thing we write to the remote server.
4697+        self._offsets = {}
4698+        self._testvs = []
4699+        self._secrets = secrets
4700+        # The segment size needs to be a multiple of the k parameter --
4701+        # any padding should have been carried out by the publisher
4702+        # already.
4703+        assert segment_size % required_shares == 0
4704+        self._segment_size = segment_size
4705+        self._data_length = data_length
4706+
4707+        # These are set later -- we define them here so that we can
4708+        # check for their existence easily
4709+
4710+        # This is the root of the share hash tree -- the Merkle tree
4711+        # over the roots of the block hash trees computed for shares in
4712+        # this upload.
4713+        self._root_hash = None
4714+
4715+        # We haven't yet written anything to the remote bucket. By
4716+        # setting this, we tell the _write method as much. The write
4717+        # method will then know that it also needs to add a write vector
4718+        # for the checkstring (or what we have of it) to the first write
4719+        # request. We'll then record that value for future use.  If
4720+        # we're expecting something to be there already, we need to call
4721+        # set_checkstring before we write anything to tell the first
4722+        # write about that.
4723+        self._written = False
4724+
4725+        # When writing data to the storage servers, we get a read vector
4726+        # for free. We'll read the checkstring, which will help us
4727+        # figure out what's gone wrong if a write fails.
4728+        self._readv = [(0, struct.calcsize(MDMFCHECKSTRING))]
4729+
4730+        # We calculate the number of segments because it tells us
4731+        # where the salt part of the file ends/share segment begins,
4732+        # and also because it provides a useful amount of bounds checking.
4733+        self._num_segments = mathutil.div_ceil(self._data_length,
4734+                                               self._segment_size)
4735+        self._block_size = self._segment_size / self._required_shares
4736+        # We also calculate the share size, to help us with block
4737+        # constraints later.
4738+        tail_size = self._data_length % self._segment_size
4739+        if not tail_size:
4740+            self._tail_block_size = self._block_size
4741+        else:
4742+            self._tail_block_size = mathutil.next_multiple(tail_size,
4743+                                                           self._required_shares)
4744+            self._tail_block_size /= self._required_shares
4745+
4746+        # We already know where the sharedata starts; right after the end
4747+        # of the header (which is defined as the signable part + the offsets)
4748+        # We can also calculate where the encrypted private key begins
4749+        # from what we know know.
4750+        self._actual_block_size = self._block_size + SALT_SIZE
4751+        data_size = self._actual_block_size * (self._num_segments - 1)
4752+        data_size += self._tail_block_size
4753+        data_size += SALT_SIZE
4754+        self._offsets['enc_privkey'] = MDMFHEADERSIZE
4755+        self._offsets['enc_privkey'] += data_size
4756+        # We'll wait for the rest. Callers can now call my "put_block" and
4757+        # "set_checkstring" methods.
4758+
4759+
4760+    def set_checkstring(self,
4761+                        seqnum_or_checkstring,
4762+                        root_hash=None,
4763+                        salt=None):
4764+        """
4765+        Set checkstring checkstring for the given shnum.
4766+
4767+        This can be invoked in one of two ways.
4768+
4769+        With one argument, I assume that you are giving me a literal
4770+        checkstring -- e.g., the output of get_checkstring. I will then
4771+        set that checkstring as it is. This form is used by unit tests.
4772+
4773+        With two arguments, I assume that you are giving me a sequence
4774+        number and root hash to make a checkstring from. In that case, I
4775+        will build a checkstring and set it for you. This form is used
4776+        by the publisher.
4777+
4778+        By default, I assume that I am writing new shares to the grid.
4779+        If you don't explcitly set your own checkstring, I will use
4780+        one that requires that the remote share not exist. You will want
4781+        to use this method if you are updating a share in-place;
4782+        otherwise, writes will fail.
4783+        """
4784+        # You're allowed to overwrite checkstrings with this method;
4785+        # I assume that users know what they are doing when they call
4786+        # it.
4787+        if root_hash:
4788+            checkstring = struct.pack(MDMFCHECKSTRING,
4789+                                      1,
4790+                                      seqnum_or_checkstring,
4791+                                      root_hash)
4792+        else:
4793+            checkstring = seqnum_or_checkstring
4794+
4795+        if checkstring == "":
4796+            # We special-case this, since len("") = 0, but we need
4797+            # length of 1 for the case of an empty share to work on the
4798+            # storage server, which is what a checkstring that is the
4799+            # empty string means.
4800+            self._testvs = []
4801+        else:
4802+            self._testvs = []
4803+            self._testvs.append((0, len(checkstring), "eq", checkstring))
4804+
4805+
4806+    def __repr__(self):
4807+        return "MDMFSlotWriteProxy for share %d" % self.shnum
4808+
4809+
4810+    def get_checkstring(self):
4811+        """
4812+        Given a share number, I return a representation of what the
4813+        checkstring for that share on the server will look like.
4814+
4815+        I am mostly used for tests.
4816+        """
4817+        if self._root_hash:
4818+            roothash = self._root_hash
4819+        else:
4820+            roothash = "\x00" * 32
4821+        return struct.pack(MDMFCHECKSTRING,
4822+                           1,
4823+                           self._seqnum,
4824+                           roothash)
4825+
4826+
4827+    def put_block(self, data, segnum, salt):
4828+        """
4829+        Put the encrypted-and-encoded data segment in the slot, along
4830+        with the salt.
4831+        """
4832+        if segnum >= self._num_segments:
4833+            raise LayoutInvalid("I won't overwrite the private key")
4834+        if len(salt) != SALT_SIZE:
4835+            raise LayoutInvalid("I was given a salt of size %d, but "
4836+                                "I wanted a salt of size %d")
4837+        if segnum + 1 == self._num_segments:
4838+            if len(data) != self._tail_block_size:
4839+                raise LayoutInvalid("I was given the wrong size block to write")
4840+        elif len(data) != self._block_size:
4841+            raise LayoutInvalid("I was given the wrong size block to write")
4842+
4843+        # We want to write at len(MDMFHEADER) + segnum * block_size.
4844+
4845+        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
4846+        data = salt + data
4847+
4848+        datavs = [tuple([offset, data])]
4849+        return self._write(datavs)
4850+
4851+
4852+    def put_encprivkey(self, encprivkey):
4853+        """
4854+        Put the encrypted private key in the remote slot.
4855+        """
4856+        assert self._offsets
4857+        assert self._offsets['enc_privkey']
4858+        # You shouldn't re-write the encprivkey after the block hash
4859+        # tree is written, since that could cause the private key to run
4860+        # into the block hash tree. Before it writes the block hash
4861+        # tree, the block hash tree writing method writes the offset of
4862+        # the salt hash tree. So that's a good indicator of whether or
4863+        # not the block hash tree has been written.
4864+        if "share_hash_chain" in self._offsets:
4865+            raise LayoutInvalid("You must write this before the block hash tree")
4866+
4867+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
4868+        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
4869+        def _on_failure():
4870+            del(self._offsets['block_hash_tree'])
4871+        return self._write(datavs, on_failure=_on_failure)
4872+
4873+
4874+    def put_blockhashes(self, blockhashes):
4875+        """
4876+        Put the block hash tree in the remote slot.
4877+
4878+        The encrypted private key must be put before the block hash
4879+        tree, since we need to know how large it is to know where the
4880+        block hash tree should go. The block hash tree must be put
4881+        before the salt hash tree, since its size determines the
4882+        offset of the share hash chain.
4883+        """
4884+        assert self._offsets
4885+        assert isinstance(blockhashes, list)
4886+        if "block_hash_tree" not in self._offsets:
4887+            raise LayoutInvalid("You must put the encrypted private key "
4888+                                "before you put the block hash tree")
4889+        # If written, the share hash chain causes the signature offset
4890+        # to be defined.
4891+        if "signature" in self._offsets:
4892+            raise LayoutInvalid("You must put the block hash tree before "
4893+                                "you put the share hash chain")
4894+        blockhashes_s = "".join(blockhashes)
4895+        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
4896+        datavs = []
4897+        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
4898+        def _on_failure():
4899+            del(self._offsets['share_hash_chain'])
4900+        return self._write(datavs, on_failure=_on_failure)
4901+
4902+
4903+    def put_sharehashes(self, sharehashes):
4904+        """
4905+        Put the share hash chain in the remote slot.
4906+
4907+        The salt hash tree must be put before the share hash chain,
4908+        since we need to know where the salt hash tree ends before we
4909+        can know where the share hash chain starts. The share hash chain
4910+        must be put before the signature, since the length of the packed
4911+        share hash chain determines the offset of the signature. Also,
4912+        semantically, you must know what the root of the salt hash tree
4913+        is before you can generate a valid signature.
4914+        """
4915+        assert isinstance(sharehashes, dict)
4916+        if "share_hash_chain" not in self._offsets:
4917+            raise LayoutInvalid("You need to put the salt hash tree before "
4918+                                "you can put the share hash chain")
4919+        # The signature comes after the share hash chain. If the
4920+        # signature has already been written, we must not write another
4921+        # share hash chain. The signature writes the verification key
4922+        # offset when it gets sent to the remote server, so we look for
4923+        # that.
4924+        if "verification_key" in self._offsets:
4925+            raise LayoutInvalid("You must write the share hash chain "
4926+                                "before you write the signature")
4927+        datavs = []
4928+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4929+                                  for i in sorted(sharehashes.keys())])
4930+        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
4931+        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
4932+        def _on_failure():
4933+            del(self._offsets['signature'])
4934+        return self._write(datavs, on_failure=_on_failure)
4935+
4936+
4937+    def put_root_hash(self, roothash):
4938+        """
4939+        Put the root hash (the root of the share hash tree) in the
4940+        remote slot.
4941+        """
4942+        # It does not make sense to be able to put the root
4943+        # hash without first putting the share hashes, since you need
4944+        # the share hashes to generate the root hash.
4945+        #
4946+        # Signature is defined by the routine that places the share hash
4947+        # chain, so it's a good thing to look for in finding out whether
4948+        # or not the share hash chain exists on the remote server.
4949+        if "signature" not in self._offsets:
4950+            raise LayoutInvalid("You need to put the share hash chain "
4951+                                "before you can put the root share hash")
4952+        if len(roothash) != HASH_SIZE:
4953+            raise LayoutInvalid("hashes and salts must be exactly %d bytes"
4954+                                 % HASH_SIZE)
4955+        datavs = []
4956+        self._root_hash = roothash
4957+        # To write both of these values, we update the checkstring on
4958+        # the remote server, which includes them
4959+        checkstring = self.get_checkstring()
4960+        datavs.append(tuple([0, checkstring]))
4961+        # This write, if successful, changes the checkstring, so we need
4962+        # to update our internal checkstring to be consistent with the
4963+        # one on the server.
4964+        def _on_success():
4965+            self._testvs = [(0, len(checkstring), "eq", checkstring)]
4966+        def _on_failure():
4967+            self._root_hash = None
4968+        return self._write(datavs,
4969+                           on_success=_on_success,
4970+                           on_failure=_on_failure)
4971+
4972+
4973+    def get_signable(self):
4974+        """
4975+        Get the first seven fields of the mutable file; the parts that
4976+        are signed.
4977+        """
4978+        if not self._root_hash:
4979+            raise LayoutInvalid("You need to set the root hash "
4980+                                "before getting something to "
4981+                                "sign")
4982+        return struct.pack(MDMFSIGNABLEHEADER,
4983+                           1,
4984+                           self._seqnum,
4985+                           self._root_hash,
4986+                           self._required_shares,
4987+                           self._total_shares,
4988+                           self._segment_size,
4989+                           self._data_length)
4990+
4991+
4992+    def put_signature(self, signature):
4993+        """
4994+        Put the signature field to the remote slot.
4995+
4996+        I require that the root hash and share hash chain have been put
4997+        to the grid before I will write the signature to the grid.
4998+        """
4999+        if "signature" not in self._offsets:
5000+            raise LayoutInvalid("You must put the share hash chain "
5001+        # It does not make sense to put a signature without first
5002+        # putting the root hash and the salt hash (since otherwise
5003+        # the signature would be incomplete), so we don't allow that.
5004+                       "before putting the signature")
5005+        if not self._root_hash:
5006+            raise LayoutInvalid("You must complete the signed prefix "
5007+                                "before computing a signature")
5008+        # If we put the signature after we put the verification key, we
5009+        # could end up running into the verification key, and will
5010+        # probably screw up the offsets as well. So we don't allow that.
5011+        # The method that writes the verification key defines the EOF
5012+        # offset before writing the verification key, so look for that.
5013+        if "EOF" in self._offsets:
5014+            raise LayoutInvalid("You must write the signature before the verification key")
5015+
5016+        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
5017+        datavs = []
5018+        datavs.append(tuple([self._offsets['signature'], signature]))
5019+        def _on_failure():
5020+            del(self._offsets['verification_key'])
5021+        return self._write(datavs, on_failure=_on_failure)
5022+
5023+
5024+    def put_verification_key(self, verification_key):
5025+        """
5026+        Put the verification key into the remote slot.
5027+
5028+        I require that the signature have been written to the storage
5029+        server before I allow the verification key to be written to the
5030+        remote server.
5031+        """
5032+        if "verification_key" not in self._offsets:
5033+            raise LayoutInvalid("You must put the signature before you "
5034+                                "can put the verification key")
5035+        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
5036+        datavs = []
5037+        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
5038+        def _on_failure():
5039+            del(self._offsets['EOF'])
5040+        return self._write(datavs, on_failure=_on_failure)
5041+
5042+    def _get_offsets_tuple(self):
5043+        return tuple([(key, value) for key, value in self._offsets.items()])
5044+
5045+    def get_verinfo(self):
5046+        return (self._seqnum,
5047+                self._root_hash,
5048+                self._required_shares,
5049+                self._total_shares,
5050+                self._segment_size,
5051+                self._data_length,
5052+                self.get_signable(),
5053+                self._get_offsets_tuple())
5054+
5055+
5056+    def finish_publishing(self):
5057+        """
5058+        Write the offset table and encoding parameters to the remote
5059+        slot, since that's the only thing we have yet to publish at this
5060+        point.
5061+        """
5062+        if "EOF" not in self._offsets:
5063+            raise LayoutInvalid("You must put the verification key before "
5064+                                "you can publish the offsets")
5065+        offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
5066+        offsets = struct.pack(MDMFOFFSETS,
5067+                              self._offsets['enc_privkey'],
5068+                              self._offsets['block_hash_tree'],
5069+                              self._offsets['share_hash_chain'],
5070+                              self._offsets['signature'],
5071+                              self._offsets['verification_key'],
5072+                              self._offsets['EOF'])
5073+        datavs = []
5074+        datavs.append(tuple([offsets_offset, offsets]))
5075+        encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
5076+        params = struct.pack(">BBQQ",
5077+                             self._required_shares,
5078+                             self._total_shares,
5079+                             self._segment_size,
5080+                             self._data_length)
5081+        datavs.append(tuple([encoding_parameters_offset, params]))
5082+        return self._write(datavs)
5083+
5084+
5085+    def _write(self, datavs, on_failure=None, on_success=None):
5086+        """I write the data vectors in datavs to the remote slot."""
5087+        tw_vectors = {}
5088+        new_share = False
5089+        if not self._testvs:
5090+            self._testvs = []
5091+            self._testvs.append(tuple([0, 1, "eq", ""]))
5092+            new_share = True
5093+        if not self._written:
5094+            # Write a new checkstring to the share when we write it, so
5095+            # that we have something to check later.
5096+            new_checkstring = self.get_checkstring()
5097+            datavs.append((0, new_checkstring))
5098+            def _first_write():
5099+                self._written = True
5100+                self._testvs = [(0, len(new_checkstring), "eq", new_checkstring)]
5101+            on_success = _first_write
5102+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
5103+        datalength = sum([len(x[1]) for x in datavs])
5104+        d = self._rref.callRemote("slot_testv_and_readv_and_writev",
5105+                                  self._storage_index,
5106+                                  self._secrets,
5107+                                  tw_vectors,
5108+                                  self._readv)
5109+        def _result(results):
5110+            if isinstance(results, failure.Failure) or not results[0]:
5111+                # Do nothing; the write was unsuccessful.
5112+                if on_failure: on_failure()
5113+            else:
5114+                if on_success: on_success()
5115+            return results
5116+        d.addCallback(_result)
5117+        return d
5118+
5119+
5120+class MDMFSlotReadProxy:
5121+    """
5122+    I read from a mutable slot filled with data written in the MDMF data
5123+    format (which is described above).
5124+
5125+    I can be initialized with some amount of data, which I will use (if
5126+    it is valid) to eliminate some of the need to fetch it from servers.
5127+    """
5128+    def __init__(self,
5129+                 rref,
5130+                 storage_index,
5131+                 shnum,
5132+                 data=""):
5133+        # Start the initialization process.
5134+        self._rref = rref
5135+        self._storage_index = storage_index
5136+        self.shnum = shnum
5137+
5138+        # Before doing anything, the reader is probably going to want to
5139+        # verify that the signature is correct. To do that, they'll need
5140+        # the verification key, and the signature. To get those, we'll
5141+        # need the offset table. So fetch the offset table on the
5142+        # assumption that that will be the first thing that a reader is
5143+        # going to do.
5144+
5145+        # The fact that these encoding parameters are None tells us
5146+        # that we haven't yet fetched them from the remote share, so we
5147+        # should. We could just not set them, but the checks will be
5148+        # easier to read if we don't have to use hasattr.
5149+        self._version_number = None
5150+        self._sequence_number = None
5151+        self._root_hash = None
5152+        # Filled in if we're dealing with an SDMF file. Unused
5153+        # otherwise.
5154+        self._salt = None
5155+        self._required_shares = None
5156+        self._total_shares = None
5157+        self._segment_size = None
5158+        self._data_length = None
5159+        self._offsets = None
5160+
5161+        # If the user has chosen to initialize us with some data, we'll
5162+        # try to satisfy subsequent data requests with that data before
5163+        # asking the storage server for it. If
5164+        self._data = data
5165+        # The way callers interact with cache in the filenode returns
5166+        # None if there isn't any cached data, but the way we index the
5167+        # cached data requires a string, so convert None to "".
5168+        if self._data == None:
5169+            self._data = ""
5170+
5171+        self._queue_observers = observer.ObserverList()
5172+        self._queue_errbacks = observer.ObserverList()
5173+        self._readvs = []
5174+
5175+
5176+    def _maybe_fetch_offsets_and_header(self, force_remote=False):
5177+        """
5178+        I fetch the offset table and the header from the remote slot if
5179+        I don't already have them. If I do have them, I do nothing and
5180+        return an empty Deferred.
5181+        """
5182+        if self._offsets:
5183+            return defer.succeed(None)
5184+        # At this point, we may be either SDMF or MDMF. Fetching 107
5185+        # bytes will be enough to get header and offsets for both SDMF and
5186+        # MDMF, though we'll be left with 4 more bytes than we
5187+        # need if this ends up being MDMF. This is probably less
5188+        # expensive than the cost of a second roundtrip.
5189+        readvs = [(0, 107)]
5190+        d = self._read(readvs, force_remote)
5191+        d.addCallback(self._process_encoding_parameters)
5192+        d.addCallback(self._process_offsets)
5193+        return d
5194+
5195+
5196+    def _process_encoding_parameters(self, encoding_parameters):
5197+        assert self.shnum in encoding_parameters
5198+        encoding_parameters = encoding_parameters[self.shnum][0]
5199+        # The first byte is the version number. It will tell us what
5200+        # to do next.
5201+        (verno,) = struct.unpack(">B", encoding_parameters[:1])
5202+        if verno == MDMF_VERSION:
5203+            read_size = MDMFHEADERWITHOUTOFFSETSSIZE
5204+            (verno,
5205+             seqnum,
5206+             root_hash,
5207+             k,
5208+             n,
5209+             segsize,
5210+             datalen) = struct.unpack(MDMFHEADERWITHOUTOFFSETS,
5211+                                      encoding_parameters[:read_size])
5212+            if segsize == 0 and datalen == 0:
5213+                # Empty file, no segments.
5214+                self._num_segments = 0
5215+            else:
5216+                self._num_segments = mathutil.div_ceil(datalen, segsize)
5217+
5218+        elif verno == SDMF_VERSION:
5219+            read_size = SIGNED_PREFIX_LENGTH
5220+            (verno,
5221+             seqnum,
5222+             root_hash,
5223+             salt,
5224+             k,
5225+             n,
5226+             segsize,
5227+             datalen) = struct.unpack(">BQ32s16s BBQQ",
5228+                                encoding_parameters[:SIGNED_PREFIX_LENGTH])
5229+            self._salt = salt
5230+            if segsize == 0 and datalen == 0:
5231+                # empty file
5232+                self._num_segments = 0
5233+            else:
5234+                # non-empty SDMF files have one segment.
5235+                self._num_segments = 1
5236+        else:
5237+            raise UnknownVersionError("You asked me to read mutable file "
5238+                                      "version %d, but I only understand "
5239+                                      "%d and %d" % (verno, SDMF_VERSION,
5240+                                                     MDMF_VERSION))
5241+
5242+        self._version_number = verno
5243+        self._sequence_number = seqnum
5244+        self._root_hash = root_hash
5245+        self._required_shares = k
5246+        self._total_shares = n
5247+        self._segment_size = segsize
5248+        self._data_length = datalen
5249+
5250+        self._block_size = self._segment_size / self._required_shares
5251+        # We can upload empty files, and need to account for this fact
5252+        # so as to avoid zero-division and zero-modulo errors.
5253+        if datalen > 0:
5254+            tail_size = self._data_length % self._segment_size
5255+        else:
5256+            tail_size = 0
5257+        if not tail_size:
5258+            self._tail_block_size = self._block_size
5259+        else:
5260+            self._tail_block_size = mathutil.next_multiple(tail_size,
5261+                                                    self._required_shares)
5262+            self._tail_block_size /= self._required_shares
5263+
5264+        return encoding_parameters
5265+
5266+
5267+    def _process_offsets(self, offsets):
5268+        if self._version_number == 0:
5269+            read_size = OFFSETS_LENGTH
5270+            read_offset = SIGNED_PREFIX_LENGTH
5271+            end = read_size + read_offset
5272+            (signature,
5273+             share_hash_chain,
5274+             block_hash_tree,
5275+             share_data,
5276+             enc_privkey,
5277+             EOF) = struct.unpack(">LLLLQQ",
5278+                                  offsets[read_offset:end])
5279+            self._offsets = {}
5280+            self._offsets['signature'] = signature
5281+            self._offsets['share_data'] = share_data
5282+            self._offsets['block_hash_tree'] = block_hash_tree
5283+            self._offsets['share_hash_chain'] = share_hash_chain
5284+            self._offsets['enc_privkey'] = enc_privkey
5285+            self._offsets['EOF'] = EOF
5286+
5287+        elif self._version_number == 1:
5288+            read_offset = MDMFHEADERWITHOUTOFFSETSSIZE
5289+            read_length = MDMFOFFSETS_LENGTH
5290+            end = read_offset + read_length
5291+            (encprivkey,
5292+             blockhashes,
5293+             sharehashes,
5294+             signature,
5295+             verification_key,
5296+             eof) = struct.unpack(MDMFOFFSETS,
5297+                                  offsets[read_offset:end])
5298+            self._offsets = {}
5299+            self._offsets['enc_privkey'] = encprivkey
5300+            self._offsets['block_hash_tree'] = blockhashes
5301+            self._offsets['share_hash_chain'] = sharehashes
5302+            self._offsets['signature'] = signature
5303+            self._offsets['verification_key'] = verification_key
5304+            self._offsets['EOF'] = eof
5305+
5306+
5307+    def get_block_and_salt(self, segnum, queue=False):
5308+        """
5309+        I return (block, salt), where block is the block data and
5310+        salt is the salt used to encrypt that segment.
5311+        """
5312+        d = self._maybe_fetch_offsets_and_header()
5313+        def _then(ignored):
5314+            if self._version_number == 1:
5315+                base_share_offset = MDMFHEADERSIZE
5316+            else:
5317+                base_share_offset = self._offsets['share_data']
5318+
5319+            if segnum + 1 > self._num_segments:
5320+                raise LayoutInvalid("Not a valid segment number")
5321+
5322+            if self._version_number == 0:
5323+                share_offset = base_share_offset + self._block_size * segnum
5324+            else:
5325+                share_offset = base_share_offset + (self._block_size + \
5326+                                                    SALT_SIZE) * segnum
5327+            if segnum + 1 == self._num_segments:
5328+                data = self._tail_block_size
5329+            else:
5330+                data = self._block_size
5331+
5332+            if self._version_number == 1:
5333+                data += SALT_SIZE
5334+
5335+            readvs = [(share_offset, data)]
5336+            return readvs
5337+        d.addCallback(_then)
5338+        d.addCallback(lambda readvs:
5339+            self._read(readvs, queue=queue))
5340+        def _process_results(results):
5341+            assert self.shnum in results
5342+            if self._version_number == 0:
5343+                # We only read the share data, but we know the salt from
5344+                # when we fetched the header
5345+                data = results[self.shnum]
5346+                if not data:
5347+                    data = ""
5348+                else:
5349+                    assert len(data) == 1
5350+                    data = data[0]
5351+                salt = self._salt
5352+            else:
5353+                data = results[self.shnum]
5354+                if not data:
5355+                    salt = data = ""
5356+                else:
5357+                    salt_and_data = results[self.shnum][0]
5358+                    salt = salt_and_data[:SALT_SIZE]
5359+                    data = salt_and_data[SALT_SIZE:]
5360+            return data, salt
5361+        d.addCallback(_process_results)
5362+        return d
5363+
5364+
5365+    def get_blockhashes(self, needed=None, queue=False, force_remote=False):
5366+        """
5367+        I return the block hash tree
5368+
5369+        I take an optional argument, needed, which is a set of indices
5370+        correspond to hashes that I should fetch. If this argument is
5371+        missing, I will fetch the entire block hash tree; otherwise, I
5372+        may attempt to fetch fewer hashes, based on what needed says
5373+        that I should do. Note that I may fetch as many hashes as I
5374+        want, so long as the set of hashes that I do fetch is a superset
5375+        of the ones that I am asked for, so callers should be prepared
5376+        to tolerate additional hashes.
5377+        """
5378+        # TODO: Return only the parts of the block hash tree necessary
5379+        # to validate the blocknum provided?
5380+        # This is a good idea, but it is hard to implement correctly. It
5381+        # is bad to fetch any one block hash more than once, so we
5382+        # probably just want to fetch the whole thing at once and then
5383+        # serve it.
5384+        if needed == set([]):
5385+            return defer.succeed([])
5386+        d = self._maybe_fetch_offsets_and_header()
5387+        def _then(ignored):
5388+            blockhashes_offset = self._offsets['block_hash_tree']
5389+            if self._version_number == 1:
5390+                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
5391+            else:
5392+                blockhashes_length = self._offsets['share_data'] - blockhashes_offset
5393+            readvs = [(blockhashes_offset, blockhashes_length)]
5394+            return readvs
5395+        d.addCallback(_then)
5396+        d.addCallback(lambda readvs:
5397+            self._read(readvs, queue=queue, force_remote=force_remote))
5398+        def _build_block_hash_tree(results):
5399+            assert self.shnum in results
5400+
5401+            rawhashes = results[self.shnum][0]
5402+            results = [rawhashes[i:i+HASH_SIZE]
5403+                       for i in range(0, len(rawhashes), HASH_SIZE)]
5404+            return results
5405+        d.addCallback(_build_block_hash_tree)
5406+        return d
5407+
5408+
5409+    def get_sharehashes(self, needed=None, queue=False, force_remote=False):
5410+        """
5411+        I return the part of the share hash chain placed to validate
5412+        this share.
5413+
5414+        I take an optional argument, needed. Needed is a set of indices
5415+        that correspond to the hashes that I should fetch. If needed is
5416+        not present, I will fetch and return the entire share hash
5417+        chain. Otherwise, I may fetch and return any part of the share
5418+        hash chain that is a superset of the part that I am asked to
5419+        fetch. Callers should be prepared to deal with more hashes than
5420+        they've asked for.
5421+        """
5422+        if needed == set([]):
5423+            return defer.succeed([])
5424+        d = self._maybe_fetch_offsets_and_header()
5425+
5426+        def _make_readvs(ignored):
5427+            sharehashes_offset = self._offsets['share_hash_chain']
5428+            if self._version_number == 0:
5429+                sharehashes_length = self._offsets['block_hash_tree'] - sharehashes_offset
5430+            else:
5431+                sharehashes_length = self._offsets['signature'] - sharehashes_offset
5432+            readvs = [(sharehashes_offset, sharehashes_length)]
5433+            return readvs
5434+        d.addCallback(_make_readvs)
5435+        d.addCallback(lambda readvs:
5436+            self._read(readvs, queue=queue, force_remote=force_remote))
5437+        def _build_share_hash_chain(results):
5438+            assert self.shnum in results
5439+
5440+            sharehashes = results[self.shnum][0]
5441+            results = [sharehashes[i:i+(HASH_SIZE + 2)]
5442+                       for i in range(0, len(sharehashes), HASH_SIZE + 2)]
5443+            results = dict([struct.unpack(">H32s", data)
5444+                            for data in results])
5445+            return results
5446+        d.addCallback(_build_share_hash_chain)
5447+        return d
5448+
5449+
5450+    def get_encprivkey(self, queue=False):
5451+        """
5452+        I return the encrypted private key.
5453+        """
5454+        d = self._maybe_fetch_offsets_and_header()
5455+
5456+        def _make_readvs(ignored):
5457+            privkey_offset = self._offsets['enc_privkey']
5458+            if self._version_number == 0:
5459+                privkey_length = self._offsets['EOF'] - privkey_offset
5460+            else:
5461+                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
5462+            readvs = [(privkey_offset, privkey_length)]
5463+            return readvs
5464+        d.addCallback(_make_readvs)
5465+        d.addCallback(lambda readvs:
5466+            self._read(readvs, queue=queue))
5467+        def _process_results(results):
5468+            assert self.shnum in results
5469+            privkey = results[self.shnum][0]
5470+            return privkey
5471+        d.addCallback(_process_results)
5472+        return d
5473+
5474+
5475+    def get_signature(self, queue=False):
5476+        """
5477+        I return the signature of my share.
5478+        """
5479+        d = self._maybe_fetch_offsets_and_header()
5480+
5481+        def _make_readvs(ignored):
5482+            signature_offset = self._offsets['signature']
5483+            if self._version_number == 1:
5484+                signature_length = self._offsets['verification_key'] - signature_offset
5485+            else:
5486+                signature_length = self._offsets['share_hash_chain'] - signature_offset
5487+            readvs = [(signature_offset, signature_length)]
5488+            return readvs
5489+        d.addCallback(_make_readvs)
5490+        d.addCallback(lambda readvs:
5491+            self._read(readvs, queue=queue))
5492+        def _process_results(results):
5493+            assert self.shnum in results
5494+            signature = results[self.shnum][0]
5495+            return signature
5496+        d.addCallback(_process_results)
5497+        return d
5498+
5499+
5500+    def get_verification_key(self, queue=False):
5501+        """
5502+        I return the verification key.
5503+        """
5504+        d = self._maybe_fetch_offsets_and_header()
5505+
5506+        def _make_readvs(ignored):
5507+            if self._version_number == 1:
5508+                vk_offset = self._offsets['verification_key']
5509+                vk_length = self._offsets['EOF'] - vk_offset
5510+            else:
5511+                vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5512+                vk_length = self._offsets['signature'] - vk_offset
5513+            readvs = [(vk_offset, vk_length)]
5514+            return readvs
5515+        d.addCallback(_make_readvs)
5516+        d.addCallback(lambda readvs:
5517+            self._read(readvs, queue=queue))
5518+        def _process_results(results):
5519+            assert self.shnum in results
5520+            verification_key = results[self.shnum][0]
5521+            return verification_key
5522+        d.addCallback(_process_results)
5523+        return d
5524+
5525+
5526+    def get_encoding_parameters(self):
5527+        """
5528+        I return (k, n, segsize, datalen)
5529+        """
5530+        d = self._maybe_fetch_offsets_and_header()
5531+        d.addCallback(lambda ignored:
5532+            (self._required_shares,
5533+             self._total_shares,
5534+             self._segment_size,
5535+             self._data_length))
5536+        return d
5537+
5538+
5539+    def get_seqnum(self):
5540+        """
5541+        I return the sequence number for this share.
5542+        """
5543+        d = self._maybe_fetch_offsets_and_header()
5544+        d.addCallback(lambda ignored:
5545+            self._sequence_number)
5546+        return d
5547+
5548+
5549+    def get_root_hash(self):
5550+        """
5551+        I return the root of the block hash tree
5552+        """
5553+        d = self._maybe_fetch_offsets_and_header()
5554+        d.addCallback(lambda ignored: self._root_hash)
5555+        return d
5556+
5557+
5558+    def get_checkstring(self):
5559+        """
5560+        I return the packed representation of the following:
5561+
5562+            - version number
5563+            - sequence number
5564+            - root hash
5565+            - salt hash
5566+
5567+        which my users use as a checkstring to detect other writers.
5568+        """
5569+        d = self._maybe_fetch_offsets_and_header()
5570+        def _build_checkstring(ignored):
5571+            if self._salt:
5572+                checkstring = strut.pack(PREFIX,
5573+                                         self._version_number,
5574+                                         self._sequence_number,
5575+                                         self._root_hash,
5576+                                         self._salt)
5577+            else:
5578+                checkstring = struct.pack(MDMFCHECKSTRING,
5579+                                          self._version_number,
5580+                                          self._sequence_number,
5581+                                          self._root_hash)
5582+
5583+            return checkstring
5584+        d.addCallback(_build_checkstring)
5585+        return d
5586+
5587+
5588+    def get_prefix(self, force_remote):
5589+        d = self._maybe_fetch_offsets_and_header(force_remote)
5590+        d.addCallback(lambda ignored:
5591+            self._build_prefix())
5592+        return d
5593+
5594+
5595+    def _build_prefix(self):
5596+        # The prefix is another name for the part of the remote share
5597+        # that gets signed. It consists of everything up to and
5598+        # including the datalength, packed by struct.
5599+        if self._version_number == SDMF_VERSION:
5600+            return struct.pack(SIGNED_PREFIX,
5601+                           self._version_number,
5602+                           self._sequence_number,
5603+                           self._root_hash,
5604+                           self._salt,
5605+                           self._required_shares,
5606+                           self._total_shares,
5607+                           self._segment_size,
5608+                           self._data_length)
5609+
5610+        else:
5611+            return struct.pack(MDMFSIGNABLEHEADER,
5612+                           self._version_number,
5613+                           self._sequence_number,
5614+                           self._root_hash,
5615+                           self._required_shares,
5616+                           self._total_shares,
5617+                           self._segment_size,
5618+                           self._data_length)
5619+
5620+
5621+    def _get_offsets_tuple(self):
5622+        # The offsets tuple is another component of the version
5623+        # information tuple. It is basically our offsets dictionary,
5624+        # itemized and in a tuple.
5625+        return self._offsets.copy()
5626+
5627+
5628+    def get_verinfo(self):
5629+        """
5630+        I return my verinfo tuple. This is used by the ServermapUpdater
5631+        to keep track of versions of mutable files.
5632+
5633+        The verinfo tuple for MDMF files contains:
5634+            - seqnum
5635+            - root hash
5636+            - a blank (nothing)
5637+            - segsize
5638+            - datalen
5639+            - k
5640+            - n
5641+            - prefix (the thing that you sign)
5642+            - a tuple of offsets
5643+
5644+        We include the nonce in MDMF to simplify processing of version
5645+        information tuples.
5646+
5647+        The verinfo tuple for SDMF files is the same, but contains a
5648+        16-byte IV instead of a hash of salts.
5649+        """
5650+        d = self._maybe_fetch_offsets_and_header()
5651+        def _build_verinfo(ignored):
5652+            if self._version_number == SDMF_VERSION:
5653+                salt_to_use = self._salt
5654+            else:
5655+                salt_to_use = None
5656+            return (self._sequence_number,
5657+                    self._root_hash,
5658+                    salt_to_use,
5659+                    self._segment_size,
5660+                    self._data_length,
5661+                    self._required_shares,
5662+                    self._total_shares,
5663+                    self._build_prefix(),
5664+                    self._get_offsets_tuple())
5665+        d.addCallback(_build_verinfo)
5666+        return d
5667+
5668+
5669+    def flush(self):
5670+        """
5671+        I flush my queue of read vectors.
5672+        """
5673+        d = self._read(self._readvs)
5674+        def _then(results):
5675+            self._readvs = []
5676+            if isinstance(results, failure.Failure):
5677+                self._queue_errbacks.notify(results)
5678+            else:
5679+                self._queue_observers.notify(results)
5680+            self._queue_observers = observer.ObserverList()
5681+            self._queue_errbacks = observer.ObserverList()
5682+        d.addBoth(_then)
5683+
5684+
5685+    def _read(self, readvs, force_remote=False, queue=False):
5686+        unsatisfiable = filter(lambda x: x[0] + x[1] > len(self._data), readvs)
5687+        # TODO: It's entirely possible to tweak this so that it just
5688+        # fulfills the requests that it can, and not demand that all
5689+        # requests are satisfiable before running it.
5690+        if not unsatisfiable and not force_remote:
5691+            results = [self._data[offset:offset+length]
5692+                       for (offset, length) in readvs]
5693+            results = {self.shnum: results}
5694+            return defer.succeed(results)
5695+        else:
5696+            if queue:
5697+                start = len(self._readvs)
5698+                self._readvs += readvs
5699+                end = len(self._readvs)
5700+                def _get_results(results, start, end):
5701+                    if not self.shnum in results:
5702+                        return {self._shnum: [""]}
5703+                    return {self.shnum: results[self.shnum][start:end]}
5704+                d = defer.Deferred()
5705+                d.addCallback(_get_results, start, end)
5706+                self._queue_observers.subscribe(d.callback)
5707+                self._queue_errbacks.subscribe(d.errback)
5708+                return d
5709+            return self._rref.callRemote("slot_readv",
5710+                                         self._storage_index,
5711+                                         [self.shnum],
5712+                                         readvs)
5713+
5714+
5715+    def is_sdmf(self):
5716+        """I tell my caller whether or not my remote file is SDMF or MDMF
5717+        """
5718+        d = self._maybe_fetch_offsets_and_header()
5719+        d.addCallback(lambda ignored:
5720+            self._version_number == 0)
5721+        return d
5722+
5723+
5724+class LayoutInvalid(Exception):
5725+    """
5726+    This isn't a valid MDMF mutable file
5727+    """
5728hunk ./src/allmydata/test/test_storage.py 2
5729 
5730-import time, os.path, stat, re, simplejson, struct
5731+import time, os.path, stat, re, simplejson, struct, shutil
5732 
5733 from twisted.trial import unittest
5734 
5735hunk ./src/allmydata/test/test_storage.py 22
5736 from allmydata.storage.expirer import LeaseCheckingCrawler
5737 from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \
5738      ReadBucketProxy
5739-from allmydata.interfaces import BadWriteEnablerError
5740-from allmydata.test.common import LoggingServiceParent
5741+from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
5742+                                     LayoutInvalid, MDMFSIGNABLEHEADER, \
5743+                                     SIGNED_PREFIX, MDMFHEADER, \
5744+                                     MDMFOFFSETS, SDMFSlotWriteProxy
5745+from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
5746+                                 SDMF_VERSION
5747+from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
5748 from allmydata.test.common_web import WebRenderingMixin
5749 from allmydata.web.storage import StorageStatus, remove_prefix
5750 
5751hunk ./src/allmydata/test/test_storage.py 106
5752 
5753 class RemoteBucket:
5754 
5755+    def __init__(self):
5756+        self.read_count = 0
5757+        self.write_count = 0
5758+
5759     def callRemote(self, methname, *args, **kwargs):
5760         def _call():
5761             meth = getattr(self.target, "remote_" + methname)
5762hunk ./src/allmydata/test/test_storage.py 114
5763             return meth(*args, **kwargs)
5764+
5765+        if methname == "slot_readv":
5766+            self.read_count += 1
5767+        if "writev" in methname:
5768+            self.write_count += 1
5769+
5770         return defer.maybeDeferred(_call)
5771 
5772hunk ./src/allmydata/test/test_storage.py 122
5773+
5774 class BucketProxy(unittest.TestCase):
5775     def make_bucket(self, name, size):
5776         basedir = os.path.join("storage", "BucketProxy", name)
5777hunk ./src/allmydata/test/test_storage.py 1313
5778         self.failUnless(os.path.exists(prefixdir), prefixdir)
5779         self.failIf(os.path.exists(bucketdir), bucketdir)
5780 
5781+
5782+class MDMFProxies(unittest.TestCase, ShouldFailMixin):
5783+    def setUp(self):
5784+        self.sparent = LoggingServiceParent()
5785+        self._lease_secret = itertools.count()
5786+        self.ss = self.create("MDMFProxies storage test server")
5787+        self.rref = RemoteBucket()
5788+        self.rref.target = self.ss
5789+        self.secrets = (self.write_enabler("we_secret"),
5790+                        self.renew_secret("renew_secret"),
5791+                        self.cancel_secret("cancel_secret"))
5792+        self.segment = "aaaaaa"
5793+        self.block = "aa"
5794+        self.salt = "a" * 16
5795+        self.block_hash = "a" * 32
5796+        self.block_hash_tree = [self.block_hash for i in xrange(6)]
5797+        self.share_hash = self.block_hash
5798+        self.share_hash_chain = dict([(i, self.share_hash) for i in xrange(6)])
5799+        self.signature = "foobarbaz"
5800+        self.verification_key = "vvvvvv"
5801+        self.encprivkey = "private"
5802+        self.root_hash = self.block_hash
5803+        self.salt_hash = self.root_hash
5804+        self.salt_hash_tree = [self.salt_hash for i in xrange(6)]
5805+        self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree)
5806+        self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain)
5807+        # blockhashes and salt hashes are serialized in the same way,
5808+        # only we lop off the first element and store that in the
5809+        # header.
5810+        self.salt_hash_tree_s = self.serialize_blockhashes(self.salt_hash_tree[1:])
5811+
5812+
5813+    def tearDown(self):
5814+        self.sparent.stopService()
5815+        shutil.rmtree(self.workdir("MDMFProxies storage test server"))
5816+
5817+
5818+    def write_enabler(self, we_tag):
5819+        return hashutil.tagged_hash("we_blah", we_tag)
5820+
5821+
5822+    def renew_secret(self, tag):
5823+        return hashutil.tagged_hash("renew_blah", str(tag))
5824+
5825+
5826+    def cancel_secret(self, tag):
5827+        return hashutil.tagged_hash("cancel_blah", str(tag))
5828+
5829+
5830+    def workdir(self, name):
5831+        basedir = os.path.join("storage", "MutableServer", name)
5832+        return basedir
5833+
5834+
5835+    def create(self, name):
5836+        workdir = self.workdir(name)
5837+        ss = StorageServer(workdir, "\x00" * 20)
5838+        ss.setServiceParent(self.sparent)
5839+        return ss
5840+
5841+
5842+    def build_test_mdmf_share(self, tail_segment=False, empty=False):
5843+        # Start with the checkstring
5844+        data = struct.pack(">BQ32s",
5845+                           1,
5846+                           0,
5847+                           self.root_hash)
5848+        self.checkstring = data
5849+        # Next, the encoding parameters
5850+        if tail_segment:
5851+            data += struct.pack(">BBQQ",
5852+                                3,
5853+                                10,
5854+                                6,
5855+                                33)
5856+        elif empty:
5857+            data += struct.pack(">BBQQ",
5858+                                3,
5859+                                10,
5860+                                0,
5861+                                0)
5862+        else:
5863+            data += struct.pack(">BBQQ",
5864+                                3,
5865+                                10,
5866+                                6,
5867+                                36)
5868+        # Now we'll build the offsets.
5869+        sharedata = ""
5870+        if not tail_segment and not empty:
5871+            for i in xrange(6):
5872+                sharedata += self.salt + self.block
5873+        elif tail_segment:
5874+            for i in xrange(5):
5875+                sharedata += self.salt + self.block
5876+            sharedata += self.salt + "a"
5877+
5878+        # The encrypted private key comes after the shares + salts
5879+        offset_size = struct.calcsize(MDMFOFFSETS)
5880+        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
5881+        # The blockhashes come after the private key
5882+        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
5883+        # The sharehashes come after the salt hashes
5884+        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
5885+        # The signature comes after the share hash chain
5886+        signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
5887+        # The verification key comes after the signature
5888+        verification_offset = signature_offset + len(self.signature)
5889+        # The EOF comes after the verification key
5890+        eof_offset = verification_offset + len(self.verification_key)
5891+        data += struct.pack(MDMFOFFSETS,
5892+                            encrypted_private_key_offset,
5893+                            blockhashes_offset,
5894+                            sharehashes_offset,
5895+                            signature_offset,
5896+                            verification_offset,
5897+                            eof_offset)
5898+        self.offsets = {}
5899+        self.offsets['enc_privkey'] = encrypted_private_key_offset
5900+        self.offsets['block_hash_tree'] = blockhashes_offset
5901+        self.offsets['share_hash_chain'] = sharehashes_offset
5902+        self.offsets['signature'] = signature_offset
5903+        self.offsets['verification_key'] = verification_offset
5904+        self.offsets['EOF'] = eof_offset
5905+        # Next, we'll add in the salts and share data,
5906+        data += sharedata
5907+        # the private key,
5908+        data += self.encprivkey
5909+        # the block hash tree,
5910+        data += self.block_hash_tree_s
5911+        # the share hash chain,
5912+        data += self.share_hash_chain_s
5913+        # the signature,
5914+        data += self.signature
5915+        # and the verification key
5916+        data += self.verification_key
5917+        return data
5918+
5919+
5920+    def write_test_share_to_server(self,
5921+                                   storage_index,
5922+                                   tail_segment=False,
5923+                                   empty=False):
5924+        """
5925+        I write some data for the read tests to read to self.ss
5926+
5927+        If tail_segment=True, then I will write a share that has a
5928+        smaller tail segment than other segments.
5929+        """
5930+        write = self.ss.remote_slot_testv_and_readv_and_writev
5931+        data = self.build_test_mdmf_share(tail_segment, empty)
5932+        # Finally, we write the whole thing to the storage server in one
5933+        # pass.
5934+        testvs = [(0, 1, "eq", "")]
5935+        tws = {}
5936+        tws[0] = (testvs, [(0, data)], None)
5937+        readv = [(0, 1)]
5938+        results = write(storage_index, self.secrets, tws, readv)
5939+        self.failUnless(results[0])
5940+
5941+
5942+    def build_test_sdmf_share(self, empty=False):
5943+        if empty:
5944+            sharedata = ""
5945+        else:
5946+            sharedata = self.segment * 6
5947+        self.sharedata = sharedata
5948+        blocksize = len(sharedata) / 3
5949+        block = sharedata[:blocksize]
5950+        self.blockdata = block
5951+        prefix = struct.pack(">BQ32s16s BBQQ",
5952+                             0, # version,
5953+                             0,
5954+                             self.root_hash,
5955+                             self.salt,
5956+                             3,
5957+                             10,
5958+                             len(sharedata),
5959+                             len(sharedata),
5960+                            )
5961+        post_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5962+        signature_offset = post_offset + len(self.verification_key)
5963+        sharehashes_offset = signature_offset + len(self.signature)
5964+        blockhashes_offset = sharehashes_offset + len(self.share_hash_chain_s)
5965+        sharedata_offset = blockhashes_offset + len(self.block_hash_tree_s)
5966+        encprivkey_offset = sharedata_offset + len(block)
5967+        eof_offset = encprivkey_offset + len(self.encprivkey)
5968+        offsets = struct.pack(">LLLLQQ",
5969+                              signature_offset,
5970+                              sharehashes_offset,
5971+                              blockhashes_offset,
5972+                              sharedata_offset,
5973+                              encprivkey_offset,
5974+                              eof_offset)
5975+        final_share = "".join([prefix,
5976+                           offsets,
5977+                           self.verification_key,
5978+                           self.signature,
5979+                           self.share_hash_chain_s,
5980+                           self.block_hash_tree_s,
5981+                           block,
5982+                           self.encprivkey])
5983+        self.offsets = {}
5984+        self.offsets['signature'] = signature_offset
5985+        self.offsets['share_hash_chain'] = sharehashes_offset
5986+        self.offsets['block_hash_tree'] = blockhashes_offset
5987+        self.offsets['share_data'] = sharedata_offset
5988+        self.offsets['enc_privkey'] = encprivkey_offset
5989+        self.offsets['EOF'] = eof_offset
5990+        return final_share
5991+
5992+
5993+    def write_sdmf_share_to_server(self,
5994+                                   storage_index,
5995+                                   empty=False):
5996+        # Some tests need SDMF shares to verify that we can still
5997+        # read them. This method writes one, which resembles but is not
5998+        assert self.rref
5999+        write = self.ss.remote_slot_testv_and_readv_and_writev
6000+        share = self.build_test_sdmf_share(empty)
6001+        testvs = [(0, 1, "eq", "")]
6002+        tws = {}
6003+        tws[0] = (testvs, [(0, share)], None)
6004+        readv = []
6005+        results = write(storage_index, self.secrets, tws, readv)
6006+        self.failUnless(results[0])
6007+
6008+
6009+    def test_read(self):
6010+        self.write_test_share_to_server("si1")
6011+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6012+        # Check that every method equals what we expect it to.
6013+        d = defer.succeed(None)
6014+        def _check_block_and_salt((block, salt)):
6015+            self.failUnlessEqual(block, self.block)
6016+            self.failUnlessEqual(salt, self.salt)
6017+
6018+        for i in xrange(6):
6019+            d.addCallback(lambda ignored, i=i:
6020+                mr.get_block_and_salt(i))
6021+            d.addCallback(_check_block_and_salt)
6022+
6023+        d.addCallback(lambda ignored:
6024+            mr.get_encprivkey())
6025+        d.addCallback(lambda encprivkey:
6026+            self.failUnlessEqual(self.encprivkey, encprivkey))
6027+
6028+        d.addCallback(lambda ignored:
6029+            mr.get_blockhashes())
6030+        d.addCallback(lambda blockhashes:
6031+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6032+
6033+        d.addCallback(lambda ignored:
6034+            mr.get_sharehashes())
6035+        d.addCallback(lambda sharehashes:
6036+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6037+
6038+        d.addCallback(lambda ignored:
6039+            mr.get_signature())
6040+        d.addCallback(lambda signature:
6041+            self.failUnlessEqual(signature, self.signature))
6042+
6043+        d.addCallback(lambda ignored:
6044+            mr.get_verification_key())
6045+        d.addCallback(lambda verification_key:
6046+            self.failUnlessEqual(verification_key, self.verification_key))
6047+
6048+        d.addCallback(lambda ignored:
6049+            mr.get_seqnum())
6050+        d.addCallback(lambda seqnum:
6051+            self.failUnlessEqual(seqnum, 0))
6052+
6053+        d.addCallback(lambda ignored:
6054+            mr.get_root_hash())
6055+        d.addCallback(lambda root_hash:
6056+            self.failUnlessEqual(self.root_hash, root_hash))
6057+
6058+        d.addCallback(lambda ignored:
6059+            mr.get_seqnum())
6060+        d.addCallback(lambda seqnum:
6061+            self.failUnlessEqual(0, seqnum))
6062+
6063+        d.addCallback(lambda ignored:
6064+            mr.get_encoding_parameters())
6065+        def _check_encoding_parameters((k, n, segsize, datalen)):
6066+            self.failUnlessEqual(k, 3)
6067+            self.failUnlessEqual(n, 10)
6068+            self.failUnlessEqual(segsize, 6)
6069+            self.failUnlessEqual(datalen, 36)
6070+        d.addCallback(_check_encoding_parameters)
6071+
6072+        d.addCallback(lambda ignored:
6073+            mr.get_checkstring())
6074+        d.addCallback(lambda checkstring:
6075+            self.failUnlessEqual(checkstring, checkstring))
6076+        return d
6077+
6078+
6079+    def test_read_with_different_tail_segment_size(self):
6080+        self.write_test_share_to_server("si1", tail_segment=True)
6081+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6082+        d = mr.get_block_and_salt(5)
6083+        def _check_tail_segment(results):
6084+            block, salt = results
6085+            self.failUnlessEqual(len(block), 1)
6086+            self.failUnlessEqual(block, "a")
6087+        d.addCallback(_check_tail_segment)
6088+        return d
6089+
6090+
6091+    def test_get_block_with_invalid_segnum(self):
6092+        self.write_test_share_to_server("si1")
6093+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6094+        d = defer.succeed(None)
6095+        d.addCallback(lambda ignored:
6096+            self.shouldFail(LayoutInvalid, "test invalid segnum",
6097+                            None,
6098+                            mr.get_block_and_salt, 7))
6099+        return d
6100+
6101+
6102+    def test_get_encoding_parameters_first(self):
6103+        self.write_test_share_to_server("si1")
6104+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6105+        d = mr.get_encoding_parameters()
6106+        def _check_encoding_parameters((k, n, segment_size, datalen)):
6107+            self.failUnlessEqual(k, 3)
6108+            self.failUnlessEqual(n, 10)
6109+            self.failUnlessEqual(segment_size, 6)
6110+            self.failUnlessEqual(datalen, 36)
6111+        d.addCallback(_check_encoding_parameters)
6112+        return d
6113+
6114+
6115+    def test_get_seqnum_first(self):
6116+        self.write_test_share_to_server("si1")
6117+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6118+        d = mr.get_seqnum()
6119+        d.addCallback(lambda seqnum:
6120+            self.failUnlessEqual(seqnum, 0))
6121+        return d
6122+
6123+
6124+    def test_get_root_hash_first(self):
6125+        self.write_test_share_to_server("si1")
6126+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6127+        d = mr.get_root_hash()
6128+        d.addCallback(lambda root_hash:
6129+            self.failUnlessEqual(root_hash, self.root_hash))
6130+        return d
6131+
6132+
6133+    def test_get_checkstring_first(self):
6134+        self.write_test_share_to_server("si1")
6135+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6136+        d = mr.get_checkstring()
6137+        d.addCallback(lambda checkstring:
6138+            self.failUnlessEqual(checkstring, self.checkstring))
6139+        return d
6140+
6141+
6142+    def test_write_read_vectors(self):
6143+        # When writing for us, the storage server will return to us a
6144+        # read vector, along with its result. If a write fails because
6145+        # the test vectors failed, this read vector can help us to
6146+        # diagnose the problem. This test ensures that the read vector
6147+        # is working appropriately.
6148+        mw = self._make_new_mw("si1", 0)
6149+        d = defer.succeed(None)
6150+
6151+        # Write one share. This should return a checkstring of nothing,
6152+        # since there is no data there.
6153+        d.addCallback(lambda ignored:
6154+            mw.put_block(self.block, 0, self.salt))
6155+        def _check_first_write(results):
6156+            result, readvs = results
6157+            self.failUnless(result)
6158+            self.failIf(readvs)
6159+        d.addCallback(_check_first_write)
6160+        # Now, there should be a different checkstring returned when
6161+        # we write other shares
6162+        d.addCallback(lambda ignored:
6163+            mw.put_block(self.block, 1, self.salt))
6164+        def _check_next_write(results):
6165+            result, readvs = results
6166+            self.failUnless(result)
6167+            self.expected_checkstring = mw.get_checkstring()
6168+            self.failUnlessIn(0, readvs)
6169+            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
6170+        d.addCallback(_check_next_write)
6171+        # Add the other four shares
6172+        for i in xrange(2, 6):
6173+            d.addCallback(lambda ignored, i=i:
6174+                mw.put_block(self.block, i, self.salt))
6175+            d.addCallback(_check_next_write)
6176+        # Add the encrypted private key
6177+        d.addCallback(lambda ignored:
6178+            mw.put_encprivkey(self.encprivkey))
6179+        d.addCallback(_check_next_write)
6180+        # Add the block hash tree and share hash tree
6181+        d.addCallback(lambda ignored:
6182+            mw.put_blockhashes(self.block_hash_tree))
6183+        d.addCallback(_check_next_write)
6184+        d.addCallback(lambda ignored:
6185+            mw.put_sharehashes(self.share_hash_chain))
6186+        d.addCallback(_check_next_write)
6187+        # Add the root hash and the salt hash. This should change the
6188+        # checkstring, but not in a way that we'll be able to see right
6189+        # now, since the read vectors are applied before the write
6190+        # vectors.
6191+        d.addCallback(lambda ignored:
6192+            mw.put_root_hash(self.root_hash))
6193+        def _check_old_testv_after_new_one_is_written(results):
6194+            result, readvs = results
6195+            self.failUnless(result)
6196+            self.failUnlessIn(0, readvs)
6197+            self.failUnlessEqual(self.expected_checkstring,
6198+                                 readvs[0][0])
6199+            new_checkstring = mw.get_checkstring()
6200+            self.failIfEqual(new_checkstring,
6201+                             readvs[0][0])
6202+        d.addCallback(_check_old_testv_after_new_one_is_written)
6203+        # Now add the signature. This should succeed, meaning that the
6204+        # data gets written and the read vector matches what the writer
6205+        # thinks should be there.
6206+        d.addCallback(lambda ignored:
6207+            mw.put_signature(self.signature))
6208+        d.addCallback(_check_next_write)
6209+        # The checkstring remains the same for the rest of the process.
6210+        return d
6211+
6212+
6213+    def test_blockhashes_after_share_hash_chain(self):
6214+        mw = self._make_new_mw("si1", 0)
6215+        d = defer.succeed(None)
6216+        # Put everything up to and including the share hash chain
6217+        for i in xrange(6):
6218+            d.addCallback(lambda ignored, i=i:
6219+                mw.put_block(self.block, i, self.salt))
6220+        d.addCallback(lambda ignored:
6221+            mw.put_encprivkey(self.encprivkey))
6222+        d.addCallback(lambda ignored:
6223+            mw.put_blockhashes(self.block_hash_tree))
6224+        d.addCallback(lambda ignored:
6225+            mw.put_sharehashes(self.share_hash_chain))
6226+
6227+        # Now try to put the block hash tree again.
6228+        d.addCallback(lambda ignored:
6229+            self.shouldFail(LayoutInvalid, "test repeat salthashes",
6230+                            None,
6231+                            mw.put_blockhashes, self.block_hash_tree))
6232+        return d
6233+
6234+
6235+    def test_encprivkey_after_blockhashes(self):
6236+        mw = self._make_new_mw("si1", 0)
6237+        d = defer.succeed(None)
6238+        # Put everything up to and including the block hash tree
6239+        for i in xrange(6):
6240+            d.addCallback(lambda ignored, i=i:
6241+                mw.put_block(self.block, i, self.salt))
6242+        d.addCallback(lambda ignored:
6243+            mw.put_encprivkey(self.encprivkey))
6244+        d.addCallback(lambda ignored:
6245+            mw.put_blockhashes(self.block_hash_tree))
6246+        d.addCallback(lambda ignored:
6247+            self.shouldFail(LayoutInvalid, "out of order private key",
6248+                            None,
6249+                            mw.put_encprivkey, self.encprivkey))
6250+        return d
6251+
6252+
6253+    def test_share_hash_chain_after_signature(self):
6254+        mw = self._make_new_mw("si1", 0)
6255+        d = defer.succeed(None)
6256+        # Put everything up to and including the signature
6257+        for i in xrange(6):
6258+            d.addCallback(lambda ignored, i=i:
6259+                mw.put_block(self.block, i, self.salt))
6260+        d.addCallback(lambda ignored:
6261+            mw.put_encprivkey(self.encprivkey))
6262+        d.addCallback(lambda ignored:
6263+            mw.put_blockhashes(self.block_hash_tree))
6264+        d.addCallback(lambda ignored:
6265+            mw.put_sharehashes(self.share_hash_chain))
6266+        d.addCallback(lambda ignored:
6267+            mw.put_root_hash(self.root_hash))
6268+        d.addCallback(lambda ignored:
6269+            mw.put_signature(self.signature))
6270+        # Now try to put the share hash chain again. This should fail
6271+        d.addCallback(lambda ignored:
6272+            self.shouldFail(LayoutInvalid, "out of order share hash chain",
6273+                            None,
6274+                            mw.put_sharehashes, self.share_hash_chain))
6275+        return d
6276+
6277+
6278+    def test_signature_after_verification_key(self):
6279+        mw = self._make_new_mw("si1", 0)
6280+        d = defer.succeed(None)
6281+        # Put everything up to and including the verification key.
6282+        for i in xrange(6):
6283+            d.addCallback(lambda ignored, i=i:
6284+                mw.put_block(self.block, i, self.salt))
6285+        d.addCallback(lambda ignored:
6286+            mw.put_encprivkey(self.encprivkey))
6287+        d.addCallback(lambda ignored:
6288+            mw.put_blockhashes(self.block_hash_tree))
6289+        d.addCallback(lambda ignored:
6290+            mw.put_sharehashes(self.share_hash_chain))
6291+        d.addCallback(lambda ignored:
6292+            mw.put_root_hash(self.root_hash))
6293+        d.addCallback(lambda ignored:
6294+            mw.put_signature(self.signature))
6295+        d.addCallback(lambda ignored:
6296+            mw.put_verification_key(self.verification_key))
6297+        # Now try to put the signature again. This should fail
6298+        d.addCallback(lambda ignored:
6299+            self.shouldFail(LayoutInvalid, "signature after verification",
6300+                            None,
6301+                            mw.put_signature, self.signature))
6302+        return d
6303+
6304+
6305+    def test_uncoordinated_write(self):
6306+        # Make two mutable writers, both pointing to the same storage
6307+        # server, both at the same storage index, and try writing to the
6308+        # same share.
6309+        mw1 = self._make_new_mw("si1", 0)
6310+        mw2 = self._make_new_mw("si1", 0)
6311+        d = defer.succeed(None)
6312+        def _check_success(results):
6313+            result, readvs = results
6314+            self.failUnless(result)
6315+
6316+        def _check_failure(results):
6317+            result, readvs = results
6318+            self.failIf(result)
6319+
6320+        d.addCallback(lambda ignored:
6321+            mw1.put_block(self.block, 0, self.salt))
6322+        d.addCallback(_check_success)
6323+        d.addCallback(lambda ignored:
6324+            mw2.put_block(self.block, 0, self.salt))
6325+        d.addCallback(_check_failure)
6326+        return d
6327+
6328+
6329+    def test_invalid_salt_size(self):
6330+        # Salts need to be 16 bytes in size. Writes that attempt to
6331+        # write more or less than this should be rejected.
6332+        mw = self._make_new_mw("si1", 0)
6333+        invalid_salt = "a" * 17 # 17 bytes
6334+        another_invalid_salt = "b" * 15 # 15 bytes
6335+        d = defer.succeed(None)
6336+        d.addCallback(lambda ignored:
6337+            self.shouldFail(LayoutInvalid, "salt too big",
6338+                            None,
6339+                            mw.put_block, self.block, 0, invalid_salt))
6340+        d.addCallback(lambda ignored:
6341+            self.shouldFail(LayoutInvalid, "salt too small",
6342+                            None,
6343+                            mw.put_block, self.block, 0,
6344+                            another_invalid_salt))
6345+        return d
6346+
6347+
6348+    def test_write_test_vectors(self):
6349+        # If we give the write proxy a bogus test vector at
6350+        # any point during the process, it should fail to write.
6351+        mw = self._make_new_mw("si1", 0)
6352+        mw.set_checkstring("this is a lie")
6353+        # The initial write should be expecting to find the improbable
6354+        # checkstring above in place; finding nothing, it should fail.
6355+        d = defer.succeed(None)
6356+        d.addCallback(lambda ignored:
6357+            mw.put_block(self.block, 0, self.salt))
6358+        def _check_failure(results):
6359+            result, readv = results
6360+            self.failIf(result)
6361+        d.addCallback(_check_failure)
6362+        # Now set the checkstring to the empty string, which
6363+        # indicates that no share is there.
6364+        d.addCallback(lambda ignored:
6365+            mw.set_checkstring(""))
6366+        d.addCallback(lambda ignored:
6367+            mw.put_block(self.block, 0, self.salt))
6368+        def _check_success(results):
6369+            result, readv = results
6370+            self.failUnless(result)
6371+        d.addCallback(_check_success)
6372+        # Now set the checkstring to something wrong
6373+        d.addCallback(lambda ignored:
6374+            mw.set_checkstring("something wrong"))
6375+        # This should fail to do anything
6376+        d.addCallback(lambda ignored:
6377+            mw.put_block(self.block, 1, self.salt))
6378+        d.addCallback(_check_failure)
6379+        # Now set it back to what it should be.
6380+        d.addCallback(lambda ignored:
6381+            mw.set_checkstring(mw.get_checkstring()))
6382+        for i in xrange(1, 6):
6383+            d.addCallback(lambda ignored, i=i:
6384+                mw.put_block(self.block, i, self.salt))
6385+            d.addCallback(_check_success)
6386+        d.addCallback(lambda ignored:
6387+            mw.put_encprivkey(self.encprivkey))
6388+        d.addCallback(_check_success)
6389+        d.addCallback(lambda ignored:
6390+            mw.put_blockhashes(self.block_hash_tree))
6391+        d.addCallback(_check_success)
6392+        d.addCallback(lambda ignored:
6393+            mw.put_sharehashes(self.share_hash_chain))
6394+        d.addCallback(_check_success)
6395+        def _keep_old_checkstring(ignored):
6396+            self.old_checkstring = mw.get_checkstring()
6397+            mw.set_checkstring("foobarbaz")
6398+        d.addCallback(_keep_old_checkstring)
6399+        d.addCallback(lambda ignored:
6400+            mw.put_root_hash(self.root_hash))
6401+        d.addCallback(_check_failure)
6402+        d.addCallback(lambda ignored:
6403+            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
6404+        def _restore_old_checkstring(ignored):
6405+            mw.set_checkstring(self.old_checkstring)
6406+        d.addCallback(_restore_old_checkstring)
6407+        d.addCallback(lambda ignored:
6408+            mw.put_root_hash(self.root_hash))
6409+        d.addCallback(_check_success)
6410+        # The checkstring should have been set appropriately for us on
6411+        # the last write; if we try to change it to something else,
6412+        # that change should cause the verification key step to fail.
6413+        d.addCallback(lambda ignored:
6414+            mw.set_checkstring("something else"))
6415+        d.addCallback(lambda ignored:
6416+            mw.put_signature(self.signature))
6417+        d.addCallback(_check_failure)
6418+        d.addCallback(lambda ignored:
6419+            mw.set_checkstring(mw.get_checkstring()))
6420+        d.addCallback(lambda ignored:
6421+            mw.put_signature(self.signature))
6422+        d.addCallback(_check_success)
6423+        d.addCallback(lambda ignored:
6424+            mw.put_verification_key(self.verification_key))
6425+        d.addCallback(_check_success)
6426+        return d
6427+
6428+
6429+    def test_offset_only_set_on_success(self):
6430+        # The write proxy should be smart enough to detect when a write
6431+        # has failed, and to temper its definition of progress based on
6432+        # that.
6433+        mw = self._make_new_mw("si1", 0)
6434+        d = defer.succeed(None)
6435+        for i in xrange(1, 6):
6436+            d.addCallback(lambda ignored, i=i:
6437+                mw.put_block(self.block, i, self.salt))
6438+        def _break_checkstring(ignored):
6439+            self._old_checkstring = mw.get_checkstring()
6440+            mw.set_checkstring("foobarbaz")
6441+
6442+        def _fix_checkstring(ignored):
6443+            mw.set_checkstring(self._old_checkstring)
6444+
6445+        d.addCallback(_break_checkstring)
6446+
6447+        # Setting the encrypted private key shouldn't work now, which is
6448+        # to be expected and is tested elsewhere. We also want to make
6449+        # sure that we can't add the block hash tree after a failed
6450+        # write of this sort.
6451+        d.addCallback(lambda ignored:
6452+            mw.put_encprivkey(self.encprivkey))
6453+        d.addCallback(lambda ignored:
6454+            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
6455+                            None,
6456+                            mw.put_blockhashes, self.block_hash_tree))
6457+        d.addCallback(_fix_checkstring)
6458+        d.addCallback(lambda ignored:
6459+            mw.put_encprivkey(self.encprivkey))
6460+        d.addCallback(_break_checkstring)
6461+        d.addCallback(lambda ignored:
6462+            mw.put_blockhashes(self.block_hash_tree))
6463+        d.addCallback(lambda ignored:
6464+            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
6465+                            None,
6466+                            mw.put_sharehashes, self.share_hash_chain))
6467+        d.addCallback(_fix_checkstring)
6468+        d.addCallback(lambda ignored:
6469+            mw.put_blockhashes(self.block_hash_tree))
6470+        d.addCallback(_break_checkstring)
6471+        d.addCallback(lambda ignored:
6472+            mw.put_sharehashes(self.share_hash_chain))
6473+        d.addCallback(lambda ignored:
6474+            self.shouldFail(LayoutInvalid, "out-of-order root hash",
6475+                            None,
6476+                            mw.put_root_hash, self.root_hash))
6477+        d.addCallback(_fix_checkstring)
6478+        d.addCallback(lambda ignored:
6479+            mw.put_sharehashes(self.share_hash_chain))
6480+        d.addCallback(_break_checkstring)
6481+        d.addCallback(lambda ignored:
6482+            mw.put_root_hash(self.root_hash))
6483+        d.addCallback(lambda ignored:
6484+            self.shouldFail(LayoutInvalid, "out-of-order signature",
6485+                            None,
6486+                            mw.put_signature, self.signature))
6487+        d.addCallback(_fix_checkstring)
6488+        d.addCallback(lambda ignored:
6489+            mw.put_root_hash(self.root_hash))
6490+        d.addCallback(_break_checkstring)
6491+        d.addCallback(lambda ignored:
6492+            mw.put_signature(self.signature))
6493+        d.addCallback(lambda ignored:
6494+            self.shouldFail(LayoutInvalid, "out-of-order verification key",
6495+                            None,
6496+                            mw.put_verification_key,
6497+                            self.verification_key))
6498+        d.addCallback(_fix_checkstring)
6499+        d.addCallback(lambda ignored:
6500+            mw.put_signature(self.signature))
6501+        d.addCallback(_break_checkstring)
6502+        d.addCallback(lambda ignored:
6503+            mw.put_verification_key(self.verification_key))
6504+        d.addCallback(lambda ignored:
6505+            self.shouldFail(LayoutInvalid, "out-of-order finish",
6506+                            None,
6507+                            mw.finish_publishing))
6508+        return d
6509+
6510+
6511+    def serialize_blockhashes(self, blockhashes):
6512+        return "".join(blockhashes)
6513+
6514+
6515+    def serialize_sharehashes(self, sharehashes):
6516+        ret = "".join([struct.pack(">H32s", i, sharehashes[i])
6517+                        for i in sorted(sharehashes.keys())])
6518+        return ret
6519+
6520+
6521+    def test_write(self):
6522+        # This translates to a file with 6 6-byte segments, and with 2-byte
6523+        # blocks.
6524+        mw = self._make_new_mw("si1", 0)
6525+        mw2 = self._make_new_mw("si1", 1)
6526+        # Test writing some blocks.
6527+        read = self.ss.remote_slot_readv
6528+        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
6529+        written_block_size = 2 + len(self.salt)
6530+        written_block = self.block + self.salt
6531+        def _check_block_write(i, share):
6532+            self.failUnlessEqual(read("si1", [share], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
6533+                                {share: [written_block]})
6534+        d = defer.succeed(None)
6535+        for i in xrange(6):
6536+            d.addCallback(lambda ignored, i=i:
6537+                mw.put_block(self.block, i, self.salt))
6538+            d.addCallback(lambda ignored, i=i:
6539+                _check_block_write(i, 0))
6540+        # Now try the same thing, but with share 1 instead of share 0.
6541+        for i in xrange(6):
6542+            d.addCallback(lambda ignored, i=i:
6543+                mw2.put_block(self.block, i, self.salt))
6544+            d.addCallback(lambda ignored, i=i:
6545+                _check_block_write(i, 1))
6546+
6547+        # Next, we make a fake encrypted private key, and put it onto the
6548+        # storage server.
6549+        d.addCallback(lambda ignored:
6550+            mw.put_encprivkey(self.encprivkey))
6551+        expected_private_key_offset = expected_sharedata_offset + \
6552+                                      len(written_block) * 6
6553+        self.failUnlessEqual(len(self.encprivkey), 7)
6554+        d.addCallback(lambda ignored:
6555+            self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
6556+                                 {0: [self.encprivkey]}))
6557+
6558+        # Next, we put a fake block hash tree.
6559+        d.addCallback(lambda ignored:
6560+            mw.put_blockhashes(self.block_hash_tree))
6561+        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
6562+        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
6563+        d.addCallback(lambda ignored:
6564+            self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
6565+                                 {0: [self.block_hash_tree_s]}))
6566+
6567+        # Next, put a fake share hash chain
6568+        d.addCallback(lambda ignored:
6569+            mw.put_sharehashes(self.share_hash_chain))
6570+        expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
6571+        d.addCallback(lambda ignored:
6572+            self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
6573+                                 {0: [self.share_hash_chain_s]}))
6574+
6575+        # Next, we put what is supposed to be the root hash of
6576+        # our share hash tree but isn't       
6577+        d.addCallback(lambda ignored:
6578+            mw.put_root_hash(self.root_hash))
6579+        # The root hash gets inserted at byte 9 (its position is in the header,
6580+        # and is fixed).
6581+        def _check(ignored):
6582+            self.failUnlessEqual(read("si1", [0], [(9, 32)]),
6583+                                 {0: [self.root_hash]})
6584+        d.addCallback(_check)
6585+
6586+        # Next, we put a signature of the header block.
6587+        d.addCallback(lambda ignored:
6588+            mw.put_signature(self.signature))
6589+        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
6590+        self.failUnlessEqual(len(self.signature), 9)
6591+        d.addCallback(lambda ignored:
6592+            self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
6593+                                 {0: [self.signature]}))
6594+
6595+        # Next, we put the verification key
6596+        d.addCallback(lambda ignored:
6597+            mw.put_verification_key(self.verification_key))
6598+        expected_verification_key_offset = expected_signature_offset + len(self.signature)
6599+        self.failUnlessEqual(len(self.verification_key), 6)
6600+        d.addCallback(lambda ignored:
6601+            self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
6602+                                 {0: [self.verification_key]}))
6603+
6604+        def _check_signable(ignored):
6605+            # Make sure that the signable is what we think it should be.
6606+            signable = mw.get_signable()
6607+            verno, seq, roothash, k, n, segsize, datalen = \
6608+                                            struct.unpack(">BQ32sBBQQ",
6609+                                                          signable)
6610+            self.failUnlessEqual(verno, 1)
6611+            self.failUnlessEqual(seq, 0)
6612+            self.failUnlessEqual(roothash, self.root_hash)
6613+            self.failUnlessEqual(k, 3)
6614+            self.failUnlessEqual(n, 10)
6615+            self.failUnlessEqual(segsize, 6)
6616+            self.failUnlessEqual(datalen, 36)
6617+        d.addCallback(_check_signable)
6618+        # Next, we cause the offset table to be published.
6619+        d.addCallback(lambda ignored:
6620+            mw.finish_publishing())
6621+        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
6622+
6623+        def _check_offsets(ignored):
6624+            # Check the version number to make sure that it is correct.
6625+            expected_version_number = struct.pack(">B", 1)
6626+            self.failUnlessEqual(read("si1", [0], [(0, 1)]),
6627+                                 {0: [expected_version_number]})
6628+            # Check the sequence number to make sure that it is correct
6629+            expected_sequence_number = struct.pack(">Q", 0)
6630+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
6631+                                 {0: [expected_sequence_number]})
6632+            # Check that the encoding parameters (k, N, segement size, data
6633+            # length) are what they should be. These are  3, 10, 6, 36
6634+            expected_k = struct.pack(">B", 3)
6635+            self.failUnlessEqual(read("si1", [0], [(41, 1)]),
6636+                                 {0: [expected_k]})
6637+            expected_n = struct.pack(">B", 10)
6638+            self.failUnlessEqual(read("si1", [0], [(42, 1)]),
6639+                                 {0: [expected_n]})
6640+            expected_segment_size = struct.pack(">Q", 6)
6641+            self.failUnlessEqual(read("si1", [0], [(43, 8)]),
6642+                                 {0: [expected_segment_size]})
6643+            expected_data_length = struct.pack(">Q", 36)
6644+            self.failUnlessEqual(read("si1", [0], [(51, 8)]),
6645+                                 {0: [expected_data_length]})
6646+            expected_offset = struct.pack(">Q", expected_private_key_offset)
6647+            self.failUnlessEqual(read("si1", [0], [(59, 8)]),
6648+                                 {0: [expected_offset]})
6649+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
6650+            self.failUnlessEqual(read("si1", [0], [(67, 8)]),
6651+                                 {0: [expected_offset]})
6652+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
6653+            self.failUnlessEqual(read("si1", [0], [(75, 8)]),
6654+                                 {0: [expected_offset]})
6655+            expected_offset = struct.pack(">Q", expected_signature_offset)
6656+            self.failUnlessEqual(read("si1", [0], [(83, 8)]),
6657+                                 {0: [expected_offset]})
6658+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
6659+            self.failUnlessEqual(read("si1", [0], [(91, 8)]),
6660+                                 {0: [expected_offset]})
6661+            expected_offset = struct.pack(">Q", expected_eof_offset)
6662+            self.failUnlessEqual(read("si1", [0], [(99, 8)]),
6663+                                 {0: [expected_offset]})
6664+        d.addCallback(_check_offsets)
6665+        return d
6666+
6667+    def _make_new_mw(self, si, share, datalength=36):
6668+        # This is a file of size 36 bytes. Since it has a segment
6669+        # size of 6, we know that it has 6 byte segments, which will
6670+        # be split into blocks of 2 bytes because our FEC k
6671+        # parameter is 3.
6672+        mw = MDMFSlotWriteProxy(share, self.rref, si, self.secrets, 0, 3, 10,
6673+                                6, datalength)
6674+        return mw
6675+
6676+
6677+    def test_write_rejected_with_too_many_blocks(self):
6678+        mw = self._make_new_mw("si0", 0)
6679+
6680+        # Try writing too many blocks. We should not be able to write
6681+        # more than 6
6682+        # blocks into each share.
6683+        d = defer.succeed(None)
6684+        for i in xrange(6):
6685+            d.addCallback(lambda ignored, i=i:
6686+                mw.put_block(self.block, i, self.salt))
6687+        d.addCallback(lambda ignored:
6688+            self.shouldFail(LayoutInvalid, "too many blocks",
6689+                            None,
6690+                            mw.put_block, self.block, 7, self.salt))
6691+        return d
6692+
6693+
6694+    def test_write_rejected_with_invalid_salt(self):
6695+        # Try writing an invalid salt. Salts are 16 bytes -- any more or
6696+        # less should cause an error.
6697+        mw = self._make_new_mw("si1", 0)
6698+        bad_salt = "a" * 17 # 17 bytes
6699+        d = defer.succeed(None)
6700+        d.addCallback(lambda ignored:
6701+            self.shouldFail(LayoutInvalid, "test_invalid_salt",
6702+                            None, mw.put_block, self.block, 7, bad_salt))
6703+        return d
6704+
6705+
6706+    def test_write_rejected_with_invalid_root_hash(self):
6707+        # Try writing an invalid root hash. This should be SHA256d, and
6708+        # 32 bytes long as a result.
6709+        mw = self._make_new_mw("si2", 0)
6710+        # 17 bytes != 32 bytes
6711+        invalid_root_hash = "a" * 17
6712+        d = defer.succeed(None)
6713+        # Before this test can work, we need to put some blocks + salts,
6714+        # a block hash tree, and a share hash tree. Otherwise, we'll see
6715+        # failures that match what we are looking for, but are caused by
6716+        # the constraints imposed on operation ordering.
6717+        for i in xrange(6):
6718+            d.addCallback(lambda ignored, i=i:
6719+                mw.put_block(self.block, i, self.salt))
6720+        d.addCallback(lambda ignored:
6721+            mw.put_encprivkey(self.encprivkey))
6722+        d.addCallback(lambda ignored:
6723+            mw.put_blockhashes(self.block_hash_tree))
6724+        d.addCallback(lambda ignored:
6725+            mw.put_sharehashes(self.share_hash_chain))
6726+        d.addCallback(lambda ignored:
6727+            self.shouldFail(LayoutInvalid, "invalid root hash",
6728+                            None, mw.put_root_hash, invalid_root_hash))
6729+        return d
6730+
6731+
6732+    def test_write_rejected_with_invalid_blocksize(self):
6733+        # The blocksize implied by the writer that we get from
6734+        # _make_new_mw is 2bytes -- any more or any less than this
6735+        # should be cause for failure, unless it is the tail segment, in
6736+        # which case it may not be failure.
6737+        invalid_block = "a"
6738+        mw = self._make_new_mw("si3", 0, 33) # implies a tail segment with
6739+                                             # one byte blocks
6740+        # 1 bytes != 2 bytes
6741+        d = defer.succeed(None)
6742+        d.addCallback(lambda ignored, invalid_block=invalid_block:
6743+            self.shouldFail(LayoutInvalid, "test blocksize too small",
6744+                            None, mw.put_block, invalid_block, 0,
6745+                            self.salt))
6746+        invalid_block = invalid_block * 3
6747+        # 3 bytes != 2 bytes
6748+        d.addCallback(lambda ignored:
6749+            self.shouldFail(LayoutInvalid, "test blocksize too large",
6750+                            None,
6751+                            mw.put_block, invalid_block, 0, self.salt))
6752+        for i in xrange(5):
6753+            d.addCallback(lambda ignored, i=i:
6754+                mw.put_block(self.block, i, self.salt))
6755+        # Try to put an invalid tail segment
6756+        d.addCallback(lambda ignored:
6757+            self.shouldFail(LayoutInvalid, "test invalid tail segment",
6758+                            None,
6759+                            mw.put_block, self.block, 5, self.salt))
6760+        valid_block = "a"
6761+        d.addCallback(lambda ignored:
6762+            mw.put_block(valid_block, 5, self.salt))
6763+        return d
6764+
6765+
6766+    def test_write_enforces_order_constraints(self):
6767+        # We require that the MDMFSlotWriteProxy be interacted with in a
6768+        # specific way.
6769+        # That way is:
6770+        # 0: __init__
6771+        # 1: write blocks and salts
6772+        # 2: Write the encrypted private key
6773+        # 3: Write the block hashes
6774+        # 4: Write the share hashes
6775+        # 5: Write the root hash and salt hash
6776+        # 6: Write the signature and verification key
6777+        # 7: Write the file.
6778+        #
6779+        # Some of these can be performed out-of-order, and some can't.
6780+        # The dependencies that I want to test here are:
6781+        #  - Private key before block hashes
6782+        #  - share hashes and block hashes before root hash
6783+        #  - root hash before signature
6784+        #  - signature before verification key
6785+        mw0 = self._make_new_mw("si0", 0)
6786+        # Write some shares
6787+        d = defer.succeed(None)
6788+        for i in xrange(6):
6789+            d.addCallback(lambda ignored, i=i:
6790+                mw0.put_block(self.block, i, self.salt))
6791+        # Try to write the block hashes before writing the encrypted
6792+        # private key
6793+        d.addCallback(lambda ignored:
6794+            self.shouldFail(LayoutInvalid, "block hashes before key",
6795+                            None, mw0.put_blockhashes,
6796+                            self.block_hash_tree))
6797+
6798+        # Write the private key.
6799+        d.addCallback(lambda ignored:
6800+            mw0.put_encprivkey(self.encprivkey))
6801+
6802+
6803+        # Try to write the share hash chain without writing the block
6804+        # hash tree
6805+        d.addCallback(lambda ignored:
6806+            self.shouldFail(LayoutInvalid, "share hash chain before "
6807+                                           "salt hash tree",
6808+                            None,
6809+                            mw0.put_sharehashes, self.share_hash_chain))
6810+
6811+        # Try to write the root hash and without writing either the
6812+        # block hashes or the or the share hashes
6813+        d.addCallback(lambda ignored:
6814+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6815+                            None,
6816+                            mw0.put_root_hash, self.root_hash))
6817+
6818+        # Now write the block hashes and try again
6819+        d.addCallback(lambda ignored:
6820+            mw0.put_blockhashes(self.block_hash_tree))
6821+
6822+        d.addCallback(lambda ignored:
6823+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6824+                            None, mw0.put_root_hash, self.root_hash))
6825+
6826+        # We haven't yet put the root hash on the share, so we shouldn't
6827+        # be able to sign it.
6828+        d.addCallback(lambda ignored:
6829+            self.shouldFail(LayoutInvalid, "signature before root hash",
6830+                            None, mw0.put_signature, self.signature))
6831+
6832+        d.addCallback(lambda ignored:
6833+            self.failUnlessRaises(LayoutInvalid, mw0.get_signable))
6834+
6835+        # ..and, since that fails, we also shouldn't be able to put the
6836+        # verification key.
6837+        d.addCallback(lambda ignored:
6838+            self.shouldFail(LayoutInvalid, "key before signature",
6839+                            None, mw0.put_verification_key,
6840+                            self.verification_key))
6841+
6842+        # Now write the share hashes.
6843+        d.addCallback(lambda ignored:
6844+            mw0.put_sharehashes(self.share_hash_chain))
6845+        # We should be able to write the root hash now too
6846+        d.addCallback(lambda ignored:
6847+            mw0.put_root_hash(self.root_hash))
6848+
6849+        # We should still be unable to put the verification key
6850+        d.addCallback(lambda ignored:
6851+            self.shouldFail(LayoutInvalid, "key before signature",
6852+                            None, mw0.put_verification_key,
6853+                            self.verification_key))
6854+
6855+        d.addCallback(lambda ignored:
6856+            mw0.put_signature(self.signature))
6857+
6858+        # We shouldn't be able to write the offsets to the remote server
6859+        # until the offset table is finished; IOW, until we have written
6860+        # the verification key.
6861+        d.addCallback(lambda ignored:
6862+            self.shouldFail(LayoutInvalid, "offsets before verification key",
6863+                            None,
6864+                            mw0.finish_publishing))
6865+
6866+        d.addCallback(lambda ignored:
6867+            mw0.put_verification_key(self.verification_key))
6868+        return d
6869+
6870+
6871+    def test_end_to_end(self):
6872+        mw = self._make_new_mw("si1", 0)
6873+        # Write a share using the mutable writer, and make sure that the
6874+        # reader knows how to read everything back to us.
6875+        d = defer.succeed(None)
6876+        for i in xrange(6):
6877+            d.addCallback(lambda ignored, i=i:
6878+                mw.put_block(self.block, i, self.salt))
6879+        d.addCallback(lambda ignored:
6880+            mw.put_encprivkey(self.encprivkey))
6881+        d.addCallback(lambda ignored:
6882+            mw.put_blockhashes(self.block_hash_tree))
6883+        d.addCallback(lambda ignored:
6884+            mw.put_sharehashes(self.share_hash_chain))
6885+        d.addCallback(lambda ignored:
6886+            mw.put_root_hash(self.root_hash))
6887+        d.addCallback(lambda ignored:
6888+            mw.put_signature(self.signature))
6889+        d.addCallback(lambda ignored:
6890+            mw.put_verification_key(self.verification_key))
6891+        d.addCallback(lambda ignored:
6892+            mw.finish_publishing())
6893+
6894+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6895+        def _check_block_and_salt((block, salt)):
6896+            self.failUnlessEqual(block, self.block)
6897+            self.failUnlessEqual(salt, self.salt)
6898+
6899+        for i in xrange(6):
6900+            d.addCallback(lambda ignored, i=i:
6901+                mr.get_block_and_salt(i))
6902+            d.addCallback(_check_block_and_salt)
6903+
6904+        d.addCallback(lambda ignored:
6905+            mr.get_encprivkey())
6906+        d.addCallback(lambda encprivkey:
6907+            self.failUnlessEqual(self.encprivkey, encprivkey))
6908+
6909+        d.addCallback(lambda ignored:
6910+            mr.get_blockhashes())
6911+        d.addCallback(lambda blockhashes:
6912+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6913+
6914+        d.addCallback(lambda ignored:
6915+            mr.get_sharehashes())
6916+        d.addCallback(lambda sharehashes:
6917+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6918+
6919+        d.addCallback(lambda ignored:
6920+            mr.get_signature())
6921+        d.addCallback(lambda signature:
6922+            self.failUnlessEqual(signature, self.signature))
6923+
6924+        d.addCallback(lambda ignored:
6925+            mr.get_verification_key())
6926+        d.addCallback(lambda verification_key:
6927+            self.failUnlessEqual(verification_key, self.verification_key))
6928+
6929+        d.addCallback(lambda ignored:
6930+            mr.get_seqnum())
6931+        d.addCallback(lambda seqnum:
6932+            self.failUnlessEqual(seqnum, 0))
6933+
6934+        d.addCallback(lambda ignored:
6935+            mr.get_root_hash())
6936+        d.addCallback(lambda root_hash:
6937+            self.failUnlessEqual(self.root_hash, root_hash))
6938+
6939+        d.addCallback(lambda ignored:
6940+            mr.get_encoding_parameters())
6941+        def _check_encoding_parameters((k, n, segsize, datalen)):
6942+            self.failUnlessEqual(k, 3)
6943+            self.failUnlessEqual(n, 10)
6944+            self.failUnlessEqual(segsize, 6)
6945+            self.failUnlessEqual(datalen, 36)
6946+        d.addCallback(_check_encoding_parameters)
6947+
6948+        d.addCallback(lambda ignored:
6949+            mr.get_checkstring())
6950+        d.addCallback(lambda checkstring:
6951+            self.failUnlessEqual(checkstring, mw.get_checkstring()))
6952+        return d
6953+
6954+
6955+    def test_is_sdmf(self):
6956+        # The MDMFSlotReadProxy should also know how to read SDMF files,
6957+        # since it will encounter them on the grid. Callers use the
6958+        # is_sdmf method to test this.
6959+        self.write_sdmf_share_to_server("si1")
6960+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6961+        d = mr.is_sdmf()
6962+        d.addCallback(lambda issdmf:
6963+            self.failUnless(issdmf))
6964+        return d
6965+
6966+
6967+    def test_reads_sdmf(self):
6968+        # The slot read proxy should, naturally, know how to tell us
6969+        # about data in the SDMF format
6970+        self.write_sdmf_share_to_server("si1")
6971+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6972+        d = defer.succeed(None)
6973+        d.addCallback(lambda ignored:
6974+            mr.is_sdmf())
6975+        d.addCallback(lambda issdmf:
6976+            self.failUnless(issdmf))
6977+
6978+        # What do we need to read?
6979+        #  - The sharedata
6980+        #  - The salt
6981+        d.addCallback(lambda ignored:
6982+            mr.get_block_and_salt(0))
6983+        def _check_block_and_salt(results):
6984+            block, salt = results
6985+            # Our original file is 36 bytes long. Then each share is 12
6986+            # bytes in size. The share is composed entirely of the
6987+            # letter a. self.block contains 2 as, so 6 * self.block is
6988+            # what we are looking for.
6989+            self.failUnlessEqual(block, self.block * 6)
6990+            self.failUnlessEqual(salt, self.salt)
6991+        d.addCallback(_check_block_and_salt)
6992+
6993+        #  - The blockhashes
6994+        d.addCallback(lambda ignored:
6995+            mr.get_blockhashes())
6996+        d.addCallback(lambda blockhashes:
6997+            self.failUnlessEqual(self.block_hash_tree,
6998+                                 blockhashes,
6999+                                 blockhashes))
7000+        #  - The sharehashes
7001+        d.addCallback(lambda ignored:
7002+            mr.get_sharehashes())
7003+        d.addCallback(lambda sharehashes:
7004+            self.failUnlessEqual(self.share_hash_chain,
7005+                                 sharehashes))
7006+        #  - The keys
7007+        d.addCallback(lambda ignored:
7008+            mr.get_encprivkey())
7009+        d.addCallback(lambda encprivkey:
7010+            self.failUnlessEqual(encprivkey, self.encprivkey, encprivkey))
7011+        d.addCallback(lambda ignored:
7012+            mr.get_verification_key())
7013+        d.addCallback(lambda verification_key:
7014+            self.failUnlessEqual(verification_key,
7015+                                 self.verification_key,
7016+                                 verification_key))
7017+        #  - The signature
7018+        d.addCallback(lambda ignored:
7019+            mr.get_signature())
7020+        d.addCallback(lambda signature:
7021+            self.failUnlessEqual(signature, self.signature, signature))
7022+
7023+        #  - The sequence number
7024+        d.addCallback(lambda ignored:
7025+            mr.get_seqnum())
7026+        d.addCallback(lambda seqnum:
7027+            self.failUnlessEqual(seqnum, 0, seqnum))
7028+
7029+        #  - The root hash
7030+        d.addCallback(lambda ignored:
7031+            mr.get_root_hash())
7032+        d.addCallback(lambda root_hash:
7033+            self.failUnlessEqual(root_hash, self.root_hash, root_hash))
7034+        return d
7035+
7036+
7037+    def test_only_reads_one_segment_sdmf(self):
7038+        # SDMF shares have only one segment, so it doesn't make sense to
7039+        # read more segments than that. The reader should know this and
7040+        # complain if we try to do that.
7041+        self.write_sdmf_share_to_server("si1")
7042+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7043+        d = defer.succeed(None)
7044+        d.addCallback(lambda ignored:
7045+            mr.is_sdmf())
7046+        d.addCallback(lambda issdmf:
7047+            self.failUnless(issdmf))
7048+        d.addCallback(lambda ignored:
7049+            self.shouldFail(LayoutInvalid, "test bad segment",
7050+                            None,
7051+                            mr.get_block_and_salt, 1))
7052+        return d
7053+
7054+
7055+    def test_read_with_prefetched_mdmf_data(self):
7056+        # The MDMFSlotReadProxy will prefill certain fields if you pass
7057+        # it data that you have already fetched. This is useful for
7058+        # cases like the Servermap, which prefetches ~2kb of data while
7059+        # finding out which shares are on the remote peer so that it
7060+        # doesn't waste round trips.
7061+        mdmf_data = self.build_test_mdmf_share()
7062+        self.write_test_share_to_server("si1")
7063+        def _make_mr(ignored, length):
7064+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:length])
7065+            return mr
7066+
7067+        d = defer.succeed(None)
7068+        # This should be enough to fill in both the encoding parameters
7069+        # and the table of offsets, which will complete the version
7070+        # information tuple.
7071+        d.addCallback(_make_mr, 107)
7072+        d.addCallback(lambda mr:
7073+            mr.get_verinfo())
7074+        def _check_verinfo(verinfo):
7075+            self.failUnless(verinfo)
7076+            self.failUnlessEqual(len(verinfo), 9)
7077+            (seqnum,
7078+             root_hash,
7079+             salt_hash,
7080+             segsize,
7081+             datalen,
7082+             k,
7083+             n,
7084+             prefix,
7085+             offsets) = verinfo
7086+            self.failUnlessEqual(seqnum, 0)
7087+            self.failUnlessEqual(root_hash, self.root_hash)
7088+            self.failUnlessEqual(segsize, 6)
7089+            self.failUnlessEqual(datalen, 36)
7090+            self.failUnlessEqual(k, 3)
7091+            self.failUnlessEqual(n, 10)
7092+            expected_prefix = struct.pack(MDMFSIGNABLEHEADER,
7093+                                          1,
7094+                                          seqnum,
7095+                                          root_hash,
7096+                                          k,
7097+                                          n,
7098+                                          segsize,
7099+                                          datalen)
7100+            self.failUnlessEqual(expected_prefix, prefix)
7101+            self.failUnlessEqual(self.rref.read_count, 0)
7102+        d.addCallback(_check_verinfo)
7103+        # This is not enough data to read a block and a share, so the
7104+        # wrapper should attempt to read this from the remote server.
7105+        d.addCallback(_make_mr, 107)
7106+        d.addCallback(lambda mr:
7107+            mr.get_block_and_salt(0))
7108+        def _check_block_and_salt((block, salt)):
7109+            self.failUnlessEqual(block, self.block)
7110+            self.failUnlessEqual(salt, self.salt)
7111+            self.failUnlessEqual(self.rref.read_count, 1)
7112+        # This should be enough data to read one block.
7113+        d.addCallback(_make_mr, 249)
7114+        d.addCallback(lambda mr:
7115+            mr.get_block_and_salt(0))
7116+        d.addCallback(_check_block_and_salt)
7117+        return d
7118+
7119+
7120+    def test_read_with_prefetched_sdmf_data(self):
7121+        sdmf_data = self.build_test_sdmf_share()
7122+        self.write_sdmf_share_to_server("si1")
7123+        def _make_mr(ignored, length):
7124+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:length])
7125+            return mr
7126+
7127+        d = defer.succeed(None)
7128+        # This should be enough to get us the encoding parameters,
7129+        # offset table, and everything else we need to build a verinfo
7130+        # string.
7131+        d.addCallback(_make_mr, 107)
7132+        d.addCallback(lambda mr:
7133+            mr.get_verinfo())
7134+        def _check_verinfo(verinfo):
7135+            self.failUnless(verinfo)
7136+            self.failUnlessEqual(len(verinfo), 9)
7137+            (seqnum,
7138+             root_hash,
7139+             salt,
7140+             segsize,
7141+             datalen,
7142+             k,
7143+             n,
7144+             prefix,
7145+             offsets) = verinfo
7146+            self.failUnlessEqual(seqnum, 0)
7147+            self.failUnlessEqual(root_hash, self.root_hash)
7148+            self.failUnlessEqual(salt, self.salt)
7149+            self.failUnlessEqual(segsize, 36)
7150+            self.failUnlessEqual(datalen, 36)
7151+            self.failUnlessEqual(k, 3)
7152+            self.failUnlessEqual(n, 10)
7153+            expected_prefix = struct.pack(SIGNED_PREFIX,
7154+                                          0,
7155+                                          seqnum,
7156+                                          root_hash,
7157+                                          salt,
7158+                                          k,
7159+                                          n,
7160+                                          segsize,
7161+                                          datalen)
7162+            self.failUnlessEqual(expected_prefix, prefix)
7163+            self.failUnlessEqual(self.rref.read_count, 0)
7164+        d.addCallback(_check_verinfo)
7165+        # This shouldn't be enough to read any share data.
7166+        d.addCallback(_make_mr, 107)
7167+        d.addCallback(lambda mr:
7168+            mr.get_block_and_salt(0))
7169+        def _check_block_and_salt((block, salt)):
7170+            self.failUnlessEqual(block, self.block * 6)
7171+            self.failUnlessEqual(salt, self.salt)
7172+            # TODO: Fix the read routine so that it reads only the data
7173+            #       that it has cached if it can't read all of it.
7174+            self.failUnlessEqual(self.rref.read_count, 2)
7175+
7176+        # This should be enough to read share data.
7177+        d.addCallback(_make_mr, self.offsets['share_data'])
7178+        d.addCallback(lambda mr:
7179+            mr.get_block_and_salt(0))
7180+        d.addCallback(_check_block_and_salt)
7181+        return d
7182+
7183+
7184+    def test_read_with_empty_mdmf_file(self):
7185+        # Some tests upload a file with no contents to test things
7186+        # unrelated to the actual handling of the content of the file.
7187+        # The reader should behave intelligently in these cases.
7188+        self.write_test_share_to_server("si1", empty=True)
7189+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7190+        # We should be able to get the encoding parameters, and they
7191+        # should be correct.
7192+        d = defer.succeed(None)
7193+        d.addCallback(lambda ignored:
7194+            mr.get_encoding_parameters())
7195+        def _check_encoding_parameters(params):
7196+            self.failUnlessEqual(len(params), 4)
7197+            k, n, segsize, datalen = params
7198+            self.failUnlessEqual(k, 3)
7199+            self.failUnlessEqual(n, 10)
7200+            self.failUnlessEqual(segsize, 0)
7201+            self.failUnlessEqual(datalen, 0)
7202+        d.addCallback(_check_encoding_parameters)
7203+
7204+        # We should not be able to fetch a block, since there are no
7205+        # blocks to fetch
7206+        d.addCallback(lambda ignored:
7207+            self.shouldFail(LayoutInvalid, "get block on empty file",
7208+                            None,
7209+                            mr.get_block_and_salt, 0))
7210+        return d
7211+
7212+
7213+    def test_read_with_empty_sdmf_file(self):
7214+        self.write_sdmf_share_to_server("si1", empty=True)
7215+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7216+        # We should be able to get the encoding parameters, and they
7217+        # should be correct
7218+        d = defer.succeed(None)
7219+        d.addCallback(lambda ignored:
7220+            mr.get_encoding_parameters())
7221+        def _check_encoding_parameters(params):
7222+            self.failUnlessEqual(len(params), 4)
7223+            k, n, segsize, datalen = params
7224+            self.failUnlessEqual(k, 3)
7225+            self.failUnlessEqual(n, 10)
7226+            self.failUnlessEqual(segsize, 0)
7227+            self.failUnlessEqual(datalen, 0)
7228+        d.addCallback(_check_encoding_parameters)
7229+
7230+        # It does not make sense to get a block in this format, so we
7231+        # should not be able to.
7232+        d.addCallback(lambda ignored:
7233+            self.shouldFail(LayoutInvalid, "get block on an empty file",
7234+                            None,
7235+                            mr.get_block_and_salt, 0))
7236+        return d
7237+
7238+
7239+    def test_verinfo_with_sdmf_file(self):
7240+        self.write_sdmf_share_to_server("si1")
7241+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7242+        # We should be able to get the version information.
7243+        d = defer.succeed(None)
7244+        d.addCallback(lambda ignored:
7245+            mr.get_verinfo())
7246+        def _check_verinfo(verinfo):
7247+            self.failUnless(verinfo)
7248+            self.failUnlessEqual(len(verinfo), 9)
7249+            (seqnum,
7250+             root_hash,
7251+             salt,
7252+             segsize,
7253+             datalen,
7254+             k,
7255+             n,
7256+             prefix,
7257+             offsets) = verinfo
7258+            self.failUnlessEqual(seqnum, 0)
7259+            self.failUnlessEqual(root_hash, self.root_hash)
7260+            self.failUnlessEqual(salt, self.salt)
7261+            self.failUnlessEqual(segsize, 36)
7262+            self.failUnlessEqual(datalen, 36)
7263+            self.failUnlessEqual(k, 3)
7264+            self.failUnlessEqual(n, 10)
7265+            expected_prefix = struct.pack(">BQ32s16s BBQQ",
7266+                                          0,
7267+                                          seqnum,
7268+                                          root_hash,
7269+                                          salt,
7270+                                          k,
7271+                                          n,
7272+                                          segsize,
7273+                                          datalen)
7274+            self.failUnlessEqual(prefix, expected_prefix)
7275+            self.failUnlessEqual(offsets, self.offsets)
7276+        d.addCallback(_check_verinfo)
7277+        return d
7278+
7279+
7280+    def test_verinfo_with_mdmf_file(self):
7281+        self.write_test_share_to_server("si1")
7282+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7283+        d = defer.succeed(None)
7284+        d.addCallback(lambda ignored:
7285+            mr.get_verinfo())
7286+        def _check_verinfo(verinfo):
7287+            self.failUnless(verinfo)
7288+            self.failUnlessEqual(len(verinfo), 9)
7289+            (seqnum,
7290+             root_hash,
7291+             IV,
7292+             segsize,
7293+             datalen,
7294+             k,
7295+             n,
7296+             prefix,
7297+             offsets) = verinfo
7298+            self.failUnlessEqual(seqnum, 0)
7299+            self.failUnlessEqual(root_hash, self.root_hash)
7300+            self.failIf(IV)
7301+            self.failUnlessEqual(segsize, 6)
7302+            self.failUnlessEqual(datalen, 36)
7303+            self.failUnlessEqual(k, 3)
7304+            self.failUnlessEqual(n, 10)
7305+            expected_prefix = struct.pack(">BQ32s BBQQ",
7306+                                          1,
7307+                                          seqnum,
7308+                                          root_hash,
7309+                                          k,
7310+                                          n,
7311+                                          segsize,
7312+                                          datalen)
7313+            self.failUnlessEqual(prefix, expected_prefix)
7314+            self.failUnlessEqual(offsets, self.offsets)
7315+        d.addCallback(_check_verinfo)
7316+        return d
7317+
7318+
7319+    def test_reader_queue(self):
7320+        self.write_test_share_to_server('si1')
7321+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7322+        d1 = mr.get_block_and_salt(0, queue=True)
7323+        d2 = mr.get_blockhashes(queue=True)
7324+        d3 = mr.get_sharehashes(queue=True)
7325+        d4 = mr.get_signature(queue=True)
7326+        d5 = mr.get_verification_key(queue=True)
7327+        dl = defer.DeferredList([d1, d2, d3, d4, d5])
7328+        mr.flush()
7329+        def _print(results):
7330+            self.failUnlessEqual(len(results), 5)
7331+            # We have one read for version information and offsets, and
7332+            # one for everything else.
7333+            self.failUnlessEqual(self.rref.read_count, 2)
7334+            block, salt = results[0][1] # results[0] is a boolean that says
7335+                                           # whether or not the operation
7336+                                           # worked.
7337+            self.failUnlessEqual(self.block, block)
7338+            self.failUnlessEqual(self.salt, salt)
7339+
7340+            blockhashes = results[1][1]
7341+            self.failUnlessEqual(self.block_hash_tree, blockhashes)
7342+
7343+            sharehashes = results[2][1]
7344+            self.failUnlessEqual(self.share_hash_chain, sharehashes)
7345+
7346+            signature = results[3][1]
7347+            self.failUnlessEqual(self.signature, signature)
7348+
7349+            verification_key = results[4][1]
7350+            self.failUnlessEqual(self.verification_key, verification_key)
7351+        dl.addCallback(_print)
7352+        return dl
7353+
7354+
7355+    def test_sdmf_writer(self):
7356+        # Go through the motions of writing an SDMF share to the storage
7357+        # server. Then read the storage server to see that the share got
7358+        # written in the way that we think it should have.
7359+
7360+        # We do this first so that the necessary instance variables get
7361+        # set the way we want them for the tests below.
7362+        data = self.build_test_sdmf_share()
7363+        sdmfr = SDMFSlotWriteProxy(0,
7364+                                   self.rref,
7365+                                   "si1",
7366+                                   self.secrets,
7367+                                   0, 3, 10, 36, 36)
7368+        # Put the block and salt.
7369+        sdmfr.put_block(self.blockdata, 0, self.salt)
7370+
7371+        # Put the encprivkey
7372+        sdmfr.put_encprivkey(self.encprivkey)
7373+
7374+        # Put the block and share hash chains
7375+        sdmfr.put_blockhashes(self.block_hash_tree)
7376+        sdmfr.put_sharehashes(self.share_hash_chain)
7377+        sdmfr.put_root_hash(self.root_hash)
7378+
7379+        # Put the signature
7380+        sdmfr.put_signature(self.signature)
7381+
7382+        # Put the verification key
7383+        sdmfr.put_verification_key(self.verification_key)
7384+
7385+        # Now check to make sure that nothing has been written yet.
7386+        self.failUnlessEqual(self.rref.write_count, 0)
7387+
7388+        # Now finish publishing
7389+        d = sdmfr.finish_publishing()
7390+        def _then(ignored):
7391+            self.failUnlessEqual(self.rref.write_count, 1)
7392+            read = self.ss.remote_slot_readv
7393+            self.failUnlessEqual(read("si1", [0], [(0, len(data))]),
7394+                                 {0: [data]})
7395+        d.addCallback(_then)
7396+        return d
7397+
7398+
7399+    def test_sdmf_writer_preexisting_share(self):
7400+        data = self.build_test_sdmf_share()
7401+        self.write_sdmf_share_to_server("si1")
7402+
7403+        # Now there is a share on the storage server. To successfully
7404+        # write, we need to set the checkstring correctly. When we
7405+        # don't, no write should occur.
7406+        sdmfw = SDMFSlotWriteProxy(0,
7407+                                   self.rref,
7408+                                   "si1",
7409+                                   self.secrets,
7410+                                   1, 3, 10, 36, 36)
7411+        sdmfw.put_block(self.blockdata, 0, self.salt)
7412+
7413+        # Put the encprivkey
7414+        sdmfw.put_encprivkey(self.encprivkey)
7415+
7416+        # Put the block and share hash chains
7417+        sdmfw.put_blockhashes(self.block_hash_tree)
7418+        sdmfw.put_sharehashes(self.share_hash_chain)
7419+
7420+        # Put the root hash
7421+        sdmfw.put_root_hash(self.root_hash)
7422+
7423+        # Put the signature
7424+        sdmfw.put_signature(self.signature)
7425+
7426+        # Put the verification key
7427+        sdmfw.put_verification_key(self.verification_key)
7428+
7429+        # We shouldn't have a checkstring yet
7430+        self.failUnlessEqual(sdmfw.get_checkstring(), "")
7431+
7432+        d = sdmfw.finish_publishing()
7433+        def _then(results):
7434+            self.failIf(results[0])
7435+            # this is the correct checkstring
7436+            self._expected_checkstring = results[1][0][0]
7437+            return self._expected_checkstring
7438+
7439+        d.addCallback(_then)
7440+        d.addCallback(sdmfw.set_checkstring)
7441+        d.addCallback(lambda ignored:
7442+            sdmfw.get_checkstring())
7443+        d.addCallback(lambda checkstring:
7444+            self.failUnlessEqual(checkstring, self._expected_checkstring))
7445+        d.addCallback(lambda ignored:
7446+            sdmfw.finish_publishing())
7447+        def _then_again(results):
7448+            self.failUnless(results[0])
7449+            read = self.ss.remote_slot_readv
7450+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
7451+                                 {0: [struct.pack(">Q", 1)]})
7452+            self.failUnlessEqual(read("si1", [0], [(9, len(data) - 9)]),
7453+                                 {0: [data[9:]]})
7454+        d.addCallback(_then_again)
7455+        return d
7456+
7457+
7458 class Stats(unittest.TestCase):
7459 
7460     def setUp(self):
7461}
7462[mutable/publish.py: cleanup + simplification
7463Kevan Carstensen <kevan@isnotajoke.com>**20100702225554
7464 Ignore-this: 36a58424ceceffb1ddc55cc5934399e2
7465] {
7466hunk ./src/allmydata/mutable/publish.py 19
7467      UncoordinatedWriteError, NotEnoughServersError
7468 from allmydata.mutable.servermap import ServerMap
7469 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
7470-     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
7471+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy, \
7472+     SDMFSlotWriteProxy
7473 
7474 KiB = 1024
7475 DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
7476hunk ./src/allmydata/mutable/publish.py 24
7477+PUSHING_BLOCKS_STATE = 0
7478+PUSHING_EVERYTHING_ELSE_STATE = 1
7479+DONE_STATE = 2
7480 
7481 class PublishStatus:
7482     implements(IPublishStatus)
7483hunk ./src/allmydata/mutable/publish.py 229
7484 
7485         self.bad_share_checkstrings = {}
7486 
7487+        # This is set at the last step of the publishing process.
7488+        self.versioninfo = ""
7489+
7490         # we use the servermap to populate the initial goal: this way we will
7491         # try to update each existing share in place.
7492         for (peerid, shnum) in self._servermap.servermap:
7493hunk ./src/allmydata/mutable/publish.py 245
7494             self.bad_share_checkstrings[key] = old_checkstring
7495             self.connections[peerid] = self._servermap.connections[peerid]
7496 
7497-        # Now, the process dovetails -- if this is an SDMF file, we need
7498-        # to write an SDMF file. Otherwise, we need to write an MDMF
7499-        # file.
7500-        if self._version == MDMF_VERSION:
7501-            return self._publish_mdmf()
7502-        else:
7503-            return self._publish_sdmf()
7504-        #return self.done_deferred
7505-
7506-    def _publish_mdmf(self):
7507-        # Next, we find homes for all of the shares that we don't have
7508-        # homes for yet.
7509         # TODO: Make this part do peer selection.
7510         self.update_goal()
7511         self.writers = {}
7512hunk ./src/allmydata/mutable/publish.py 248
7513-        # For each (peerid, shnum) in self.goal, we make an
7514-        # MDMFSlotWriteProxy for that peer. We'll use this to write
7515+        if self._version == MDMF_VERSION:
7516+            writer_class = MDMFSlotWriteProxy
7517+        else:
7518+            writer_class = SDMFSlotWriteProxy
7519+
7520+        # For each (peerid, shnum) in self.goal, we make a
7521+        # write proxy for that peer. We'll use this to write
7522         # shares to the peer.
7523         for key in self.goal:
7524             peerid, shnum = key
7525hunk ./src/allmydata/mutable/publish.py 263
7526             cancel_secret = self._node.get_cancel_secret(peerid)
7527             secrets = (write_enabler, renew_secret, cancel_secret)
7528 
7529-            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
7530-                                                      self.connections[peerid],
7531-                                                      self._storage_index,
7532-                                                      secrets,
7533-                                                      self._new_seqnum,
7534-                                                      self.required_shares,
7535-                                                      self.total_shares,
7536-                                                      self.segment_size,
7537-                                                      len(self.newdata))
7538+            self.writers[shnum] =  writer_class(shnum,
7539+                                                self.connections[peerid],
7540+                                                self._storage_index,
7541+                                                secrets,
7542+                                                self._new_seqnum,
7543+                                                self.required_shares,
7544+                                                self.total_shares,
7545+                                                self.segment_size,
7546+                                                len(self.newdata))
7547+            self.writers[shnum].peerid = peerid
7548             if (peerid, shnum) in self._servermap.servermap:
7549                 old_versionid, old_timestamp = self._servermap.servermap[key]
7550                 (old_seqnum, old_root_hash, old_salt, old_segsize,
7551hunk ./src/allmydata/mutable/publish.py 278
7552                  old_datalength, old_k, old_N, old_prefix,
7553                  old_offsets_tuple) = old_versionid
7554-                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
7555+                self.writers[shnum].set_checkstring(old_seqnum,
7556+                                                    old_root_hash,
7557+                                                    old_salt)
7558+            elif (peerid, shnum) in self.bad_share_checkstrings:
7559+                old_checkstring = self.bad_share_checkstrings[(peerid, shnum)]
7560+                self.writers[shnum].set_checkstring(old_checkstring)
7561+
7562+        # Our remote shares will not have a complete checkstring until
7563+        # after we are done writing share data and have started to write
7564+        # blocks. In the meantime, we need to know what to look for when
7565+        # writing, so that we can detect UncoordinatedWriteErrors.
7566+        self._checkstring = self.writers.values()[0].get_checkstring()
7567 
7568         # Now, we start pushing shares.
7569         self._status.timings["setup"] = time.time() - self._started
7570hunk ./src/allmydata/mutable/publish.py 293
7571-        def _start_pushing(res):
7572-            self._started_pushing = time.time()
7573-            return res
7574-
7575         # First, we encrypt, encode, and publish the shares that we need
7576         # to encrypt, encode, and publish.
7577 
7578hunk ./src/allmydata/mutable/publish.py 306
7579 
7580         d = defer.succeed(None)
7581         self.log("Starting push")
7582-        for i in xrange(self.num_segments - 1):
7583-            d.addCallback(lambda ignored, i=i:
7584-                self.push_segment(i))
7585-            d.addCallback(self._turn_barrier)
7586-        # We have at least one segment, so we will have a tail segment
7587-        if self.num_segments > 0:
7588-            d.addCallback(lambda ignored:
7589-                self.push_tail_segment())
7590-
7591-        d.addCallback(lambda ignored:
7592-            self.push_encprivkey())
7593-        d.addCallback(lambda ignored:
7594-            self.push_blockhashes())
7595-        d.addCallback(lambda ignored:
7596-            self.push_sharehashes())
7597-        d.addCallback(lambda ignored:
7598-            self.push_toplevel_hashes_and_signature())
7599-        d.addCallback(lambda ignored:
7600-            self.finish_publishing())
7601-        return d
7602-
7603-
7604-    def _publish_sdmf(self):
7605-        self._status.timings["setup"] = time.time() - self._started
7606-        self.salt = os.urandom(16)
7607 
7608hunk ./src/allmydata/mutable/publish.py 307
7609-        d = self._encrypt_and_encode()
7610-        d.addCallback(self._generate_shares)
7611-        def _start_pushing(res):
7612-            self._started_pushing = time.time()
7613-            return res
7614-        d.addCallback(_start_pushing)
7615-        d.addCallback(self.loop) # trigger delivery
7616-        d.addErrback(self._fatal_error)
7617+        self._state = PUSHING_BLOCKS_STATE
7618+        self._push()
7619 
7620         return self.done_deferred
7621 
7622hunk ./src/allmydata/mutable/publish.py 327
7623                                                   segment_size)
7624         else:
7625             self.num_segments = 0
7626+
7627+        self.log("building encoding parameters for file")
7628+        self.log("got segsize %d" % self.segment_size)
7629+        self.log("got %d segments" % self.num_segments)
7630+
7631         if self._version == SDMF_VERSION:
7632             assert self.num_segments in (0, 1) # SDMF
7633hunk ./src/allmydata/mutable/publish.py 334
7634-            return
7635         # calculate the tail segment size.
7636hunk ./src/allmydata/mutable/publish.py 335
7637-        self.tail_segment_size = len(self.newdata) % segment_size
7638 
7639hunk ./src/allmydata/mutable/publish.py 336
7640-        if self.tail_segment_size == 0:
7641+        if segment_size and self.newdata:
7642+            self.tail_segment_size = len(self.newdata) % segment_size
7643+        else:
7644+            self.tail_segment_size = 0
7645+
7646+        if self.tail_segment_size == 0 and segment_size:
7647             # The tail segment is the same size as the other segments.
7648             self.tail_segment_size = segment_size
7649 
7650hunk ./src/allmydata/mutable/publish.py 345
7651-        # We'll make an encoder ahead-of-time for the normal-sized
7652-        # segments (defined as any segment of segment_size size.
7653-        # (the part of the code that puts the tail segment will make its
7654-        #  own encoder for that part)
7655+        # Make FEC encoders
7656         fec = codec.CRSEncoder()
7657         fec.set_params(self.segment_size,
7658                        self.required_shares, self.total_shares)
7659hunk ./src/allmydata/mutable/publish.py 352
7660         self.piece_size = fec.get_block_size()
7661         self.fec = fec
7662 
7663+        if self.tail_segment_size == self.segment_size:
7664+            self.tail_fec = self.fec
7665+        else:
7666+            tail_fec = codec.CRSEncoder()
7667+            tail_fec.set_params(self.tail_segment_size,
7668+                                self.required_shares,
7669+                                self.total_shares)
7670+            self.tail_fec = tail_fec
7671+
7672+        self._current_segment = 0
7673+
7674+
7675+    def _push(self, ignored=None):
7676+        """
7677+        I manage state transitions. In particular, I see that we still
7678+        have a good enough number of writers to complete the upload
7679+        successfully.
7680+        """
7681+        # Can we still successfully publish this file?
7682+        # TODO: Keep track of outstanding queries before aborting the
7683+        #       process.
7684+        if len(self.writers) <= self.required_shares or self.surprised:
7685+            return self._failure()
7686+
7687+        # Figure out what we need to do next. Each of these needs to
7688+        # return a deferred so that we don't block execution when this
7689+        # is first called in the upload method.
7690+        if self._state == PUSHING_BLOCKS_STATE:
7691+            return self.push_segment(self._current_segment)
7692+
7693+        # XXX: Do we want more granularity in states? Is that useful at
7694+        #      all?
7695+        #      Yes -- quicker reaction to UCW.
7696+        elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
7697+            return self.push_everything_else()
7698+
7699+        # If we make it to this point, we were successful in placing the
7700+        # file.
7701+        return self._done(None)
7702+
7703 
7704     def push_segment(self, segnum):
7705hunk ./src/allmydata/mutable/publish.py 394
7706+        if self.num_segments == 0 and self._version == SDMF_VERSION:
7707+            self._add_dummy_salts()
7708+
7709+        if segnum == self.num_segments:
7710+            # We don't have any more segments to push.
7711+            self._state = PUSHING_EVERYTHING_ELSE_STATE
7712+            return self._push()
7713+
7714+        d = self._encode_segment(segnum)
7715+        d.addCallback(self._push_segment, segnum)
7716+        def _increment_segnum(ign):
7717+            self._current_segment += 1
7718+        # XXX: I don't think we need to do addBoth here -- any errBacks
7719+        # should be handled within push_segment.
7720+        d.addBoth(_increment_segnum)
7721+        d.addBoth(self._push)
7722+
7723+
7724+    def _add_dummy_salts(self):
7725+        """
7726+        SDMF files need a salt even if they're empty, or the signature
7727+        won't make sense. This method adds a dummy salt to each of our
7728+        SDMF writers so that they can write the signature later.
7729+        """
7730+        salt = os.urandom(16)
7731+        assert self._version == SDMF_VERSION
7732+
7733+        for writer in self.writers.itervalues():
7734+            writer.put_salt(salt)
7735+
7736+
7737+    def _encode_segment(self, segnum):
7738+        """
7739+        I encrypt and encode the segment segnum.
7740+        """
7741         started = time.time()
7742hunk ./src/allmydata/mutable/publish.py 430
7743-        segsize = self.segment_size
7744+
7745+        if segnum + 1 == self.num_segments:
7746+            segsize = self.tail_segment_size
7747+        else:
7748+            segsize = self.segment_size
7749+
7750+
7751+        offset = self.segment_size * segnum
7752+        length = segsize + offset
7753         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
7754hunk ./src/allmydata/mutable/publish.py 440
7755-        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
7756+        data = self.newdata[offset:length]
7757         assert len(data) == segsize
7758 
7759         salt = os.urandom(16)
7760hunk ./src/allmydata/mutable/publish.py 455
7761         started = now
7762 
7763         # now apply FEC
7764+        if segnum + 1 == self.num_segments:
7765+            fec = self.tail_fec
7766+        else:
7767+            fec = self.fec
7768 
7769         self._status.set_status("Encoding")
7770         crypttext_pieces = [None] * self.required_shares
7771hunk ./src/allmydata/mutable/publish.py 462
7772-        piece_size = self.piece_size
7773+        piece_size = fec.get_block_size()
7774         for i in range(len(crypttext_pieces)):
7775             offset = i * piece_size
7776             piece = crypttext[offset:offset+piece_size]
7777hunk ./src/allmydata/mutable/publish.py 469
7778             piece = piece + "\x00"*(piece_size - len(piece)) # padding
7779             crypttext_pieces[i] = piece
7780             assert len(piece) == piece_size
7781-        d = self.fec.encode(crypttext_pieces)
7782+        d = fec.encode(crypttext_pieces)
7783         def _done_encoding(res):
7784             elapsed = time.time() - started
7785             self._status.timings["encode"] = elapsed
7786hunk ./src/allmydata/mutable/publish.py 473
7787-            return res
7788+            return (res, salt)
7789         d.addCallback(_done_encoding)
7790hunk ./src/allmydata/mutable/publish.py 475
7791-
7792-        def _push_shares_and_salt(results):
7793-            shares, shareids = results
7794-            dl = []
7795-            for i in xrange(len(shares)):
7796-                sharedata = shares[i]
7797-                shareid = shareids[i]
7798-                block_hash = hashutil.block_hash(salt + sharedata)
7799-                self.blockhashes[shareid].append(block_hash)
7800-
7801-                # find the writer for this share
7802-                d = self.writers[shareid].put_block(sharedata, segnum, salt)
7803-                dl.append(d)
7804-            # TODO: Naturally, we need to check on the results of these.
7805-            return defer.DeferredList(dl)
7806-        d.addCallback(_push_shares_and_salt)
7807         return d
7808 
7809 
7810hunk ./src/allmydata/mutable/publish.py 478
7811-    def push_tail_segment(self):
7812-        # This is essentially the same as push_segment, except that we
7813-        # don't use the cached encoder that we use elsewhere.
7814-        self.log("Pushing tail segment")
7815+    def _push_segment(self, encoded_and_salt, segnum):
7816+        """
7817+        I push (data, salt) as segment number segnum.
7818+        """
7819+        results, salt = encoded_and_salt
7820+        shares, shareids = results
7821         started = time.time()
7822hunk ./src/allmydata/mutable/publish.py 485
7823-        segsize = self.segment_size
7824-        data = self.newdata[segsize * (self.num_segments-1):]
7825-        assert len(data) == self.tail_segment_size
7826-        salt = os.urandom(16)
7827-
7828-        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
7829-        enc = AES(key)
7830-        crypttext = enc.process(data)
7831-        assert len(crypttext) == len(data)
7832+        dl = []
7833+        for i in xrange(len(shares)):
7834+            sharedata = shares[i]
7835+            shareid = shareids[i]
7836+            if self._version == MDMF_VERSION:
7837+                hashed = salt + sharedata
7838+            else:
7839+                hashed = sharedata
7840+            block_hash = hashutil.block_hash(hashed)
7841+            self.blockhashes[shareid].append(block_hash)
7842 
7843hunk ./src/allmydata/mutable/publish.py 496
7844-        now = time.time()
7845-        self._status.timings['encrypt'] = now - started
7846-        started = now
7847+            # find the writer for this share
7848+            writer = self.writers[shareid]
7849+            d = writer.put_block(sharedata, segnum, salt)
7850+            d.addCallback(self._got_write_answer, writer, started)
7851+            d.addErrback(self._connection_problem, writer)
7852+            dl.append(d)
7853+            # TODO: Naturally, we need to check on the results of these.
7854+        return defer.DeferredList(dl)
7855 
7856hunk ./src/allmydata/mutable/publish.py 505
7857-        self._status.set_status("Encoding")
7858-        tail_fec = codec.CRSEncoder()
7859-        tail_fec.set_params(self.tail_segment_size,
7860-                            self.required_shares,
7861-                            self.total_shares)
7862 
7863hunk ./src/allmydata/mutable/publish.py 506
7864-        crypttext_pieces = [None] * self.required_shares
7865-        piece_size = tail_fec.get_block_size()
7866-        for i in range(len(crypttext_pieces)):
7867-            offset = i * piece_size
7868-            piece = crypttext[offset:offset+piece_size]
7869-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
7870-            crypttext_pieces[i] = piece
7871-            assert len(piece) == piece_size
7872-        d = tail_fec.encode(crypttext_pieces)
7873-        def _push_shares_and_salt(results):
7874-            shares, shareids = results
7875-            dl = []
7876-            for i in xrange(len(shares)):
7877-                sharedata = shares[i]
7878-                shareid = shareids[i]
7879-                block_hash = hashutil.block_hash(salt + sharedata)
7880-                self.blockhashes[shareid].append(block_hash)
7881-                # find the writer for this share
7882-                d = self.writers[shareid].put_block(sharedata,
7883-                                                    self.num_segments - 1,
7884-                                                    salt)
7885-                dl.append(d)
7886-            # TODO: Naturally, we need to check on the results of these.
7887-            return defer.DeferredList(dl)
7888-        d.addCallback(_push_shares_and_salt)
7889+    def push_everything_else(self):
7890+        """
7891+        I put everything else associated with a share.
7892+        """
7893+        encprivkey = self._encprivkey
7894+        d = self.push_encprivkey()
7895+        d.addCallback(self.push_blockhashes)
7896+        d.addCallback(self.push_sharehashes)
7897+        d.addCallback(self.push_toplevel_hashes_and_signature)
7898+        d.addCallback(self.finish_publishing)
7899+        def _change_state(ignored):
7900+            self._state = DONE_STATE
7901+        d.addCallback(_change_state)
7902+        d.addCallback(self._push)
7903         return d
7904 
7905 
7906hunk ./src/allmydata/mutable/publish.py 527
7907         started = time.time()
7908         encprivkey = self._encprivkey
7909         dl = []
7910-        def _spy_on_writer(results):
7911-            print results
7912-            return results
7913-        for shnum, writer in self.writers.iteritems():
7914+        for writer in self.writers.itervalues():
7915             d = writer.put_encprivkey(encprivkey)
7916hunk ./src/allmydata/mutable/publish.py 529
7917+            d.addCallback(self._got_write_answer, writer, started)
7918+            d.addErrback(self._connection_problem, writer)
7919             dl.append(d)
7920         d = defer.DeferredList(dl)
7921         return d
7922hunk ./src/allmydata/mutable/publish.py 536
7923 
7924 
7925-    def push_blockhashes(self):
7926+    def push_blockhashes(self, ignored):
7927         started = time.time()
7928         dl = []
7929hunk ./src/allmydata/mutable/publish.py 539
7930-        def _spy_on_results(results):
7931-            print results
7932-            return results
7933         self.sharehash_leaves = [None] * len(self.blockhashes)
7934         for shnum, blockhashes in self.blockhashes.iteritems():
7935             t = hashtree.HashTree(blockhashes)
7936hunk ./src/allmydata/mutable/publish.py 545
7937             self.blockhashes[shnum] = list(t)
7938             # set the leaf for future use.
7939             self.sharehash_leaves[shnum] = t[0]
7940-            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
7941+            writer = self.writers[shnum]
7942+            d = writer.put_blockhashes(self.blockhashes[shnum])
7943+            d.addCallback(self._got_write_answer, writer, started)
7944+            d.addErrback(self._connection_problem, self.writers[shnum])
7945             dl.append(d)
7946         d = defer.DeferredList(dl)
7947         return d
7948hunk ./src/allmydata/mutable/publish.py 554
7949 
7950 
7951-    def push_sharehashes(self):
7952+    def push_sharehashes(self, ignored):
7953+        started = time.time()
7954         share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
7955         share_hash_chain = {}
7956         ds = []
7957hunk ./src/allmydata/mutable/publish.py 559
7958-        def _spy_on_results(results):
7959-            print results
7960-            return results
7961         for shnum in xrange(len(self.sharehash_leaves)):
7962             needed_indices = share_hash_tree.needed_hashes(shnum)
7963             self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
7964hunk ./src/allmydata/mutable/publish.py 563
7965                                              for i in needed_indices] )
7966-            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
7967+            writer = self.writers[shnum]
7968+            d = writer.put_sharehashes(self.sharehashes[shnum])
7969+            d.addCallback(self._got_write_answer, writer, started)
7970+            d.addErrback(self._connection_problem, writer)
7971             ds.append(d)
7972         self.root_hash = share_hash_tree[0]
7973         d = defer.DeferredList(ds)
7974hunk ./src/allmydata/mutable/publish.py 573
7975         return d
7976 
7977 
7978-    def push_toplevel_hashes_and_signature(self):
7979+    def push_toplevel_hashes_and_signature(self, ignored):
7980         # We need to to three things here:
7981         #   - Push the root hash and salt hash
7982         #   - Get the checkstring of the resulting layout; sign that.
7983hunk ./src/allmydata/mutable/publish.py 578
7984         #   - Push the signature
7985+        started = time.time()
7986         ds = []
7987hunk ./src/allmydata/mutable/publish.py 580
7988-        def _spy_on_results(results):
7989-            print results
7990-            return results
7991         for shnum in xrange(self.total_shares):
7992hunk ./src/allmydata/mutable/publish.py 581
7993-            d = self.writers[shnum].put_root_hash(self.root_hash)
7994+            writer = self.writers[shnum]
7995+            d = writer.put_root_hash(self.root_hash)
7996+            d.addCallback(self._got_write_answer, writer, started)
7997             ds.append(d)
7998         d = defer.DeferredList(ds)
7999hunk ./src/allmydata/mutable/publish.py 586
8000-        def _make_and_place_signature(ignored):
8001-            signable = self.writers[0].get_signable()
8002-            self.signature = self._privkey.sign(signable)
8003-
8004-            ds = []
8005-            for (shnum, writer) in self.writers.iteritems():
8006-                d = writer.put_signature(self.signature)
8007-                ds.append(d)
8008-            return defer.DeferredList(ds)
8009-        d.addCallback(_make_and_place_signature)
8010+        d.addCallback(self._update_checkstring)
8011+        d.addCallback(self._make_and_place_signature)
8012         return d
8013 
8014 
8015hunk ./src/allmydata/mutable/publish.py 591
8016-    def finish_publishing(self):
8017+    def _update_checkstring(self, ignored):
8018+        """
8019+        After putting the root hash, MDMF files will have the
8020+        checkstring written to the storage server. This means that we
8021+        can update our copy of the checkstring so we can detect
8022+        uncoordinated writes. SDMF files will have the same checkstring,
8023+        so we need not do anything.
8024+        """
8025+        self._checkstring = self.writers.values()[0].get_checkstring()
8026+
8027+
8028+    def _make_and_place_signature(self, ignored):
8029+        """
8030+        I create and place the signature.
8031+        """
8032+        started = time.time()
8033+        signable = self.writers[0].get_signable()
8034+        self.signature = self._privkey.sign(signable)
8035+
8036+        ds = []
8037+        for (shnum, writer) in self.writers.iteritems():
8038+            d = writer.put_signature(self.signature)
8039+            d.addCallback(self._got_write_answer, writer, started)
8040+            d.addErrback(self._connection_problem, writer)
8041+            ds.append(d)
8042+        return defer.DeferredList(ds)
8043+
8044+
8045+    def finish_publishing(self, ignored):
8046         # We're almost done -- we just need to put the verification key
8047         # and the offsets
8048hunk ./src/allmydata/mutable/publish.py 622
8049+        started = time.time()
8050         ds = []
8051         verification_key = self._pubkey.serialize()
8052 
8053hunk ./src/allmydata/mutable/publish.py 626
8054-        def _spy_on_results(results):
8055-            print results
8056-            return results
8057+
8058+        # TODO: Bad, since we remove from this same dict. We need to
8059+        # make a copy, or just use a non-iterated value.
8060         for (shnum, writer) in self.writers.iteritems():
8061             d = writer.put_verification_key(verification_key)
8062hunk ./src/allmydata/mutable/publish.py 631
8063+            d.addCallback(self._got_write_answer, writer, started)
8064+            d.addCallback(self._record_verinfo)
8065             d.addCallback(lambda ignored, writer=writer:
8066                 writer.finish_publishing())
8067hunk ./src/allmydata/mutable/publish.py 635
8068+            d.addCallback(self._got_write_answer, writer, started)
8069+            d.addErrback(self._connection_problem, writer)
8070             ds.append(d)
8071         return defer.DeferredList(ds)
8072 
8073hunk ./src/allmydata/mutable/publish.py 641
8074 
8075-    def _turn_barrier(self, res):
8076-        # putting this method in a Deferred chain imposes a guaranteed
8077-        # reactor turn between the pre- and post- portions of that chain.
8078-        # This can be useful to limit memory consumption: since Deferreds do
8079-        # not do tail recursion, code which uses defer.succeed(result) for
8080-        # consistency will cause objects to live for longer than you might
8081-        # normally expect.
8082-        return fireEventually(res)
8083+    def _record_verinfo(self, ignored):
8084+        self.versioninfo = self.writers.values()[0].get_verinfo()
8085 
8086 
8087hunk ./src/allmydata/mutable/publish.py 645
8088-    def _fatal_error(self, f):
8089-        self.log("error during loop", failure=f, level=log.UNUSUAL)
8090-        self._done(f)
8091+    def _connection_problem(self, f, writer):
8092+        """
8093+        We ran into a connection problem while working with writer, and
8094+        need to deal with that.
8095+        """
8096+        self.log("found problem: %s" % str(f))
8097+        self._last_failure = f
8098+        del(self.writers[writer.shnum])
8099 
8100hunk ./src/allmydata/mutable/publish.py 654
8101-    def _update_status(self):
8102-        self._status.set_status("Sending Shares: %d placed out of %d, "
8103-                                "%d messages outstanding" %
8104-                                (len(self.placed),
8105-                                 len(self.goal),
8106-                                 len(self.outstanding)))
8107-        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
8108 
8109     def loop(self, ignored=None):
8110         self.log("entering loop", level=log.NOISY)
8111hunk ./src/allmydata/mutable/publish.py 778
8112             self.log_goal(self.goal, "after update: ")
8113 
8114 
8115-    def _encrypt_and_encode(self):
8116-        # this returns a Deferred that fires with a list of (sharedata,
8117-        # sharenum) tuples. TODO: cache the ciphertext, only produce the
8118-        # shares that we care about.
8119-        self.log("_encrypt_and_encode")
8120-
8121-        self._status.set_status("Encrypting")
8122-        started = time.time()
8123+    def _got_write_answer(self, answer, writer, started):
8124+        if not answer:
8125+            # SDMF writers only pretend to write when readers set their
8126+            # blocks, salts, and so on -- they actually just write once,
8127+            # at the end of the upload process. In fake writes, they
8128+            # return defer.succeed(None). If we see that, we shouldn't
8129+            # bother checking it.
8130+            return
8131 
8132hunk ./src/allmydata/mutable/publish.py 787
8133-        key = hashutil.ssk_readkey_data_hash(self.salt, self.readkey)
8134-        enc = AES(key)
8135-        crypttext = enc.process(self.newdata)
8136-        assert len(crypttext) == len(self.newdata)
8137+        peerid = writer.peerid
8138+        lp = self.log("_got_write_answer from %s, share %d" %
8139+                      (idlib.shortnodeid_b2a(peerid), writer.shnum))
8140 
8141         now = time.time()
8142hunk ./src/allmydata/mutable/publish.py 792
8143-        self._status.timings["encrypt"] = now - started
8144-        started = now
8145-
8146-        # now apply FEC
8147-
8148-        self._status.set_status("Encoding")
8149-        fec = codec.CRSEncoder()
8150-        fec.set_params(self.segment_size,
8151-                       self.required_shares, self.total_shares)
8152-        piece_size = fec.get_block_size()
8153-        crypttext_pieces = [None] * self.required_shares
8154-        for i in range(len(crypttext_pieces)):
8155-            offset = i * piece_size
8156-            piece = crypttext[offset:offset+piece_size]
8157-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
8158-            crypttext_pieces[i] = piece
8159-            assert len(piece) == piece_size
8160-
8161-        d = fec.encode(crypttext_pieces)
8162-        def _done_encoding(res):
8163-            elapsed = time.time() - started
8164-            self._status.timings["encode"] = elapsed
8165-            return res
8166-        d.addCallback(_done_encoding)
8167-        return d
8168-
8169-
8170-    def _generate_shares(self, shares_and_shareids):
8171-        # this sets self.shares and self.root_hash
8172-        self.log("_generate_shares")
8173-        self._status.set_status("Generating Shares")
8174-        started = time.time()
8175-
8176-        # we should know these by now
8177-        privkey = self._privkey
8178-        encprivkey = self._encprivkey
8179-        pubkey = self._pubkey
8180-
8181-        (shares, share_ids) = shares_and_shareids
8182-
8183-        assert len(shares) == len(share_ids)
8184-        assert len(shares) == self.total_shares
8185-        all_shares = {}
8186-        block_hash_trees = {}
8187-        share_hash_leaves = [None] * len(shares)
8188-        for i in range(len(shares)):
8189-            share_data = shares[i]
8190-            shnum = share_ids[i]
8191-            all_shares[shnum] = share_data
8192-
8193-            # build the block hash tree. SDMF has only one leaf.
8194-            leaves = [hashutil.block_hash(share_data)]
8195-            t = hashtree.HashTree(leaves)
8196-            block_hash_trees[shnum] = list(t)
8197-            share_hash_leaves[shnum] = t[0]
8198-        for leaf in share_hash_leaves:
8199-            assert leaf is not None
8200-        share_hash_tree = hashtree.HashTree(share_hash_leaves)
8201-        share_hash_chain = {}
8202-        for shnum in range(self.total_shares):
8203-            needed_hashes = share_hash_tree.needed_hashes(shnum)
8204-            share_hash_chain[shnum] = dict( [ (i, share_hash_tree[i])
8205-                                              for i in needed_hashes ] )
8206-        root_hash = share_hash_tree[0]
8207-        assert len(root_hash) == 32
8208-        self.log("my new root_hash is %s" % base32.b2a(root_hash))
8209-        self._new_version_info = (self._new_seqnum, root_hash, self.salt)
8210-
8211-        prefix = pack_prefix(self._new_seqnum, root_hash, self.salt,
8212-                             self.required_shares, self.total_shares,
8213-                             self.segment_size, len(self.newdata))
8214-
8215-        # now pack the beginning of the share. All shares are the same up
8216-        # to the signature, then they have divergent share hash chains,
8217-        # then completely different block hash trees + salt + share data,
8218-        # then they all share the same encprivkey at the end. The sizes
8219-        # of everything are the same for all shares.
8220-
8221-        sign_started = time.time()
8222-        signature = privkey.sign(prefix)
8223-        self._status.timings["sign"] = time.time() - sign_started
8224-
8225-        verification_key = pubkey.serialize()
8226-
8227-        final_shares = {}
8228-        for shnum in range(self.total_shares):
8229-            final_share = pack_share(prefix,
8230-                                     verification_key,
8231-                                     signature,
8232-                                     share_hash_chain[shnum],
8233-                                     block_hash_trees[shnum],
8234-                                     all_shares[shnum],
8235-                                     encprivkey)
8236-            final_shares[shnum] = final_share
8237-        elapsed = time.time() - started
8238-        self._status.timings["pack"] = elapsed
8239-        self.shares = final_shares
8240-        self.root_hash = root_hash
8241-
8242-        # we also need to build up the version identifier for what we're
8243-        # pushing. Extract the offsets from one of our shares.
8244-        assert final_shares
8245-        offsets = unpack_header(final_shares.values()[0])[-1]
8246-        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
8247-        verinfo = (self._new_seqnum, root_hash, self.salt,
8248-                   self.segment_size, len(self.newdata),
8249-                   self.required_shares, self.total_shares,
8250-                   prefix, offsets_tuple)
8251-        self.versioninfo = verinfo
8252-
8253-
8254-
8255-    def _send_shares(self, needed):
8256-        self.log("_send_shares")
8257-
8258-        # we're finally ready to send out our shares. If we encounter any
8259-        # surprises here, it's because somebody else is writing at the same
8260-        # time. (Note: in the future, when we remove the _query_peers() step
8261-        # and instead speculate about [or remember] which shares are where,
8262-        # surprises here are *not* indications of UncoordinatedWriteError,
8263-        # and we'll need to respond to them more gracefully.)
8264-
8265-        # needed is a set of (peerid, shnum) tuples. The first thing we do is
8266-        # organize it by peerid.
8267-
8268-        peermap = DictOfSets()
8269-        for (peerid, shnum) in needed:
8270-            peermap.add(peerid, shnum)
8271-
8272-        # the next thing is to build up a bunch of test vectors. The
8273-        # semantics of Publish are that we perform the operation if the world
8274-        # hasn't changed since the ServerMap was constructed (more or less).
8275-        # For every share we're trying to place, we create a test vector that
8276-        # tests to see if the server*share still corresponds to the
8277-        # map.
8278-
8279-        all_tw_vectors = {} # maps peerid to tw_vectors
8280-        sm = self._servermap.servermap
8281-
8282-        for key in needed:
8283-            (peerid, shnum) = key
8284-
8285-            if key in sm:
8286-                # an old version of that share already exists on the
8287-                # server, according to our servermap. We will create a
8288-                # request that attempts to replace it.
8289-                old_versionid, old_timestamp = sm[key]
8290-                (old_seqnum, old_root_hash, old_salt, old_segsize,
8291-                 old_datalength, old_k, old_N, old_prefix,
8292-                 old_offsets_tuple) = old_versionid
8293-                old_checkstring = pack_checkstring(old_seqnum,
8294-                                                   old_root_hash,
8295-                                                   old_salt)
8296-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8297-
8298-            elif key in self.bad_share_checkstrings:
8299-                old_checkstring = self.bad_share_checkstrings[key]
8300-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8301-
8302-            else:
8303-                # add a testv that requires the share not exist
8304-
8305-                # Unfortunately, foolscap-0.2.5 has a bug in the way inbound
8306-                # constraints are handled. If the same object is referenced
8307-                # multiple times inside the arguments, foolscap emits a
8308-                # 'reference' token instead of a distinct copy of the
8309-                # argument. The bug is that these 'reference' tokens are not
8310-                # accepted by the inbound constraint code. To work around
8311-                # this, we need to prevent python from interning the
8312-                # (constant) tuple, by creating a new copy of this vector
8313-                # each time.
8314-
8315-                # This bug is fixed in foolscap-0.2.6, and even though this
8316-                # version of Tahoe requires foolscap-0.3.1 or newer, we are
8317-                # supposed to be able to interoperate with older versions of
8318-                # Tahoe which are allowed to use older versions of foolscap,
8319-                # including foolscap-0.2.5 . In addition, I've seen other
8320-                # foolscap problems triggered by 'reference' tokens (see #541
8321-                # for details). So we must keep this workaround in place.
8322-
8323-                #testv = (0, 1, 'eq', "")
8324-                testv = tuple([0, 1, 'eq', ""])
8325-
8326-            testvs = [testv]
8327-            # the write vector is simply the share
8328-            writev = [(0, self.shares[shnum])]
8329-
8330-            if peerid not in all_tw_vectors:
8331-                all_tw_vectors[peerid] = {}
8332-                # maps shnum to (testvs, writevs, new_length)
8333-            assert shnum not in all_tw_vectors[peerid]
8334-
8335-            all_tw_vectors[peerid][shnum] = (testvs, writev, None)
8336-
8337-        # we read the checkstring back from each share, however we only use
8338-        # it to detect whether there was a new share that we didn't know
8339-        # about. The success or failure of the write will tell us whether
8340-        # there was a collision or not. If there is a collision, the first
8341-        # thing we'll do is update the servermap, which will find out what
8342-        # happened. We could conceivably reduce a roundtrip by using the
8343-        # readv checkstring to populate the servermap, but really we'd have
8344-        # to read enough data to validate the signatures too, so it wouldn't
8345-        # be an overall win.
8346-        read_vector = [(0, struct.calcsize(SIGNED_PREFIX))]
8347-
8348-        # ok, send the messages!
8349-        self.log("sending %d shares" % len(all_tw_vectors), level=log.NOISY)
8350-        started = time.time()
8351-        for (peerid, tw_vectors) in all_tw_vectors.items():
8352-
8353-            write_enabler = self._node.get_write_enabler(peerid)
8354-            renew_secret = self._node.get_renewal_secret(peerid)
8355-            cancel_secret = self._node.get_cancel_secret(peerid)
8356-            secrets = (write_enabler, renew_secret, cancel_secret)
8357-            shnums = tw_vectors.keys()
8358-
8359-            for shnum in shnums:
8360-                self.outstanding.add( (peerid, shnum) )
8361-
8362-            d = self._do_testreadwrite(peerid, secrets,
8363-                                       tw_vectors, read_vector)
8364-            d.addCallbacks(self._got_write_answer, self._got_write_error,
8365-                           callbackArgs=(peerid, shnums, started),
8366-                           errbackArgs=(peerid, shnums, started))
8367-            # tolerate immediate errback, like with DeadReferenceError
8368-            d.addBoth(fireEventually)
8369-            d.addCallback(self.loop)
8370-            d.addErrback(self._fatal_error)
8371-
8372-        self._update_status()
8373-        self.log("%d shares sent" % len(all_tw_vectors), level=log.NOISY)
8374+        elapsed = now - started
8375 
8376hunk ./src/allmydata/mutable/publish.py 794
8377-    def _do_testreadwrite(self, peerid, secrets,
8378-                          tw_vectors, read_vector):
8379-        storage_index = self._storage_index
8380-        ss = self.connections[peerid]
8381+        self._status.add_per_server_time(peerid, elapsed)
8382 
8383hunk ./src/allmydata/mutable/publish.py 796
8384-        #print "SS[%s] is %s" % (idlib.shortnodeid_b2a(peerid), ss), ss.tracker.interfaceName
8385-        d = ss.callRemote("slot_testv_and_readv_and_writev",
8386-                          storage_index,
8387-                          secrets,
8388-                          tw_vectors,
8389-                          read_vector)
8390-        return d
8391+        wrote, read_data = answer
8392 
8393hunk ./src/allmydata/mutable/publish.py 798
8394-    def _got_write_answer(self, answer, peerid, shnums, started):
8395-        lp = self.log("_got_write_answer from %s" %
8396-                      idlib.shortnodeid_b2a(peerid))
8397-        for shnum in shnums:
8398-            self.outstanding.discard( (peerid, shnum) )
8399+        surprise_shares = set(read_data.keys()) - set([writer.shnum])
8400 
8401hunk ./src/allmydata/mutable/publish.py 800
8402-        now = time.time()
8403-        elapsed = now - started
8404-        self._status.add_per_server_time(peerid, elapsed)
8405+        # We need to remove from surprise_shares any shares that we are
8406+        # knowingly also writing to that peer from other writers.
8407 
8408hunk ./src/allmydata/mutable/publish.py 803
8409-        wrote, read_data = answer
8410+        # TODO: Precompute this.
8411+        known_shnums = [x.shnum for x in self.writers.values()
8412+                        if x.peerid == peerid]
8413+        surprise_shares -= set(known_shnums)
8414+        self.log("found the following surprise shares: %s" %
8415+                 str(surprise_shares))
8416 
8417hunk ./src/allmydata/mutable/publish.py 810
8418-        surprise_shares = set(read_data.keys()) - set(shnums)
8419+        # Now surprise shares contains all of the shares that we did not
8420+        # expect to be there.
8421 
8422         surprised = False
8423         for shnum in surprise_shares:
8424hunk ./src/allmydata/mutable/publish.py 817
8425             # read_data is a dict mapping shnum to checkstring (SIGNED_PREFIX)
8426             checkstring = read_data[shnum][0]
8427-            their_version_info = unpack_checkstring(checkstring)
8428-            if their_version_info == self._new_version_info:
8429+            # What we want to do here is to see if their (seqnum,
8430+            # roothash, salt) is the same as our (seqnum, roothash,
8431+            # salt), or the equivalent for MDMF. The best way to do this
8432+            # is to store a packed representation of our checkstring
8433+            # somewhere, then not bother unpacking the other
8434+            # checkstring.
8435+            if checkstring == self._checkstring:
8436                 # they have the right share, somehow
8437 
8438                 if (peerid,shnum) in self.goal:
8439hunk ./src/allmydata/mutable/publish.py 902
8440             self.log("our testv failed, so the write did not happen",
8441                      parent=lp, level=log.WEIRD, umid="8sc26g")
8442             self.surprised = True
8443-            self.bad_peers.add(peerid) # don't ask them again
8444+            # TODO: This needs to
8445+            self.bad_peers.add(writer) # don't ask them again
8446             # use the checkstring to add information to the log message
8447             for (shnum,readv) in read_data.items():
8448                 checkstring = readv[0]
8449hunk ./src/allmydata/mutable/publish.py 928
8450             # self.loop() will take care of finding new homes
8451             return
8452 
8453-        for shnum in shnums:
8454-            self.placed.add( (peerid, shnum) )
8455-            # and update the servermap
8456-            self._servermap.add_new_share(peerid, shnum,
8457+        # and update the servermap
8458+        # self.versioninfo is set during the last phase of publishing.
8459+        # If we get there, we know that responses correspond to placed
8460+        # shares, and can safely execute these statements.
8461+        if self.versioninfo:
8462+            self.log("wrote successfully: adding new share to servermap")
8463+            self._servermap.add_new_share(peerid, writer.shnum,
8464                                           self.versioninfo, started)
8465hunk ./src/allmydata/mutable/publish.py 936
8466-
8467-        # self.loop() will take care of checking to see if we're done
8468-        return
8469+            self.placed.add( (peerid, writer.shnum) )
8470 
8471hunk ./src/allmydata/mutable/publish.py 938
8472-    def _got_write_error(self, f, peerid, shnums, started):
8473-        for shnum in shnums:
8474-            self.outstanding.discard( (peerid, shnum) )
8475-        self.bad_peers.add(peerid)
8476-        if self._first_write_error is None:
8477-            self._first_write_error = f
8478-        self.log(format="error while writing shares %(shnums)s to peerid %(peerid)s",
8479-                 shnums=list(shnums), peerid=idlib.shortnodeid_b2a(peerid),
8480-                 failure=f,
8481-                 level=log.UNUSUAL)
8482         # self.loop() will take care of checking to see if we're done
8483         return
8484 
8485hunk ./src/allmydata/mutable/publish.py 949
8486         now = time.time()
8487         self._status.timings["total"] = now - self._started
8488         self._status.set_active(False)
8489-        if isinstance(res, failure.Failure):
8490-            self.log("Publish done, with failure", failure=res,
8491-                     level=log.WEIRD, umid="nRsR9Q")
8492-            self._status.set_status("Failed")
8493-        elif self.surprised:
8494-            self.log("Publish done, UncoordinatedWriteError", level=log.UNUSUAL)
8495-            self._status.set_status("UncoordinatedWriteError")
8496-            # deliver a failure
8497-            res = failure.Failure(UncoordinatedWriteError())
8498-            # TODO: recovery
8499-        else:
8500-            self.log("Publish done, success")
8501-            self._status.set_status("Finished")
8502-            self._status.set_progress(1.0)
8503+        self.log("Publish done, success")
8504+        self._status.set_status("Finished")
8505+        self._status.set_progress(1.0)
8506         eventually(self.done_deferred.callback, res)
8507 
8508hunk ./src/allmydata/mutable/publish.py 954
8509+    def _failure(self):
8510+
8511+        if not self.surprised:
8512+            # We ran out of servers
8513+            self.log("Publish ran out of good servers, "
8514+                     "last failure was: %s" % str(self._last_failure))
8515+            e = NotEnoughServersError("Ran out of non-bad servers, "
8516+                                      "last failure was %s" %
8517+                                      str(self._last_failure))
8518+        else:
8519+            # We ran into shares that we didn't recognize, which means
8520+            # that we need to return an UncoordinatedWriteError.
8521+            self.log("Publish failed with UncoordinatedWriteError")
8522+            e = UncoordinatedWriteError()
8523+        f = failure.Failure(e)
8524+        eventually(self.done_deferred.callback, f)
8525}
8526[test/test_mutable.py: remove tests that are no longer relevant
8527Kevan Carstensen <kevan@isnotajoke.com>**20100702225710
8528 Ignore-this: 90a26b4cc4b2e190a635474ba7097e21
8529] hunk ./src/allmydata/test/test_mutable.py 627
8530         return d
8531 
8532 
8533-class MakeShares(unittest.TestCase):
8534-    def test_encrypt(self):
8535-        nm = make_nodemaker()
8536-        CONTENTS = "some initial contents"
8537-        d = nm.create_mutable_file(CONTENTS)
8538-        def _created(fn):
8539-            p = Publish(fn, nm.storage_broker, None)
8540-            p.salt = "SALT" * 4
8541-            p.readkey = "\x00" * 16
8542-            p.newdata = CONTENTS
8543-            p.required_shares = 3
8544-            p.total_shares = 10
8545-            p.setup_encoding_parameters()
8546-            return p._encrypt_and_encode()
8547-        d.addCallback(_created)
8548-        def _done(shares_and_shareids):
8549-            (shares, share_ids) = shares_and_shareids
8550-            self.failUnlessEqual(len(shares), 10)
8551-            for sh in shares:
8552-                self.failUnless(isinstance(sh, str))
8553-                self.failUnlessEqual(len(sh), 7)
8554-            self.failUnlessEqual(len(share_ids), 10)
8555-        d.addCallback(_done)
8556-        return d
8557-    test_encrypt.todo = "Write an equivalent of this for the new uploader"
8558-
8559-    def test_generate(self):
8560-        nm = make_nodemaker()
8561-        CONTENTS = "some initial contents"
8562-        d = nm.create_mutable_file(CONTENTS)
8563-        def _created(fn):
8564-            self._fn = fn
8565-            p = Publish(fn, nm.storage_broker, None)
8566-            self._p = p
8567-            p.newdata = CONTENTS
8568-            p.required_shares = 3
8569-            p.total_shares = 10
8570-            p.setup_encoding_parameters()
8571-            p._new_seqnum = 3
8572-            p.salt = "SALT" * 4
8573-            # make some fake shares
8574-            shares_and_ids = ( ["%07d" % i for i in range(10)], range(10) )
8575-            p._privkey = fn.get_privkey()
8576-            p._encprivkey = fn.get_encprivkey()
8577-            p._pubkey = fn.get_pubkey()
8578-            return p._generate_shares(shares_and_ids)
8579-        d.addCallback(_created)
8580-        def _generated(res):
8581-            p = self._p
8582-            final_shares = p.shares
8583-            root_hash = p.root_hash
8584-            self.failUnlessEqual(len(root_hash), 32)
8585-            self.failUnless(isinstance(final_shares, dict))
8586-            self.failUnlessEqual(len(final_shares), 10)
8587-            self.failUnlessEqual(sorted(final_shares.keys()), range(10))
8588-            for i,sh in final_shares.items():
8589-                self.failUnless(isinstance(sh, str))
8590-                # feed the share through the unpacker as a sanity-check
8591-                pieces = unpack_share(sh)
8592-                (u_seqnum, u_root_hash, IV, k, N, segsize, datalen,
8593-                 pubkey, signature, share_hash_chain, block_hash_tree,
8594-                 share_data, enc_privkey) = pieces
8595-                self.failUnlessEqual(u_seqnum, 3)
8596-                self.failUnlessEqual(u_root_hash, root_hash)
8597-                self.failUnlessEqual(k, 3)
8598-                self.failUnlessEqual(N, 10)
8599-                self.failUnlessEqual(segsize, 21)
8600-                self.failUnlessEqual(datalen, len(CONTENTS))
8601-                self.failUnlessEqual(pubkey, p._pubkey.serialize())
8602-                sig_material = struct.pack(">BQ32s16s BBQQ",
8603-                                           0, p._new_seqnum, root_hash, IV,
8604-                                           k, N, segsize, datalen)
8605-                self.failUnless(p._pubkey.verify(sig_material, signature))
8606-                #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
8607-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
8608-                for shnum,share_hash in share_hash_chain.items():
8609-                    self.failUnless(isinstance(shnum, int))
8610-                    self.failUnless(isinstance(share_hash, str))
8611-                    self.failUnlessEqual(len(share_hash), 32)
8612-                self.failUnless(isinstance(block_hash_tree, list))
8613-                self.failUnlessEqual(len(block_hash_tree), 1) # very small tree
8614-                self.failUnlessEqual(IV, "SALT"*4)
8615-                self.failUnlessEqual(len(share_data), len("%07d" % 1))
8616-                self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
8617-        d.addCallback(_generated)
8618-        return d
8619-    test_generate.todo = "Write an equivalent of this for the new uploader"
8620-
8621-    # TODO: when we publish to 20 peers, we should get one share per peer on 10
8622-    # when we publish to 3 peers, we should get either 3 or 4 shares per peer
8623-    # when we publish to zero peers, we should get a NotEnoughSharesError
8624-
8625 class PublishMixin:
8626     def publish_one(self):
8627         # publish a file and create shares, which can then be manipulated
8628[interfaces.py: create IMutableUploadable
8629Kevan Carstensen <kevan@isnotajoke.com>**20100706215217
8630 Ignore-this: bee202ec2bfbd8e41f2d4019cce176c7
8631] hunk ./src/allmydata/interfaces.py 1693
8632         """The upload is finished, and whatever filehandle was in use may be
8633         closed."""
8634 
8635+
8636+class IMutableUploadable(Interface):
8637+    """
8638+    I represent content that is due to be uploaded to a mutable filecap.
8639+    """
8640+    # This is somewhat simpler than the IUploadable interface above
8641+    # because mutable files do not need to be concerned with possibly
8642+    # generating a CHK, nor with per-file keys. It is a subset of the
8643+    # methods in IUploadable, though, so we could just as well implement
8644+    # the mutable uploadables as IUploadables that don't happen to use
8645+    # those methods (with the understanding that the unused methods will
8646+    # never be called on such objects)
8647+    def get_size():
8648+        """
8649+        Returns a Deferred that fires with the size of the content held
8650+        by the uploadable.
8651+        """
8652+
8653+    def read(length):
8654+        """
8655+        Returns a list of strings which, when concatenated, are the next
8656+        length bytes of the file, or fewer if there are fewer bytes
8657+        between the current location and the end of the file.
8658+        """
8659+
8660+    def close():
8661+        """
8662+        The process that used the Uploadable is finished using it, so
8663+        the uploadable may be closed.
8664+        """
8665+
8666 class IUploadResults(Interface):
8667     """I am returned by upload() methods. I contain a number of public
8668     attributes which can be read to determine the results of the upload. Some
8669[mutable/publish.py: add MutableDataHandle and MutableFileHandle
8670Kevan Carstensen <kevan@isnotajoke.com>**20100706215257
8671 Ignore-this: 295ea3bc2a962fd14fb7877fc76c011c
8672] {
8673hunk ./src/allmydata/mutable/publish.py 8
8674 from zope.interface import implements
8675 from twisted.internet import defer
8676 from twisted.python import failure
8677-from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
8678+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \
8679+                                 IMutableUploadable
8680 from allmydata.util import base32, hashutil, mathutil, idlib, log
8681 from allmydata import hashtree, codec
8682 from allmydata.storage.server import si_b2a
8683hunk ./src/allmydata/mutable/publish.py 971
8684             e = UncoordinatedWriteError()
8685         f = failure.Failure(e)
8686         eventually(self.done_deferred.callback, f)
8687+
8688+
8689+class MutableFileHandle:
8690+    """
8691+    I am a mutable uploadable built around a filehandle-like object,
8692+    usually either a StringIO instance or a handle to an actual file.
8693+    """
8694+    implements(IMutableUploadable)
8695+
8696+    def __init__(self, filehandle):
8697+        # The filehandle is defined as a generally file-like object that
8698+        # has these two methods. We don't care beyond that.
8699+        assert hasattr(filehandle, "read")
8700+        assert hasattr(filehandle, "close")
8701+
8702+        self._filehandle = filehandle
8703+
8704+
8705+    def get_size(self):
8706+        """
8707+        I return the amount of data in my filehandle.
8708+        """
8709+        if not hasattr(self, "_size"):
8710+            old_position = self._filehandle.tell()
8711+            # Seek to the end of the file by seeking 0 bytes from the
8712+            # file's end
8713+            self._filehandle.seek(0, os.SEEK_END)
8714+            self._size = self._filehandle.tell()
8715+            # Restore the previous position, in case this was called
8716+            # after a read.
8717+            self._filehandle.seek(old_position)
8718+            assert self._filehandle.tell() == old_position
8719+
8720+        assert hasattr(self, "_size")
8721+        return self._size
8722+
8723+
8724+    def read(self, length):
8725+        """
8726+        I return some data (up to length bytes) from my filehandle.
8727+
8728+        In most cases, I return length bytes. If I don't, it is because
8729+        length is longer than the distance between my current position
8730+        in the file that I represent and its end. In that case, I return
8731+        as many bytes as I can before going over the EOF.
8732+        """
8733+        return [self._filehandle.read(length)]
8734+
8735+
8736+    def close(self):
8737+        """
8738+        I close the underlying filehandle. Any further operations on the
8739+        filehandle fail at this point.
8740+        """
8741+        self._filehandle.close()
8742+
8743+
8744+class MutableDataHandle(MutableFileHandle):
8745+    """
8746+    I am a mutable uploadable built around a string, which I then cast
8747+    into a StringIO and treat as a filehandle.
8748+    """
8749+
8750+    def __init__(self, s):
8751+        # Take a string and return a file-like uploadable.
8752+        assert isinstance(s, str)
8753+
8754+        MutableFileHandle.__init__(self, StringIO(s))
8755}
8756[mutable/publish.py: reorganize in preparation of file-like uploadables
8757Kevan Carstensen <kevan@isnotajoke.com>**20100706215541
8758 Ignore-this: 5346c9f919ee5b73807c8f287c64e8ce
8759] {
8760hunk ./src/allmydata/mutable/publish.py 4
8761 
8762 
8763 import os, struct, time
8764+from StringIO import StringIO
8765 from itertools import count
8766 from zope.interface import implements
8767 from twisted.internet import defer
8768hunk ./src/allmydata/mutable/publish.py 118
8769         self._status.set_helper(False)
8770         self._status.set_progress(0.0)
8771         self._status.set_active(True)
8772-        # We use this to control how the file is written.
8773-        version = self._node.get_version()
8774-        assert version in (SDMF_VERSION, MDMF_VERSION)
8775-        self._version = version
8776+        self._version = self._node.get_version()
8777+        assert self._version in (SDMF_VERSION, MDMF_VERSION)
8778+
8779 
8780     def get_status(self):
8781         return self._status
8782hunk ./src/allmydata/mutable/publish.py 141
8783 
8784         # 0. Setup encoding parameters, encoder, and other such things.
8785         # 1. Encrypt, encode, and publish segments.
8786+        self.data = StringIO(newdata)
8787+        self.datalength = len(newdata)
8788 
8789hunk ./src/allmydata/mutable/publish.py 144
8790-        self.log("starting publish, datalen is %s" % len(newdata))
8791-        self._status.set_size(len(newdata))
8792+        self.log("starting publish, datalen is %s" % self.datalength)
8793+        self._status.set_size(self.datalength)
8794         self._status.set_status("Started")
8795         self._started = time.time()
8796 
8797hunk ./src/allmydata/mutable/publish.py 193
8798         self.full_peerlist = full_peerlist # for use later, immutable
8799         self.bad_peers = set() # peerids who have errbacked/refused requests
8800 
8801-        self.newdata = newdata
8802-
8803         # This will set self.segment_size, self.num_segments, and
8804         # self.fec.
8805         self.setup_encoding_parameters()
8806hunk ./src/allmydata/mutable/publish.py 272
8807                                                 self.required_shares,
8808                                                 self.total_shares,
8809                                                 self.segment_size,
8810-                                                len(self.newdata))
8811+                                                self.datalength)
8812             self.writers[shnum].peerid = peerid
8813             if (peerid, shnum) in self._servermap.servermap:
8814                 old_versionid, old_timestamp = self._servermap.servermap[key]
8815hunk ./src/allmydata/mutable/publish.py 318
8816         if self._version == MDMF_VERSION:
8817             segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
8818         else:
8819-            segment_size = len(self.newdata) # SDMF is only one segment
8820+            segment_size = self.datalength # SDMF is only one segment
8821         # this must be a multiple of self.required_shares
8822         segment_size = mathutil.next_multiple(segment_size,
8823                                               self.required_shares)
8824hunk ./src/allmydata/mutable/publish.py 324
8825         self.segment_size = segment_size
8826         if segment_size:
8827-            self.num_segments = mathutil.div_ceil(len(self.newdata),
8828+            self.num_segments = mathutil.div_ceil(self.datalength,
8829                                                   segment_size)
8830         else:
8831             self.num_segments = 0
8832hunk ./src/allmydata/mutable/publish.py 337
8833             assert self.num_segments in (0, 1) # SDMF
8834         # calculate the tail segment size.
8835 
8836-        if segment_size and self.newdata:
8837-            self.tail_segment_size = len(self.newdata) % segment_size
8838+        if segment_size and self.datalength:
8839+            self.tail_segment_size = self.datalength % segment_size
8840         else:
8841             self.tail_segment_size = 0
8842 
8843hunk ./src/allmydata/mutable/publish.py 438
8844             segsize = self.segment_size
8845 
8846 
8847-        offset = self.segment_size * segnum
8848-        length = segsize + offset
8849         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
8850hunk ./src/allmydata/mutable/publish.py 439
8851-        data = self.newdata[offset:length]
8852+        data = self.data.read(segsize)
8853+
8854         assert len(data) == segsize
8855 
8856         salt = os.urandom(16)
8857hunk ./src/allmydata/mutable/publish.py 502
8858             d.addCallback(self._got_write_answer, writer, started)
8859             d.addErrback(self._connection_problem, writer)
8860             dl.append(d)
8861-            # TODO: Naturally, we need to check on the results of these.
8862         return defer.DeferredList(dl)
8863 
8864 
8865}
8866[test/test_mutable.py: write tests for MutableFileHandle and MutableDataHandle
8867Kevan Carstensen <kevan@isnotajoke.com>**20100706215649
8868 Ignore-this: df719a0c52b4bbe9be4fae206c7ab3e7
8869] {
8870hunk ./src/allmydata/test/test_mutable.py 2
8871 
8872-import struct
8873+import struct, os
8874 from cStringIO import StringIO
8875 from twisted.trial import unittest
8876 from twisted.internet import defer, reactor
8877hunk ./src/allmydata/test/test_mutable.py 26
8878      NeedMoreDataError, UnrecoverableFileError, UncoordinatedWriteError, \
8879      NotEnoughServersError, CorruptShareError
8880 from allmydata.mutable.retrieve import Retrieve
8881-from allmydata.mutable.publish import Publish
8882+from allmydata.mutable.publish import Publish, MutableFileHandle, \
8883+                                      MutableDataHandle
8884 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
8885 from allmydata.mutable.layout import unpack_header, unpack_share, \
8886                                      MDMFSlotReadProxy
8887hunk ./src/allmydata/test/test_mutable.py 2465
8888         d.addCallback(lambda data:
8889             self.failUnlessEqual(data, CONTENTS))
8890         return d
8891+
8892+
8893+class FileHandle(unittest.TestCase):
8894+    def setUp(self):
8895+        self.test_data = "Test Data" * 50000
8896+        self.sio = StringIO(self.test_data)
8897+        self.uploadable = MutableFileHandle(self.sio)
8898+
8899+
8900+    def test_filehandle_read(self):
8901+        self.basedir = "mutable/FileHandle/test_filehandle_read"
8902+        chunk_size = 10
8903+        for i in xrange(0, len(self.test_data), chunk_size):
8904+            data = self.uploadable.read(chunk_size)
8905+            data = "".join(data)
8906+            start = i
8907+            end = i + chunk_size
8908+            self.failUnlessEqual(data, self.test_data[start:end])
8909+
8910+
8911+    def test_filehandle_get_size(self):
8912+        self.basedir = "mutable/FileHandle/test_filehandle_get_size"
8913+        actual_size = len(self.test_data)
8914+        size = self.uploadable.get_size()
8915+        self.failUnlessEqual(size, actual_size)
8916+
8917+
8918+    def test_filehandle_get_size_out_of_order(self):
8919+        # We should be able to call get_size whenever we want without
8920+        # disturbing the location of the seek pointer.
8921+        chunk_size = 100
8922+        data = self.uploadable.read(chunk_size)
8923+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8924+
8925+        # Now get the size.
8926+        size = self.uploadable.get_size()
8927+        self.failUnlessEqual(size, len(self.test_data))
8928+
8929+        # Now get more data. We should be right where we left off.
8930+        more_data = self.uploadable.read(chunk_size)
8931+        start = chunk_size
8932+        end = chunk_size * 2
8933+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
8934+
8935+
8936+    def test_filehandle_file(self):
8937+        # Make sure that the MutableFileHandle works on a file as well
8938+        # as a StringIO object, since in some cases it will be asked to
8939+        # deal with files.
8940+        self.basedir = self.mktemp()
8941+        # necessary? What am I doing wrong here?
8942+        os.mkdir(self.basedir)
8943+        f_path = os.path.join(self.basedir, "test_file")
8944+        f = open(f_path, "w")
8945+        f.write(self.test_data)
8946+        f.close()
8947+        f = open(f_path, "r")
8948+
8949+        uploadable = MutableFileHandle(f)
8950+
8951+        data = uploadable.read(len(self.test_data))
8952+        self.failUnlessEqual("".join(data), self.test_data)
8953+        size = uploadable.get_size()
8954+        self.failUnlessEqual(size, len(self.test_data))
8955+
8956+
8957+    def test_close(self):
8958+        # Make sure that the MutableFileHandle closes its handle when
8959+        # told to do so.
8960+        self.uploadable.close()
8961+        self.failUnless(self.sio.closed)
8962+
8963+
8964+class DataHandle(unittest.TestCase):
8965+    def setUp(self):
8966+        self.test_data = "Test Data" * 50000
8967+        self.uploadable = MutableDataHandle(self.test_data)
8968+
8969+
8970+    def test_datahandle_read(self):
8971+        chunk_size = 10
8972+        for i in xrange(0, len(self.test_data), chunk_size):
8973+            data = self.uploadable.read(chunk_size)
8974+            data = "".join(data)
8975+            start = i
8976+            end = i + chunk_size
8977+            self.failUnlessEqual(data, self.test_data[start:end])
8978+
8979+
8980+    def test_datahandle_get_size(self):
8981+        actual_size = len(self.test_data)
8982+        size = self.uploadable.get_size()
8983+        self.failUnlessEqual(size, actual_size)
8984+
8985+
8986+    def test_datahandle_get_size_out_of_order(self):
8987+        # We should be able to call get_size whenever we want without
8988+        # disturbing the location of the seek pointer.
8989+        chunk_size = 100
8990+        data = self.uploadable.read(chunk_size)
8991+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8992+
8993+        # Now get the size.
8994+        size = self.uploadable.get_size()
8995+        self.failUnlessEqual(size, len(self.test_data))
8996+
8997+        # Now get more data. We should be right where we left off.
8998+        more_data = self.uploadable.read(chunk_size)
8999+        start = chunk_size
9000+        end = chunk_size * 2
9001+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
9002}
9003[Alter tests to work with the new APIs
9004Kevan Carstensen <kevan@isnotajoke.com>**20100708000031
9005 Ignore-this: 1f377904ac61ce40e9a04716fbd2ad95
9006] {
9007hunk ./src/allmydata/test/common.py 12
9008 from allmydata import uri, dirnode, client
9009 from allmydata.introducer.server import IntroducerNode
9010 from allmydata.interfaces import IMutableFileNode, IImmutableFileNode, \
9011-     FileTooLargeError, NotEnoughSharesError, ICheckable
9012+     FileTooLargeError, NotEnoughSharesError, ICheckable, \
9013+     IMutableUploadable
9014 from allmydata.check_results import CheckResults, CheckAndRepairResults, \
9015      DeepCheckResults, DeepCheckAndRepairResults
9016 from allmydata.mutable.common import CorruptShareError
9017hunk ./src/allmydata/test/common.py 18
9018 from allmydata.mutable.layout import unpack_header
9019+from allmydata.mutable.publish import MutableDataHandle
9020 from allmydata.storage.server import storage_index_to_dir
9021 from allmydata.storage.mutable import MutableShareFile
9022 from allmydata.util import hashutil, log, fileutil, pollmixin
9023hunk ./src/allmydata/test/common.py 182
9024         self.init_from_cap(make_mutable_file_cap())
9025     def create(self, contents, key_generator=None, keysize=None):
9026         initial_contents = self._get_initial_contents(contents)
9027-        if len(initial_contents) > self.MUTABLE_SIZELIMIT:
9028+        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
9029             raise FileTooLargeError("SDMF is limited to one segment, and "
9030hunk ./src/allmydata/test/common.py 184
9031-                                    "%d > %d" % (len(initial_contents),
9032+                                    "%d > %d" % (initial_contents.get_size(),
9033                                                  self.MUTABLE_SIZELIMIT))
9034hunk ./src/allmydata/test/common.py 186
9035-        self.all_contents[self.storage_index] = initial_contents
9036+        data = initial_contents.read(initial_contents.get_size())
9037+        data = "".join(data)
9038+        self.all_contents[self.storage_index] = data
9039         return defer.succeed(self)
9040     def _get_initial_contents(self, contents):
9041hunk ./src/allmydata/test/common.py 191
9042-        if isinstance(contents, str):
9043-            return contents
9044         if contents is None:
9045hunk ./src/allmydata/test/common.py 192
9046-            return ""
9047+            return MutableDataHandle("")
9048+
9049+        if IMutableUploadable.providedBy(contents):
9050+            return contents
9051+
9052         assert callable(contents), "%s should be callable, not %s" % \
9053                (contents, type(contents))
9054         return contents(self)
9055hunk ./src/allmydata/test/common.py 309
9056         return defer.succeed(self.all_contents[self.storage_index])
9057 
9058     def overwrite(self, new_contents):
9059-        if len(new_contents) > self.MUTABLE_SIZELIMIT:
9060+        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
9061             raise FileTooLargeError("SDMF is limited to one segment, and "
9062hunk ./src/allmydata/test/common.py 311
9063-                                    "%d > %d" % (len(new_contents),
9064+                                    "%d > %d" % (new_contents.get_size(),
9065                                                  self.MUTABLE_SIZELIMIT))
9066         assert not self.is_readonly()
9067hunk ./src/allmydata/test/common.py 314
9068-        self.all_contents[self.storage_index] = new_contents
9069+        new_data = new_contents.read(new_contents.get_size())
9070+        new_data = "".join(new_data)
9071+        self.all_contents[self.storage_index] = new_data
9072         return defer.succeed(None)
9073     def modify(self, modifier):
9074         # this does not implement FileTooLargeError, but the real one does
9075hunk ./src/allmydata/test/common.py 324
9076     def _modify(self, modifier):
9077         assert not self.is_readonly()
9078         old_contents = self.all_contents[self.storage_index]
9079-        self.all_contents[self.storage_index] = modifier(old_contents, None, True)
9080+        new_data = modifier(old_contents, None, True)
9081+        if new_data is not None:
9082+            new_data = new_data.read(new_data.get_size())
9083+            new_data = "".join(new_data)
9084+        self.all_contents[self.storage_index] = new_data
9085         return None
9086 
9087 def make_mutable_file_cap():
9088hunk ./src/allmydata/test/test_checker.py 11
9089 from allmydata.test.no_network import GridTestMixin
9090 from allmydata.immutable.upload import Data
9091 from allmydata.test.common_web import WebRenderingMixin
9092+from allmydata.mutable.publish import MutableDataHandle
9093 
9094 class FakeClient:
9095     def get_storage_broker(self):
9096hunk ./src/allmydata/test/test_checker.py 291
9097         def _stash_immutable(ur):
9098             self.imm = c0.create_node_from_uri(ur.uri)
9099         d.addCallback(_stash_immutable)
9100-        d.addCallback(lambda ign: c0.create_mutable_file("contents"))
9101+        d.addCallback(lambda ign:
9102+            c0.create_mutable_file(MutableDataHandle("contents")))
9103         def _stash_mutable(node):
9104             self.mut = node
9105         d.addCallback(_stash_mutable)
9106hunk ./src/allmydata/test/test_cli.py 12
9107 from allmydata.util import fileutil, hashutil, base32
9108 from allmydata import uri
9109 from allmydata.immutable import upload
9110+from allmydata.mutable.publish import MutableDataHandle
9111 from allmydata.dirnode import normalize
9112 
9113 # Test that the scripts can be imported -- although the actual tests of their
9114hunk ./src/allmydata/test/test_cli.py 1975
9115         self.set_up_grid()
9116         c0 = self.g.clients[0]
9117         DATA = "data" * 100
9118-        d = c0.create_mutable_file(DATA)
9119+        DATA_uploadable = MutableDataHandle(DATA)
9120+        d = c0.create_mutable_file(DATA_uploadable)
9121         def _stash_uri(n):
9122             self.uri = n.get_uri()
9123         d.addCallback(_stash_uri)
9124hunk ./src/allmydata/test/test_cli.py 2077
9125                                            upload.Data("literal",
9126                                                         convergence="")))
9127         d.addCallback(_stash_uri, "small")
9128-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"1"))
9129+        d.addCallback(lambda ign:
9130+            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
9131         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9132         d.addCallback(_stash_uri, "mutable")
9133 
9134hunk ./src/allmydata/test/test_cli.py 2096
9135         # root/small
9136         # root/mutable
9137 
9138+        # We haven't broken anything yet, so this should all be healthy.
9139         d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
9140                                               self.rooturi))
9141         def _check2((rc, out, err)):
9142hunk ./src/allmydata/test/test_cli.py 2111
9143                             in lines, out)
9144         d.addCallback(_check2)
9145 
9146+        # Similarly, all of these results should be as we expect them to
9147+        # be for a healthy file layout.
9148         d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
9149         def _check_stats((rc, out, err)):
9150             self.failUnlessReallyEqual(err, "")
9151hunk ./src/allmydata/test/test_cli.py 2128
9152             self.failUnlessIn(" 317-1000 : 1    (1000 B, 1000 B)", lines)
9153         d.addCallback(_check_stats)
9154 
9155+        # Now we break things.
9156         def _clobber_shares(ignored):
9157             shares = self.find_uri_shares(self.uris[u"gööd"])
9158             self.failUnlessReallyEqual(len(shares), 10)
9159hunk ./src/allmydata/test/test_cli.py 2147
9160         d.addCallback(_clobber_shares)
9161 
9162         # root
9163-        # root/gööd  [9 shares]
9164+        # root/gööd  [1 missing share]
9165         # root/small
9166         # root/mutable [1 corrupt share]
9167 
9168hunk ./src/allmydata/test/test_cli.py 2153
9169         d.addCallback(lambda ign:
9170                       self.do_cli("deep-check", "--verbose", self.rooturi))
9171+        # This should reveal the missing share, but not the corrupt
9172+        # share, since we didn't tell the deep check operation to also
9173+        # verify.
9174         def _check3((rc, out, err)):
9175             self.failUnlessReallyEqual(err, "")
9176             self.failUnlessReallyEqual(rc, 0)
9177hunk ./src/allmydata/test/test_cli.py 2204
9178                                   "--verbose", "--verify", "--repair",
9179                                   self.rooturi))
9180         def _check6((rc, out, err)):
9181+            # We've just repaired the directory. There is no reason for
9182+            # that repair to be unsuccessful.
9183             self.failUnlessReallyEqual(err, "")
9184             self.failUnlessReallyEqual(rc, 0)
9185             lines = out.splitlines()
9186hunk ./src/allmydata/test/test_deepcheck.py 9
9187 from twisted.internet import threads # CLI tests use deferToThread
9188 from allmydata.immutable import upload
9189 from allmydata.mutable.common import UnrecoverableFileError
9190+from allmydata.mutable.publish import MutableDataHandle
9191 from allmydata.util import idlib
9192 from allmydata.util import base32
9193 from allmydata.scripts import runner
9194hunk ./src/allmydata/test/test_deepcheck.py 38
9195         self.basedir = "deepcheck/MutableChecker/good"
9196         self.set_up_grid()
9197         CONTENTS = "a little bit of data"
9198-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9199+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9200+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9201         def _created(node):
9202             self.node = node
9203             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9204hunk ./src/allmydata/test/test_deepcheck.py 61
9205         self.basedir = "deepcheck/MutableChecker/corrupt"
9206         self.set_up_grid()
9207         CONTENTS = "a little bit of data"
9208-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9209+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9210+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9211         def _stash_and_corrupt(node):
9212             self.node = node
9213             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9214hunk ./src/allmydata/test/test_deepcheck.py 99
9215         self.basedir = "deepcheck/MutableChecker/delete_share"
9216         self.set_up_grid()
9217         CONTENTS = "a little bit of data"
9218-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9219+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9220+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9221         def _stash_and_delete(node):
9222             self.node = node
9223             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9224hunk ./src/allmydata/test/test_deepcheck.py 223
9225             self.root = n
9226             self.root_uri = n.get_uri()
9227         d.addCallback(_created_root)
9228-        d.addCallback(lambda ign: c0.create_mutable_file("mutable file contents"))
9229+        d.addCallback(lambda ign:
9230+            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
9231         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
9232         def _created_mutable(n):
9233             self.mutable = n
9234hunk ./src/allmydata/test/test_deepcheck.py 965
9235     def create_mangled(self, ignored, name):
9236         nodetype, mangletype = name.split("-", 1)
9237         if nodetype == "mutable":
9238-            d = self.g.clients[0].create_mutable_file("mutable file contents")
9239+            mutable_uploadable = MutableDataHandle("mutable file contents")
9240+            d = self.g.clients[0].create_mutable_file(mutable_uploadable)
9241             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
9242         elif nodetype == "large":
9243             large = upload.Data("Lots of data\n" * 1000 + name + "\n", None)
9244hunk ./src/allmydata/test/test_dirnode.py 1305
9245     implements(IMutableFileNode)
9246     counter = 0
9247     def __init__(self, initial_contents=""):
9248-        self.data = self._get_initial_contents(initial_contents)
9249+        data = self._get_initial_contents(initial_contents)
9250+        self.data = data.read(data.get_size())
9251+        self.data = "".join(self.data)
9252+
9253         counter = FakeMutableFile.counter
9254         FakeMutableFile.counter += 1
9255         writekey = hashutil.ssk_writekey_hash(str(counter))
9256hunk ./src/allmydata/test/test_dirnode.py 1355
9257         pass
9258 
9259     def modify(self, modifier):
9260-        self.data = modifier(self.data, None, True)
9261+        data = modifier(self.data, None, True)
9262+        self.data = data.read(data.get_size())
9263+        self.data = "".join(self.data)
9264         return defer.succeed(None)
9265 
9266 class FakeNodeMaker(NodeMaker):
9267hunk ./src/allmydata/test/test_hung_server.py 10
9268 from allmydata.util.consumer import download_to_data
9269 from allmydata.immutable import upload
9270 from allmydata.mutable.common import UnrecoverableFileError
9271+from allmydata.mutable.publish import MutableDataHandle
9272 from allmydata.storage.common import storage_index_to_dir
9273 from allmydata.test.no_network import GridTestMixin
9274 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
9275hunk ./src/allmydata/test/test_hung_server.py 96
9276         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
9277 
9278         if mutable:
9279-            d = nm.create_mutable_file(mutable_plaintext)
9280+            uploadable = MutableDataHandle(mutable_plaintext)
9281+            d = nm.create_mutable_file(uploadable)
9282             def _uploaded_mutable(node):
9283                 self.uri = node.get_uri()
9284                 self.shares = self.find_uri_shares(self.uri)
9285hunk ./src/allmydata/test/test_mutable.py 297
9286             d.addCallback(lambda smap: smap.dump(StringIO()))
9287             d.addCallback(lambda sio:
9288                           self.failUnless("3-of-10" in sio.getvalue()))
9289-            d.addCallback(lambda res: n.overwrite("contents 1"))
9290+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9291             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9292             d.addCallback(lambda res: n.download_best_version())
9293             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9294hunk ./src/allmydata/test/test_mutable.py 304
9295             d.addCallback(lambda res: n.get_size_of_best_version())
9296             d.addCallback(lambda size:
9297                           self.failUnlessEqual(size, len("contents 1")))
9298-            d.addCallback(lambda res: n.overwrite("contents 2"))
9299+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9300             d.addCallback(lambda res: n.download_best_version())
9301             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9302             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9303hunk ./src/allmydata/test/test_mutable.py 308
9304-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9305+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9306             d.addCallback(lambda res: n.download_best_version())
9307             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9308             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9309hunk ./src/allmydata/test/test_mutable.py 320
9310             # mapupdate-to-retrieve data caching (i.e. make the shares larger
9311             # than the default readsize, which is 2000 bytes). A 15kB file
9312             # will have 5kB shares.
9313-            d.addCallback(lambda res: n.overwrite("large size file" * 1000))
9314+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
9315             d.addCallback(lambda res: n.download_best_version())
9316             d.addCallback(lambda res:
9317                           self.failUnlessEqual(res, "large size file" * 1000))
9318hunk ./src/allmydata/test/test_mutable.py 343
9319             # to make them big enough to force the file to be uploaded
9320             # in more than one segment.
9321             big_contents = "contents1" * 100000 # about 900 KiB
9322+            big_contents_uploadable = MutableDataHandle(big_contents)
9323             d.addCallback(lambda ignored:
9324hunk ./src/allmydata/test/test_mutable.py 345
9325-                n.overwrite(big_contents))
9326+                n.overwrite(big_contents_uploadable))
9327             d.addCallback(lambda ignored:
9328                 n.download_best_version())
9329             d.addCallback(lambda data:
9330hunk ./src/allmydata/test/test_mutable.py 355
9331             # segments, so that we make the downloader deal with
9332             # multiple segments.
9333             bigger_contents = "contents2" * 1000000 # about 9MiB
9334+            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
9335             d.addCallback(lambda ignored:
9336hunk ./src/allmydata/test/test_mutable.py 357
9337-                n.overwrite(bigger_contents))
9338+                n.overwrite(bigger_contents_uploadable))
9339             d.addCallback(lambda ignored:
9340                 n.download_best_version())
9341             d.addCallback(lambda data:
9342hunk ./src/allmydata/test/test_mutable.py 368
9343 
9344 
9345     def test_create_with_initial_contents(self):
9346-        d = self.nodemaker.create_mutable_file("contents 1")
9347+        upload1 = MutableDataHandle("contents 1")
9348+        d = self.nodemaker.create_mutable_file(upload1)
9349         def _created(n):
9350             d = n.download_best_version()
9351             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9352hunk ./src/allmydata/test/test_mutable.py 373
9353-            d.addCallback(lambda res: n.overwrite("contents 2"))
9354+            upload2 = MutableDataHandle("contents 2")
9355+            d.addCallback(lambda res: n.overwrite(upload2))
9356             d.addCallback(lambda res: n.download_best_version())
9357             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9358             return d
9359hunk ./src/allmydata/test/test_mutable.py 380
9360         d.addCallback(_created)
9361         return d
9362+    test_create_with_initial_contents.timeout = 15
9363 
9364 
9365     def test_create_mdmf_with_initial_contents(self):
9366hunk ./src/allmydata/test/test_mutable.py 385
9367         initial_contents = "foobarbaz" * 131072 # 900KiB
9368-        d = self.nodemaker.create_mutable_file(initial_contents,
9369+        initial_contents_uploadable = MutableDataHandle(initial_contents)
9370+        d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
9371                                                version=MDMF_VERSION)
9372         def _created(n):
9373             d = n.download_best_version()
9374hunk ./src/allmydata/test/test_mutable.py 392
9375             d.addCallback(lambda data:
9376                 self.failUnlessEqual(data, initial_contents))
9377+            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
9378             d.addCallback(lambda ignored:
9379hunk ./src/allmydata/test/test_mutable.py 394
9380-                n.overwrite(initial_contents + "foobarbaz"))
9381+                n.overwrite(uploadable2))
9382             d.addCallback(lambda ignored:
9383                 n.download_best_version())
9384             d.addCallback(lambda data:
9385hunk ./src/allmydata/test/test_mutable.py 413
9386             key = n.get_writekey()
9387             self.failUnless(isinstance(key, str), key)
9388             self.failUnlessEqual(len(key), 16) # AES key size
9389-            return data
9390+            return MutableDataHandle(data)
9391         d = self.nodemaker.create_mutable_file(_make_contents)
9392         def _created(n):
9393             return n.download_best_version()
9394hunk ./src/allmydata/test/test_mutable.py 429
9395             key = n.get_writekey()
9396             self.failUnless(isinstance(key, str), key)
9397             self.failUnlessEqual(len(key), 16)
9398-            return data
9399+            return MutableDataHandle(data)
9400         d = self.nodemaker.create_mutable_file(_make_contents,
9401                                                version=MDMF_VERSION)
9402         d.addCallback(lambda n:
9403hunk ./src/allmydata/test/test_mutable.py 441
9404 
9405     def test_create_with_too_large_contents(self):
9406         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9407-        d = self.nodemaker.create_mutable_file(BIG)
9408+        BIG_uploadable = MutableDataHandle(BIG)
9409+        d = self.nodemaker.create_mutable_file(BIG_uploadable)
9410         def _created(n):
9411hunk ./src/allmydata/test/test_mutable.py 444
9412-            d = n.overwrite(BIG)
9413+            other_BIG_uploadable = MutableDataHandle(BIG)
9414+            d = n.overwrite(other_BIG_uploadable)
9415             return d
9416         d.addCallback(_created)
9417         return d
9418hunk ./src/allmydata/test/test_mutable.py 459
9419 
9420     def test_modify(self):
9421         def _modifier(old_contents, servermap, first_time):
9422-            return old_contents + "line2"
9423+            new_contents = old_contents + "line2"
9424+            return MutableDataHandle(new_contents)
9425         def _non_modifier(old_contents, servermap, first_time):
9426hunk ./src/allmydata/test/test_mutable.py 462
9427-            return old_contents
9428+            return MutableDataHandle(old_contents)
9429         def _none_modifier(old_contents, servermap, first_time):
9430             return None
9431         def _error_modifier(old_contents, servermap, first_time):
9432hunk ./src/allmydata/test/test_mutable.py 468
9433             raise ValueError("oops")
9434         def _toobig_modifier(old_contents, servermap, first_time):
9435-            return "b" * (self.OLD_MAX_SEGMENT_SIZE+1)
9436+            new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9437+            return MutableDataHandle(new_content)
9438         calls = []
9439         def _ucw_error_modifier(old_contents, servermap, first_time):
9440             # simulate an UncoordinatedWriteError once
9441hunk ./src/allmydata/test/test_mutable.py 476
9442             calls.append(1)
9443             if len(calls) <= 1:
9444                 raise UncoordinatedWriteError("simulated")
9445-            return old_contents + "line3"
9446+            new_contents = old_contents + "line3"
9447+            return MutableDataHandle(new_contents)
9448         def _ucw_error_non_modifier(old_contents, servermap, first_time):
9449             # simulate an UncoordinatedWriteError once, and don't actually
9450             # modify the contents on subsequent invocations
9451hunk ./src/allmydata/test/test_mutable.py 484
9452             calls.append(1)
9453             if len(calls) <= 1:
9454                 raise UncoordinatedWriteError("simulated")
9455-            return old_contents
9456+            return MutableDataHandle(old_contents)
9457 
9458hunk ./src/allmydata/test/test_mutable.py 486
9459-        d = self.nodemaker.create_mutable_file("line1")
9460+        initial_contents = "line1"
9461+        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
9462         def _created(n):
9463             d = n.modify(_modifier)
9464             d.addCallback(lambda res: n.download_best_version())
9465hunk ./src/allmydata/test/test_mutable.py 548
9466 
9467     def test_modify_backoffer(self):
9468         def _modifier(old_contents, servermap, first_time):
9469-            return old_contents + "line2"
9470+            return MutableDataHandle(old_contents + "line2")
9471         calls = []
9472         def _ucw_error_modifier(old_contents, servermap, first_time):
9473             # simulate an UncoordinatedWriteError once
9474hunk ./src/allmydata/test/test_mutable.py 555
9475             calls.append(1)
9476             if len(calls) <= 1:
9477                 raise UncoordinatedWriteError("simulated")
9478-            return old_contents + "line3"
9479+            return MutableDataHandle(old_contents + "line3")
9480         def _always_ucw_error_modifier(old_contents, servermap, first_time):
9481             raise UncoordinatedWriteError("simulated")
9482         def _backoff_stopper(node, f):
9483hunk ./src/allmydata/test/test_mutable.py 570
9484         giveuper._delay = 0.1
9485         giveuper.factor = 1
9486 
9487-        d = self.nodemaker.create_mutable_file("line1")
9488+        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
9489         def _created(n):
9490             d = n.modify(_modifier)
9491             d.addCallback(lambda res: n.download_best_version())
9492hunk ./src/allmydata/test/test_mutable.py 620
9493             d.addCallback(lambda smap: smap.dump(StringIO()))
9494             d.addCallback(lambda sio:
9495                           self.failUnless("3-of-10" in sio.getvalue()))
9496-            d.addCallback(lambda res: n.overwrite("contents 1"))
9497+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9498             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9499             d.addCallback(lambda res: n.download_best_version())
9500             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9501hunk ./src/allmydata/test/test_mutable.py 624
9502-            d.addCallback(lambda res: n.overwrite("contents 2"))
9503+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9504             d.addCallback(lambda res: n.download_best_version())
9505             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9506             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9507hunk ./src/allmydata/test/test_mutable.py 628
9508-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9509+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9510             d.addCallback(lambda res: n.download_best_version())
9511             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9512             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9513hunk ./src/allmydata/test/test_mutable.py 646
9514         # publish a file and create shares, which can then be manipulated
9515         # later.
9516         self.CONTENTS = "New contents go here" * 1000
9517+        self.uploadable = MutableDataHandle(self.CONTENTS)
9518         self._storage = FakeStorage()
9519         self._nodemaker = make_nodemaker(self._storage)
9520         self._storage_broker = self._nodemaker.storage_broker
9521hunk ./src/allmydata/test/test_mutable.py 650
9522-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9523+        d = self._nodemaker.create_mutable_file(self.uploadable)
9524         def _created(node):
9525             self._fn = node
9526             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9527hunk ./src/allmydata/test/test_mutable.py 662
9528         # an MDMF file.
9529         # self.CONTENTS should have more than one segment.
9530         self.CONTENTS = "This is an MDMF file" * 100000
9531+        self.uploadable = MutableDataHandle(self.CONTENTS)
9532         self._storage = FakeStorage()
9533         self._nodemaker = make_nodemaker(self._storage)
9534         self._storage_broker = self._nodemaker.storage_broker
9535hunk ./src/allmydata/test/test_mutable.py 666
9536-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
9537+        d = self._nodemaker.create_mutable_file(self.uploadable, version=MDMF_VERSION)
9538         def _created(node):
9539             self._fn = node
9540             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9541hunk ./src/allmydata/test/test_mutable.py 678
9542         # like publish_one, except that the result is guaranteed to be
9543         # an SDMF file
9544         self.CONTENTS = "This is an SDMF file" * 1000
9545+        self.uploadable = MutableDataHandle(self.CONTENTS)
9546         self._storage = FakeStorage()
9547         self._nodemaker = make_nodemaker(self._storage)
9548         self._storage_broker = self._nodemaker.storage_broker
9549hunk ./src/allmydata/test/test_mutable.py 682
9550-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
9551+        d = self._nodemaker.create_mutable_file(self.uploadable, version=SDMF_VERSION)
9552         def _created(node):
9553             self._fn = node
9554             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9555hunk ./src/allmydata/test/test_mutable.py 696
9556                          "Contents 2",
9557                          "Contents 3a",
9558                          "Contents 3b"]
9559+        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
9560         self._copied_shares = {}
9561         self._storage = FakeStorage()
9562         self._nodemaker = make_nodemaker(self._storage)
9563hunk ./src/allmydata/test/test_mutable.py 700
9564-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
9565+        d = self._nodemaker.create_mutable_file(self.uploadables[0], version=version) # seqnum=1
9566         def _created(node):
9567             self._fn = node
9568             # now create multiple versions of the same file, and accumulate
9569hunk ./src/allmydata/test/test_mutable.py 707
9570             # their shares, so we can mix and match them later.
9571             d = defer.succeed(None)
9572             d.addCallback(self._copy_shares, 0)
9573-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[1])) #s2
9574+            d.addCallback(lambda res: node.overwrite(self.uploadables[1])) #s2
9575             d.addCallback(self._copy_shares, 1)
9576hunk ./src/allmydata/test/test_mutable.py 709
9577-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[2])) #s3
9578+            d.addCallback(lambda res: node.overwrite(self.uploadables[2])) #s3
9579             d.addCallback(self._copy_shares, 2)
9580hunk ./src/allmydata/test/test_mutable.py 711
9581-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[3])) #s4a
9582+            d.addCallback(lambda res: node.overwrite(self.uploadables[3])) #s4a
9583             d.addCallback(self._copy_shares, 3)
9584             # now we replace all the shares with version s3, and upload a new
9585             # version to get s4b.
9586hunk ./src/allmydata/test/test_mutable.py 717
9587             rollback = dict([(i,2) for i in range(10)])
9588             d.addCallback(lambda res: self._set_versions(rollback))
9589-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[4])) #s4b
9590+            d.addCallback(lambda res: node.overwrite(self.uploadables[4])) #s4b
9591             d.addCallback(self._copy_shares, 4)
9592             # we leave the storage in state 4
9593             return d
9594hunk ./src/allmydata/test/test_mutable.py 826
9595         # create a new file, which is large enough to knock the privkey out
9596         # of the early part of the file
9597         LARGE = "These are Larger contents" * 200 # about 5KB
9598-        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE))
9599+        LARGE_uploadable = MutableDataHandle(LARGE)
9600+        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
9601         def _created(large_fn):
9602             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
9603             return self.make_servermap(MODE_WRITE, large_fn2)
9604hunk ./src/allmydata/test/test_mutable.py 1842
9605 class MultipleEncodings(unittest.TestCase):
9606     def setUp(self):
9607         self.CONTENTS = "New contents go here"
9608+        self.uploadable = MutableDataHandle(self.CONTENTS)
9609         self._storage = FakeStorage()
9610         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
9611         self._storage_broker = self._nodemaker.storage_broker
9612hunk ./src/allmydata/test/test_mutable.py 1846
9613-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9614+        d = self._nodemaker.create_mutable_file(self.uploadable)
9615         def _created(node):
9616             self._fn = node
9617         d.addCallback(_created)
9618hunk ./src/allmydata/test/test_mutable.py 1872
9619         s = self._storage
9620         s._peers = {} # clear existing storage
9621         p2 = Publish(fn2, self._storage_broker, None)
9622-        d = p2.publish(data)
9623+        uploadable = MutableDataHandle(data)
9624+        d = p2.publish(uploadable)
9625         def _published(res):
9626             shares = s._peers
9627             s._peers = {}
9628hunk ./src/allmydata/test/test_mutable.py 2049
9629         self._set_versions(target)
9630 
9631         def _modify(oldversion, servermap, first_time):
9632-            return oldversion + " modified"
9633+            return MutableDataHandle(oldversion + " modified")
9634         d = self._fn.modify(_modify)
9635         d.addCallback(lambda res: self._fn.download_best_version())
9636         expected = self.CONTENTS[2] + " modified"
9637hunk ./src/allmydata/test/test_mutable.py 2175
9638         self.basedir = "mutable/Problems/test_publish_surprise"
9639         self.set_up_grid()
9640         nm = self.g.clients[0].nodemaker
9641-        d = nm.create_mutable_file("contents 1")
9642+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9643         def _created(n):
9644             d = defer.succeed(None)
9645             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9646hunk ./src/allmydata/test/test_mutable.py 2185
9647             d.addCallback(_got_smap1)
9648             # then modify the file, leaving the old map untouched
9649             d.addCallback(lambda res: log.msg("starting winning write"))
9650-            d.addCallback(lambda res: n.overwrite("contents 2"))
9651+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9652             # now attempt to modify the file with the old servermap. This
9653             # will look just like an uncoordinated write, in which every
9654             # single share got updated between our mapupdate and our publish
9655hunk ./src/allmydata/test/test_mutable.py 2194
9656                           self.shouldFail(UncoordinatedWriteError,
9657                                           "test_publish_surprise", None,
9658                                           n.upload,
9659-                                          "contents 2a", self.old_map))
9660+                                          MutableDataHandle("contents 2a"), self.old_map))
9661             return d
9662         d.addCallback(_created)
9663         return d
9664hunk ./src/allmydata/test/test_mutable.py 2203
9665         self.basedir = "mutable/Problems/test_retrieve_surprise"
9666         self.set_up_grid()
9667         nm = self.g.clients[0].nodemaker
9668-        d = nm.create_mutable_file("contents 1")
9669+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9670         def _created(n):
9671             d = defer.succeed(None)
9672             d.addCallback(lambda res: n.get_servermap(MODE_READ))
9673hunk ./src/allmydata/test/test_mutable.py 2213
9674             d.addCallback(_got_smap1)
9675             # then modify the file, leaving the old map untouched
9676             d.addCallback(lambda res: log.msg("starting winning write"))
9677-            d.addCallback(lambda res: n.overwrite("contents 2"))
9678+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9679             # now attempt to retrieve the old version with the old servermap.
9680             # This will look like someone has changed the file since we
9681             # updated the servermap.
9682hunk ./src/allmydata/test/test_mutable.py 2241
9683         self.basedir = "mutable/Problems/test_unexpected_shares"
9684         self.set_up_grid()
9685         nm = self.g.clients[0].nodemaker
9686-        d = nm.create_mutable_file("contents 1")
9687+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9688         def _created(n):
9689             d = defer.succeed(None)
9690             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9691hunk ./src/allmydata/test/test_mutable.py 2253
9692                 self.g.remove_server(peer0)
9693                 # then modify the file, leaving the old map untouched
9694                 log.msg("starting winning write")
9695-                return n.overwrite("contents 2")
9696+                return n.overwrite(MutableDataHandle("contents 2"))
9697             d.addCallback(_got_smap1)
9698             # now attempt to modify the file with the old servermap. This
9699             # will look just like an uncoordinated write, in which every
9700hunk ./src/allmydata/test/test_mutable.py 2263
9701                           self.shouldFail(UncoordinatedWriteError,
9702                                           "test_surprise", None,
9703                                           n.upload,
9704-                                          "contents 2a", self.old_map))
9705+                                          MutableDataHandle("contents 2a"), self.old_map))
9706             return d
9707         d.addCallback(_created)
9708         return d
9709hunk ./src/allmydata/test/test_mutable.py 2267
9710+    test_unexpected_shares.timeout = 15
9711 
9712     def test_bad_server(self):
9713         # Break one server, then create the file: the initial publish should
9714hunk ./src/allmydata/test/test_mutable.py 2303
9715         d.addCallback(_break_peer0)
9716         # now "create" the file, using the pre-established key, and let the
9717         # initial publish finally happen
9718-        d.addCallback(lambda res: nm.create_mutable_file("contents 1"))
9719+        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
9720         # that ought to work
9721         def _got_node(n):
9722             d = n.download_best_version()
9723hunk ./src/allmydata/test/test_mutable.py 2312
9724             def _break_peer1(res):
9725                 self.connection1.broken = True
9726             d.addCallback(_break_peer1)
9727-            d.addCallback(lambda res: n.overwrite("contents 2"))
9728+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9729             # that ought to work too
9730             d.addCallback(lambda res: n.download_best_version())
9731             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9732hunk ./src/allmydata/test/test_mutable.py 2344
9733         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
9734         self.g.break_server(peerids[0])
9735 
9736-        d = nm.create_mutable_file("contents 1")
9737+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9738         def _created(n):
9739             d = n.download_best_version()
9740             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9741hunk ./src/allmydata/test/test_mutable.py 2352
9742             def _break_second_server(res):
9743                 self.g.break_server(peerids[1])
9744             d.addCallback(_break_second_server)
9745-            d.addCallback(lambda res: n.overwrite("contents 2"))
9746+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9747             # that ought to work too
9748             d.addCallback(lambda res: n.download_best_version())
9749             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9750hunk ./src/allmydata/test/test_mutable.py 2371
9751         d = self.shouldFail(NotEnoughServersError,
9752                             "test_publish_all_servers_bad",
9753                             "Ran out of non-bad servers",
9754-                            nm.create_mutable_file, "contents")
9755+                            nm.create_mutable_file, MutableDataHandle("contents"))
9756         return d
9757 
9758     def test_publish_no_servers(self):
9759hunk ./src/allmydata/test/test_mutable.py 2383
9760         d = self.shouldFail(NotEnoughServersError,
9761                             "test_publish_no_servers",
9762                             "Ran out of non-bad servers",
9763-                            nm.create_mutable_file, "contents")
9764+                            nm.create_mutable_file, MutableDataHandle("contents"))
9765         return d
9766     test_publish_no_servers.timeout = 30
9767 
9768hunk ./src/allmydata/test/test_mutable.py 2401
9769         # we need some contents that are large enough to push the privkey out
9770         # of the early part of the file
9771         LARGE = "These are Larger contents" * 2000 # about 50KB
9772-        d = nm.create_mutable_file(LARGE)
9773+        LARGE_uploadable = MutableDataHandle(LARGE)
9774+        d = nm.create_mutable_file(LARGE_uploadable)
9775         def _created(n):
9776             self.uri = n.get_uri()
9777             self.n2 = nm.create_from_cap(self.uri)
9778hunk ./src/allmydata/test/test_mutable.py 2438
9779         self.set_up_grid(num_servers=20)
9780         nm = self.g.clients[0].nodemaker
9781         LARGE = "These are Larger contents" * 2000 # about 50KiB
9782+        LARGE_uploadable = MutableDataHandle(LARGE)
9783         nm._node_cache = DevNullDictionary() # disable the nodecache
9784 
9785hunk ./src/allmydata/test/test_mutable.py 2441
9786-        d = nm.create_mutable_file(LARGE)
9787+        d = nm.create_mutable_file(LARGE_uploadable)
9788         def _created(n):
9789             self.uri = n.get_uri()
9790             self.n2 = nm.create_from_cap(self.uri)
9791hunk ./src/allmydata/test/test_mutable.py 2464
9792         self.set_up_grid(num_servers=20)
9793         nm = self.g.clients[0].nodemaker
9794         CONTENTS = "contents" * 2000
9795-        d = nm.create_mutable_file(CONTENTS)
9796+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9797+        d = nm.create_mutable_file(CONTENTS_uploadable)
9798         def _created(node):
9799             self._node = node
9800         d.addCallback(_created)
9801hunk ./src/allmydata/test/test_system.py 22
9802 from allmydata.monitor import Monitor
9803 from allmydata.mutable.common import NotWriteableError
9804 from allmydata.mutable import layout as mutable_layout
9805+from allmydata.mutable.publish import MutableDataHandle
9806 from foolscap.api import DeadReferenceError
9807 from twisted.python.failure import Failure
9808 from twisted.web.client import getPage
9809hunk ./src/allmydata/test/test_system.py 460
9810     def test_mutable(self):
9811         self.basedir = "system/SystemTest/test_mutable"
9812         DATA = "initial contents go here."  # 25 bytes % 3 != 0
9813+        DATA_uploadable = MutableDataHandle(DATA)
9814         NEWDATA = "new contents yay"
9815hunk ./src/allmydata/test/test_system.py 462
9816+        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
9817         NEWERDATA = "this is getting old"
9818hunk ./src/allmydata/test/test_system.py 464
9819+        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
9820 
9821         d = self.set_up_nodes(use_key_generator=True)
9822 
9823hunk ./src/allmydata/test/test_system.py 471
9824         def _create_mutable(res):
9825             c = self.clients[0]
9826             log.msg("starting create_mutable_file")
9827-            d1 = c.create_mutable_file(DATA)
9828+            d1 = c.create_mutable_file(DATA_uploadable)
9829             def _done(res):
9830                 log.msg("DONE: %s" % (res,))
9831                 self._mutable_node_1 = res
9832hunk ./src/allmydata/test/test_system.py 558
9833             self.failUnlessEqual(res, DATA)
9834             # replace the data
9835             log.msg("starting replace1")
9836-            d1 = newnode.overwrite(NEWDATA)
9837+            d1 = newnode.overwrite(NEWDATA_uploadable)
9838             d1.addCallback(lambda res: newnode.download_best_version())
9839             return d1
9840         d.addCallback(_check_download_3)
9841hunk ./src/allmydata/test/test_system.py 572
9842             newnode2 = self.clients[3].create_node_from_uri(uri)
9843             self._newnode3 = self.clients[3].create_node_from_uri(uri)
9844             log.msg("starting replace2")
9845-            d1 = newnode1.overwrite(NEWERDATA)
9846+            d1 = newnode1.overwrite(NEWERDATA_uploadable)
9847             d1.addCallback(lambda res: newnode2.download_best_version())
9848             return d1
9849         d.addCallback(_check_download_4)
9850hunk ./src/allmydata/test/test_system.py 642
9851         def _check_empty_file(res):
9852             # make sure we can create empty files, this usually screws up the
9853             # segsize math
9854-            d1 = self.clients[2].create_mutable_file("")
9855+            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
9856             d1.addCallback(lambda newnode: newnode.download_best_version())
9857             d1.addCallback(lambda res: self.failUnlessEqual("", res))
9858             return d1
9859hunk ./src/allmydata/test/test_system.py 673
9860                                  self.key_generator_svc.key_generator.pool_size + size_delta)
9861 
9862         d.addCallback(check_kg_poolsize, 0)
9863-        d.addCallback(lambda junk: self.clients[3].create_mutable_file('hello, world'))
9864+        d.addCallback(lambda junk:
9865+            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
9866         d.addCallback(check_kg_poolsize, -1)
9867         d.addCallback(lambda junk: self.clients[3].create_dirnode())
9868         d.addCallback(check_kg_poolsize, -2)
9869hunk ./src/allmydata/test/test_web.py 3183
9870         def _stash_mutable_uri(n, which):
9871             self.uris[which] = n.get_uri()
9872             assert isinstance(self.uris[which], str)
9873-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9874+        d.addCallback(lambda ign:
9875+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9876         d.addCallback(_stash_mutable_uri, "corrupt")
9877         d.addCallback(lambda ign:
9878                       c0.upload(upload.Data("literal", convergence="")))
9879hunk ./src/allmydata/test/test_web.py 3330
9880         def _stash_mutable_uri(n, which):
9881             self.uris[which] = n.get_uri()
9882             assert isinstance(self.uris[which], str)
9883-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9884+        d.addCallback(lambda ign:
9885+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9886         d.addCallback(_stash_mutable_uri, "corrupt")
9887 
9888         def _compute_fileurls(ignored):
9889hunk ./src/allmydata/test/test_web.py 3993
9890         def _stash_mutable_uri(n, which):
9891             self.uris[which] = n.get_uri()
9892             assert isinstance(self.uris[which], str)
9893-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
9894+        d.addCallback(lambda ign:
9895+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
9896         d.addCallback(_stash_mutable_uri, "mutable")
9897 
9898         def _compute_fileurls(ignored):
9899hunk ./src/allmydata/test/test_web.py 4093
9900                                                         convergence="")))
9901         d.addCallback(_stash_uri, "small")
9902 
9903-        d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
9904+        d.addCallback(lambda ign:
9905+            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
9906         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9907         d.addCallback(_stash_uri, "mutable")
9908 
9909}
9910[Alter mutable files to use file-like objects for publishing instead of strings.
9911Kevan Carstensen <kevan@isnotajoke.com>**20100708000732
9912 Ignore-this: 8dd07d95386b6d540bc21289f981ebd0
9913] {
9914hunk ./src/allmydata/dirnode.py 11
9915 from allmydata.mutable.common import NotWriteableError
9916 from allmydata.mutable.filenode import MutableFileNode
9917 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
9918+from allmydata.mutable.publish import MutableDataHandle
9919 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
9920      IImmutableFileNode, IMutableFileNode, \
9921      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
9922hunk ./src/allmydata/dirnode.py 104
9923 
9924         del children[self.name]
9925         new_contents = self.node._pack_contents(children)
9926-        return new_contents
9927+        uploadable = MutableDataHandle(new_contents)
9928+        return uploadable
9929 
9930 
9931 class MetadataSetter:
9932hunk ./src/allmydata/dirnode.py 130
9933 
9934         children[name] = (child, metadata)
9935         new_contents = self.node._pack_contents(children)
9936-        return new_contents
9937+        uploadable = MutableDataHandle(new_contents)
9938+        return uploadable
9939 
9940 
9941 class Adder:
9942hunk ./src/allmydata/dirnode.py 175
9943 
9944             children[name] = (child, metadata)
9945         new_contents = self.node._pack_contents(children)
9946-        return new_contents
9947+        uploadable = MutableDataHandle(new_contents)
9948+        return uploadable
9949 
9950 def _encrypt_rw_uri(writekey, rw_uri):
9951     precondition(isinstance(rw_uri, str), rw_uri)
9952hunk ./src/allmydata/mutable/filenode.py 7
9953 from zope.interface import implements
9954 from twisted.internet import defer, reactor
9955 from foolscap.api import eventually
9956-from allmydata.interfaces import IMutableFileNode, \
9957-     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
9958+from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
9959+                                 NotEnoughSharesError, \
9960+                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
9961 from allmydata.util import hashutil, log
9962 from allmydata.util.assertutil import precondition
9963 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
9964hunk ./src/allmydata/mutable/filenode.py 16
9965 from allmydata.monitor import Monitor
9966 from pycryptopp.cipher.aes import AES
9967 
9968-from allmydata.mutable.publish import Publish
9969+from allmydata.mutable.publish import Publish, MutableFileHandle, \
9970+                                      MutableDataHandle
9971 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
9972      ResponseCache, UncoordinatedWriteError
9973 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
9974hunk ./src/allmydata/mutable/filenode.py 133
9975         return self._upload(initial_contents, None)
9976 
9977     def _get_initial_contents(self, contents):
9978-        if isinstance(contents, str):
9979-            return contents
9980         if contents is None:
9981hunk ./src/allmydata/mutable/filenode.py 134
9982-            return ""
9983+            return MutableDataHandle("")
9984+
9985+        if IMutableUploadable.providedBy(contents):
9986+            return contents
9987+
9988         assert callable(contents), "%s should be callable, not %s" % \
9989                (contents, type(contents))
9990         return contents(self)
9991hunk ./src/allmydata/mutable/filenode.py 353
9992     def overwrite(self, new_contents):
9993         return self._do_serialized(self._overwrite, new_contents)
9994     def _overwrite(self, new_contents):
9995+        assert IMutableUploadable.providedBy(new_contents)
9996+
9997         servermap = ServerMap()
9998         d = self._update_servermap(servermap, mode=MODE_WRITE)
9999         d.addCallback(lambda ignored: self._upload(new_contents, servermap))
10000hunk ./src/allmydata/mutable/filenode.py 431
10001                 # recovery when it observes UCWE, we need to do a second
10002                 # publish. See #551 for details. We'll basically loop until
10003                 # we managed an uncontested publish.
10004-                new_contents = old_contents
10005-            precondition(isinstance(new_contents, str),
10006-                         "Modifier function must return a string or None")
10007+                old_uploadable = MutableDataHandle(old_contents)
10008+                new_contents = old_uploadable
10009+            precondition((IMutableUploadable.providedBy(new_contents) or
10010+                          new_contents is None),
10011+                         "Modifier function must return an IMutableUploadable "
10012+                         "or None")
10013             return self._upload(new_contents, servermap)
10014         d.addCallback(_apply)
10015         return d
10016hunk ./src/allmydata/mutable/filenode.py 472
10017         return self._do_serialized(self._upload, new_contents, servermap)
10018     def _upload(self, new_contents, servermap):
10019         assert self._pubkey, "update_servermap must be called before publish"
10020+        assert IMutableUploadable.providedBy(new_contents)
10021+
10022         p = Publish(self, self._storage_broker, servermap)
10023         if self._history:
10024hunk ./src/allmydata/mutable/filenode.py 476
10025-            self._history.notify_publish(p.get_status(), len(new_contents))
10026+            self._history.notify_publish(p.get_status(), new_contents.get_size())
10027         d = p.publish(new_contents)
10028hunk ./src/allmydata/mutable/filenode.py 478
10029-        d.addCallback(self._did_upload, len(new_contents))
10030+        d.addCallback(self._did_upload, new_contents.get_size())
10031         return d
10032     def _did_upload(self, res, size):
10033         self._most_recent_size = size
10034hunk ./src/allmydata/mutable/publish.py 141
10035 
10036         # 0. Setup encoding parameters, encoder, and other such things.
10037         # 1. Encrypt, encode, and publish segments.
10038-        self.data = StringIO(newdata)
10039-        self.datalength = len(newdata)
10040+        assert IMutableUploadable.providedBy(newdata)
10041+
10042+        self.data = newdata
10043+        self.datalength = newdata.get_size()
10044 
10045         self.log("starting publish, datalen is %s" % self.datalength)
10046         self._status.set_size(self.datalength)
10047hunk ./src/allmydata/mutable/publish.py 442
10048 
10049         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
10050         data = self.data.read(segsize)
10051+        # XXX: This is dumb. Why return a list?
10052+        data = "".join(data)
10053 
10054         assert len(data) == segsize
10055 
10056hunk ./src/allmydata/mutable/repairer.py 5
10057 from zope.interface import implements
10058 from twisted.internet import defer
10059 from allmydata.interfaces import IRepairResults, ICheckResults
10060+from allmydata.mutable.publish import MutableDataHandle
10061 
10062 class RepairResults:
10063     implements(IRepairResults)
10064hunk ./src/allmydata/mutable/repairer.py 108
10065             raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.")
10066 
10067         d = self.node.download_version(smap, best_version, fetch_privkey=True)
10068+        d.addCallback(lambda data:
10069+            MutableDataHandle(data))
10070         d.addCallback(self.node.upload, smap)
10071         d.addCallback(self.get_results, smap)
10072         return d
10073hunk ./src/allmydata/nodemaker.py 9
10074 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
10075 from allmydata.immutable.upload import Data
10076 from allmydata.mutable.filenode import MutableFileNode
10077+from allmydata.mutable.publish import MutableDataHandle
10078 from allmydata.dirnode import DirectoryNode, pack_children
10079 from allmydata.unknown import UnknownNode
10080 from allmydata import uri
10081merger 0.0 (
10082merger 0.0 (
10083hunk ./src/allmydata/nodemaker.py 107
10084-                                     pack_children(n, initial_children))
10085+                                     pack_children(n, initial_children),
10086+                                     version)
10087hunk ./src/allmydata/nodemaker.py 107
10088-                                     pack_children(n, initial_children))
10089+                                     pack_children(initial_children, n.get_writekey()))
10090)
10091hunk ./src/allmydata/nodemaker.py 107
10092-                                     pack_children(n, initial_children),
10093+                                     MutableDataHandle(
10094+                                        pack_children(n, initial_children)),
10095)
10096hunk ./src/allmydata/web/filenode.py 12
10097 from allmydata.interfaces import ExistingChildError
10098 from allmydata.monitor import Monitor
10099 from allmydata.immutable.upload import FileHandle
10100+from allmydata.mutable.publish import MutableFileHandle
10101 from allmydata.util import log, base32
10102 
10103 from allmydata.web.common import text_plain, WebError, RenderMixin, \
10104hunk ./src/allmydata/web/filenode.py 27
10105         # a new file is being uploaded in our place.
10106         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10107         if mutable:
10108-            req.content.seek(0)
10109-            data = req.content.read()
10110+            data = MutableFileHandle(req.content)
10111             d = client.create_mutable_file(data)
10112             def _uploaded(newnode):
10113                 d2 = self.parentnode.set_node(self.name, newnode,
10114hunk ./src/allmydata/web/filenode.py 61
10115         d.addCallback(lambda res: childnode.get_uri())
10116         return d
10117 
10118-    def _read_data_from_formpost(self, req):
10119-        # SDMF: files are small, and we can only upload data, so we read
10120-        # the whole file into memory before uploading.
10121-        contents = req.fields["file"]
10122-        contents.file.seek(0)
10123-        data = contents.file.read()
10124-        return data
10125 
10126     def replace_me_with_a_formpost(self, req, client, replace):
10127         # create a new file, maybe mutable, maybe immutable
10128hunk ./src/allmydata/web/filenode.py 66
10129         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10130 
10131+        # create an immutable file
10132+        contents = req.fields["file"]
10133         if mutable:
10134hunk ./src/allmydata/web/filenode.py 69
10135-            data = self._read_data_from_formpost(req)
10136-            d = client.create_mutable_file(data)
10137+            uploadable = MutableFileHandle(contents.file)
10138+            d = client.create_mutable_file(uploadable)
10139             def _uploaded(newnode):
10140                 d2 = self.parentnode.set_node(self.name, newnode,
10141                                               overwrite=replace)
10142hunk ./src/allmydata/web/filenode.py 78
10143                 return d2
10144             d.addCallback(_uploaded)
10145             return d
10146-        # create an immutable file
10147-        contents = req.fields["file"]
10148+
10149         uploadable = FileHandle(contents.file, convergence=client.convergence)
10150         d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
10151         d.addCallback(lambda newnode: newnode.get_uri())
10152hunk ./src/allmydata/web/filenode.py 84
10153         return d
10154 
10155+
10156 class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
10157     def __init__(self, client, parentnode, name):
10158         rend.Page.__init__(self)
10159hunk ./src/allmydata/web/filenode.py 278
10160 
10161     def replace_my_contents(self, req):
10162         req.content.seek(0)
10163-        new_contents = req.content.read()
10164+        new_contents = MutableFileHandle(req.content)
10165         d = self.node.overwrite(new_contents)
10166         d.addCallback(lambda res: self.node.get_uri())
10167         return d
10168hunk ./src/allmydata/web/filenode.py 286
10169     def replace_my_contents_with_a_formpost(self, req):
10170         # we have a mutable file. Get the data from the formpost, and replace
10171         # the mutable file's contents with it.
10172-        new_contents = self._read_data_from_formpost(req)
10173+        new_contents = req.fields['file']
10174+        new_contents = MutableFileHandle(new_contents.file)
10175+
10176         d = self.node.overwrite(new_contents)
10177         d.addCallback(lambda res: self.node.get_uri())
10178         return d
10179hunk ./src/allmydata/web/unlinked.py 7
10180 from twisted.internet import defer
10181 from nevow import rend, url, tags as T
10182 from allmydata.immutable.upload import FileHandle
10183+from allmydata.mutable.publish import MutableFileHandle
10184 from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
10185      convert_children_json, WebError
10186 from allmydata.web import status
10187hunk ./src/allmydata/web/unlinked.py 23
10188 def PUTUnlinkedSSK(req, client):
10189     # SDMF: files are small, and we can only upload data
10190     req.content.seek(0)
10191-    data = req.content.read()
10192+    data = MutableFileHandle(req.content)
10193     d = client.create_mutable_file(data)
10194     d.addCallback(lambda n: n.get_uri())
10195     return d
10196hunk ./src/allmydata/web/unlinked.py 87
10197     # "POST /uri", to create an unlinked file.
10198     # SDMF: files are small, and we can only upload data
10199     contents = req.fields["file"]
10200-    contents.file.seek(0)
10201-    data = contents.file.read()
10202+    data = MutableFileHandle(contents.file)
10203     d = client.create_mutable_file(data)
10204     d.addCallback(lambda n: n.get_uri())
10205     return d
10206}
10207[test/test_sftp.py: alter a setup routine to work with new mutable file APIs.
10208Kevan Carstensen <kevan@isnotajoke.com>**20100708193522
10209 Ignore-this: 434bbe1347072076c0836d26fca8ac8a
10210] {
10211hunk ./src/allmydata/test/test_sftp.py 32
10212 
10213 from allmydata.util.consumer import download_to_data
10214 from allmydata.immutable import upload
10215+from allmydata.mutable import publish
10216 from allmydata.test.no_network import GridTestMixin
10217 from allmydata.test.common import ShouldFailMixin
10218 from allmydata.test.common_util import ReallyEqualMixin
10219hunk ./src/allmydata/test/test_sftp.py 84
10220         return d
10221 
10222     def _set_up_tree(self):
10223-        d = self.client.create_mutable_file("mutable file contents")
10224+        u = publish.MutableDataHandle("mutable file contents")
10225+        d = self.client.create_mutable_file(u)
10226         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
10227         def _created_mutable(n):
10228             self.mutable = n
10229}
10230[mutable/publish.py: make MutableFileHandle seek to the beginning of its file handle before reading.
10231Kevan Carstensen <kevan@isnotajoke.com>**20100708193600
10232 Ignore-this: 453a737dc62a79c77b3d360fed9000ab
10233] hunk ./src/allmydata/mutable/publish.py 989
10234         assert hasattr(filehandle, "close")
10235 
10236         self._filehandle = filehandle
10237+        # We must start reading at the beginning of the file, or we risk
10238+        # encountering errors when the data read does not match the size
10239+        # reported to the uploader.
10240+        self._filehandle.seek(0)
10241 
10242 
10243     def get_size(self):
10244[Refactor download interfaces to be more uniform, per #993
10245Kevan Carstensen <kevan@isnotajoke.com>**20100709232912
10246 Ignore-this: 277c5699c4a2dd7c03ecfa0a28458f5b
10247] {
10248hunk ./src/allmydata/immutable/filenode.py 10
10249 from foolscap.api import eventually
10250 from allmydata.interfaces import IImmutableFileNode, ICheckable, \
10251      IDownloadTarget, IUploadResults
10252-from allmydata.util import dictutil, log, base32
10253+from allmydata.util import dictutil, log, base32, consumer
10254 from allmydata.uri import CHKFileURI, LiteralFileURI
10255 from allmydata.immutable.checker import Checker
10256 from allmydata.check_results import CheckResults, CheckAndRepairResults
10257hunk ./src/allmydata/immutable/filenode.py 318
10258                       self.download_cache.read(consumer, offset, size))
10259         return d
10260 
10261+    # IReadable, IFileNode
10262+
10263+    def get_best_readable_version(self):
10264+        """
10265+        Return an IReadable of the best version of this file. Since
10266+        immutable files can have only one version, we just return the
10267+        current filenode.
10268+        """
10269+        return self
10270+
10271+
10272+    def download_best_version(self):
10273+        """
10274+        Download the best version of this file, returning its contents
10275+        as a bytestring. Since there is only one version of an immutable
10276+        file, we download and return the contents of this file.
10277+        """
10278+        d = consumer.download_to_data(self)
10279+        return d
10280+
10281+    # for an immutable file, download_to_data (specified in IReadable)
10282+    # is the same as download_best_version (specified in IFileNode). For
10283+    # mutable files, the difference is more meaningful, since they can
10284+    # have multiple versions.
10285+    download_to_data = download_best_version
10286+
10287+
10288+    # get_size() (IReadable), get_current_size() (IFilesystemNode), and
10289+    # get_size_of_best_version(IFileNode) are all the same for immutable
10290+    # files.
10291+    get_size_of_best_version = get_current_size
10292+
10293+
10294 class LiteralProducer:
10295     implements(IPushProducer)
10296     def resumeProducing(self):
10297hunk ./src/allmydata/immutable/filenode.py 409
10298         d = basic.FileSender().beginFileTransfer(StringIO(data), consumer)
10299         d.addCallback(lambda lastSent: consumer)
10300         return d
10301+
10302+    # IReadable, IFileNode, IFilesystemNode
10303+    def get_best_readable_version(self):
10304+        return self
10305+
10306+
10307+    def download_best_version(self):
10308+        return defer.succeed(self.u.data)
10309+
10310+
10311+    download_to_data = download_best_version
10312+    get_size_of_best_version = get_current_size
10313hunk ./src/allmydata/interfaces.py 563
10314 class MustNotBeUnknownRWError(CapConstraintError):
10315     """Cannot add an unknown child cap specified in a rw_uri field."""
10316 
10317+
10318+class IReadable(Interface):
10319+    """I represent a readable object -- either an immutable file, or a
10320+    specific version of a mutable file.
10321+    """
10322+
10323+    def is_readonly():
10324+        """Return True if this reference provides mutable access to the given
10325+        file or directory (i.e. if you can modify it), or False if not. Note
10326+        that even if this reference is read-only, someone else may hold a
10327+        read-write reference to it.
10328+
10329+        For an IReadable returned by get_best_readable_version(), this will
10330+        always return True, but for instances of subinterfaces such as
10331+        IMutableFileVersion, it may return False."""
10332+
10333+    def is_mutable():
10334+        """Return True if this file or directory is mutable (by *somebody*,
10335+        not necessarily you), False if it is is immutable. Note that a file
10336+        might be mutable overall, but your reference to it might be
10337+        read-only. On the other hand, all references to an immutable file
10338+        will be read-only; there are no read-write references to an immutable
10339+        file."""
10340+
10341+    def get_storage_index():
10342+        """Return the storage index of the file."""
10343+
10344+    def get_size():
10345+        """Return the length (in bytes) of this readable object."""
10346+
10347+    def download_to_data():
10348+        """Download all of the file contents. I return a Deferred that fires
10349+        with the contents as a byte string."""
10350+
10351+    def read(consumer, offset=0, size=None):
10352+        """Download a portion (possibly all) of the file's contents, making
10353+        them available to the given IConsumer. Return a Deferred that fires
10354+        (with the consumer) when the consumer is unregistered (either because
10355+        the last byte has been given to it, or because the consumer threw an
10356+        exception during write(), possibly because it no longer wants to
10357+        receive data). The portion downloaded will start at 'offset' and
10358+        contain 'size' bytes (or the remainder of the file if size==None).
10359+
10360+        The consumer will be used in non-streaming mode: an IPullProducer
10361+        will be attached to it.
10362+
10363+        The consumer will not receive data right away: several network trips
10364+        must occur first. The order of events will be::
10365+
10366+         consumer.registerProducer(p, streaming)
10367+          (if streaming == False)::
10368+           consumer does p.resumeProducing()
10369+            consumer.write(data)
10370+           consumer does p.resumeProducing()
10371+            consumer.write(data).. (repeat until all data is written)
10372+         consumer.unregisterProducer()
10373+         deferred.callback(consumer)
10374+
10375+        If a download error occurs, or an exception is raised by
10376+        consumer.registerProducer() or consumer.write(), I will call
10377+        consumer.unregisterProducer() and then deliver the exception via
10378+        deferred.errback(). To cancel the download, the consumer should call
10379+        p.stopProducing(), which will result in an exception being delivered
10380+        via deferred.errback().
10381+
10382+        See src/allmydata/util/consumer.py for an example of a simple
10383+        download-to-memory consumer.
10384+        """
10385+
10386+
10387+class IMutableFileVersion(IReadable):
10388+    """I provide access to a particular version of a mutable file. The
10389+    access is read/write if I was obtained from a filenode derived from
10390+    a write cap, or read-only if the filenode was derived from a read cap.
10391+    """
10392+
10393+    def get_sequence_number():
10394+        """Return the sequence number of this version."""
10395+
10396+    def get_servermap():
10397+        """Return the IMutableFileServerMap instance that was used to create
10398+        this object.
10399+        """
10400+
10401+    def get_writekey():
10402+        """Return this filenode's writekey, or None if the node does not have
10403+        write-capability. This may be used to assist with data structures
10404+        that need to make certain data available only to writers, such as the
10405+        read-write child caps in dirnodes. The recommended process is to have
10406+        reader-visible data be submitted to the filenode in the clear (where
10407+        it will be encrypted by the filenode using the readkey), but encrypt
10408+        writer-visible data using this writekey.
10409+        """
10410+
10411+    # TODO: Can this be overwrite instead of replace?
10412+    def replace(new_contents):
10413+        """Replace the contents of the mutable file, provided that no other
10414+        node has published (or is attempting to publish, concurrently) a
10415+        newer version of the file than this one.
10416+
10417+        I will avoid modifying any share that is different than the version
10418+        given by get_sequence_number(). However, if another node is writing
10419+        to the file at the same time as me, I may manage to update some shares
10420+        while they update others. If I see any evidence of this, I will signal
10421+        UncoordinatedWriteError, and the file will be left in an inconsistent
10422+        state (possibly the version you provided, possibly the old version,
10423+        possibly somebody else's version, and possibly a mix of shares from
10424+        all of these).
10425+
10426+        The recommended response to UncoordinatedWriteError is to either
10427+        return it to the caller (since they failed to coordinate their
10428+        writes), or to attempt some sort of recovery. It may be sufficient to
10429+        wait a random interval (with exponential backoff) and repeat your
10430+        operation. If I do not signal UncoordinatedWriteError, then I was
10431+        able to write the new version without incident.
10432+
10433+        I return a Deferred that fires (with a PublishStatus object) when the
10434+        update has completed.
10435+        """
10436+
10437+    def modify(modifier_cb):
10438+        """Modify the contents of the file, by downloading this version,
10439+        applying the modifier function (or bound method), then uploading
10440+        the new version. This will succeed as long as no other node
10441+        publishes a version between the download and the upload.
10442+        I return a Deferred that fires (with a PublishStatus object) when
10443+        the update is complete.
10444+
10445+        The modifier callable will be given three arguments: a string (with
10446+        the old contents), a 'first_time' boolean, and a servermap. As with
10447+        download_to_data(), the old contents will be from this version,
10448+        but the modifier can use the servermap to make other decisions
10449+        (such as refusing to apply the delta if there are multiple parallel
10450+        versions, or if there is evidence of a newer unrecoverable version).
10451+        'first_time' will be True the first time the modifier is called,
10452+        and False on any subsequent calls.
10453+
10454+        The callable should return a string with the new contents. The
10455+        callable must be prepared to be called multiple times, and must
10456+        examine the input string to see if the change that it wants to make
10457+        is already present in the old version. If it does not need to make
10458+        any changes, it can either return None, or return its input string.
10459+
10460+        If the modifier raises an exception, it will be returned in the
10461+        errback.
10462+        """
10463+
10464+
10465 # The hierarchy looks like this:
10466 #  IFilesystemNode
10467 #   IFileNode
10468hunk ./src/allmydata/interfaces.py 801
10469     def raise_error():
10470         """Raise any error associated with this node."""
10471 
10472+    # XXX: These may not be appropriate outside the context of an IReadable.
10473     def get_size():
10474         """Return the length (in bytes) of the data this node represents. For
10475         directory nodes, I return the size of the backing store. I return
10476hunk ./src/allmydata/interfaces.py 818
10477 class IFileNode(IFilesystemNode):
10478     """I am a node which represents a file: a sequence of bytes. I am not a
10479     container, like IDirectoryNode."""
10480+    def get_best_readable_version():
10481+        """Return a Deferred that fires with an IReadable for the 'best'
10482+        available version of the file. The IReadable provides only read
10483+        access, even if this filenode was derived from a write cap.
10484 
10485hunk ./src/allmydata/interfaces.py 823
10486-class IImmutableFileNode(IFileNode):
10487-    def read(consumer, offset=0, size=None):
10488-        """Download a portion (possibly all) of the file's contents, making
10489-        them available to the given IConsumer. Return a Deferred that fires
10490-        (with the consumer) when the consumer is unregistered (either because
10491-        the last byte has been given to it, or because the consumer threw an
10492-        exception during write(), possibly because it no longer wants to
10493-        receive data). The portion downloaded will start at 'offset' and
10494-        contain 'size' bytes (or the remainder of the file if size==None).
10495-
10496-        The consumer will be used in non-streaming mode: an IPullProducer
10497-        will be attached to it.
10498+        For an immutable file, there is only one version. For a mutable
10499+        file, the 'best' version is the recoverable version with the
10500+        highest sequence number. If no uncoordinated writes have occurred,
10501+        and if enough shares are available, then this will be the most
10502+        recent version that has been uploaded. If no version is recoverable,
10503+        the Deferred will errback with an UnrecoverableFileError.
10504+        """
10505 
10506hunk ./src/allmydata/interfaces.py 831
10507-        The consumer will not receive data right away: several network trips
10508-        must occur first. The order of events will be::
10509+    def download_best_version():
10510+        """Download the contents of the version that would be returned
10511+        by get_best_readable_version(). This is equivalent to calling
10512+        download_to_data() on the IReadable given by that method.
10513 
10514hunk ./src/allmydata/interfaces.py 836
10515-         consumer.registerProducer(p, streaming)
10516-          (if streaming == False)::
10517-           consumer does p.resumeProducing()
10518-            consumer.write(data)
10519-           consumer does p.resumeProducing()
10520-            consumer.write(data).. (repeat until all data is written)
10521-         consumer.unregisterProducer()
10522-         deferred.callback(consumer)
10523+        I return a Deferred that fires with a byte string when the file
10524+        has been fully downloaded. To support streaming download, use
10525+        the 'read' method of IReadable. If no version is recoverable,
10526+        the Deferred will errback with an UnrecoverableFileError.
10527+        """
10528 
10529hunk ./src/allmydata/interfaces.py 842
10530-        If a download error occurs, or an exception is raised by
10531-        consumer.registerProducer() or consumer.write(), I will call
10532-        consumer.unregisterProducer() and then deliver the exception via
10533-        deferred.errback(). To cancel the download, the consumer should call
10534-        p.stopProducing(), which will result in an exception being delivered
10535-        via deferred.errback().
10536+    def get_size_of_best_version():
10537+        """Find the size of the version that would be returned by
10538+        get_best_readable_version().
10539 
10540hunk ./src/allmydata/interfaces.py 846
10541-        See src/allmydata/util/consumer.py for an example of a simple
10542-        download-to-memory consumer.
10543+        I return a Deferred that fires with an integer. If no version
10544+        is recoverable, the Deferred will errback with an
10545+        UnrecoverableFileError.
10546         """
10547 
10548hunk ./src/allmydata/interfaces.py 851
10549+
10550+class IImmutableFileNode(IFileNode, IReadable):
10551+    """I am a node representing an immutable file. Immutable files have
10552+    only one version"""
10553+
10554+
10555 class IMutableFileNode(IFileNode):
10556     """I provide access to a 'mutable file', which retains its identity
10557     regardless of what contents are put in it.
10558hunk ./src/allmydata/interfaces.py 916
10559     only be retrieved and updated all-at-once, as a single big string. Future
10560     versions of our mutable files will remove this restriction.
10561     """
10562-
10563-    def download_best_version():
10564-        """Download the 'best' available version of the file, meaning one of
10565-        the recoverable versions with the highest sequence number. If no
10566+    def get_best_mutable_version():
10567+        """Return a Deferred that fires with an IMutableFileVersion for
10568+        the 'best' available version of the file. The best version is
10569+        the recoverable version with the highest sequence number. If no
10570         uncoordinated writes have occurred, and if enough shares are
10571hunk ./src/allmydata/interfaces.py 921
10572-        available, then this will be the most recent version that has been
10573-        uploaded.
10574-
10575-        I update an internal servermap with MODE_READ, determine which
10576-        version of the file is indicated by
10577-        servermap.best_recoverable_version(), and return a Deferred that
10578-        fires with its contents. If no version is recoverable, the Deferred
10579-        will errback with UnrecoverableFileError.
10580-        """
10581-
10582-    def get_size_of_best_version():
10583-        """Find the size of the version that would be downloaded with
10584-        download_best_version(), without actually downloading the whole file.
10585+        available, then this will be the most recent version that has
10586+        been uploaded.
10587 
10588hunk ./src/allmydata/interfaces.py 924
10589-        I return a Deferred that fires with an integer.
10590+        If no version is recoverable, the Deferred will errback with an
10591+        UnrecoverableFileError.
10592         """
10593 
10594     def overwrite(new_contents):
10595hunk ./src/allmydata/interfaces.py 964
10596         errback.
10597         """
10598 
10599-
10600     def get_servermap(mode):
10601         """Return a Deferred that fires with an IMutableFileServerMap
10602         instance, updated using the given mode.
10603hunk ./src/allmydata/test/test_filenode.py 98
10604         def _check_segment(res):
10605             self.failUnlessEqual(res, DATA[1:1+5])
10606         d.addCallback(_check_segment)
10607+        d.addCallback(lambda ignored:
10608+            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
10609+        d.addCallback(lambda ignored:
10610+            fn1.get_size_of_best_version())
10611+        d.addCallback(lambda size:
10612+            self.failUnlessEqual(size, len(DATA)))
10613+        d.addCallback(lambda ignored:
10614+            fn1.download_to_data())
10615+        d.addCallback(lambda data:
10616+            self.failUnlessEqual(data, DATA))
10617+        d.addCallback(lambda ignored:
10618+            fn1.download_best_version())
10619+        d.addCallback(lambda data:
10620+            self.failUnlessEqual(data, DATA))
10621 
10622         return d
10623 
10624hunk ./src/allmydata/test/test_immutable.py 153
10625         return d
10626 
10627 
10628+    def test_download_to_data(self):
10629+        d = self.n.download_to_data()
10630+        d.addCallback(lambda data:
10631+            self.failUnlessEqual(data, common.TEST_DATA))
10632+        return d
10633+
10634+
10635+    def test_download_best_version(self):
10636+        d = self.n.download_best_version()
10637+        d.addCallback(lambda data:
10638+            self.failUnlessEqual(data, common.TEST_DATA))
10639+        return d
10640+
10641+
10642+    def test_get_best_readable_version(self):
10643+        n = self.n.get_best_readable_version()
10644+        self.failUnlessEqual(n, self.n)
10645+
10646+    def test_get_size_of_best_version(self):
10647+        d = self.n.get_size_of_best_version()
10648+        d.addCallback(lambda size:
10649+            self.failUnlessEqual(size, len(common.TEST_DATA)))
10650+        return d
10651+
10652+
10653 # XXX extend these tests to show bad behavior of various kinds from servers: raising exception from each remove_foo() method, for example
10654 
10655 # XXX test disconnect DeadReferenceError from get_buckets and get_block_whatsit
10656}
10657[frontends/sftpd.py: alter a mutable file overwrite to work with the new API
10658Kevan Carstensen <kevan@isnotajoke.com>**20100717014446
10659 Ignore-this: 2c57bbc8d9ab97a0e9af0634c00efc86
10660] {
10661hunk ./src/allmydata/frontends/sftpd.py 33
10662 from allmydata.interfaces import IFileNode, IDirectoryNode, ExistingChildError, \
10663      NoSuchChildError, ChildOfWrongTypeError
10664 from allmydata.mutable.common import NotWriteableError
10665+from allmydata.mutable.publish import MutableFileHandle
10666 from allmydata.immutable.upload import FileHandle
10667 from allmydata.dirnode import update_metadata
10668 from allmydata.util.fileutil import EncryptedTemporaryFile
10669merger 0.0 (
10670hunk ./src/allmydata/frontends/sftpd.py 664
10671-            # TODO: use download interface described in #993 when implemented.
10672hunk ./src/allmydata/frontends/sftpd.py 664
10673-            # TODO: use download interface described in #993 when implemented.
10674-            if filenode.is_mutable():
10675-                self.async.addCallback(lambda ign: filenode.download_best_version())
10676-                def _downloaded(data):
10677-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
10678-                    self.consumer.write(data)
10679-                    self.consumer.finish()
10680-                    return None
10681-                self.async.addCallback(_downloaded)
10682-            else:
10683-                download_size = filenode.get_size()
10684-                assert download_size is not None, "download_size is None"
10685+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
10686+
10687+            def _read(version):
10688+                download_size = version.get_size()
10689+                assert download_size is not None
10690+
10691)
10692hunk ./src/allmydata/frontends/sftpd.py 677
10693                 download_size = filenode.get_size()
10694                 assert download_size is not None, "download_size is None"
10695                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
10696-                def _read(ign):
10697-                    if noisy: self.log("_read immutable", level=NOISY)
10698-                    filenode.read(self.consumer, 0, None)
10699-                self.async.addCallback(_read)
10700+
10701+                if noisy: self.log("_read", level=NOISY)
10702+                version.read(self.consumer, 0, None)
10703+            self.async.addCallback(_read)
10704 
10705         eventually(self.async.callback, None)
10706 
10707hunk ./src/allmydata/frontends/sftpd.py 824
10708                     assert parent and childname, (parent, childname, self.metadata)
10709                     d2.addCallback(lambda ign: parent.set_metadata_for(childname, self.metadata))
10710 
10711-                d2.addCallback(lambda ign: self.consumer.get_current_size())
10712-                d2.addCallback(lambda size: self.consumer.read(0, size))
10713-                d2.addCallback(lambda new_contents: self.filenode.overwrite(new_contents))
10714+                d2.addCallback(lambda ign: self.filenode.overwrite(MutableFileHandle(self.consumer.get_file())))
10715             else:
10716                 def _add_file(ign):
10717                     self.log("_add_file childname=%r" % (childname,), level=OPERATIONAL)
10718}
10719[mutable/filenode.py: implement most of IVersion, per #993
10720Kevan Carstensen <kevan@isnotajoke.com>**20100717014516
10721 Ignore-this: d4551142b32ea97040ce0e98a394fde5
10722] {
10723hunk ./src/allmydata/mutable/filenode.py 8
10724 from twisted.internet import defer, reactor
10725 from foolscap.api import eventually
10726 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
10727-                                 NotEnoughSharesError, \
10728-                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
10729-from allmydata.util import hashutil, log
10730+     NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
10731+     IMutableFileVersion
10732+from allmydata.util import hashutil, log, consumer
10733 from allmydata.util.assertutil import precondition
10734 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
10735 from allmydata.monitor import Monitor
10736hunk ./src/allmydata/mutable/filenode.py 17
10737 from pycryptopp.cipher.aes import AES
10738 
10739 from allmydata.mutable.publish import Publish, MutableFileHandle, \
10740-                                      MutableDataHandle
10741+                                      MutableData
10742 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
10743      ResponseCache, UncoordinatedWriteError
10744 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
10745hunk ./src/allmydata/mutable/filenode.py 134
10746 
10747     def _get_initial_contents(self, contents):
10748         if contents is None:
10749-            return MutableDataHandle("")
10750+            return MutableData("")
10751 
10752         if IMutableUploadable.providedBy(contents):
10753             return contents
10754hunk ./src/allmydata/mutable/filenode.py 208
10755 
10756     def get_size(self):
10757         return self._most_recent_size
10758+
10759     def get_current_size(self):
10760         d = self.get_size_of_best_version()
10761         d.addCallback(self._stash_size)
10762hunk ./src/allmydata/mutable/filenode.py 213
10763         return d
10764+
10765     def _stash_size(self, size):
10766         self._most_recent_size = size
10767         return size
10768hunk ./src/allmydata/mutable/filenode.py 272
10769             return cmp(self.__class__, them.__class__)
10770         return cmp(self._uri, them._uri)
10771 
10772-    def _do_serialized(self, cb, *args, **kwargs):
10773-        # note: to avoid deadlock, this callable is *not* allowed to invoke
10774-        # other serialized methods within this (or any other)
10775-        # MutableFileNode. The callable should be a bound method of this same
10776-        # MFN instance.
10777-        d = defer.Deferred()
10778-        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
10779-        # we need to put off d.callback until this Deferred is finished being
10780-        # processed. Otherwise the caller's subsequent activities (like,
10781-        # doing other things with this node) can cause reentrancy problems in
10782-        # the Deferred code itself
10783-        self._serializer.addBoth(lambda res: eventually(d.callback, res))
10784-        # add a log.err just in case something really weird happens, because
10785-        # self._serializer stays around forever, therefore we won't see the
10786-        # usual Unhandled Error in Deferred that would give us a hint.
10787-        self._serializer.addErrback(log.err)
10788-        return d
10789 
10790     #################################
10791     # ICheckable
10792hunk ./src/allmydata/mutable/filenode.py 297
10793 
10794 
10795     #################################
10796-    # IMutableFileNode
10797+    # IFileNode
10798+
10799+    def get_best_readable_version(self):
10800+        """
10801+        I return a Deferred that fires with a MutableFileVersion
10802+        representing the best readable version of the file that I
10803+        represent
10804+        """
10805+        return self.get_readable_version()
10806+
10807+
10808+    def get_readable_version(self, servermap=None, version=None):
10809+        """
10810+        I return a Deferred that fires with an MutableFileVersion for my
10811+        version argument, if there is a recoverable file of that version
10812+        on the grid. If there is no recoverable version, I fire with an
10813+        UnrecoverableFileError.
10814+
10815+        If a servermap is provided, I look in there for the requested
10816+        version. If no servermap is provided, I create and update a new
10817+        one.
10818+
10819+        If no version is provided, then I return a MutableFileVersion
10820+        representing the best recoverable version of the file.
10821+        """
10822+        d = self._get_version_from_servermap(MODE_READ, servermap, version)
10823+        def _build_version((servermap, their_version)):
10824+            assert their_version in servermap.recoverable_versions()
10825+            assert their_version in servermap.make_versionmap()
10826+
10827+            mfv = MutableFileVersion(self,
10828+                                     servermap,
10829+                                     their_version,
10830+                                     self._storage_index,
10831+                                     self._storage_broker,
10832+                                     self._readkey,
10833+                                     history=self._history)
10834+            assert mfv.is_readonly()
10835+            # our caller can use this to download the contents of the
10836+            # mutable file.
10837+            return mfv
10838+        return d.addCallback(_build_version)
10839+
10840+
10841+    def _get_version_from_servermap(self,
10842+                                    mode,
10843+                                    servermap=None,
10844+                                    version=None):
10845+        """
10846+        I return a Deferred that fires with (servermap, version).
10847+
10848+        This function performs validation and a servermap update. If it
10849+        returns (servermap, version), the caller can assume that:
10850+            - servermap was last updated in mode.
10851+            - version is recoverable, and corresponds to the servermap.
10852+
10853+        If version and servermap are provided to me, I will validate
10854+        that version exists in the servermap, and that the servermap was
10855+        updated correctly.
10856+
10857+        If version is not provided, but servermap is, I will validate
10858+        the servermap and return the best recoverable version that I can
10859+        find in the servermap.
10860+
10861+        If the version is provided but the servermap isn't, I will
10862+        obtain a servermap that has been updated in the correct mode and
10863+        validate that version is found and recoverable.
10864+
10865+        If neither servermap nor version are provided, I will obtain a
10866+        servermap updated in the correct mode, and return the best
10867+        recoverable version that I can find in there.
10868+        """
10869+        # XXX: wording ^^^^
10870+        if servermap and servermap.last_update_mode == mode:
10871+            d = defer.succeed(servermap)
10872+        else:
10873+            d = self._get_servermap(mode)
10874+
10875+        def _get_version(servermap, version):
10876+            if version and version not in servermap.recoverable_versions():
10877+                version = None
10878+            else:
10879+                version = servermap.best_recoverable_version()
10880+            if not version:
10881+                raise UnrecoverableFileError("no recoverable versions")
10882+            return (servermap, version)
10883+        return d.addCallback(_get_version, version)
10884+
10885 
10886     def download_best_version(self):
10887hunk ./src/allmydata/mutable/filenode.py 387
10888+        """
10889+        I return a Deferred that fires with the contents of the best
10890+        version of this mutable file.
10891+        """
10892         return self._do_serialized(self._download_best_version)
10893hunk ./src/allmydata/mutable/filenode.py 392
10894+
10895+
10896     def _download_best_version(self):
10897hunk ./src/allmydata/mutable/filenode.py 395
10898-        servermap = ServerMap()
10899-        d = self._try_once_to_download_best_version(servermap, MODE_READ)
10900-        def _maybe_retry(f):
10901-            f.trap(NotEnoughSharesError)
10902-            # the download is worth retrying once. Make sure to use the
10903-            # old servermap, since it is what remembers the bad shares,
10904-            # but use MODE_WRITE to make it look for even more shares.
10905-            # TODO: consider allowing this to retry multiple times.. this
10906-            # approach will let us tolerate about 8 bad shares, I think.
10907-            return self._try_once_to_download_best_version(servermap,
10908-                                                           MODE_WRITE)
10909+        """
10910+        I am the serialized sibling of download_best_version.
10911+        """
10912+        d = self.get_best_readable_version()
10913+        d.addCallback(self._record_size)
10914+        d.addCallback(lambda version: version.download_to_data())
10915+
10916+        # It is possible that the download will fail because there
10917+        # aren't enough shares to be had. If so, we will try again after
10918+        # updating the servermap in MODE_WRITE, which may find more
10919+        # shares than updating in MODE_READ, as we just did. We can do
10920+        # this by getting the best mutable version and downloading from
10921+        # that -- the best mutable version will be a MutableFileVersion
10922+        # with a servermap that was last updated in MODE_WRITE, as we
10923+        # want. If this fails, then we give up.
10924+        def _maybe_retry(failure):
10925+            failure.trap(NotEnoughSharesError)
10926+
10927+            d = self.get_best_mutable_version()
10928+            d.addCallback(self._record_size)
10929+            d.addCallback(lambda version: version.download_to_data())
10930+            return d
10931+
10932         d.addErrback(_maybe_retry)
10933         return d
10934hunk ./src/allmydata/mutable/filenode.py 420
10935-    def _try_once_to_download_best_version(self, servermap, mode):
10936-        d = self._update_servermap(servermap, mode)
10937-        d.addCallback(self._once_updated_download_best_version, servermap)
10938-        return d
10939-    def _once_updated_download_best_version(self, ignored, servermap):
10940-        goal = servermap.best_recoverable_version()
10941-        if not goal:
10942-            raise UnrecoverableFileError("no recoverable versions")
10943-        return self._try_once_to_download_version(servermap, goal)
10944+
10945+
10946+    def _record_size(self, mfv):
10947+        """
10948+        I record the size of a mutable file version.
10949+        """
10950+        self._most_recent_size = mfv.get_size()
10951+        return mfv
10952+
10953 
10954     def get_size_of_best_version(self):
10955hunk ./src/allmydata/mutable/filenode.py 431
10956-        d = self.get_servermap(MODE_READ)
10957-        def _got_servermap(smap):
10958-            ver = smap.best_recoverable_version()
10959-            if not ver:
10960-                raise UnrecoverableFileError("no recoverable version")
10961-            return smap.size_of_version(ver)
10962-        d.addCallback(_got_servermap)
10963-        return d
10964+        """
10965+        I return the size of the best version of this mutable file.
10966+
10967+        This is equivalent to calling get_size() on the result of
10968+        get_best_readable_version().
10969+        """
10970+        d = self.get_best_readable_version()
10971+        return d.addCallback(lambda mfv: mfv.get_size())
10972+
10973+
10974+    #################################
10975+    # IMutableFileNode
10976+
10977+    def get_best_mutable_version(self, servermap=None):
10978+        """
10979+        I return a Deferred that fires with a MutableFileVersion
10980+        representing the best readable version of the file that I
10981+        represent. I am like get_best_readable_version, except that I
10982+        will try to make a writable version if I can.
10983+        """
10984+        return self.get_mutable_version(servermap=servermap)
10985+
10986+
10987+    def get_mutable_version(self, servermap=None, version=None):
10988+        """
10989+        I return a version of this mutable file. I return a Deferred
10990+        that fires with a MutableFileVersion
10991+
10992+        If version is provided, the Deferred will fire with a
10993+        MutableFileVersion initailized with that version. Otherwise, it
10994+        will fire with the best version that I can recover.
10995+
10996+        If servermap is provided, I will use that to find versions
10997+        instead of performing my own servermap update.
10998+        """
10999+        if self.is_readonly():
11000+            return self.get_readable_version(servermap=servermap,
11001+                                             version=version)
11002+
11003+        # get_mutable_version => write intent, so we require that the
11004+        # servermap is updated in MODE_WRITE
11005+        d = self._get_version_from_servermap(MODE_WRITE, servermap, version)
11006+        def _build_version((servermap, smap_version)):
11007+            # these should have been set by the servermap update.
11008+            assert self._secret_holder
11009+            assert self._writekey
11010+
11011+            mfv = MutableFileVersion(self,
11012+                                     servermap,
11013+                                     smap_version,
11014+                                     self._storage_index,
11015+                                     self._storage_broker,
11016+                                     self._readkey,
11017+                                     self._writekey,
11018+                                     self._secret_holder,
11019+                                     history=self._history)
11020+            assert not mfv.is_readonly()
11021+            return mfv
11022+
11023+        return d.addCallback(_build_version)
11024+
11025+
11026+    # XXX: I'm uncomfortable with the difference between upload and
11027+    #      overwrite, which, FWICT, is basically that you don't have to
11028+    #      do a servermap update before you overwrite. We split them up
11029+    #      that way anyway, so I guess there's no real difficulty in
11030+    #      offering both ways to callers, but it also makes the
11031+    #      public-facing API cluttery, and makes it hard to discern the
11032+    #      right way of doing things.
11033 
11034hunk ./src/allmydata/mutable/filenode.py 501
11035+    # In general, we leave it to callers to ensure that they aren't
11036+    # going to cause UncoordinatedWriteErrors when working with
11037+    # MutableFileVersions. We know that the next three operations
11038+    # (upload, overwrite, and modify) will all operate on the same
11039+    # version, so we say that only one of them can be going on at once,
11040+    # and serialize them to ensure that that actually happens, since as
11041+    # the caller in this situation it is our job to do that.
11042     def overwrite(self, new_contents):
11043hunk ./src/allmydata/mutable/filenode.py 509
11044+        """
11045+        I overwrite the contents of the best recoverable version of this
11046+        mutable file with new_contents. This is equivalent to calling
11047+        overwrite on the result of get_best_mutable_version with
11048+        new_contents as an argument. I return a Deferred that eventually
11049+        fires with the results of my replacement process.
11050+        """
11051         return self._do_serialized(self._overwrite, new_contents)
11052hunk ./src/allmydata/mutable/filenode.py 517
11053+
11054+
11055     def _overwrite(self, new_contents):
11056hunk ./src/allmydata/mutable/filenode.py 520
11057-        assert IMutableUploadable.providedBy(new_contents)
11058+        """
11059+        I am the serialized sibling of overwrite.
11060+        """
11061+        d = self.get_best_mutable_version()
11062+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
11063+
11064+
11065+
11066+    def upload(self, new_contents, servermap):
11067+        """
11068+        I overwrite the contents of the best recoverable version of this
11069+        mutable file with new_contents, using servermap instead of
11070+        creating/updating our own servermap. I return a Deferred that
11071+        fires with the results of my upload.
11072+        """
11073+        return self._do_serialized(self._upload, new_contents, servermap)
11074+
11075+
11076+    def _upload(self, new_contents, servermap):
11077+        """
11078+        I am the serialized sibling of upload.
11079+        """
11080+        d = self.get_best_mutable_version(servermap)
11081+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
11082+
11083+
11084+    def modify(self, modifier, backoffer=None):
11085+        """
11086+        I modify the contents of the best recoverable version of this
11087+        mutable file with the modifier. This is equivalent to calling
11088+        modify on the result of get_best_mutable_version. I return a
11089+        Deferred that eventually fires with an UploadResults instance
11090+        describing this process.
11091+        """
11092+        return self._do_serialized(self._modify, modifier, backoffer)
11093+
11094+
11095+    def _modify(self, modifier, backoffer):
11096+        """
11097+        I am the serialized sibling of modify.
11098+        """
11099+        d = self.get_best_mutable_version()
11100+        return d.addCallback(lambda mfv: mfv.modify(modifier, backoffer))
11101+
11102+
11103+    def download_version(self, servermap, version, fetch_privkey=False):
11104+        """
11105+        Download the specified version of this mutable file. I return a
11106+        Deferred that fires with the contents of the specified version
11107+        as a bytestring, or errbacks if the file is not recoverable.
11108+        """
11109+        d = self.get_readable_version(servermap, version)
11110+        return d.addCallback(lambda mfv: mfv.download_to_data(fetch_privkey))
11111+
11112+
11113+    def get_servermap(self, mode):
11114+        """
11115+        I return a servermap that has been updated in mode.
11116+
11117+        mode should be one of MODE_READ, MODE_WRITE, MODE_CHECK or
11118+        MODE_ANYTHING. See servermap.py for more on what these mean.
11119+        """
11120+        return self._do_serialized(self._get_servermap, mode)
11121+
11122 
11123hunk ./src/allmydata/mutable/filenode.py 585
11124+    def _get_servermap(self, mode):
11125+        """
11126+        I am a serialized twin to get_servermap.
11127+        """
11128         servermap = ServerMap()
11129hunk ./src/allmydata/mutable/filenode.py 590
11130-        d = self._update_servermap(servermap, mode=MODE_WRITE)
11131-        d.addCallback(lambda ignored: self._upload(new_contents, servermap))
11132+        return self._update_servermap(servermap, mode)
11133+
11134+
11135+    def _update_servermap(self, servermap, mode):
11136+        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11137+                             mode)
11138+        if self._history:
11139+            self._history.notify_mapupdate(u.get_status())
11140+        return u.update()
11141+
11142+
11143+    def set_version(self, version):
11144+        # I can be set in two ways:
11145+        #  1. When the node is created.
11146+        #  2. (for an existing share) when the Servermap is updated
11147+        #     before I am read.
11148+        assert version in (MDMF_VERSION, SDMF_VERSION)
11149+        self._protocol_version = version
11150+
11151+
11152+    def get_version(self):
11153+        return self._protocol_version
11154+
11155+
11156+    def _do_serialized(self, cb, *args, **kwargs):
11157+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11158+        # other serialized methods within this (or any other)
11159+        # MutableFileNode. The callable should be a bound method of this same
11160+        # MFN instance.
11161+        d = defer.Deferred()
11162+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11163+        # we need to put off d.callback until this Deferred is finished being
11164+        # processed. Otherwise the caller's subsequent activities (like,
11165+        # doing other things with this node) can cause reentrancy problems in
11166+        # the Deferred code itself
11167+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11168+        # add a log.err just in case something really weird happens, because
11169+        # self._serializer stays around forever, therefore we won't see the
11170+        # usual Unhandled Error in Deferred that would give us a hint.
11171+        self._serializer.addErrback(log.err)
11172         return d
11173 
11174 
11175hunk ./src/allmydata/mutable/filenode.py 633
11176+    def _upload(self, new_contents, servermap):
11177+        """
11178+        A MutableFileNode still has to have some way of getting
11179+        published initially, which is what I am here for. After that,
11180+        all publishing, updating, modifying and so on happens through
11181+        MutableFileVersions.
11182+        """
11183+        assert self._pubkey, "update_servermap must be called before publish"
11184+
11185+        p = Publish(self, self._storage_broker, servermap)
11186+        if self._history:
11187+            self._history.notify_publish(p.get_status(),
11188+                                         new_contents.get_size())
11189+        d = p.publish(new_contents)
11190+        d.addCallback(self._did_upload, new_contents.get_size())
11191+        return d
11192+
11193+
11194+    def _did_upload(self, res, size):
11195+        self._most_recent_size = size
11196+        return res
11197+
11198+
11199+class MutableFileVersion:
11200+    """
11201+    I represent a specific version (most likely the best version) of a
11202+    mutable file.
11203+
11204+    Since I implement IReadable, instances which hold a
11205+    reference to an instance of me are guaranteed the ability (absent
11206+    connection difficulties or unrecoverable versions) to read the file
11207+    that I represent. Depending on whether I was initialized with a
11208+    write capability or not, I may also provide callers the ability to
11209+    overwrite or modify the contents of the mutable file that I
11210+    reference.
11211+    """
11212+    implements(IMutableFileVersion)
11213+
11214+    def __init__(self,
11215+                 node,
11216+                 servermap,
11217+                 version,
11218+                 storage_index,
11219+                 storage_broker,
11220+                 readcap,
11221+                 writekey=None,
11222+                 write_secrets=None,
11223+                 history=None):
11224+
11225+        self._node = node
11226+        self._servermap = servermap
11227+        self._version = version
11228+        self._storage_index = storage_index
11229+        self._write_secrets = write_secrets
11230+        self._history = history
11231+        self._storage_broker = storage_broker
11232+
11233+        #assert isinstance(readcap, IURI)
11234+        self._readcap = readcap
11235+
11236+        self._writekey = writekey
11237+        self._serializer = defer.succeed(None)
11238+        self._size = None
11239+
11240+
11241+    def get_sequence_number(self):
11242+        """
11243+        Get the sequence number of the mutable version that I represent.
11244+        """
11245+        return 0
11246+
11247+
11248+    # TODO: Terminology?
11249+    def get_writekey(self):
11250+        """
11251+        I return a writekey or None if I don't have a writekey.
11252+        """
11253+        return self._writekey
11254+
11255+
11256+    def overwrite(self, new_contents):
11257+        """
11258+        I overwrite the contents of this mutable file version with the
11259+        data in new_contents.
11260+        """
11261+        assert not self.is_readonly()
11262+
11263+        return self._do_serialized(self._overwrite, new_contents)
11264+
11265+
11266+    def _overwrite(self, new_contents):
11267+        assert IMutableUploadable.providedBy(new_contents)
11268+        assert self._servermap.last_update_mode == MODE_WRITE
11269+
11270+        return self._upload(new_contents)
11271+
11272+
11273     def modify(self, modifier, backoffer=None):
11274         """I use a modifier callback to apply a change to the mutable file.
11275         I implement the following pseudocode::
11276hunk ./src/allmydata/mutable/filenode.py 770
11277         backoffer should not invoke any methods on this MutableFileNode
11278         instance, and it needs to be highly conscious of deadlock issues.
11279         """
11280+        assert not self.is_readonly()
11281+
11282         return self._do_serialized(self._modify, modifier, backoffer)
11283hunk ./src/allmydata/mutable/filenode.py 773
11284+
11285+
11286     def _modify(self, modifier, backoffer):
11287hunk ./src/allmydata/mutable/filenode.py 776
11288-        servermap = ServerMap()
11289         if backoffer is None:
11290             backoffer = BackoffAgent().delay
11291hunk ./src/allmydata/mutable/filenode.py 778
11292-        return self._modify_and_retry(servermap, modifier, backoffer, True)
11293-    def _modify_and_retry(self, servermap, modifier, backoffer, first_time):
11294-        d = self._modify_once(servermap, modifier, first_time)
11295+        return self._modify_and_retry(modifier, backoffer, True)
11296+
11297+
11298+    def _modify_and_retry(self, modifier, backoffer, first_time):
11299+        """
11300+        I try to apply modifier to the contents of this version of the
11301+        mutable file. If I succeed, I return an UploadResults instance
11302+        describing my success. If I fail, I try again after waiting for
11303+        a little bit.
11304+        """
11305+        log.msg("doing modify")
11306+        d = self._modify_once(modifier, first_time)
11307         def _retry(f):
11308             f.trap(UncoordinatedWriteError)
11309             d2 = defer.maybeDeferred(backoffer, self, f)
11310hunk ./src/allmydata/mutable/filenode.py 794
11311             d2.addCallback(lambda ignored:
11312-                           self._modify_and_retry(servermap, modifier,
11313+                           self._modify_and_retry(modifier,
11314                                                   backoffer, False))
11315             return d2
11316         d.addErrback(_retry)
11317hunk ./src/allmydata/mutable/filenode.py 799
11318         return d
11319-    def _modify_once(self, servermap, modifier, first_time):
11320-        d = self._update_servermap(servermap, MODE_WRITE)
11321-        d.addCallback(self._once_updated_download_best_version, servermap)
11322+
11323+
11324+    def _modify_once(self, modifier, first_time):
11325+        """
11326+        I attempt to apply a modifier to the contents of the mutable
11327+        file.
11328+        """
11329+        assert self._servermap.last_update_mode == MODE_WRITE
11330+
11331+        # download_to_data is serialized, so we have to call this to
11332+        # avoid deadlock.
11333+        d = self._try_to_download_data()
11334         def _apply(old_contents):
11335hunk ./src/allmydata/mutable/filenode.py 812
11336-            new_contents = modifier(old_contents, servermap, first_time)
11337+            new_contents = modifier(old_contents, self._servermap, first_time)
11338             if new_contents is None or new_contents == old_contents:
11339hunk ./src/allmydata/mutable/filenode.py 814
11340+                log.msg("no changes")
11341                 # no changes need to be made
11342                 if first_time:
11343                     return
11344hunk ./src/allmydata/mutable/filenode.py 822
11345                 # recovery when it observes UCWE, we need to do a second
11346                 # publish. See #551 for details. We'll basically loop until
11347                 # we managed an uncontested publish.
11348-                old_uploadable = MutableDataHandle(old_contents)
11349+                old_uploadable = MutableData(old_contents)
11350                 new_contents = old_uploadable
11351             precondition((IMutableUploadable.providedBy(new_contents) or
11352                           new_contents is None),
11353hunk ./src/allmydata/mutable/filenode.py 828
11354                          "Modifier function must return an IMutableUploadable "
11355                          "or None")
11356-            return self._upload(new_contents, servermap)
11357+            return self._upload(new_contents)
11358         d.addCallback(_apply)
11359         return d
11360 
11361hunk ./src/allmydata/mutable/filenode.py 832
11362-    def get_servermap(self, mode):
11363-        return self._do_serialized(self._get_servermap, mode)
11364-    def _get_servermap(self, mode):
11365-        servermap = ServerMap()
11366-        return self._update_servermap(servermap, mode)
11367-    def _update_servermap(self, servermap, mode):
11368-        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11369-                             mode)
11370-        if self._history:
11371-            self._history.notify_mapupdate(u.get_status())
11372-        return u.update()
11373 
11374hunk ./src/allmydata/mutable/filenode.py 833
11375-    def download_version(self, servermap, version, fetch_privkey=False):
11376-        return self._do_serialized(self._try_once_to_download_version,
11377-                                   servermap, version, fetch_privkey)
11378-    def _try_once_to_download_version(self, servermap, version,
11379-                                      fetch_privkey=False):
11380-        r = Retrieve(self, servermap, version, fetch_privkey)
11381+    def is_readonly(self):
11382+        """
11383+        I return True if this MutableFileVersion provides no write
11384+        access to the file that it encapsulates, and False if it
11385+        provides the ability to modify the file.
11386+        """
11387+        return self._writekey is None
11388+
11389+
11390+    def is_mutable(self):
11391+        """
11392+        I return True, since mutable files are always mutable by
11393+        somebody.
11394+        """
11395+        return True
11396+
11397+
11398+    def get_storage_index(self):
11399+        """
11400+        I return the storage index of the reference that I encapsulate.
11401+        """
11402+        return self._storage_index
11403+
11404+
11405+    def get_size(self):
11406+        """
11407+        I return the length, in bytes, of this readable object.
11408+        """
11409+        return self._servermap.size_of_version(self._version)
11410+
11411+
11412+    def download_to_data(self, fetch_privkey=False):
11413+        """
11414+        I return a Deferred that fires with the contents of this
11415+        readable object as a byte string.
11416+
11417+        """
11418+        c = consumer.MemoryConsumer()
11419+        d = self.read(c, fetch_privkey=fetch_privkey)
11420+        d.addCallback(lambda mc: "".join(mc.chunks))
11421+        return d
11422+
11423+
11424+    def _try_to_download_data(self):
11425+        """
11426+        I am an unserialized cousin of download_to_data; I am called
11427+        from the children of modify() to download the data associated
11428+        with this mutable version.
11429+        """
11430+        c = consumer.MemoryConsumer()
11431+        # modify will almost certainly write, so we need the privkey.
11432+        d = self._read(c, fetch_privkey=True)
11433+        d.addCallback(lambda mc: "".join(mc.chunks))
11434+        return d
11435+
11436+
11437+    def _update_servermap(self, mode=MODE_READ):
11438+        """
11439+        I update our Servermap according to my mode argument. I return a
11440+        Deferred that fires with None when this has finished. The
11441+        updated Servermap will be at self._servermap in that case.
11442+        """
11443+        d = self._node.get_servermap(mode)
11444+
11445+        def _got_servermap(servermap):
11446+            assert servermap.last_update_mode == mode
11447+
11448+            self._servermap = servermap
11449+        d.addCallback(_got_servermap)
11450+        return d
11451+
11452+
11453+    def read(self, consumer, offset=0, size=None, fetch_privkey=False):
11454+        """
11455+        I read a portion (possibly all) of the mutable file that I
11456+        reference into consumer.
11457+        """
11458+        return self._do_serialized(self._read, consumer, offset, size,
11459+                                   fetch_privkey)
11460+
11461+
11462+    def _read(self, consumer, offset=0, size=None, fetch_privkey=False):
11463+        """
11464+        I am the serialized companion of read.
11465+        """
11466+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
11467         if self._history:
11468             self._history.notify_retrieve(r.get_status())
11469hunk ./src/allmydata/mutable/filenode.py 921
11470-        d = r.download()
11471-        d.addCallback(self._downloaded_version)
11472+        d = r.download(consumer, offset, size)
11473+        return d
11474+
11475+
11476+    def _do_serialized(self, cb, *args, **kwargs):
11477+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11478+        # other serialized methods within this (or any other)
11479+        # MutableFileNode. The callable should be a bound method of this same
11480+        # MFN instance.
11481+        d = defer.Deferred()
11482+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11483+        # we need to put off d.callback until this Deferred is finished being
11484+        # processed. Otherwise the caller's subsequent activities (like,
11485+        # doing other things with this node) can cause reentrancy problems in
11486+        # the Deferred code itself
11487+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11488+        # add a log.err just in case something really weird happens, because
11489+        # self._serializer stays around forever, therefore we won't see the
11490+        # usual Unhandled Error in Deferred that would give us a hint.
11491+        self._serializer.addErrback(log.err)
11492         return d
11493hunk ./src/allmydata/mutable/filenode.py 942
11494-    def _downloaded_version(self, data):
11495-        self._most_recent_size = len(data)
11496-        return data
11497 
11498hunk ./src/allmydata/mutable/filenode.py 943
11499-    def upload(self, new_contents, servermap):
11500-        return self._do_serialized(self._upload, new_contents, servermap)
11501-    def _upload(self, new_contents, servermap):
11502-        assert self._pubkey, "update_servermap must be called before publish"
11503-        assert IMutableUploadable.providedBy(new_contents)
11504 
11505hunk ./src/allmydata/mutable/filenode.py 944
11506-        p = Publish(self, self._storage_broker, servermap)
11507+    def _upload(self, new_contents):
11508+        #assert self._pubkey, "update_servermap must be called before publish"
11509+        p = Publish(self._node, self._storage_broker, self._servermap)
11510         if self._history:
11511hunk ./src/allmydata/mutable/filenode.py 948
11512-            self._history.notify_publish(p.get_status(), new_contents.get_size())
11513+            self._history.notify_publish(p.get_status(),
11514+                                         new_contents.get_size())
11515         d = p.publish(new_contents)
11516         d.addCallback(self._did_upload, new_contents.get_size())
11517         return d
11518hunk ./src/allmydata/mutable/filenode.py 953
11519-    def _did_upload(self, res, size):
11520-        self._most_recent_size = size
11521-        return res
11522-
11523-
11524-    def set_version(self, version):
11525-        # I can be set in two ways:
11526-        #  1. When the node is created.
11527-        #  2. (for an existing share) when the Servermap is updated
11528-        #     before I am read.
11529-        assert version in (MDMF_VERSION, SDMF_VERSION)
11530-        self._protocol_version = version
11531 
11532 
11533hunk ./src/allmydata/mutable/filenode.py 955
11534-    def get_version(self):
11535-        return self._protocol_version
11536+    def _did_upload(self, res, size):
11537+        self._size = size
11538+        return res
11539}
11540[mutable/publish.py: enable segmented uploading for big files, change constants and wording, change MutableDataHandle to MutableData
11541Kevan Carstensen <kevan@isnotajoke.com>**20100717014549
11542 Ignore-this: f736c60c90ff09c98544af17146cf654
11543] {
11544hunk ./src/allmydata/mutable/publish.py 145
11545 
11546         self.data = newdata
11547         self.datalength = newdata.get_size()
11548+        if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
11549+            self._version = MDMF_VERSION
11550+        else:
11551+            self._version = SDMF_VERSION
11552 
11553         self.log("starting publish, datalen is %s" % self.datalength)
11554         self._status.set_size(self.datalength)
11555hunk ./src/allmydata/mutable/publish.py 1007
11556             old_position = self._filehandle.tell()
11557             # Seek to the end of the file by seeking 0 bytes from the
11558             # file's end
11559-            self._filehandle.seek(0, os.SEEK_END)
11560+            self._filehandle.seek(0, 2) # 2 == os.SEEK_END in 2.5+
11561             self._size = self._filehandle.tell()
11562             # Restore the previous position, in case this was called
11563             # after a read.
11564hunk ./src/allmydata/mutable/publish.py 1022
11565         """
11566         I return some data (up to length bytes) from my filehandle.
11567 
11568-        In most cases, I return length bytes. If I don't, it is because
11569-        length is longer than the distance between my current position
11570-        in the file that I represent and its end. In that case, I return
11571-        as many bytes as I can before going over the EOF.
11572+        In most cases, I return length bytes, but sometimes I won't --
11573+        for example, if I am asked to read beyond the end of a file, or
11574+        an error occurs.
11575         """
11576         return [self._filehandle.read(length)]
11577 
11578hunk ./src/allmydata/mutable/publish.py 1037
11579         self._filehandle.close()
11580 
11581 
11582-class MutableDataHandle(MutableFileHandle):
11583+class MutableData(MutableFileHandle):
11584     """
11585     I am a mutable uploadable built around a string, which I then cast
11586     into a StringIO and treat as a filehandle.
11587}
11588[immutable/filenode.py: fix broken implementation of #993 interfaces
11589Kevan Carstensen <kevan@isnotajoke.com>**20100717015049
11590 Ignore-this: 19ac8cf5d31ac88d4a1998ac342db004
11591] {
11592hunk ./src/allmydata/immutable/filenode.py 326
11593         immutable files can have only one version, we just return the
11594         current filenode.
11595         """
11596-        return self
11597+        return defer.succeed(self)
11598 
11599 
11600     def download_best_version(self):
11601hunk ./src/allmydata/immutable/filenode.py 412
11602 
11603     # IReadable, IFileNode, IFilesystemNode
11604     def get_best_readable_version(self):
11605-        return self
11606+        return defer.succeed(self)
11607 
11608 
11609     def download_best_version(self):
11610}
11611[mutable/retrieve.py: alter Retrieve so that it can download parts of mutable files
11612Kevan Carstensen <kevan@isnotajoke.com>**20100717015123
11613 Ignore-this: f49a6d3c05afc784aff0a4c63964a3e5
11614] {
11615hunk ./src/allmydata/mutable/retrieve.py 7
11616 from zope.interface import implements
11617 from twisted.internet import defer
11618 from twisted.python import failure
11619+from twisted.internet.interfaces import IPushProducer, IConsumer
11620 from foolscap.api import DeadReferenceError, eventually, fireEventually
11621 from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
11622                                  MDMF_VERSION, SDMF_VERSION
11623hunk ./src/allmydata/mutable/retrieve.py 86
11624     # times, and each will have a separate response chain. However the
11625     # Retrieve object will remain tied to a specific version of the file, and
11626     # will use a single ServerMap instance.
11627+    implements(IPushProducer)
11628 
11629     def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
11630                  verify=False):
11631hunk ./src/allmydata/mutable/retrieve.py 129
11632         # 3. When we are validating readers, we need to validate the
11633         #    signature on the prefix. Do we? We already do this in the
11634         #    servermap update?
11635-        #
11636-        # (just work on 1 and 2 for now, I guess)
11637         self._verify = False
11638         if verify:
11639             self._verify = True
11640hunk ./src/allmydata/mutable/retrieve.py 143
11641         self._status.set_size(datalength)
11642         self._status.set_encoding(k, N)
11643         self.readers = {}
11644+        self._paused = False
11645+        self._paused_deferred = None
11646+
11647 
11648     def get_status(self):
11649         return self._status
11650hunk ./src/allmydata/mutable/retrieve.py 157
11651             kwargs["facility"] = "tahoe.mutable.retrieve"
11652         return log.msg(*args, **kwargs)
11653 
11654-    def download(self):
11655+
11656+    ###################
11657+    # IPushProducer
11658+
11659+    def pauseProducing(self):
11660+        """
11661+        I am called by my download target if we have produced too much
11662+        data for it to handle. I make the downloader stop producing new
11663+        data until my resumeProducing method is called.
11664+        """
11665+        if self._paused:
11666+            return
11667+
11668+        # fired when the download is unpaused.
11669+        self._pause_deferred = defer.Deferred()
11670+        self._paused = True
11671+
11672+
11673+    def resumeProducing(self):
11674+        """
11675+        I am called by my download target once it is ready to begin
11676+        receiving data again.
11677+        """
11678+        if not self._paused:
11679+            return
11680+
11681+        self._paused = False
11682+        p = self._pause_deferred
11683+        self._pause_deferred = None
11684+        eventually(p.callback, None)
11685+
11686+
11687+    def _check_for_paused(self, res):
11688+        """
11689+        I am called just before a write to the consumer. I return a
11690+        Deferred that eventually fires with the data that is to be
11691+        written to the consumer. If the download has not been paused,
11692+        the Deferred fires immediately. Otherwise, the Deferred fires
11693+        when the downloader is unpaused.
11694+        """
11695+        if self._paused:
11696+            d = defer.Deferred()
11697+            self._pause_defered.addCallback(lambda ignored: d.callback(res))
11698+            return d
11699+        return defer.succeed(res)
11700+
11701+
11702+    def download(self, consumer=None, offset=0, size=None):
11703+        assert IConsumer.providedBy(consumer) or self._verify
11704+
11705+        if consumer:
11706+            self._consumer = consumer
11707+            # we provide IPushProducer, so streaming=True, per
11708+            # IConsumer.
11709+            self._consumer.registerProducer(self, streaming=True)
11710+
11711         self._done_deferred = defer.Deferred()
11712         self._started = time.time()
11713         self._status.set_status("Retrieving Shares")
11714hunk ./src/allmydata/mutable/retrieve.py 217
11715 
11716+        self._offset = offset
11717+        self._read_length = size
11718+
11719         # first, which servers can we use?
11720         versionmap = self.servermap.make_versionmap()
11721         shares = versionmap[self.verinfo]
11722hunk ./src/allmydata/mutable/retrieve.py 278
11723         assert len(self.remaining_sharemap) >= k
11724 
11725         self.log("starting download")
11726+        self._paused = False
11727         self._add_active_peers()
11728         # The download process beyond this is a state machine.
11729         # _add_active_peers will select the peers that we want to use
11730hunk ./src/allmydata/mutable/retrieve.py 324
11731 
11732         self._segment_decoder = codec.CRSDecoder()
11733         self._segment_decoder.set_params(segsize, k, n)
11734-        self._current_segment = 0
11735 
11736         if  not self._tail_data_size:
11737             self._tail_data_size = segsize
11738hunk ./src/allmydata/mutable/retrieve.py 349
11739             # So we don't have to do this later.
11740             self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
11741 
11742-        # If we have more than one segment, we are an SDMF file, which
11743-        # means that we need to validate the salts as we receive them.
11744-        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
11745-        self._salt_hash_tree[0] = IV # from the prefix.
11746+        # Our last task is to tell the downloader where to start and
11747+        # where to stop. We use three parameters for that:
11748+        #   - self._start_segment: the segment that we need to start
11749+        #     downloading from.
11750+        #   - self._current_segment: the next segment that we need to
11751+        #     download.
11752+        #   - self._last_segment: The last segment that we were asked to
11753+        #     download.
11754+        #
11755+        #  We say that the download is complete when
11756+        #  self._current_segment > self._last_segment. We use
11757+        #  self._start_segment and self._last_segment to know when to
11758+        #  strip things off of segments, and how much to strip.
11759+        if self._offset:
11760+            self.log("got offset: %d" % self._offset)
11761+            # our start segment is the first segment containing the
11762+            # offset we were given.
11763+            start = mathutil.div_ceil(self._offset,
11764+                                      self._segment_size)
11765+            # this gets us the first segment after self._offset. Then
11766+            # our start segment is the one before it.
11767+            start -= 1
11768+
11769+            assert start < self._num_segments
11770+            self._start_segment = start
11771+            self.log("got start segment: %d" % self._start_segment)
11772+        else:
11773+            self._start_segment = 0
11774+
11775+
11776+        if self._read_length:
11777+            # our end segment is the last segment containing part of the
11778+            # segment that we were asked to read.
11779+            self.log("got read length %d" % self._read_length)
11780+            end_data = self._offset + self._read_length
11781+            end = mathutil.div_ceil(end_data,
11782+                                    self._segment_size)
11783+            end -= 1
11784+            assert end < self._num_segments
11785+            self._last_segment = end
11786+            self.log("got end segment: %d" % self._last_segment)
11787+        else:
11788+            self._last_segment = self._num_segments - 1
11789 
11790hunk ./src/allmydata/mutable/retrieve.py 393
11791+        self._current_segment = self._start_segment
11792 
11793     def _add_active_peers(self):
11794         """
11795hunk ./src/allmydata/mutable/retrieve.py 637
11796         that this Retrieve is currently responsible for downloading.
11797         """
11798         assert len(self._active_readers) >= self._required_shares
11799-        if self._current_segment < self._num_segments:
11800+        if self._current_segment <= self._last_segment:
11801             d = self._process_segment(self._current_segment)
11802         else:
11803             d = defer.succeed(None)
11804hunk ./src/allmydata/mutable/retrieve.py 701
11805             d.addCallback(self._decrypt_segment)
11806             d.addErrback(self._validation_or_decoding_failed,
11807                          self._active_readers)
11808+            # check to see whether we've been paused before writing
11809+            # anything.
11810+            d.addCallback(self._check_for_paused)
11811             d.addCallback(self._set_segment)
11812             return d
11813         else:
11814hunk ./src/allmydata/mutable/retrieve.py 716
11815         target that is handling the file download.
11816         """
11817         self.log("got plaintext for segment %d" % self._current_segment)
11818-        self._plaintext += segment
11819+        if self._current_segment == self._start_segment:
11820+            # We're on the first segment. It's possible that we want
11821+            # only some part of the end of this segment, and that we
11822+            # just downloaded the whole thing to get that part. If so,
11823+            # we need to account for that and give the reader just the
11824+            # data that they want.
11825+            n = self._offset % self._segment_size
11826+            self.log("stripping %d bytes off of the first segment" % n)
11827+            self.log("original segment length: %d" % len(segment))
11828+            segment = segment[n:]
11829+            self.log("new segment length: %d" % len(segment))
11830+
11831+        if self._current_segment == self._last_segment and self._read_length is not None:
11832+            # We're on the last segment. It's possible that we only want
11833+            # part of the beginning of this segment, and that we
11834+            # downloaded the whole thing anyway. Make sure to give the
11835+            # caller only the portion of the segment that they want to
11836+            # receive.
11837+            extra = self._read_length
11838+            if self._start_segment != self._last_segment:
11839+                extra -= self._segment_size - \
11840+                            (self._offset % self._segment_size)
11841+            extra %= self._segment_size
11842+            self.log("original segment length: %d" % len(segment))
11843+            segment = segment[:extra]
11844+            self.log("new segment length: %d" % len(segment))
11845+            self.log("only taking %d bytes of the last segment" % extra)
11846+
11847+        if not self._verify:
11848+            self._consumer.write(segment)
11849+        else:
11850+            # we don't care about the plaintext if we are doing a verify.
11851+            segment = None
11852         self._current_segment += 1
11853 
11854 
11855hunk ./src/allmydata/mutable/retrieve.py 848
11856                                         reader.shnum,
11857                                         "corrupt hashes: %s" % e)
11858 
11859-        # TODO: Validate the salt, too.
11860         self.log('share %d is valid for segment %d' % (reader.shnum,
11861                                                        segnum))
11862         return {reader.shnum: (block, salt)}
11863hunk ./src/allmydata/mutable/retrieve.py 1014
11864               _done_deferred to errback.
11865         """
11866         self.log("checking for doneness")
11867-        if self._current_segment == self._num_segments:
11868+        if self._current_segment > self._last_segment:
11869             # No more segments to download, we're done.
11870             self.log("got plaintext, done")
11871             return self._done()
11872hunk ./src/allmydata/mutable/retrieve.py 1043
11873             ret = list(self._bad_shares)
11874             self.log("done verifying, found %d bad shares" % len(ret))
11875         else:
11876-            ret = self._plaintext
11877+            # TODO: upload status here?
11878+            ret = self._consumer
11879+            self._consumer.unregisterProducer()
11880         eventually(self._done_deferred.callback, ret)
11881 
11882 
11883hunk ./src/allmydata/mutable/retrieve.py 1066
11884                       "encoding %(k)d-of-%(n)d")
11885             args = {"have": self._current_segment,
11886                     "total": self._num_segments,
11887+                    "need": self._last_segment,
11888                     "k": self._required_shares,
11889                     "n": self._total_shares,
11890                     "bad": len(self._bad_shares)}
11891}
11892[change MutableDataHandle to MutableData in code.
11893Kevan Carstensen <kevan@isnotajoke.com>**20100717015210
11894 Ignore-this: f85ae425eabc21b47ad60bd6bf1f7dec
11895] {
11896hunk ./src/allmydata/dirnode.py 11
11897 from allmydata.mutable.common import NotWriteableError
11898 from allmydata.mutable.filenode import MutableFileNode
11899 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
11900-from allmydata.mutable.publish import MutableDataHandle
11901+from allmydata.mutable.publish import MutableData
11902 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
11903      IImmutableFileNode, IMutableFileNode, \
11904      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
11905hunk ./src/allmydata/dirnode.py 104
11906 
11907         del children[self.name]
11908         new_contents = self.node._pack_contents(children)
11909-        uploadable = MutableDataHandle(new_contents)
11910+        uploadable = MutableData(new_contents)
11911         return uploadable
11912 
11913 
11914hunk ./src/allmydata/dirnode.py 130
11915 
11916         children[name] = (child, metadata)
11917         new_contents = self.node._pack_contents(children)
11918-        uploadable = MutableDataHandle(new_contents)
11919+        uploadable = MutableData(new_contents)
11920         return uploadable
11921 
11922 
11923hunk ./src/allmydata/dirnode.py 175
11924 
11925             children[name] = (child, metadata)
11926         new_contents = self.node._pack_contents(children)
11927-        uploadable = MutableDataHandle(new_contents)
11928+        uploadable = MutableData(new_contents)
11929         return uploadable
11930 
11931 def _encrypt_rw_uri(writekey, rw_uri):
11932hunk ./src/allmydata/mutable/repairer.py 5
11933 from zope.interface import implements
11934 from twisted.internet import defer
11935 from allmydata.interfaces import IRepairResults, ICheckResults
11936-from allmydata.mutable.publish import MutableDataHandle
11937+from allmydata.mutable.publish import MutableData
11938 
11939 class RepairResults:
11940     implements(IRepairResults)
11941hunk ./src/allmydata/mutable/repairer.py 109
11942 
11943         d = self.node.download_version(smap, best_version, fetch_privkey=True)
11944         d.addCallback(lambda data:
11945-            MutableDataHandle(data))
11946+            MutableData(data))
11947         d.addCallback(self.node.upload, smap)
11948         d.addCallback(self.get_results, smap)
11949         return d
11950hunk ./src/allmydata/nodemaker.py 9
11951 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
11952 from allmydata.immutable.upload import Data
11953 from allmydata.mutable.filenode import MutableFileNode
11954-from allmydata.mutable.publish import MutableDataHandle
11955+from allmydata.mutable.publish import MutableData
11956 from allmydata.dirnode import DirectoryNode, pack_children
11957 from allmydata.unknown import UnknownNode
11958 from allmydata import uri
11959merger 0.0 (
11960merger 0.0 (
11961hunk ./src/allmydata/nodemaker.py 107
11962-                                     pack_children(n, initial_children),
11963+                                     MutableDataHandle(
11964+                                        pack_children(n, initial_children)),
11965merger 0.0 (
11966hunk ./src/allmydata/nodemaker.py 107
11967-                                     pack_children(n, initial_children))
11968+                                     pack_children(n, initial_children),
11969+                                     version)
11970hunk ./src/allmydata/nodemaker.py 107
11971-                                     pack_children(n, initial_children))
11972+                                     pack_children(initial_children, n.get_writekey()))
11973)
11974)
11975hunk ./src/allmydata/nodemaker.py 107
11976-                                     MutableDataHandle(
11977+                                     MutableData(
11978)
11979hunk ./src/allmydata/test/common.py 18
11980      DeepCheckResults, DeepCheckAndRepairResults
11981 from allmydata.mutable.common import CorruptShareError
11982 from allmydata.mutable.layout import unpack_header
11983-from allmydata.mutable.publish import MutableDataHandle
11984+from allmydata.mutable.publish import MutableData
11985 from allmydata.storage.server import storage_index_to_dir
11986 from allmydata.storage.mutable import MutableShareFile
11987 from allmydata.util import hashutil, log, fileutil, pollmixin
11988hunk ./src/allmydata/test/common.py 192
11989         return defer.succeed(self)
11990     def _get_initial_contents(self, contents):
11991         if contents is None:
11992-            return MutableDataHandle("")
11993+            return MutableData("")
11994 
11995         if IMutableUploadable.providedBy(contents):
11996             return contents
11997hunk ./src/allmydata/test/test_checker.py 11
11998 from allmydata.test.no_network import GridTestMixin
11999 from allmydata.immutable.upload import Data
12000 from allmydata.test.common_web import WebRenderingMixin
12001-from allmydata.mutable.publish import MutableDataHandle
12002+from allmydata.mutable.publish import MutableData
12003 
12004 class FakeClient:
12005     def get_storage_broker(self):
12006hunk ./src/allmydata/test/test_checker.py 292
12007             self.imm = c0.create_node_from_uri(ur.uri)
12008         d.addCallback(_stash_immutable)
12009         d.addCallback(lambda ign:
12010-            c0.create_mutable_file(MutableDataHandle("contents")))
12011+            c0.create_mutable_file(MutableData("contents")))
12012         def _stash_mutable(node):
12013             self.mut = node
12014         d.addCallback(_stash_mutable)
12015hunk ./src/allmydata/test/test_cli.py 12
12016 from allmydata.util import fileutil, hashutil, base32
12017 from allmydata import uri
12018 from allmydata.immutable import upload
12019-from allmydata.mutable.publish import MutableDataHandle
12020+from allmydata.mutable.publish import MutableData
12021 from allmydata.dirnode import normalize
12022 
12023 # Test that the scripts can be imported -- although the actual tests of their
12024hunk ./src/allmydata/test/test_cli.py 1975
12025         self.set_up_grid()
12026         c0 = self.g.clients[0]
12027         DATA = "data" * 100
12028-        DATA_uploadable = MutableDataHandle(DATA)
12029+        DATA_uploadable = MutableData(DATA)
12030         d = c0.create_mutable_file(DATA_uploadable)
12031         def _stash_uri(n):
12032             self.uri = n.get_uri()
12033hunk ./src/allmydata/test/test_cli.py 2078
12034                                                         convergence="")))
12035         d.addCallback(_stash_uri, "small")
12036         d.addCallback(lambda ign:
12037-            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
12038+            c0.create_mutable_file(MutableData(DATA+"1")))
12039         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
12040         d.addCallback(_stash_uri, "mutable")
12041 
12042hunk ./src/allmydata/test/test_deepcheck.py 9
12043 from twisted.internet import threads # CLI tests use deferToThread
12044 from allmydata.immutable import upload
12045 from allmydata.mutable.common import UnrecoverableFileError
12046-from allmydata.mutable.publish import MutableDataHandle
12047+from allmydata.mutable.publish import MutableData
12048 from allmydata.util import idlib
12049 from allmydata.util import base32
12050 from allmydata.scripts import runner
12051hunk ./src/allmydata/test/test_deepcheck.py 38
12052         self.basedir = "deepcheck/MutableChecker/good"
12053         self.set_up_grid()
12054         CONTENTS = "a little bit of data"
12055-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12056+        CONTENTS_uploadable = MutableData(CONTENTS)
12057         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12058         def _created(node):
12059             self.node = node
12060hunk ./src/allmydata/test/test_deepcheck.py 61
12061         self.basedir = "deepcheck/MutableChecker/corrupt"
12062         self.set_up_grid()
12063         CONTENTS = "a little bit of data"
12064-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12065+        CONTENTS_uploadable = MutableData(CONTENTS)
12066         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12067         def _stash_and_corrupt(node):
12068             self.node = node
12069hunk ./src/allmydata/test/test_deepcheck.py 99
12070         self.basedir = "deepcheck/MutableChecker/delete_share"
12071         self.set_up_grid()
12072         CONTENTS = "a little bit of data"
12073-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12074+        CONTENTS_uploadable = MutableData(CONTENTS)
12075         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12076         def _stash_and_delete(node):
12077             self.node = node
12078hunk ./src/allmydata/test/test_deepcheck.py 224
12079             self.root_uri = n.get_uri()
12080         d.addCallback(_created_root)
12081         d.addCallback(lambda ign:
12082-            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
12083+            c0.create_mutable_file(MutableData("mutable file contents")))
12084         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
12085         def _created_mutable(n):
12086             self.mutable = n
12087hunk ./src/allmydata/test/test_deepcheck.py 965
12088     def create_mangled(self, ignored, name):
12089         nodetype, mangletype = name.split("-", 1)
12090         if nodetype == "mutable":
12091-            mutable_uploadable = MutableDataHandle("mutable file contents")
12092+            mutable_uploadable = MutableData("mutable file contents")
12093             d = self.g.clients[0].create_mutable_file(mutable_uploadable)
12094             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
12095         elif nodetype == "large":
12096hunk ./src/allmydata/test/test_hung_server.py 10
12097 from allmydata.util.consumer import download_to_data
12098 from allmydata.immutable import upload
12099 from allmydata.mutable.common import UnrecoverableFileError
12100-from allmydata.mutable.publish import MutableDataHandle
12101+from allmydata.mutable.publish import MutableData
12102 from allmydata.storage.common import storage_index_to_dir
12103 from allmydata.test.no_network import GridTestMixin
12104 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
12105hunk ./src/allmydata/test/test_hung_server.py 96
12106         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
12107 
12108         if mutable:
12109-            uploadable = MutableDataHandle(mutable_plaintext)
12110+            uploadable = MutableData(mutable_plaintext)
12111             d = nm.create_mutable_file(uploadable)
12112             def _uploaded_mutable(node):
12113                 self.uri = node.get_uri()
12114hunk ./src/allmydata/test/test_mutable.py 27
12115      NotEnoughServersError, CorruptShareError
12116 from allmydata.mutable.retrieve import Retrieve
12117 from allmydata.mutable.publish import Publish, MutableFileHandle, \
12118-                                      MutableDataHandle
12119+                                      MutableData
12120 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
12121 from allmydata.mutable.layout import unpack_header, unpack_share, \
12122                                      MDMFSlotReadProxy
12123hunk ./src/allmydata/test/test_mutable.py 297
12124             d.addCallback(lambda smap: smap.dump(StringIO()))
12125             d.addCallback(lambda sio:
12126                           self.failUnless("3-of-10" in sio.getvalue()))
12127-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
12128+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
12129             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
12130             d.addCallback(lambda res: n.download_best_version())
12131             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12132hunk ./src/allmydata/test/test_mutable.py 304
12133             d.addCallback(lambda res: n.get_size_of_best_version())
12134             d.addCallback(lambda size:
12135                           self.failUnlessEqual(size, len("contents 1")))
12136-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12137+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12138             d.addCallback(lambda res: n.download_best_version())
12139             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12140             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12141hunk ./src/allmydata/test/test_mutable.py 308
12142-            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
12143+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
12144             d.addCallback(lambda res: n.download_best_version())
12145             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
12146             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
12147hunk ./src/allmydata/test/test_mutable.py 320
12148             # mapupdate-to-retrieve data caching (i.e. make the shares larger
12149             # than the default readsize, which is 2000 bytes). A 15kB file
12150             # will have 5kB shares.
12151-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
12152+            d.addCallback(lambda res: n.overwrite(MutableData("large size file" * 1000)))
12153             d.addCallback(lambda res: n.download_best_version())
12154             d.addCallback(lambda res:
12155                           self.failUnlessEqual(res, "large size file" * 1000))
12156hunk ./src/allmydata/test/test_mutable.py 343
12157             # to make them big enough to force the file to be uploaded
12158             # in more than one segment.
12159             big_contents = "contents1" * 100000 # about 900 KiB
12160-            big_contents_uploadable = MutableDataHandle(big_contents)
12161+            big_contents_uploadable = MutableData(big_contents)
12162             d.addCallback(lambda ignored:
12163                 n.overwrite(big_contents_uploadable))
12164             d.addCallback(lambda ignored:
12165hunk ./src/allmydata/test/test_mutable.py 355
12166             # segments, so that we make the downloader deal with
12167             # multiple segments.
12168             bigger_contents = "contents2" * 1000000 # about 9MiB
12169-            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
12170+            bigger_contents_uploadable = MutableData(bigger_contents)
12171             d.addCallback(lambda ignored:
12172                 n.overwrite(bigger_contents_uploadable))
12173             d.addCallback(lambda ignored:
12174hunk ./src/allmydata/test/test_mutable.py 368
12175 
12176 
12177     def test_create_with_initial_contents(self):
12178-        upload1 = MutableDataHandle("contents 1")
12179+        upload1 = MutableData("contents 1")
12180         d = self.nodemaker.create_mutable_file(upload1)
12181         def _created(n):
12182             d = n.download_best_version()
12183hunk ./src/allmydata/test/test_mutable.py 373
12184             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12185-            upload2 = MutableDataHandle("contents 2")
12186+            upload2 = MutableData("contents 2")
12187             d.addCallback(lambda res: n.overwrite(upload2))
12188             d.addCallback(lambda res: n.download_best_version())
12189             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12190hunk ./src/allmydata/test/test_mutable.py 385
12191 
12192     def test_create_mdmf_with_initial_contents(self):
12193         initial_contents = "foobarbaz" * 131072 # 900KiB
12194-        initial_contents_uploadable = MutableDataHandle(initial_contents)
12195+        initial_contents_uploadable = MutableData(initial_contents)
12196         d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
12197                                                version=MDMF_VERSION)
12198         def _created(n):
12199hunk ./src/allmydata/test/test_mutable.py 392
12200             d = n.download_best_version()
12201             d.addCallback(lambda data:
12202                 self.failUnlessEqual(data, initial_contents))
12203-            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
12204+            uploadable2 = MutableData(initial_contents + "foobarbaz")
12205             d.addCallback(lambda ignored:
12206                 n.overwrite(uploadable2))
12207             d.addCallback(lambda ignored:
12208hunk ./src/allmydata/test/test_mutable.py 413
12209             key = n.get_writekey()
12210             self.failUnless(isinstance(key, str), key)
12211             self.failUnlessEqual(len(key), 16) # AES key size
12212-            return MutableDataHandle(data)
12213+            return MutableData(data)
12214         d = self.nodemaker.create_mutable_file(_make_contents)
12215         def _created(n):
12216             return n.download_best_version()
12217hunk ./src/allmydata/test/test_mutable.py 429
12218             key = n.get_writekey()
12219             self.failUnless(isinstance(key, str), key)
12220             self.failUnlessEqual(len(key), 16)
12221-            return MutableDataHandle(data)
12222+            return MutableData(data)
12223         d = self.nodemaker.create_mutable_file(_make_contents,
12224                                                version=MDMF_VERSION)
12225         d.addCallback(lambda n:
12226hunk ./src/allmydata/test/test_mutable.py 441
12227 
12228     def test_create_with_too_large_contents(self):
12229         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
12230-        BIG_uploadable = MutableDataHandle(BIG)
12231+        BIG_uploadable = MutableData(BIG)
12232         d = self.nodemaker.create_mutable_file(BIG_uploadable)
12233         def _created(n):
12234hunk ./src/allmydata/test/test_mutable.py 444
12235-            other_BIG_uploadable = MutableDataHandle(BIG)
12236+            other_BIG_uploadable = MutableData(BIG)
12237             d = n.overwrite(other_BIG_uploadable)
12238             return d
12239         d.addCallback(_created)
12240hunk ./src/allmydata/test/test_mutable.py 460
12241     def test_modify(self):
12242         def _modifier(old_contents, servermap, first_time):
12243             new_contents = old_contents + "line2"
12244-            return MutableDataHandle(new_contents)
12245+            return MutableData(new_contents)
12246         def _non_modifier(old_contents, servermap, first_time):
12247hunk ./src/allmydata/test/test_mutable.py 462
12248-            return MutableDataHandle(old_contents)
12249+            return MutableData(old_contents)
12250         def _none_modifier(old_contents, servermap, first_time):
12251             return None
12252         def _error_modifier(old_contents, servermap, first_time):
12253hunk ./src/allmydata/test/test_mutable.py 469
12254             raise ValueError("oops")
12255         def _toobig_modifier(old_contents, servermap, first_time):
12256             new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
12257-            return MutableDataHandle(new_content)
12258+            return MutableData(new_content)
12259         calls = []
12260         def _ucw_error_modifier(old_contents, servermap, first_time):
12261             # simulate an UncoordinatedWriteError once
12262hunk ./src/allmydata/test/test_mutable.py 477
12263             if len(calls) <= 1:
12264                 raise UncoordinatedWriteError("simulated")
12265             new_contents = old_contents + "line3"
12266-            return MutableDataHandle(new_contents)
12267+            return MutableData(new_contents)
12268         def _ucw_error_non_modifier(old_contents, servermap, first_time):
12269             # simulate an UncoordinatedWriteError once, and don't actually
12270             # modify the contents on subsequent invocations
12271hunk ./src/allmydata/test/test_mutable.py 484
12272             calls.append(1)
12273             if len(calls) <= 1:
12274                 raise UncoordinatedWriteError("simulated")
12275-            return MutableDataHandle(old_contents)
12276+            return MutableData(old_contents)
12277 
12278         initial_contents = "line1"
12279hunk ./src/allmydata/test/test_mutable.py 487
12280-        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
12281+        d = self.nodemaker.create_mutable_file(MutableData(initial_contents))
12282         def _created(n):
12283             d = n.modify(_modifier)
12284             d.addCallback(lambda res: n.download_best_version())
12285hunk ./src/allmydata/test/test_mutable.py 548
12286 
12287     def test_modify_backoffer(self):
12288         def _modifier(old_contents, servermap, first_time):
12289-            return MutableDataHandle(old_contents + "line2")
12290+            return MutableData(old_contents + "line2")
12291         calls = []
12292         def _ucw_error_modifier(old_contents, servermap, first_time):
12293             # simulate an UncoordinatedWriteError once
12294hunk ./src/allmydata/test/test_mutable.py 555
12295             calls.append(1)
12296             if len(calls) <= 1:
12297                 raise UncoordinatedWriteError("simulated")
12298-            return MutableDataHandle(old_contents + "line3")
12299+            return MutableData(old_contents + "line3")
12300         def _always_ucw_error_modifier(old_contents, servermap, first_time):
12301             raise UncoordinatedWriteError("simulated")
12302         def _backoff_stopper(node, f):
12303hunk ./src/allmydata/test/test_mutable.py 570
12304         giveuper._delay = 0.1
12305         giveuper.factor = 1
12306 
12307-        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
12308+        d = self.nodemaker.create_mutable_file(MutableData("line1"))
12309         def _created(n):
12310             d = n.modify(_modifier)
12311             d.addCallback(lambda res: n.download_best_version())
12312hunk ./src/allmydata/test/test_mutable.py 620
12313             d.addCallback(lambda smap: smap.dump(StringIO()))
12314             d.addCallback(lambda sio:
12315                           self.failUnless("3-of-10" in sio.getvalue()))
12316-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
12317+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
12318             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
12319             d.addCallback(lambda res: n.download_best_version())
12320             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12321hunk ./src/allmydata/test/test_mutable.py 624
12322-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12323+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12324             d.addCallback(lambda res: n.download_best_version())
12325             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12326             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12327hunk ./src/allmydata/test/test_mutable.py 628
12328-            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
12329+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
12330             d.addCallback(lambda res: n.download_best_version())
12331             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
12332             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
12333hunk ./src/allmydata/test/test_mutable.py 646
12334         # publish a file and create shares, which can then be manipulated
12335         # later.
12336         self.CONTENTS = "New contents go here" * 1000
12337-        self.uploadable = MutableDataHandle(self.CONTENTS)
12338+        self.uploadable = MutableData(self.CONTENTS)
12339         self._storage = FakeStorage()
12340         self._nodemaker = make_nodemaker(self._storage)
12341         self._storage_broker = self._nodemaker.storage_broker
12342hunk ./src/allmydata/test/test_mutable.py 662
12343         # an MDMF file.
12344         # self.CONTENTS should have more than one segment.
12345         self.CONTENTS = "This is an MDMF file" * 100000
12346-        self.uploadable = MutableDataHandle(self.CONTENTS)
12347+        self.uploadable = MutableData(self.CONTENTS)
12348         self._storage = FakeStorage()
12349         self._nodemaker = make_nodemaker(self._storage)
12350         self._storage_broker = self._nodemaker.storage_broker
12351hunk ./src/allmydata/test/test_mutable.py 678
12352         # like publish_one, except that the result is guaranteed to be
12353         # an SDMF file
12354         self.CONTENTS = "This is an SDMF file" * 1000
12355-        self.uploadable = MutableDataHandle(self.CONTENTS)
12356+        self.uploadable = MutableData(self.CONTENTS)
12357         self._storage = FakeStorage()
12358         self._nodemaker = make_nodemaker(self._storage)
12359         self._storage_broker = self._nodemaker.storage_broker
12360hunk ./src/allmydata/test/test_mutable.py 696
12361                          "Contents 2",
12362                          "Contents 3a",
12363                          "Contents 3b"]
12364-        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
12365+        self.uploadables = [MutableData(d) for d in self.CONTENTS]
12366         self._copied_shares = {}
12367         self._storage = FakeStorage()
12368         self._nodemaker = make_nodemaker(self._storage)
12369hunk ./src/allmydata/test/test_mutable.py 826
12370         # create a new file, which is large enough to knock the privkey out
12371         # of the early part of the file
12372         LARGE = "These are Larger contents" * 200 # about 5KB
12373-        LARGE_uploadable = MutableDataHandle(LARGE)
12374+        LARGE_uploadable = MutableData(LARGE)
12375         d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
12376         def _created(large_fn):
12377             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
12378hunk ./src/allmydata/test/test_mutable.py 1842
12379 class MultipleEncodings(unittest.TestCase):
12380     def setUp(self):
12381         self.CONTENTS = "New contents go here"
12382-        self.uploadable = MutableDataHandle(self.CONTENTS)
12383+        self.uploadable = MutableData(self.CONTENTS)
12384         self._storage = FakeStorage()
12385         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
12386         self._storage_broker = self._nodemaker.storage_broker
12387hunk ./src/allmydata/test/test_mutable.py 1872
12388         s = self._storage
12389         s._peers = {} # clear existing storage
12390         p2 = Publish(fn2, self._storage_broker, None)
12391-        uploadable = MutableDataHandle(data)
12392+        uploadable = MutableData(data)
12393         d = p2.publish(uploadable)
12394         def _published(res):
12395             shares = s._peers
12396hunk ./src/allmydata/test/test_mutable.py 2049
12397         self._set_versions(target)
12398 
12399         def _modify(oldversion, servermap, first_time):
12400-            return MutableDataHandle(oldversion + " modified")
12401+            return MutableData(oldversion + " modified")
12402         d = self._fn.modify(_modify)
12403         d.addCallback(lambda res: self._fn.download_best_version())
12404         expected = self.CONTENTS[2] + " modified"
12405hunk ./src/allmydata/test/test_mutable.py 2175
12406         self.basedir = "mutable/Problems/test_publish_surprise"
12407         self.set_up_grid()
12408         nm = self.g.clients[0].nodemaker
12409-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12410+        d = nm.create_mutable_file(MutableData("contents 1"))
12411         def _created(n):
12412             d = defer.succeed(None)
12413             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12414hunk ./src/allmydata/test/test_mutable.py 2185
12415             d.addCallback(_got_smap1)
12416             # then modify the file, leaving the old map untouched
12417             d.addCallback(lambda res: log.msg("starting winning write"))
12418-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12419+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12420             # now attempt to modify the file with the old servermap. This
12421             # will look just like an uncoordinated write, in which every
12422             # single share got updated between our mapupdate and our publish
12423hunk ./src/allmydata/test/test_mutable.py 2194
12424                           self.shouldFail(UncoordinatedWriteError,
12425                                           "test_publish_surprise", None,
12426                                           n.upload,
12427-                                          MutableDataHandle("contents 2a"), self.old_map))
12428+                                          MutableData("contents 2a"), self.old_map))
12429             return d
12430         d.addCallback(_created)
12431         return d
12432hunk ./src/allmydata/test/test_mutable.py 2203
12433         self.basedir = "mutable/Problems/test_retrieve_surprise"
12434         self.set_up_grid()
12435         nm = self.g.clients[0].nodemaker
12436-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12437+        d = nm.create_mutable_file(MutableData("contents 1"))
12438         def _created(n):
12439             d = defer.succeed(None)
12440             d.addCallback(lambda res: n.get_servermap(MODE_READ))
12441hunk ./src/allmydata/test/test_mutable.py 2213
12442             d.addCallback(_got_smap1)
12443             # then modify the file, leaving the old map untouched
12444             d.addCallback(lambda res: log.msg("starting winning write"))
12445-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12446+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12447             # now attempt to retrieve the old version with the old servermap.
12448             # This will look like someone has changed the file since we
12449             # updated the servermap.
12450hunk ./src/allmydata/test/test_mutable.py 2241
12451         self.basedir = "mutable/Problems/test_unexpected_shares"
12452         self.set_up_grid()
12453         nm = self.g.clients[0].nodemaker
12454-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12455+        d = nm.create_mutable_file(MutableData("contents 1"))
12456         def _created(n):
12457             d = defer.succeed(None)
12458             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12459hunk ./src/allmydata/test/test_mutable.py 2253
12460                 self.g.remove_server(peer0)
12461                 # then modify the file, leaving the old map untouched
12462                 log.msg("starting winning write")
12463-                return n.overwrite(MutableDataHandle("contents 2"))
12464+                return n.overwrite(MutableData("contents 2"))
12465             d.addCallback(_got_smap1)
12466             # now attempt to modify the file with the old servermap. This
12467             # will look just like an uncoordinated write, in which every
12468hunk ./src/allmydata/test/test_mutable.py 2263
12469                           self.shouldFail(UncoordinatedWriteError,
12470                                           "test_surprise", None,
12471                                           n.upload,
12472-                                          MutableDataHandle("contents 2a"), self.old_map))
12473+                                          MutableData("contents 2a"), self.old_map))
12474             return d
12475         d.addCallback(_created)
12476         return d
12477hunk ./src/allmydata/test/test_mutable.py 2303
12478         d.addCallback(_break_peer0)
12479         # now "create" the file, using the pre-established key, and let the
12480         # initial publish finally happen
12481-        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
12482+        d.addCallback(lambda res: nm.create_mutable_file(MutableData("contents 1")))
12483         # that ought to work
12484         def _got_node(n):
12485             d = n.download_best_version()
12486hunk ./src/allmydata/test/test_mutable.py 2312
12487             def _break_peer1(res):
12488                 self.connection1.broken = True
12489             d.addCallback(_break_peer1)
12490-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12491+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12492             # that ought to work too
12493             d.addCallback(lambda res: n.download_best_version())
12494             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12495hunk ./src/allmydata/test/test_mutable.py 2344
12496         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
12497         self.g.break_server(peerids[0])
12498 
12499-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12500+        d = nm.create_mutable_file(MutableData("contents 1"))
12501         def _created(n):
12502             d = n.download_best_version()
12503             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12504hunk ./src/allmydata/test/test_mutable.py 2352
12505             def _break_second_server(res):
12506                 self.g.break_server(peerids[1])
12507             d.addCallback(_break_second_server)
12508-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12509+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12510             # that ought to work too
12511             d.addCallback(lambda res: n.download_best_version())
12512             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12513hunk ./src/allmydata/test/test_mutable.py 2371
12514         d = self.shouldFail(NotEnoughServersError,
12515                             "test_publish_all_servers_bad",
12516                             "Ran out of non-bad servers",
12517-                            nm.create_mutable_file, MutableDataHandle("contents"))
12518+                            nm.create_mutable_file, MutableData("contents"))
12519         return d
12520 
12521     def test_publish_no_servers(self):
12522hunk ./src/allmydata/test/test_mutable.py 2383
12523         d = self.shouldFail(NotEnoughServersError,
12524                             "test_publish_no_servers",
12525                             "Ran out of non-bad servers",
12526-                            nm.create_mutable_file, MutableDataHandle("contents"))
12527+                            nm.create_mutable_file, MutableData("contents"))
12528         return d
12529     test_publish_no_servers.timeout = 30
12530 
12531hunk ./src/allmydata/test/test_mutable.py 2401
12532         # we need some contents that are large enough to push the privkey out
12533         # of the early part of the file
12534         LARGE = "These are Larger contents" * 2000 # about 50KB
12535-        LARGE_uploadable = MutableDataHandle(LARGE)
12536+        LARGE_uploadable = MutableData(LARGE)
12537         d = nm.create_mutable_file(LARGE_uploadable)
12538         def _created(n):
12539             self.uri = n.get_uri()
12540hunk ./src/allmydata/test/test_mutable.py 2438
12541         self.set_up_grid(num_servers=20)
12542         nm = self.g.clients[0].nodemaker
12543         LARGE = "These are Larger contents" * 2000 # about 50KiB
12544-        LARGE_uploadable = MutableDataHandle(LARGE)
12545+        LARGE_uploadable = MutableData(LARGE)
12546         nm._node_cache = DevNullDictionary() # disable the nodecache
12547 
12548         d = nm.create_mutable_file(LARGE_uploadable)
12549hunk ./src/allmydata/test/test_mutable.py 2464
12550         self.set_up_grid(num_servers=20)
12551         nm = self.g.clients[0].nodemaker
12552         CONTENTS = "contents" * 2000
12553-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12554+        CONTENTS_uploadable = MutableData(CONTENTS)
12555         d = nm.create_mutable_file(CONTENTS_uploadable)
12556         def _created(node):
12557             self._node = node
12558hunk ./src/allmydata/test/test_mutable.py 2565
12559 class DataHandle(unittest.TestCase):
12560     def setUp(self):
12561         self.test_data = "Test Data" * 50000
12562-        self.uploadable = MutableDataHandle(self.test_data)
12563+        self.uploadable = MutableData(self.test_data)
12564 
12565 
12566     def test_datahandle_read(self):
12567hunk ./src/allmydata/test/test_sftp.py 84
12568         return d
12569 
12570     def _set_up_tree(self):
12571-        u = publish.MutableDataHandle("mutable file contents")
12572+        u = publish.MutableData("mutable file contents")
12573         d = self.client.create_mutable_file(u)
12574         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
12575         def _created_mutable(n):
12576hunk ./src/allmydata/test/test_system.py 22
12577 from allmydata.monitor import Monitor
12578 from allmydata.mutable.common import NotWriteableError
12579 from allmydata.mutable import layout as mutable_layout
12580-from allmydata.mutable.publish import MutableDataHandle
12581+from allmydata.mutable.publish import MutableData
12582 from foolscap.api import DeadReferenceError
12583 from twisted.python.failure import Failure
12584 from twisted.web.client import getPage
12585hunk ./src/allmydata/test/test_system.py 460
12586     def test_mutable(self):
12587         self.basedir = "system/SystemTest/test_mutable"
12588         DATA = "initial contents go here."  # 25 bytes % 3 != 0
12589-        DATA_uploadable = MutableDataHandle(DATA)
12590+        DATA_uploadable = MutableData(DATA)
12591         NEWDATA = "new contents yay"
12592hunk ./src/allmydata/test/test_system.py 462
12593-        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
12594+        NEWDATA_uploadable = MutableData(NEWDATA)
12595         NEWERDATA = "this is getting old"
12596hunk ./src/allmydata/test/test_system.py 464
12597-        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
12598+        NEWERDATA_uploadable = MutableData(NEWERDATA)
12599 
12600         d = self.set_up_nodes(use_key_generator=True)
12601 
12602hunk ./src/allmydata/test/test_system.py 642
12603         def _check_empty_file(res):
12604             # make sure we can create empty files, this usually screws up the
12605             # segsize math
12606-            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
12607+            d1 = self.clients[2].create_mutable_file(MutableData(""))
12608             d1.addCallback(lambda newnode: newnode.download_best_version())
12609             d1.addCallback(lambda res: self.failUnlessEqual("", res))
12610             return d1
12611hunk ./src/allmydata/test/test_system.py 674
12612 
12613         d.addCallback(check_kg_poolsize, 0)
12614         d.addCallback(lambda junk:
12615-            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
12616+            self.clients[3].create_mutable_file(MutableData('hello, world')))
12617         d.addCallback(check_kg_poolsize, -1)
12618         d.addCallback(lambda junk: self.clients[3].create_dirnode())
12619         d.addCallback(check_kg_poolsize, -2)
12620hunk ./src/allmydata/test/test_web.py 3184
12621             self.uris[which] = n.get_uri()
12622             assert isinstance(self.uris[which], str)
12623         d.addCallback(lambda ign:
12624-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
12625+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12626         d.addCallback(_stash_mutable_uri, "corrupt")
12627         d.addCallback(lambda ign:
12628                       c0.upload(upload.Data("literal", convergence="")))
12629hunk ./src/allmydata/test/test_web.py 3331
12630             self.uris[which] = n.get_uri()
12631             assert isinstance(self.uris[which], str)
12632         d.addCallback(lambda ign:
12633-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
12634+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12635         d.addCallback(_stash_mutable_uri, "corrupt")
12636 
12637         def _compute_fileurls(ignored):
12638hunk ./src/allmydata/test/test_web.py 3994
12639             self.uris[which] = n.get_uri()
12640             assert isinstance(self.uris[which], str)
12641         d.addCallback(lambda ign:
12642-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
12643+            c0.create_mutable_file(publish.MutableData(DATA+"2")))
12644         d.addCallback(_stash_mutable_uri, "mutable")
12645 
12646         def _compute_fileurls(ignored):
12647hunk ./src/allmydata/test/test_web.py 4094
12648         d.addCallback(_stash_uri, "small")
12649 
12650         d.addCallback(lambda ign:
12651-            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
12652+            c0.create_mutable_file(publish.MutableData("mutable")))
12653         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
12654         d.addCallback(_stash_uri, "mutable")
12655 
12656}
12657[tests: fix tests that were broken by #993
12658Kevan Carstensen <kevan@isnotajoke.com>**20100717015230
12659 Ignore-this: f0ace6538c6d824b7fe271c40a7ebf8d
12660] {
12661hunk ./src/allmydata/test/common.py 152
12662         consumer.write(data[start:end])
12663         return consumer
12664 
12665+
12666+    def get_best_readable_version(self):
12667+        return defer.succeed(self)
12668+
12669+
12670+    download_best_version = download_to_data
12671+
12672+
12673+    def download_to_data(self):
12674+        return download_to_data(self)
12675+
12676+
12677+    def get_size_of_best_version(self):
12678+        return defer.succeed(self.get_size)
12679+
12680+
12681 def make_chk_file_cap(size):
12682     return uri.CHKFileURI(key=os.urandom(16),
12683                           uri_extension_hash=os.urandom(32),
12684hunk ./src/allmydata/test/common.py 318
12685         return d
12686 
12687     def download_best_version(self):
12688+        return defer.succeed(self._download_best_version())
12689+
12690+
12691+    def _download_best_version(self, ignored=None):
12692         if isinstance(self.my_uri, uri.LiteralFileURI):
12693hunk ./src/allmydata/test/common.py 323
12694-            return defer.succeed(self.my_uri.data)
12695+            return self.my_uri.data
12696         if self.storage_index not in self.all_contents:
12697hunk ./src/allmydata/test/common.py 325
12698-            return defer.fail(NotEnoughSharesError(None, 0, 3))
12699-        return defer.succeed(self.all_contents[self.storage_index])
12700+            raise NotEnoughSharesError(None, 0, 3)
12701+        return self.all_contents[self.storage_index]
12702+
12703 
12704     def overwrite(self, new_contents):
12705         if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
12706hunk ./src/allmydata/test/common.py 352
12707         self.all_contents[self.storage_index] = new_data
12708         return None
12709 
12710+    # As actually implemented, MutableFilenode and MutableFileVersion
12711+    # are distinct. However, nothing in the webapi uses (yet) that
12712+    # distinction -- it just uses the unified download interface
12713+    # provided by get_best_readable_version and read. When we start
12714+    # doing cooler things like LDMF, we will want to revise this code to
12715+    # be less simplistic.
12716+    def get_best_readable_version(self):
12717+        return defer.succeed(self)
12718+
12719+
12720+    def read(self, consumer, offset=0, size=None):
12721+        data = self._download_best_version()
12722+        if size:
12723+            data = data[offset:offset+size]
12724+        consumer.write(data)
12725+        return defer.succeed(consumer)
12726+
12727+
12728 def make_mutable_file_cap():
12729     return uri.WriteableSSKFileURI(writekey=os.urandom(16),
12730                                    fingerprint=os.urandom(32))
12731hunk ./src/allmydata/test/test_filenode.py 98
12732         def _check_segment(res):
12733             self.failUnlessEqual(res, DATA[1:1+5])
12734         d.addCallback(_check_segment)
12735-        d.addCallback(lambda ignored:
12736-            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
12737+        d.addCallback(lambda ignored: fn1.get_best_readable_version())
12738+        d.addCallback(lambda fn2: self.failUnlessEqual(fn1, fn2))
12739         d.addCallback(lambda ignored:
12740             fn1.get_size_of_best_version())
12741         d.addCallback(lambda size:
12742hunk ./src/allmydata/test/test_immutable.py 168
12743 
12744 
12745     def test_get_best_readable_version(self):
12746-        n = self.n.get_best_readable_version()
12747-        self.failUnlessEqual(n, self.n)
12748+        d = self.n.get_best_readable_version()
12749+        d.addCallback(lambda n2:
12750+            self.failUnlessEqual(n2, self.n))
12751+        return d
12752 
12753     def test_get_size_of_best_version(self):
12754         d = self.n.get_size_of_best_version()
12755hunk ./src/allmydata/test/test_mutable.py 8
12756 from twisted.internet import defer, reactor
12757 from allmydata import uri, client
12758 from allmydata.nodemaker import NodeMaker
12759-from allmydata.util import base32
12760+from allmydata.util import base32, consumer
12761 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
12762      ssk_pubkey_fingerprint_hash
12763hunk ./src/allmydata/test/test_mutable.py 11
12764+from allmydata.util.deferredutil import gatherResults
12765 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
12766      NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
12767 from allmydata.monitor import Monitor
12768hunk ./src/allmydata/test/test_mutable.py 1000
12769         if version is None:
12770             version = servermap.best_recoverable_version()
12771         r = Retrieve(self._fn, servermap, version)
12772-        return r.download()
12773+        c = consumer.MemoryConsumer()
12774+        d = r.download(consumer=c)
12775+        d.addCallback(lambda mc: "".join(mc.chunks))
12776+        return d
12777+
12778 
12779     def test_basic(self):
12780         d = self.make_servermap()
12781hunk ./src/allmydata/test/test_mutable.py 1263
12782                             in str(servermap.problems[0]))
12783             ver = servermap.best_recoverable_version()
12784             r = Retrieve(self._fn, servermap, ver)
12785-            return r.download()
12786+            c = consumer.MemoryConsumer()
12787+            return r.download(c)
12788         d.addCallback(_do_retrieve)
12789hunk ./src/allmydata/test/test_mutable.py 1266
12790+        d.addCallback(lambda mc: "".join(mc.chunks))
12791         d.addCallback(lambda new_contents:
12792                       self.failUnlessEqual(new_contents, self.CONTENTS))
12793         return d
12794}
12795[test/test_immutable.py: add tests for #993-related modifications
12796Kevan Carstensen <kevan@isnotajoke.com>**20100717015402
12797 Ignore-this: d94ad98bd0d322ead85af2e0aa95be38
12798] hunk ./src/allmydata/test/test_mutable.py 2607
12799         start = chunk_size
12800         end = chunk_size * 2
12801         self.failUnlessEqual("".join(more_data), self.test_data[start:end])
12802+
12803+
12804+class Version(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
12805+    def setUp(self):
12806+        GridTestMixin.setUp(self)
12807+        self.basedir = self.mktemp()
12808+        self.set_up_grid()
12809+        self.c = self.g.clients[0]
12810+        self.nm = self.c.nodemaker
12811+        self.data = "test data" * 100000 # about 900 KiB; MDMF
12812+        self.small_data = "test data" * 10 # about 90 B; SDMF
12813+        return self.do_upload()
12814+
12815+
12816+    def do_upload(self):
12817+        d1 = self.nm.create_mutable_file(MutableData(self.data),
12818+                                         version=MDMF_VERSION)
12819+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
12820+        dl = gatherResults([d1, d2])
12821+        def _then((n1, n2)):
12822+            assert isinstance(n1, MutableFileNode)
12823+            assert isinstance(n2, MutableFileNode)
12824+
12825+            self.mdmf_node = n1
12826+            self.sdmf_node = n2
12827+        dl.addCallback(_then)
12828+        return dl
12829+
12830+
12831+    def test_get_readonly_mutable_version(self):
12832+        # Attempting to get a mutable version of a mutable file from a
12833+        # filenode initialized with a readcap should return a readonly
12834+        # version of that same node.
12835+        ro = self.mdmf_node.get_readonly()
12836+        d = ro.get_best_mutable_version()
12837+        d.addCallback(lambda version:
12838+            self.failUnless(version.is_readonly()))
12839+        d.addCallback(lambda ignored:
12840+            self.sdmf_node.get_readonly())
12841+        d.addCallback(lambda version:
12842+            self.failUnless(version.is_readonly()))
12843+        return d
12844+
12845+
12846+    def test_get_sequence_number(self):
12847+        d = self.mdmf_node.get_best_readable_version()
12848+        d.addCallback(lambda bv:
12849+            self.failUnlessEqual(bv.get_sequence_number(), 0))
12850+        d.addCallback(lambda ignored:
12851+            self.sdmf_node.get_best_readable_version())
12852+        d.addCallback(lambda bv:
12853+            self.failUnlessEqual(bv.get_sequence_number(), 0))
12854+        # Now update. The sequence number in both cases should be 1 in
12855+        # both cases.
12856+        def _do_update(ignored):
12857+            new_data = MutableData("foo bar baz" * 100000)
12858+            new_small_data = MutableData("foo bar baz" * 10)
12859+            d1 = self.mdmf_node.overwrite(new_data)
12860+            d2 = self.sdmf_node.overwrite(new_small_data)
12861+            dl = gatherResults([d1, d2])
12862+            return dl
12863+        d.addCallback(_do_update)
12864+        d.addCallback(lambda ignored:
12865+            self.mdmf_node.get_best_readable_version())
12866+        d.addCallback(lambda bv:
12867+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12868+        d.addCallback(lambda ignored:
12869+            self.sdmf_node.get_best_readable_version())
12870+        d.addCallback(lambda bv:
12871+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12872+        return d
12873+
12874+
12875+    def test_get_writekey(self):
12876+        d = self.mdmf_node.get_best_mutable_version()
12877+        d.addCallback(lambda bv:
12878+            self.failUnlessEqual(bv.get_writekey(),
12879+                                 self.mdmf_node.get_writekey()))
12880+        d.addCallback(lambda ignored:
12881+            self.sdmf_node.get_best_mutable_version())
12882+        d.addCallback(lambda bv:
12883+            self.failUnlessEqual(bv.get_writekey(),
12884+                                 self.sdmf_node.get_writekey()))
12885+        return d
12886+
12887+
12888+    def test_get_storage_index(self):
12889+        d = self.mdmf_node.get_best_mutable_version()
12890+        d.addCallback(lambda bv:
12891+            self.failUnlessEqual(bv.get_storage_index(),
12892+                                 self.mdmf_node.get_storage_index()))
12893+        d.addCallback(lambda ignored:
12894+            self.sdmf_node.get_best_mutable_version())
12895+        d.addCallback(lambda bv:
12896+            self.failUnlessEqual(bv.get_storage_index(),
12897+                                 self.sdmf_node.get_storage_index()))
12898+        return d
12899+
12900+
12901+    def test_get_readonly_version(self):
12902+        d = self.mdmf_node.get_best_readable_version()
12903+        d.addCallback(lambda bv:
12904+            self.failUnless(bv.is_readonly()))
12905+        d.addCallback(lambda ignored:
12906+            self.sdmf_node.get_best_readable_version())
12907+        d.addCallback(lambda bv:
12908+            self.failUnless(bv.is_readonly()))
12909+        return d
12910+
12911+
12912+    def test_get_mutable_version(self):
12913+        d = self.mdmf_node.get_best_mutable_version()
12914+        d.addCallback(lambda bv:
12915+            self.failIf(bv.is_readonly()))
12916+        d.addCallback(lambda ignored:
12917+            self.sdmf_node.get_best_mutable_version())
12918+        d.addCallback(lambda bv:
12919+            self.failIf(bv.is_readonly()))
12920+        return d
12921+
12922+
12923+    def test_toplevel_overwrite(self):
12924+        new_data = MutableData("foo bar baz" * 100000)
12925+        new_small_data = MutableData("foo bar baz" * 10)
12926+        d = self.mdmf_node.overwrite(new_data)
12927+        d.addCallback(lambda ignored:
12928+            self.mdmf_node.download_best_version())
12929+        d.addCallback(lambda data:
12930+            self.failUnlessEqual(data, "foo bar baz" * 100000))
12931+        d.addCallback(lambda ignored:
12932+            self.sdmf_node.overwrite(new_small_data))
12933+        d.addCallback(lambda ignored:
12934+            self.sdmf_node.download_best_version())
12935+        d.addCallback(lambda data:
12936+            self.failUnlessEqual(data, "foo bar baz" * 10))
12937+        return d
12938+
12939+
12940+    def test_toplevel_modify(self):
12941+        def modifier(old_contents, servermap, first_time):
12942+            return MutableData(old_contents + "modified")
12943+        d = self.mdmf_node.modify(modifier)
12944+        d.addCallback(lambda ignored:
12945+            self.mdmf_node.download_best_version())
12946+        d.addCallback(lambda data:
12947+            self.failUnlessIn("modified", data))
12948+        d.addCallback(lambda ignored:
12949+            self.sdmf_node.modify(modifier))
12950+        d.addCallback(lambda ignored:
12951+            self.sdmf_node.download_best_version())
12952+        d.addCallback(lambda data:
12953+            self.failUnlessIn("modified", data))
12954+        return d
12955+
12956+
12957+    def test_version_modify(self):
12958+        # TODO: When we can publish multiple versions, alter this test
12959+        # to modify a version other than the best usable version, then
12960+        # test to see that the best recoverable version is that.
12961+        def modifier(old_contents, servermap, first_time):
12962+            return MutableData(old_contents + "modified")
12963+        d = self.mdmf_node.modify(modifier)
12964+        d.addCallback(lambda ignored:
12965+            self.mdmf_node.download_best_version())
12966+        d.addCallback(lambda data:
12967+            self.failUnlessIn("modified", data))
12968+        d.addCallback(lambda ignored:
12969+            self.sdmf_node.modify(modifier))
12970+        d.addCallback(lambda ignored:
12971+            self.sdmf_node.download_best_version())
12972+        d.addCallback(lambda data:
12973+            self.failUnlessIn("modified", data))
12974+        return d
12975+
12976+
12977+    def test_download_version(self):
12978+        # This will only pass once we get the ability to publish
12979+        # multiple recoverable versions.
12980+        self.failUnless(False)
12981+
12982+
12983+    def test_partial_read(self):
12984+        # read only a few bytes at a time, and see that the results are
12985+        # what we expect.
12986+        d = self.mdmf_node.get_best_readable_version()
12987+        def _read_data(version):
12988+            c = consumer.MemoryConsumer()
12989+            d2 = defer.succeed(None)
12990+            for i in xrange(0, len(self.data), 10000):
12991+                d2.addCallback(lambda ignored, i=i: version.read(c, i, 10000))
12992+            d2.addCallback(lambda ignored:
12993+                self.failUnlessEqual(self.data, "".join(c.chunks)))
12994+            return d2
12995+        d.addCallback(_read_data)
12996+        return d
12997+
12998+
12999+    def test_read(self):
13000+        d = self.mdmf_node.get_best_readable_version()
13001+        def _read_data(version):
13002+            c = consumer.MemoryConsumer()
13003+            d2 = defer.succeed(None)
13004+            d2.addCallback(lambda ignored: version.read(c))
13005+            d2.addCallback(lambda ignored:
13006+                self.failUnlessEqual("".join(c.chunks), self.data))
13007+            return d2
13008+        d.addCallback(_read_data)
13009+        return d
13010+
13011+
13012+    def test_download_best_version(self):
13013+        d = self.mdmf_node.download_best_version()
13014+        d.addCallback(lambda data:
13015+            self.failUnlessEqual(data, self.data))
13016+        d.addCallback(lambda ignored:
13017+            self.sdmf_node.download_best_version())
13018+        d.addCallback(lambda data:
13019+            self.failUnlessEqual(data, self.small_data))
13020+        return d
13021[web/filenode.py: alter download code to use the new #993 interface.
13022Kevan Carstensen <kevan@isnotajoke.com>**20100717015426
13023 Ignore-this: 4e8b89f9b616755f1f644ecea4cda0bb
13024] {
13025hunk ./src/allmydata/web/filenode.py 167
13026             # properly. So we assume that at least the browser will agree
13027             # with itself, and echo back the same bytes that we were given.
13028             filename = get_arg(req, "filename", self.name) or "unknown"
13029-            if self.node.is_mutable():
13030-                # some day: d = self.node.get_best_version()
13031-                d = makeMutableDownloadable(self.node)
13032-            else:
13033-                d = defer.succeed(self.node)
13034+            d = self.node.get_best_readable_version()
13035             d.addCallback(lambda dn: FileDownloader(dn, filename))
13036             return d
13037         if t == "json":
13038hunk ./src/allmydata/web/filenode.py 191
13039         if t:
13040             raise WebError("GET file: bad t=%s" % t)
13041         filename = get_arg(req, "filename", self.name) or "unknown"
13042-        if self.node.is_mutable():
13043-            # some day: d = self.node.get_best_version()
13044-            d = makeMutableDownloadable(self.node)
13045-        else:
13046-            d = defer.succeed(self.node)
13047+        d = self.node.get_best_readable_version()
13048         d.addCallback(lambda dn: FileDownloader(dn, filename))
13049         return d
13050 
13051hunk ./src/allmydata/web/filenode.py 285
13052         d.addCallback(lambda res: self.node.get_uri())
13053         return d
13054 
13055-class MutableDownloadable:
13056-    #implements(IDownloadable)
13057-    def __init__(self, size, node):
13058-        self.size = size
13059-        self.node = node
13060-    def get_size(self):
13061-        return self.size
13062-    def is_mutable(self):
13063-        return True
13064-    def read(self, consumer, offset=0, size=None):
13065-        d = self.node.download_best_version()
13066-        d.addCallback(self._got_data, consumer, offset, size)
13067-        return d
13068-    def _got_data(self, contents, consumer, offset, size):
13069-        start = offset
13070-        if size is not None:
13071-            end = offset+size
13072-        else:
13073-            end = self.size
13074-        # SDMF: we can write the whole file in one big chunk
13075-        consumer.write(contents[start:end])
13076-        return consumer
13077-
13078-def makeMutableDownloadable(n):
13079-    d = defer.maybeDeferred(n.get_size_of_best_version)
13080-    d.addCallback(MutableDownloadable, n)
13081-    return d
13082 
13083 class FileDownloader(rend.Page):
13084     # since we override the rendering process (to let the tahoe Downloader
13085}
13086[test/common.py: remove FileTooLargeErrors that tested for an SDMF limitation that no longer exists
13087Kevan Carstensen <kevan@isnotajoke.com>**20100717015501
13088 Ignore-this: 8b17689d9391a4870a327c1d7c0b3225
13089] {
13090hunk ./src/allmydata/test/common.py 198
13091         self.init_from_cap(make_mutable_file_cap())
13092     def create(self, contents, key_generator=None, keysize=None):
13093         initial_contents = self._get_initial_contents(contents)
13094-        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
13095-            raise FileTooLargeError("SDMF is limited to one segment, and "
13096-                                    "%d > %d" % (initial_contents.get_size(),
13097-                                                 self.MUTABLE_SIZELIMIT))
13098         data = initial_contents.read(initial_contents.get_size())
13099         data = "".join(data)
13100         self.all_contents[self.storage_index] = data
13101hunk ./src/allmydata/test/common.py 326
13102 
13103 
13104     def overwrite(self, new_contents):
13105-        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
13106-            raise FileTooLargeError("SDMF is limited to one segment, and "
13107-                                    "%d > %d" % (new_contents.get_size(),
13108-                                                 self.MUTABLE_SIZELIMIT))
13109         assert not self.is_readonly()
13110         new_data = new_contents.read(new_contents.get_size())
13111         new_data = "".join(new_data)
13112}
13113[nodemaker.py: resolve a conflict introduced in one of the 1.7.1 patches
13114Kevan Carstensen <kevan@isnotajoke.com>**20100720213109
13115 Ignore-this: 4e7d4e611f4cdf04824e9040167aa11
13116] hunk ./src/allmydata/nodemaker.py 107
13117                          "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
13118             node.raise_error()
13119         d = self.create_mutable_file(lambda n:
13120-                                     pack_children(n, initial_children))
13121+                                     MutableData(pack_children(initial_children,
13122+                                                    n.get_writekey())),
13123+                                     version)
13124         d.addCallback(self._create_dirnode)
13125         return d
13126 
13127[frontends/sftpd.py: fix conflicts with trunk
13128Kevan Carstensen <kevan@isnotajoke.com>**20100727224651
13129 Ignore-this: 5636e7a27162bf3ca14d6c9dc07a015
13130] {
13131hunk ./src/allmydata/frontends/sftpd.py 664
13132         else:
13133             assert IFileNode.providedBy(filenode), filenode
13134 
13135-            # TODO: use download interface described in #993 when implemented.
13136-            if filenode.is_mutable():
13137-                self.async.addCallback(lambda ign: filenode.download_best_version())
13138-                def _downloaded(data):
13139-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
13140-                    self.consumer.write(data)
13141-                    self.consumer.finish()
13142-                    return None
13143-                self.async.addCallback(_downloaded)
13144-            else:
13145-                download_size = filenode.get_size()
13146-                assert download_size is not None, "download_size is None"
13147+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
13148+
13149+            def _read(version):
13150+                if noisy: self.log("_read", level=NOISY)
13151+                download_size = version.get_size()
13152+                assert download_size is not None
13153+
13154                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
13155 
13156hunk ./src/allmydata/frontends/sftpd.py 673
13157-                if noisy: self.log("_read", level=NOISY)
13158                 version.read(self.consumer, 0, None)
13159             self.async.addCallback(_read)
13160 
13161}
13162[interfaces.py: Create an IWritable interface
13163Kevan Carstensen <kevan@isnotajoke.com>**20100727224703
13164 Ignore-this: 3fd15da701c31c024963d7ee5c896124
13165] hunk ./src/allmydata/interfaces.py 633
13166         """
13167 
13168 
13169+class IWritable(Interface):
13170+    """
13171+    I define methods that callers can use to update SDMF and MDMF
13172+    mutable files on a Tahoe-LAFS grid.
13173+    """
13174+    # XXX: For the moment, we have only this. It is possible that we
13175+    #      want to move overwrite() and modify() in here too.
13176+    def update(data, offset):
13177+        """
13178+        I write the data from my data argument to the MDMF file,
13179+        starting at offset. I continue writing data until my data
13180+        argument is exhausted, appending data to the file as necessary.
13181+        """
13182+        # assert IMutableUploadable.providedBy(data)
13183+        # to append data: offset=node.get_size_of_best_version()
13184+        # do we want to support compacting MDMF?
13185+        # for an MDMF file, this can be done with O(data.get_size())
13186+        # memory. For an SDMF file, any modification takes
13187+        # O(node.get_size_of_best_version()).
13188+
13189+
13190 class IMutableFileVersion(IReadable):
13191     """I provide access to a particular version of a mutable file. The
13192     access is read/write if I was obtained from a filenode derived from
13193[mutable/layout.py: Alter MDMFSlotWriteProxy to perform all write operations in one actual write operation
13194Kevan Carstensen <kevan@isnotajoke.com>**20100727224725
13195 Ignore-this: 41d577e9d65eba9a38a4051c2a05d4be
13196] {
13197hunk ./src/allmydata/mutable/layout.py 814
13198         # last thing we write to the remote server.
13199         self._offsets = {}
13200         self._testvs = []
13201+        # This is a list of write vectors that will be sent to our
13202+        # remote server once we are directed to write things there.
13203+        self._writevs = []
13204         self._secrets = secrets
13205         # The segment size needs to be a multiple of the k parameter --
13206         # any padding should have been carried out by the publisher
13207hunk ./src/allmydata/mutable/layout.py 947
13208 
13209     def put_block(self, data, segnum, salt):
13210         """
13211-        Put the encrypted-and-encoded data segment in the slot, along
13212-        with the salt.
13213+        I queue a write vector for the data, salt, and segment number
13214+        provided to me. I return None, as I do not actually cause
13215+        anything to be written yet.
13216         """
13217         if segnum >= self._num_segments:
13218             raise LayoutInvalid("I won't overwrite the private key")
13219hunk ./src/allmydata/mutable/layout.py 967
13220         offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
13221         data = salt + data
13222 
13223-        datavs = [tuple([offset, data])]
13224-        return self._write(datavs)
13225+        self._writevs.append(tuple([offset, data]))
13226 
13227 
13228     def put_encprivkey(self, encprivkey):
13229hunk ./src/allmydata/mutable/layout.py 972
13230         """
13231-        Put the encrypted private key in the remote slot.
13232+        I queue a write vector for the encrypted private key provided to
13233+        me.
13234         """
13235         assert self._offsets
13236         assert self._offsets['enc_privkey']
13237hunk ./src/allmydata/mutable/layout.py 986
13238         if "share_hash_chain" in self._offsets:
13239             raise LayoutInvalid("You must write this before the block hash tree")
13240 
13241-        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
13242-        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
13243-        def _on_failure():
13244-            del(self._offsets['block_hash_tree'])
13245-        return self._write(datavs, on_failure=_on_failure)
13246+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + \
13247+            len(encprivkey)
13248+        self._writevs.append(tuple([self._offsets['enc_privkey'], encprivkey]))
13249 
13250 
13251     def put_blockhashes(self, blockhashes):
13252hunk ./src/allmydata/mutable/layout.py 993
13253         """
13254-        Put the block hash tree in the remote slot.
13255+        I queue a write vector to put the block hash tree in blockhashes
13256+        onto the remote server.
13257 
13258hunk ./src/allmydata/mutable/layout.py 996
13259-        The encrypted private key must be put before the block hash
13260+        The encrypted private key must be queued before the block hash
13261         tree, since we need to know how large it is to know where the
13262         block hash tree should go. The block hash tree must be put
13263         before the salt hash tree, since its size determines the
13264hunk ./src/allmydata/mutable/layout.py 1014
13265                                 "you put the share hash chain")
13266         blockhashes_s = "".join(blockhashes)
13267         self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
13268-        datavs = []
13269-        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
13270-        def _on_failure():
13271-            del(self._offsets['share_hash_chain'])
13272-        return self._write(datavs, on_failure=_on_failure)
13273+
13274+        self._writevs.append(tuple([self._offsets['block_hash_tree'],
13275+                                  blockhashes_s]))
13276 
13277 
13278     def put_sharehashes(self, sharehashes):
13279hunk ./src/allmydata/mutable/layout.py 1021
13280         """
13281-        Put the share hash chain in the remote slot.
13282+        I queue a write vector to put the share hash chain in my
13283+        argument onto the remote server.
13284 
13285hunk ./src/allmydata/mutable/layout.py 1024
13286-        The salt hash tree must be put before the share hash chain,
13287+        The salt hash tree must be queued before the share hash chain,
13288         since we need to know where the salt hash tree ends before we
13289         can know where the share hash chain starts. The share hash chain
13290         must be put before the signature, since the length of the packed
13291hunk ./src/allmydata/mutable/layout.py 1044
13292         if "verification_key" in self._offsets:
13293             raise LayoutInvalid("You must write the share hash chain "
13294                                 "before you write the signature")
13295-        datavs = []
13296         sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
13297                                   for i in sorted(sharehashes.keys())])
13298         self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
13299hunk ./src/allmydata/mutable/layout.py 1047
13300-        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
13301-        def _on_failure():
13302-            del(self._offsets['signature'])
13303-        return self._write(datavs, on_failure=_on_failure)
13304+        self._writevs.append(tuple([self._offsets['share_hash_chain'],
13305+                            sharehashes_s]))
13306 
13307 
13308     def put_root_hash(self, roothash):
13309hunk ./src/allmydata/mutable/layout.py 1069
13310         if len(roothash) != HASH_SIZE:
13311             raise LayoutInvalid("hashes and salts must be exactly %d bytes"
13312                                  % HASH_SIZE)
13313-        datavs = []
13314         self._root_hash = roothash
13315         # To write both of these values, we update the checkstring on
13316         # the remote server, which includes them
13317hunk ./src/allmydata/mutable/layout.py 1073
13318         checkstring = self.get_checkstring()
13319-        datavs.append(tuple([0, checkstring]))
13320+        self._writevs.append(tuple([0, checkstring]))
13321         # This write, if successful, changes the checkstring, so we need
13322         # to update our internal checkstring to be consistent with the
13323         # one on the server.
13324hunk ./src/allmydata/mutable/layout.py 1077
13325-        def _on_success():
13326-            self._testvs = [(0, len(checkstring), "eq", checkstring)]
13327-        def _on_failure():
13328-            self._root_hash = None
13329-        return self._write(datavs,
13330-                           on_success=_on_success,
13331-                           on_failure=_on_failure)
13332 
13333 
13334     def get_signable(self):
13335hunk ./src/allmydata/mutable/layout.py 1100
13336 
13337     def put_signature(self, signature):
13338         """
13339-        Put the signature field to the remote slot.
13340+        I queue a write vector for the signature of the MDMF share.
13341 
13342         I require that the root hash and share hash chain have been put
13343         to the grid before I will write the signature to the grid.
13344hunk ./src/allmydata/mutable/layout.py 1123
13345             raise LayoutInvalid("You must write the signature before the verification key")
13346 
13347         self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
13348-        datavs = []
13349-        datavs.append(tuple([self._offsets['signature'], signature]))
13350-        def _on_failure():
13351-            del(self._offsets['verification_key'])
13352-        return self._write(datavs, on_failure=_on_failure)
13353+        self._writevs.append(tuple([self._offsets['signature'], signature]))
13354 
13355 
13356     def put_verification_key(self, verification_key):
13357hunk ./src/allmydata/mutable/layout.py 1128
13358         """
13359-        Put the verification key into the remote slot.
13360+        I queue a write vector for the verification key.
13361 
13362         I require that the signature have been written to the storage
13363         server before I allow the verification key to be written to the
13364hunk ./src/allmydata/mutable/layout.py 1138
13365             raise LayoutInvalid("You must put the signature before you "
13366                                 "can put the verification key")
13367         self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
13368-        datavs = []
13369-        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
13370-        def _on_failure():
13371-            del(self._offsets['EOF'])
13372-        return self._write(datavs, on_failure=_on_failure)
13373+        self._writevs.append(tuple([self._offsets['verification_key'],
13374+                            verification_key]))
13375+
13376 
13377     def _get_offsets_tuple(self):
13378         return tuple([(key, value) for key, value in self._offsets.items()])
13379hunk ./src/allmydata/mutable/layout.py 1145
13380 
13381+
13382     def get_verinfo(self):
13383         return (self._seqnum,
13384                 self._root_hash,
13385hunk ./src/allmydata/mutable/layout.py 1159
13386 
13387     def finish_publishing(self):
13388         """
13389-        Write the offset table and encoding parameters to the remote
13390-        slot, since that's the only thing we have yet to publish at this
13391-        point.
13392+        I add a write vector for the offsets table, and then cause all
13393+        of the write vectors that I've dealt with so far to be published
13394+        to the remote server, ending the write process.
13395         """
13396         if "EOF" not in self._offsets:
13397             raise LayoutInvalid("You must put the verification key before "
13398hunk ./src/allmydata/mutable/layout.py 1174
13399                               self._offsets['signature'],
13400                               self._offsets['verification_key'],
13401                               self._offsets['EOF'])
13402-        datavs = []
13403-        datavs.append(tuple([offsets_offset, offsets]))
13404+        self._writevs.append(tuple([offsets_offset, offsets]))
13405         encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
13406         params = struct.pack(">BBQQ",
13407                              self._required_shares,
13408hunk ./src/allmydata/mutable/layout.py 1181
13409                              self._total_shares,
13410                              self._segment_size,
13411                              self._data_length)
13412-        datavs.append(tuple([encoding_parameters_offset, params]))
13413-        return self._write(datavs)
13414+        self._writevs.append(tuple([encoding_parameters_offset, params]))
13415+        return self._write(self._writevs)
13416 
13417 
13418     def _write(self, datavs, on_failure=None, on_success=None):
13419}
13420[test/test_mutable.py: test that write operations occur all at once
13421Kevan Carstensen <kevan@isnotajoke.com>**20100727224817
13422 Ignore-this: 44cb37c6887ee9baa3e67645ece9555d
13423] {
13424hunk ./src/allmydata/test/test_mutable.py 100
13425         self.storage = storage
13426         self.queries = 0
13427     def callRemote(self, methname, *args, **kwargs):
13428+        self.queries += 1
13429         def _call():
13430             meth = getattr(self, methname)
13431             return meth(*args, **kwargs)
13432hunk ./src/allmydata/test/test_mutable.py 109
13433         return d
13434 
13435     def callRemoteOnly(self, methname, *args, **kwargs):
13436+        self.queries += 1
13437         d = self.callRemote(methname, *args, **kwargs)
13438         d.addBoth(lambda ignore: None)
13439         pass
13440hunk ./src/allmydata/test/test_mutable.py 370
13441         return d
13442 
13443 
13444+    def test_mdmf_write_count(self):
13445+        # Publishing an MDMF file should only cause one write for each
13446+        # share that is to be published. Otherwise, we introduce
13447+        # undesirable semantics that are a regression from SDMF
13448+        upload = MutableData("MDMF" * 100000) # about 400 KiB
13449+        d = self.nodemaker.create_mutable_file(upload,
13450+                                               version=MDMF_VERSION)
13451+        def _check_server_write_counts(ignored):
13452+            sb = self.nodemaker.storage_broker
13453+            peers = sb.test_servers.values()
13454+            for peer in peers:
13455+                self.failUnlessEqual(peer.queries, 1)
13456+        d.addCallback(_check_server_write_counts)
13457+        return d
13458+
13459+
13460     def test_create_with_initial_contents(self):
13461         upload1 = MutableData("contents 1")
13462         d = self.nodemaker.create_mutable_file(upload1)
13463}
13464[test/test_storage.py: modify proxy tests to work with the new writing semantics
13465Kevan Carstensen <kevan@isnotajoke.com>**20100727224853
13466 Ignore-this: 2b6bdde6dc9d8e4e7f096cdb725b40cf
13467] {
13468hunk ./src/allmydata/test/test_storage.py 1681
13469         # diagnose the problem. This test ensures that the read vector
13470         # is working appropriately.
13471         mw = self._make_new_mw("si1", 0)
13472-        d = defer.succeed(None)
13473 
13474hunk ./src/allmydata/test/test_storage.py 1682
13475-        # Write one share. This should return a checkstring of nothing,
13476-        # since there is no data there.
13477-        d.addCallback(lambda ignored:
13478-            mw.put_block(self.block, 0, self.salt))
13479-        def _check_first_write(results):
13480-            result, readvs = results
13481-            self.failUnless(result)
13482-            self.failIf(readvs)
13483-        d.addCallback(_check_first_write)
13484-        # Now, there should be a different checkstring returned when
13485-        # we write other shares
13486-        d.addCallback(lambda ignored:
13487-            mw.put_block(self.block, 1, self.salt))
13488-        def _check_next_write(results):
13489-            result, readvs = results
13490+        for i in xrange(6):
13491+            mw.put_block(self.block, i, self.salt)
13492+        mw.put_encprivkey(self.encprivkey)
13493+        mw.put_blockhashes(self.block_hash_tree)
13494+        mw.put_sharehashes(self.share_hash_chain)
13495+        mw.put_root_hash(self.root_hash)
13496+        mw.put_signature(self.signature)
13497+        mw.put_verification_key(self.verification_key)
13498+        d = mw.finish_publishing()
13499+        def _then(results):
13500+            self.failUnless(len(results), 2)
13501+            result, readv = results
13502             self.failUnless(result)
13503hunk ./src/allmydata/test/test_storage.py 1695
13504-            self.expected_checkstring = mw.get_checkstring()
13505-            self.failUnlessIn(0, readvs)
13506-            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
13507-        d.addCallback(_check_next_write)
13508-        # Add the other four shares
13509-        for i in xrange(2, 6):
13510-            d.addCallback(lambda ignored, i=i:
13511-                mw.put_block(self.block, i, self.salt))
13512-            d.addCallback(_check_next_write)
13513-        # Add the encrypted private key
13514-        d.addCallback(lambda ignored:
13515-            mw.put_encprivkey(self.encprivkey))
13516-        d.addCallback(_check_next_write)
13517-        # Add the block hash tree and share hash tree
13518-        d.addCallback(lambda ignored:
13519-            mw.put_blockhashes(self.block_hash_tree))
13520-        d.addCallback(_check_next_write)
13521-        d.addCallback(lambda ignored:
13522-            mw.put_sharehashes(self.share_hash_chain))
13523-        d.addCallback(_check_next_write)
13524-        # Add the root hash and the salt hash. This should change the
13525-        # checkstring, but not in a way that we'll be able to see right
13526-        # now, since the read vectors are applied before the write
13527-        # vectors.
13528+            self.failIf(readv)
13529+            self.old_checkstring = mw.get_checkstring()
13530+            mw.set_checkstring("")
13531+        d.addCallback(_then)
13532         d.addCallback(lambda ignored:
13533hunk ./src/allmydata/test/test_storage.py 1700
13534-            mw.put_root_hash(self.root_hash))
13535-        def _check_old_testv_after_new_one_is_written(results):
13536+            mw.finish_publishing())
13537+        def _then_again(results):
13538+            self.failUnlessEqual(len(results), 2)
13539             result, readvs = results
13540hunk ./src/allmydata/test/test_storage.py 1704
13541-            self.failUnless(result)
13542+            self.failIf(result)
13543             self.failUnlessIn(0, readvs)
13544hunk ./src/allmydata/test/test_storage.py 1706
13545-            self.failUnlessEqual(self.expected_checkstring,
13546-                                 readvs[0][0])
13547-            new_checkstring = mw.get_checkstring()
13548-            self.failIfEqual(new_checkstring,
13549-                             readvs[0][0])
13550-        d.addCallback(_check_old_testv_after_new_one_is_written)
13551-        # Now add the signature. This should succeed, meaning that the
13552-        # data gets written and the read vector matches what the writer
13553-        # thinks should be there.
13554-        d.addCallback(lambda ignored:
13555-            mw.put_signature(self.signature))
13556-        d.addCallback(_check_next_write)
13557+            readv = readvs[0][0]
13558+            self.failUnlessEqual(readv, self.old_checkstring)
13559+        d.addCallback(_then_again)
13560         # The checkstring remains the same for the rest of the process.
13561         return d
13562 
13563hunk ./src/allmydata/test/test_storage.py 1811
13564         # same share.
13565         mw1 = self._make_new_mw("si1", 0)
13566         mw2 = self._make_new_mw("si1", 0)
13567-        d = defer.succeed(None)
13568+
13569         def _check_success(results):
13570             result, readvs = results
13571             self.failUnless(result)
13572hunk ./src/allmydata/test/test_storage.py 1820
13573             result, readvs = results
13574             self.failIf(result)
13575 
13576-        d.addCallback(lambda ignored:
13577-            mw1.put_block(self.block, 0, self.salt))
13578+        def _write_share(mw):
13579+            for i in xrange(6):
13580+                mw.put_block(self.block, i, self.salt)
13581+            mw.put_encprivkey(self.encprivkey)
13582+            mw.put_blockhashes(self.block_hash_tree)
13583+            mw.put_sharehashes(self.share_hash_chain)
13584+            mw.put_root_hash(self.root_hash)
13585+            mw.put_signature(self.signature)
13586+            mw.put_verification_key(self.verification_key)
13587+            return mw.finish_publishing()
13588+        d = _write_share(mw1)
13589         d.addCallback(_check_success)
13590         d.addCallback(lambda ignored:
13591hunk ./src/allmydata/test/test_storage.py 1833
13592-            mw2.put_block(self.block, 0, self.salt))
13593+            _write_share(mw2))
13594         d.addCallback(_check_failure)
13595         return d
13596 
13597hunk ./src/allmydata/test/test_storage.py 1859
13598 
13599     def test_write_test_vectors(self):
13600         # If we give the write proxy a bogus test vector at
13601-        # any point during the process, it should fail to write.
13602+        # any point during the process, it should fail to write when we
13603+        # tell it to write.
13604+        def _check_failure(results):
13605+            self.failUnlessEqual(len(results), 2)
13606+            res, d = results
13607+            self.failIf(res)
13608+
13609+        def _check_success(results):
13610+            self.failUnlessEqual(len(results), 2)
13611+            res, d = results
13612+            self.failUnless(results)
13613+
13614         mw = self._make_new_mw("si1", 0)
13615         mw.set_checkstring("this is a lie")
13616hunk ./src/allmydata/test/test_storage.py 1873
13617-        # The initial write should be expecting to find the improbable
13618-        # checkstring above in place; finding nothing, it should fail.
13619-        d = defer.succeed(None)
13620-        d.addCallback(lambda ignored:
13621-            mw.put_block(self.block, 0, self.salt))
13622-        def _check_failure(results):
13623-            result, readv = results
13624-            self.failIf(result)
13625+        for i in xrange(6):
13626+            mw.put_block(self.block, i, self.salt)
13627+        mw.put_encprivkey(self.encprivkey)
13628+        mw.put_blockhashes(self.block_hash_tree)
13629+        mw.put_sharehashes(self.share_hash_chain)
13630+        mw.put_root_hash(self.root_hash)
13631+        mw.put_signature(self.signature)
13632+        mw.put_verification_key(self.verification_key)
13633+        d = mw.finish_publishing()
13634         d.addCallback(_check_failure)
13635hunk ./src/allmydata/test/test_storage.py 1883
13636-        # Now set the checkstring to the empty string, which
13637-        # indicates that no share is there.
13638         d.addCallback(lambda ignored:
13639             mw.set_checkstring(""))
13640         d.addCallback(lambda ignored:
13641hunk ./src/allmydata/test/test_storage.py 1886
13642-            mw.put_block(self.block, 0, self.salt))
13643-        def _check_success(results):
13644-            result, readv = results
13645-            self.failUnless(result)
13646-        d.addCallback(_check_success)
13647-        # Now set the checkstring to something wrong
13648-        d.addCallback(lambda ignored:
13649-            mw.set_checkstring("something wrong"))
13650-        # This should fail to do anything
13651-        d.addCallback(lambda ignored:
13652-            mw.put_block(self.block, 1, self.salt))
13653-        d.addCallback(_check_failure)
13654-        # Now set it back to what it should be.
13655-        d.addCallback(lambda ignored:
13656-            mw.set_checkstring(mw.get_checkstring()))
13657-        for i in xrange(1, 6):
13658-            d.addCallback(lambda ignored, i=i:
13659-                mw.put_block(self.block, i, self.salt))
13660-            d.addCallback(_check_success)
13661-        d.addCallback(lambda ignored:
13662-            mw.put_encprivkey(self.encprivkey))
13663-        d.addCallback(_check_success)
13664-        d.addCallback(lambda ignored:
13665-            mw.put_blockhashes(self.block_hash_tree))
13666-        d.addCallback(_check_success)
13667-        d.addCallback(lambda ignored:
13668-            mw.put_sharehashes(self.share_hash_chain))
13669-        d.addCallback(_check_success)
13670-        def _keep_old_checkstring(ignored):
13671-            self.old_checkstring = mw.get_checkstring()
13672-            mw.set_checkstring("foobarbaz")
13673-        d.addCallback(_keep_old_checkstring)
13674-        d.addCallback(lambda ignored:
13675-            mw.put_root_hash(self.root_hash))
13676-        d.addCallback(_check_failure)
13677-        d.addCallback(lambda ignored:
13678-            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
13679-        def _restore_old_checkstring(ignored):
13680-            mw.set_checkstring(self.old_checkstring)
13681-        d.addCallback(_restore_old_checkstring)
13682-        d.addCallback(lambda ignored:
13683-            mw.put_root_hash(self.root_hash))
13684-        d.addCallback(_check_success)
13685-        # The checkstring should have been set appropriately for us on
13686-        # the last write; if we try to change it to something else,
13687-        # that change should cause the verification key step to fail.
13688-        d.addCallback(lambda ignored:
13689-            mw.set_checkstring("something else"))
13690-        d.addCallback(lambda ignored:
13691-            mw.put_signature(self.signature))
13692-        d.addCallback(_check_failure)
13693-        d.addCallback(lambda ignored:
13694-            mw.set_checkstring(mw.get_checkstring()))
13695-        d.addCallback(lambda ignored:
13696-            mw.put_signature(self.signature))
13697-        d.addCallback(_check_success)
13698-        d.addCallback(lambda ignored:
13699-            mw.put_verification_key(self.verification_key))
13700+            mw.finish_publishing())
13701         d.addCallback(_check_success)
13702         return d
13703 
13704hunk ./src/allmydata/test/test_storage.py 1891
13705 
13706-    def test_offset_only_set_on_success(self):
13707-        # The write proxy should be smart enough to detect when a write
13708-        # has failed, and to temper its definition of progress based on
13709-        # that.
13710-        mw = self._make_new_mw("si1", 0)
13711-        d = defer.succeed(None)
13712-        for i in xrange(1, 6):
13713-            d.addCallback(lambda ignored, i=i:
13714-                mw.put_block(self.block, i, self.salt))
13715-        def _break_checkstring(ignored):
13716-            self._old_checkstring = mw.get_checkstring()
13717-            mw.set_checkstring("foobarbaz")
13718-
13719-        def _fix_checkstring(ignored):
13720-            mw.set_checkstring(self._old_checkstring)
13721-
13722-        d.addCallback(_break_checkstring)
13723-
13724-        # Setting the encrypted private key shouldn't work now, which is
13725-        # to be expected and is tested elsewhere. We also want to make
13726-        # sure that we can't add the block hash tree after a failed
13727-        # write of this sort.
13728-        d.addCallback(lambda ignored:
13729-            mw.put_encprivkey(self.encprivkey))
13730-        d.addCallback(lambda ignored:
13731-            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
13732-                            None,
13733-                            mw.put_blockhashes, self.block_hash_tree))
13734-        d.addCallback(_fix_checkstring)
13735-        d.addCallback(lambda ignored:
13736-            mw.put_encprivkey(self.encprivkey))
13737-        d.addCallback(_break_checkstring)
13738-        d.addCallback(lambda ignored:
13739-            mw.put_blockhashes(self.block_hash_tree))
13740-        d.addCallback(lambda ignored:
13741-            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
13742-                            None,
13743-                            mw.put_sharehashes, self.share_hash_chain))
13744-        d.addCallback(_fix_checkstring)
13745-        d.addCallback(lambda ignored:
13746-            mw.put_blockhashes(self.block_hash_tree))
13747-        d.addCallback(_break_checkstring)
13748-        d.addCallback(lambda ignored:
13749-            mw.put_sharehashes(self.share_hash_chain))
13750-        d.addCallback(lambda ignored:
13751-            self.shouldFail(LayoutInvalid, "out-of-order root hash",
13752-                            None,
13753-                            mw.put_root_hash, self.root_hash))
13754-        d.addCallback(_fix_checkstring)
13755-        d.addCallback(lambda ignored:
13756-            mw.put_sharehashes(self.share_hash_chain))
13757-        d.addCallback(_break_checkstring)
13758-        d.addCallback(lambda ignored:
13759-            mw.put_root_hash(self.root_hash))
13760-        d.addCallback(lambda ignored:
13761-            self.shouldFail(LayoutInvalid, "out-of-order signature",
13762-                            None,
13763-                            mw.put_signature, self.signature))
13764-        d.addCallback(_fix_checkstring)
13765-        d.addCallback(lambda ignored:
13766-            mw.put_root_hash(self.root_hash))
13767-        d.addCallback(_break_checkstring)
13768-        d.addCallback(lambda ignored:
13769-            mw.put_signature(self.signature))
13770-        d.addCallback(lambda ignored:
13771-            self.shouldFail(LayoutInvalid, "out-of-order verification key",
13772-                            None,
13773-                            mw.put_verification_key,
13774-                            self.verification_key))
13775-        d.addCallback(_fix_checkstring)
13776-        d.addCallback(lambda ignored:
13777-            mw.put_signature(self.signature))
13778-        d.addCallback(_break_checkstring)
13779-        d.addCallback(lambda ignored:
13780-            mw.put_verification_key(self.verification_key))
13781-        d.addCallback(lambda ignored:
13782-            self.shouldFail(LayoutInvalid, "out-of-order finish",
13783-                            None,
13784-                            mw.finish_publishing))
13785-        return d
13786-
13787-
13788     def serialize_blockhashes(self, blockhashes):
13789         return "".join(blockhashes)
13790 
13791hunk ./src/allmydata/test/test_storage.py 1905
13792         # This translates to a file with 6 6-byte segments, and with 2-byte
13793         # blocks.
13794         mw = self._make_new_mw("si1", 0)
13795-        mw2 = self._make_new_mw("si1", 1)
13796         # Test writing some blocks.
13797         read = self.ss.remote_slot_readv
13798         expected_sharedata_offset = struct.calcsize(MDMFHEADER)
13799hunk ./src/allmydata/test/test_storage.py 1910
13800         written_block_size = 2 + len(self.salt)
13801         written_block = self.block + self.salt
13802-        def _check_block_write(i, share):
13803-            self.failUnlessEqual(read("si1", [share], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
13804-                                {share: [written_block]})
13805-        d = defer.succeed(None)
13806         for i in xrange(6):
13807hunk ./src/allmydata/test/test_storage.py 1911
13808-            d.addCallback(lambda ignored, i=i:
13809-                mw.put_block(self.block, i, self.salt))
13810-            d.addCallback(lambda ignored, i=i:
13811-                _check_block_write(i, 0))
13812-        # Now try the same thing, but with share 1 instead of share 0.
13813-        for i in xrange(6):
13814-            d.addCallback(lambda ignored, i=i:
13815-                mw2.put_block(self.block, i, self.salt))
13816-            d.addCallback(lambda ignored, i=i:
13817-                _check_block_write(i, 1))
13818+            mw.put_block(self.block, i, self.salt)
13819 
13820hunk ./src/allmydata/test/test_storage.py 1913
13821-        # Next, we make a fake encrypted private key, and put it onto the
13822-        # storage server.
13823-        d.addCallback(lambda ignored:
13824-            mw.put_encprivkey(self.encprivkey))
13825-        expected_private_key_offset = expected_sharedata_offset + \
13826+        mw.put_encprivkey(self.encprivkey)
13827+        mw.put_blockhashes(self.block_hash_tree)
13828+        mw.put_sharehashes(self.share_hash_chain)
13829+        mw.put_root_hash(self.root_hash)
13830+        mw.put_signature(self.signature)
13831+        mw.put_verification_key(self.verification_key)
13832+        d = mw.finish_publishing()
13833+        def _check_publish(results):
13834+            self.failUnlessEqual(len(results), 2)
13835+            result, ign = results
13836+            self.failUnless(result, "publish failed")
13837+            for i in xrange(6):
13838+                self.failUnlessEqual(read("si1", [0], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
13839+                                {0: [written_block]})
13840+
13841+            expected_private_key_offset = expected_sharedata_offset + \
13842                                       len(written_block) * 6
13843hunk ./src/allmydata/test/test_storage.py 1930
13844-        self.failUnlessEqual(len(self.encprivkey), 7)
13845-        d.addCallback(lambda ignored:
13846+            self.failUnlessEqual(len(self.encprivkey), 7)
13847             self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
13848hunk ./src/allmydata/test/test_storage.py 1932
13849-                                 {0: [self.encprivkey]}))
13850+                                 {0: [self.encprivkey]})
13851 
13852hunk ./src/allmydata/test/test_storage.py 1934
13853-        # Next, we put a fake block hash tree.
13854-        d.addCallback(lambda ignored:
13855-            mw.put_blockhashes(self.block_hash_tree))
13856-        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
13857-        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
13858-        d.addCallback(lambda ignored:
13859+            expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
13860+            self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
13861             self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
13862hunk ./src/allmydata/test/test_storage.py 1937
13863-                                 {0: [self.block_hash_tree_s]}))
13864+                                 {0: [self.block_hash_tree_s]})
13865 
13866hunk ./src/allmydata/test/test_storage.py 1939
13867-        # Next, put a fake share hash chain
13868-        d.addCallback(lambda ignored:
13869-            mw.put_sharehashes(self.share_hash_chain))
13870-        expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
13871-        d.addCallback(lambda ignored:
13872+            expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
13873             self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
13874hunk ./src/allmydata/test/test_storage.py 1941
13875-                                 {0: [self.share_hash_chain_s]}))
13876+                                 {0: [self.share_hash_chain_s]})
13877 
13878hunk ./src/allmydata/test/test_storage.py 1943
13879-        # Next, we put what is supposed to be the root hash of
13880-        # our share hash tree but isn't       
13881-        d.addCallback(lambda ignored:
13882-            mw.put_root_hash(self.root_hash))
13883-        # The root hash gets inserted at byte 9 (its position is in the header,
13884-        # and is fixed).
13885-        def _check(ignored):
13886             self.failUnlessEqual(read("si1", [0], [(9, 32)]),
13887                                  {0: [self.root_hash]})
13888hunk ./src/allmydata/test/test_storage.py 1945
13889-        d.addCallback(_check)
13890-
13891-        # Next, we put a signature of the header block.
13892-        d.addCallback(lambda ignored:
13893-            mw.put_signature(self.signature))
13894-        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
13895-        self.failUnlessEqual(len(self.signature), 9)
13896-        d.addCallback(lambda ignored:
13897+            expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
13898+            self.failUnlessEqual(len(self.signature), 9)
13899             self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
13900hunk ./src/allmydata/test/test_storage.py 1948
13901-                                 {0: [self.signature]}))
13902+                                 {0: [self.signature]})
13903 
13904hunk ./src/allmydata/test/test_storage.py 1950
13905-        # Next, we put the verification key
13906-        d.addCallback(lambda ignored:
13907-            mw.put_verification_key(self.verification_key))
13908-        expected_verification_key_offset = expected_signature_offset + len(self.signature)
13909-        self.failUnlessEqual(len(self.verification_key), 6)
13910-        d.addCallback(lambda ignored:
13911+            expected_verification_key_offset = expected_signature_offset + len(self.signature)
13912+            self.failUnlessEqual(len(self.verification_key), 6)
13913             self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
13914hunk ./src/allmydata/test/test_storage.py 1953
13915-                                 {0: [self.verification_key]}))
13916+                                 {0: [self.verification_key]})
13917 
13918hunk ./src/allmydata/test/test_storage.py 1955
13919-        def _check_signable(ignored):
13920-            # Make sure that the signable is what we think it should be.
13921             signable = mw.get_signable()
13922             verno, seq, roothash, k, n, segsize, datalen = \
13923                                             struct.unpack(">BQ32sBBQQ",
13924hunk ./src/allmydata/test/test_storage.py 1966
13925             self.failUnlessEqual(n, 10)
13926             self.failUnlessEqual(segsize, 6)
13927             self.failUnlessEqual(datalen, 36)
13928-        d.addCallback(_check_signable)
13929-        # Next, we cause the offset table to be published.
13930-        d.addCallback(lambda ignored:
13931-            mw.finish_publishing())
13932-        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
13933+            expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
13934 
13935hunk ./src/allmydata/test/test_storage.py 1968
13936-        def _check_offsets(ignored):
13937             # Check the version number to make sure that it is correct.
13938             expected_version_number = struct.pack(">B", 1)
13939             self.failUnlessEqual(read("si1", [0], [(0, 1)]),
13940hunk ./src/allmydata/test/test_storage.py 2008
13941             expected_offset = struct.pack(">Q", expected_eof_offset)
13942             self.failUnlessEqual(read("si1", [0], [(99, 8)]),
13943                                  {0: [expected_offset]})
13944-        d.addCallback(_check_offsets)
13945+        d.addCallback(_check_publish)
13946         return d
13947 
13948     def _make_new_mw(self, si, share, datalength=36):
13949}
13950[mutable/publish.py: alter mutable publisher to work with new writing semantics
13951Kevan Carstensen <kevan@isnotajoke.com>**20100727225001
13952 Ignore-this: a6b4628e749e09bfcddf3309271b5831
13953] {
13954hunk ./src/allmydata/mutable/publish.py 389
13955         if self._state == PUSHING_BLOCKS_STATE:
13956             return self.push_segment(self._current_segment)
13957 
13958-        # XXX: Do we want more granularity in states? Is that useful at
13959-        #      all?
13960-        #      Yes -- quicker reaction to UCW.
13961         elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
13962             return self.push_everything_else()
13963 
13964hunk ./src/allmydata/mutable/publish.py 490
13965         results, salt = encoded_and_salt
13966         shares, shareids = results
13967         started = time.time()
13968-        dl = []
13969         for i in xrange(len(shares)):
13970             sharedata = shares[i]
13971             shareid = shareids[i]
13972hunk ./src/allmydata/mutable/publish.py 502
13973 
13974             # find the writer for this share
13975             writer = self.writers[shareid]
13976-            d = writer.put_block(sharedata, segnum, salt)
13977-            d.addCallback(self._got_write_answer, writer, started)
13978-            d.addErrback(self._connection_problem, writer)
13979-            dl.append(d)
13980-        return defer.DeferredList(dl)
13981+            writer.put_block(sharedata, segnum, salt)
13982 
13983 
13984     def push_everything_else(self):
13985hunk ./src/allmydata/mutable/publish.py 510
13986         I put everything else associated with a share.
13987         """
13988         encprivkey = self._encprivkey
13989-        d = self.push_encprivkey()
13990-        d.addCallback(self.push_blockhashes)
13991-        d.addCallback(self.push_sharehashes)
13992-        d.addCallback(self.push_toplevel_hashes_and_signature)
13993-        d.addCallback(self.finish_publishing)
13994+        self.push_encprivkey()
13995+        self.push_blockhashes()
13996+        self.push_sharehashes()
13997+        self.push_toplevel_hashes_and_signature()
13998+        d = self.finish_publishing()
13999         def _change_state(ignored):
14000             self._state = DONE_STATE
14001         d.addCallback(_change_state)
14002hunk ./src/allmydata/mutable/publish.py 525
14003     def push_encprivkey(self):
14004         started = time.time()
14005         encprivkey = self._encprivkey
14006-        dl = []
14007         for writer in self.writers.itervalues():
14008hunk ./src/allmydata/mutable/publish.py 526
14009-            d = writer.put_encprivkey(encprivkey)
14010-            d.addCallback(self._got_write_answer, writer, started)
14011-            d.addErrback(self._connection_problem, writer)
14012-            dl.append(d)
14013-        d = defer.DeferredList(dl)
14014-        return d
14015+            writer.put_encprivkey(encprivkey)
14016 
14017 
14018hunk ./src/allmydata/mutable/publish.py 529
14019-    def push_blockhashes(self, ignored):
14020+    def push_blockhashes(self):
14021         started = time.time()
14022hunk ./src/allmydata/mutable/publish.py 531
14023-        dl = []
14024         self.sharehash_leaves = [None] * len(self.blockhashes)
14025         for shnum, blockhashes in self.blockhashes.iteritems():
14026             t = hashtree.HashTree(blockhashes)
14027hunk ./src/allmydata/mutable/publish.py 538
14028             # set the leaf for future use.
14029             self.sharehash_leaves[shnum] = t[0]
14030             writer = self.writers[shnum]
14031-            d = writer.put_blockhashes(self.blockhashes[shnum])
14032-            d.addCallback(self._got_write_answer, writer, started)
14033-            d.addErrback(self._connection_problem, self.writers[shnum])
14034-            dl.append(d)
14035-        d = defer.DeferredList(dl)
14036-        return d
14037+            writer.put_blockhashes(self.blockhashes[shnum])
14038 
14039 
14040hunk ./src/allmydata/mutable/publish.py 541
14041-    def push_sharehashes(self, ignored):
14042+    def push_sharehashes(self):
14043         started = time.time()
14044         share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
14045         share_hash_chain = {}
14046hunk ./src/allmydata/mutable/publish.py 545
14047-        ds = []
14048         for shnum in xrange(len(self.sharehash_leaves)):
14049             needed_indices = share_hash_tree.needed_hashes(shnum)
14050             self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
14051hunk ./src/allmydata/mutable/publish.py 550
14052                                              for i in needed_indices] )
14053             writer = self.writers[shnum]
14054-            d = writer.put_sharehashes(self.sharehashes[shnum])
14055-            d.addCallback(self._got_write_answer, writer, started)
14056-            d.addErrback(self._connection_problem, writer)
14057-            ds.append(d)
14058+            writer.put_sharehashes(self.sharehashes[shnum])
14059         self.root_hash = share_hash_tree[0]
14060hunk ./src/allmydata/mutable/publish.py 552
14061-        d = defer.DeferredList(ds)
14062-        return d
14063 
14064 
14065hunk ./src/allmydata/mutable/publish.py 554
14066-    def push_toplevel_hashes_and_signature(self, ignored):
14067+    def push_toplevel_hashes_and_signature(self):
14068         # We need to to three things here:
14069         #   - Push the root hash and salt hash
14070         #   - Get the checkstring of the resulting layout; sign that.
14071hunk ./src/allmydata/mutable/publish.py 560
14072         #   - Push the signature
14073         started = time.time()
14074-        ds = []
14075         for shnum in xrange(self.total_shares):
14076             writer = self.writers[shnum]
14077hunk ./src/allmydata/mutable/publish.py 562
14078-            d = writer.put_root_hash(self.root_hash)
14079-            d.addCallback(self._got_write_answer, writer, started)
14080-            ds.append(d)
14081-        d = defer.DeferredList(ds)
14082-        d.addCallback(self._update_checkstring)
14083-        d.addCallback(self._make_and_place_signature)
14084-        return d
14085+            writer.put_root_hash(self.root_hash)
14086+        self._update_checkstring()
14087+        self._make_and_place_signature()
14088 
14089 
14090hunk ./src/allmydata/mutable/publish.py 567
14091-    def _update_checkstring(self, ignored):
14092+    def _update_checkstring(self):
14093         """
14094         After putting the root hash, MDMF files will have the
14095         checkstring written to the storage server. This means that we
14096hunk ./src/allmydata/mutable/publish.py 578
14097         self._checkstring = self.writers.values()[0].get_checkstring()
14098 
14099 
14100-    def _make_and_place_signature(self, ignored):
14101+    def _make_and_place_signature(self):
14102         """
14103         I create and place the signature.
14104         """
14105hunk ./src/allmydata/mutable/publish.py 586
14106         signable = self.writers[0].get_signable()
14107         self.signature = self._privkey.sign(signable)
14108 
14109-        ds = []
14110         for (shnum, writer) in self.writers.iteritems():
14111hunk ./src/allmydata/mutable/publish.py 587
14112-            d = writer.put_signature(self.signature)
14113-            d.addCallback(self._got_write_answer, writer, started)
14114-            d.addErrback(self._connection_problem, writer)
14115-            ds.append(d)
14116-        return defer.DeferredList(ds)
14117+            writer.put_signature(self.signature)
14118 
14119 
14120hunk ./src/allmydata/mutable/publish.py 590
14121-    def finish_publishing(self, ignored):
14122+    def finish_publishing(self):
14123         # We're almost done -- we just need to put the verification key
14124         # and the offsets
14125         started = time.time()
14126hunk ./src/allmydata/mutable/publish.py 601
14127         # TODO: Bad, since we remove from this same dict. We need to
14128         # make a copy, or just use a non-iterated value.
14129         for (shnum, writer) in self.writers.iteritems():
14130-            d = writer.put_verification_key(verification_key)
14131-            d.addCallback(self._got_write_answer, writer, started)
14132-            d.addCallback(self._record_verinfo)
14133-            d.addCallback(lambda ignored, writer=writer:
14134-                writer.finish_publishing())
14135+            writer.put_verification_key(verification_key)
14136+            d = writer.finish_publishing()
14137             d.addCallback(self._got_write_answer, writer, started)
14138             d.addErrback(self._connection_problem, writer)
14139             ds.append(d)
14140hunk ./src/allmydata/mutable/publish.py 606
14141+        self._record_verinfo()
14142         return defer.DeferredList(ds)
14143 
14144 
14145hunk ./src/allmydata/mutable/publish.py 610
14146-    def _record_verinfo(self, ignored):
14147+    def _record_verinfo(self):
14148         self.versioninfo = self.writers.values()[0].get_verinfo()
14149 
14150 
14151}
14152[test/test_mutable.py: Add tests for new servermap behavior
14153Kevan Carstensen <kevan@isnotajoke.com>**20100728232434
14154 Ignore-this: aa6da7dbc9f86eb8840c8f0e779e644d
14155] {
14156hunk ./src/allmydata/test/test_mutable.py 773
14157     def setUp(self):
14158         return self.publish_one()
14159 
14160-    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None):
14161+    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None,
14162+                       update_range=None):
14163         if fn is None:
14164             fn = self._fn
14165         if sb is None:
14166hunk ./src/allmydata/test/test_mutable.py 780
14167             sb = self._storage_broker
14168         smu = ServermapUpdater(fn, sb, Monitor(),
14169-                               ServerMap(), mode)
14170+                               ServerMap(), mode, update_range=update_range)
14171         d = smu.update()
14172         return d
14173 
14174hunk ./src/allmydata/test/test_mutable.py 855
14175         d.addCallback(lambda sm: self.failUnlessOneRecoverable(sm, 10))
14176         return d
14177 
14178+
14179     def test_mark_bad(self):
14180         d = defer.succeed(None)
14181         ms = self.make_servermap
14182hunk ./src/allmydata/test/test_mutable.py 970
14183         return d
14184 
14185 
14186+    def test_fetch_update(self):
14187+        d = defer.succeed(None)
14188+        d.addCallback(lambda ignored:
14189+            self.publish_mdmf())
14190+        d.addCallback(lambda ignored:
14191+            self.make_servermap(mode=MODE_WRITE, update_range=(1, 2)))
14192+        def _check_servermap(sm):
14193+            # 10 shares
14194+            self.failUnlessEqual(len(sm.update_data), 10)
14195+            # one version
14196+            for data in sm.update_data.itervalues():
14197+                self.failUnlessEqual(len(data), 1)
14198+        d.addCallback(_check_servermap)
14199+        return d
14200+
14201+
14202     def test_servermapupdater_finds_sdmf_files(self):
14203         d = defer.succeed(None)
14204         d.addCallback(lambda ignored:
14205hunk ./src/allmydata/test/test_mutable.py 1756
14206 
14207     def test_mdmf_repairable_5shares(self):
14208         d = self.publish_mdmf()
14209-        def _delete_all_shares(ign):
14210+        def _delete_some_shares(ign):
14211             shares = self._storage._peers
14212             for peerid in shares:
14213                 for shnum in list(shares[peerid]):
14214hunk ./src/allmydata/test/test_mutable.py 1762
14215                     if shnum > 5:
14216                         del shares[peerid][shnum]
14217-        d.addCallback(_delete_all_shares)
14218+        d.addCallback(_delete_some_shares)
14219         d.addCallback(lambda ign: self._fn.check(Monitor()))
14220hunk ./src/allmydata/test/test_mutable.py 1764
14221+        def _check(cr):
14222+            self.failIf(cr.is_healthy())
14223+            self.failUnless(cr.is_recoverable())
14224+            return cr
14225+        d.addCallback(_check)
14226         d.addCallback(lambda check_results: self._fn.repair(check_results))
14227hunk ./src/allmydata/test/test_mutable.py 1770
14228-        def _check(crr):
14229+        def _check1(crr):
14230             self.failUnlessEqual(crr.get_successful(), True)
14231hunk ./src/allmydata/test/test_mutable.py 1772
14232-        d.addCallback(_check)
14233+        d.addCallback(_check1)
14234         return d
14235 
14236 
14237}
14238[mutable/filenode.py: add an update method.
14239Kevan Carstensen <kevan@isnotajoke.com>**20100730234029
14240 Ignore-this: 3ed4dcfb8a247812ed357216913334e7
14241] {
14242hunk ./src/allmydata/mutable/filenode.py 9
14243 from foolscap.api import eventually
14244 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
14245      NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
14246-     IMutableFileVersion
14247-from allmydata.util import hashutil, log, consumer
14248+     IMutableFileVersion, IWritable
14249+from allmydata.util import hashutil, log, consumer, deferredutil
14250 from allmydata.util.assertutil import precondition
14251 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
14252 from allmydata.monitor import Monitor
14253hunk ./src/allmydata/mutable/filenode.py 17
14254 from pycryptopp.cipher.aes import AES
14255 
14256 from allmydata.mutable.publish import Publish, MutableFileHandle, \
14257-                                      MutableData
14258+                                      MutableData,\
14259+                                      DEFAULT_MAX_SEGMENT_SIZE, \
14260+                                      TransformingUploadable
14261 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
14262      ResponseCache, UncoordinatedWriteError
14263 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
14264hunk ./src/allmydata/mutable/filenode.py 671
14265     overwrite or modify the contents of the mutable file that I
14266     reference.
14267     """
14268-    implements(IMutableFileVersion)
14269+    implements(IMutableFileVersion, IWritable)
14270 
14271     def __init__(self,
14272                  node,
14273hunk ./src/allmydata/mutable/filenode.py 960
14274     def _did_upload(self, res, size):
14275         self._size = size
14276         return res
14277+
14278+    def _update(self, data, offset):
14279+        """
14280+        Do an update of this mutable file version by inserting data at
14281+        offset within the file. If offset is the EOF, this is an append
14282+        operation. I return a Deferred that fires with the results of
14283+        the update operation when it has completed.
14284+
14285+        In cases where update does not append any data, or where it does
14286+        not append so many blocks that the block count crosses a
14287+        power-of-two boundary, this operation will use roughly
14288+        O(data.get_size()) memory/bandwidth/CPU to perform the update.
14289+        Otherwise, it must download, re-encode, and upload the entire
14290+        file again, which will use O(filesize) resources.
14291+        """
14292+        return self._do_serialized(self._update, data, offset)
14293+
14294+
14295+    def _update(self, data, offset):
14296+        """
14297+        I update the mutable file version represented by this particular
14298+        IMutableVersion by inserting the data in data at the offset
14299+        offset. I return a Deferred that fires when this has been
14300+        completed.
14301+        """
14302+        d = self._do_update_update(data, offset)
14303+        d.addCallback(self._decode_and_decrypt_segments)
14304+        d.addCallback(self._build_uploadable_and_finish)
14305+        return d
14306+
14307+
14308+    def _do_update_update(self, data, offset):
14309+        """
14310+        I start the Servermap update that gets us the data we need to
14311+        continue the update process. I return a Deferred that fires when
14312+        the servermap update is done.
14313+        """
14314+        assert IMutableUploadable.providedBy(data)
14315+        assert self.is_mutable()
14316+        # offset == self.get_size() is valid and means that we are
14317+        # appending data to the file.
14318+        assert offset <= self.get_size()
14319+
14320+        datasize = data.get_size()
14321+        # We'll need the segment that the data starts in, regardless of
14322+        # what we'll do later.
14323+        start_segment = mathutil.div_ceil(offset, DEFAULT_MAX_SEGMENT_SIZE)
14324+        start_segment -= 1
14325+
14326+        # We only need the end segment if the data we append does not go
14327+        # beyond the current end-of-file.
14328+        end_segment = start_segment
14329+        if offset + data.get_size() < self.get_size():
14330+            end_data = offset + data.get_size()
14331+            end_segment = mathutil.div_ceil(end_data, DEFAULT_MAX_SEGMENT_SIZE)
14332+            end_segment -= 1
14333+
14334+        # Now ask for the servermap to be updated in MODE_WRITE with
14335+        # this update range.
14336+        u = ServermapUpdater(self, self._storage_broker, Monitor(),
14337+                             self._servermap,
14338+                             mode=MODE_WRITE,
14339+                             update_range=(start_segment, end_segment))
14340+        return u.update()
14341+
14342+
14343+    def _decode_and_decrypt_segments(self, ignored, data, offset):
14344+        """
14345+        After the servermap update, I take the encrypted and encoded
14346+        data that the servermap fetched while doing its update and
14347+        transform it into decoded-and-decrypted plaintext that can be
14348+        used by the new uploadable. I return a Deferred that fires with
14349+        the segments.
14350+        """
14351+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
14352+        # decode: takes in our blocks and salts from the servermap,
14353+        # returns a Deferred that fires with the corresponding plaintext
14354+        # segments. Does not download -- simply takes advantage of
14355+        # existing infrastructure within the Retrieve class to avoid
14356+        # duplicating code.
14357+        sm = self._servermap
14358+        # XXX: If the methods in the servermap don't work as
14359+        # abstractions, you should rewrite them instead of going around
14360+        # them.
14361+        data = sm.update_data
14362+        data = [data[1] for i in update_data if i[0] == self._verinfo]
14363+        start_seg_data = [d[0] for d in data]
14364+        end_seg_data = [d[1] for d in data]
14365+        d1 = r.decode(start_seg_data)
14366+        d2 = r.decode(end_seg_data)
14367+        return deferredutil.gatherResults([d1, d2])
14368+
14369+
14370+    def _build_uploadable_and_finish(self, segments, data, offset):
14371+        """
14372+        After the process has the plaintext segments, I build the
14373+        TransformingUploadable that the publisher will eventually
14374+        re-upload to the grid. I then invoke the publisher with that
14375+        uploadable, and return a Deferred when the publish operation has
14376+        completed without issue.
14377+        """
14378+        u = TransformingUploadable(data, offset,
14379+                                   DEFAULT_MAX_SEGMENT_SIZE,
14380+                                   segments[0],
14381+                                   segments[1])
14382+        p = Publish(self._node, self._storage_broker, self._servermap)
14383+        return p.update(data, offset, blockhashes)
14384}
14385[mutable/publish.py: learn how to update as well as publish files.
14386Kevan Carstensen <kevan@isnotajoke.com>**20100730234056
14387 Ignore-this: 4c04857280da970f5c1c6c75466f1cd9
14388] {
14389hunk ./src/allmydata/mutable/publish.py 132
14390             kwargs["facility"] = "tahoe.mutable.publish"
14391         return log.msg(*args, **kwargs)
14392 
14393+
14394+    def update(self, data, offset, blockhashes):
14395+        """
14396+        I replace the contents of this file with the contents of data,
14397+        starting at offset. I return a Deferred that fires with None
14398+        when the replacement has been completed, or with an error if
14399+        something went wrong during the process.
14400+
14401+        Note that this process will not upload new shares. If the file
14402+        being updated is in need of repair, callers will have to repair
14403+        it on their own.
14404+        """
14405+        # How this works:
14406+        # 1: Make peer assignments. We'll assign each share that we know
14407+        # about on the grid to that peer that currently holds that
14408+        # share, and will not place any new shares.
14409+        # 2: Setup encoding parameters. Most of these will stay the same
14410+        # -- datalength will change, as will some of the offsets.
14411+        # 3. Upload the new segments.
14412+        # 4. Be done.
14413+        assert IMutableUploadable.providedBy(data)
14414+
14415+        self.data = data
14416+
14417+        # XXX: Use the MutableFileVersion instead.
14418+        self.datalength = self._node.get_size()
14419+        if offset + data.get_size() > self.datalength:
14420+            self.datalength = offset + data.get_size()
14421+
14422+        if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
14423+            self._version = MDMF_VERSION
14424+        else:
14425+            self._version = SDMF_VERSION
14426+
14427+        self.log("starting update")
14428+        self.log("adding new data of length %d at offset %d" % \
14429+                    data.get_size(), offset)
14430+        self.log("new data length is %d" % self.datalength)
14431+        self._status.set_size(self.datalength)
14432+        self._status.set_status("Started")
14433+        self._started = time.time()
14434+
14435+        self.done_deferred = defer.Deferred()
14436+
14437+        self._writekey = self._node.get_writekey()
14438+        assert self._writekey, "need write capability to publish"
14439+
14440+        # first, which servers will we publish to? We require that the
14441+        # servermap was updated in MODE_WRITE, so we can depend upon the
14442+        # peerlist computed by that process instead of computing our own.
14443+        assert self._servermap
14444+        assert self._servermap.last_update_mode in (MODE_WRITE, MODE_CHECK)
14445+        # we will push a version that is one larger than anything present
14446+        # in the grid, according to the servermap.
14447+        self._new_seqnum = self._servermap.highest_seqnum() + 1
14448+        self._status.set_servermap(self._servermap)
14449+
14450+        self.log(format="new seqnum will be %(seqnum)d",
14451+                 seqnum=self._new_seqnum, level=log.NOISY)
14452+
14453+        # We're updating an existing file, so all of the following
14454+        # should be available.
14455+        self.readkey = self._node.get_readkey()
14456+        self.required_shares = self._node.get_required_shares()
14457+        assert self.required_shares is not None
14458+        self.total_shares = self._node.get_total_shares()
14459+        assert self.total_shares is not None
14460+        self._status.set_encoding(self.required_shares, self.total_shares)
14461+
14462+        self._pubkey = self._node.get_pubkey()
14463+        assert self._pubkey
14464+        self._privkey = self._node.get_privkey()
14465+        assert self._privkey
14466+        self._encprivkey = self._node.get_encprivkey()
14467+
14468+        sb = self._storage_broker
14469+        full_peerlist = sb.get_servers_for_index(self._storage_index)
14470+        self.full_peerlist = full_peerlist # for use later, immutable
14471+        self.bad_peers = set() # peerids who have errbacked/refused requests
14472+
14473+        # This will set self.segment_size, self.num_segments, and
14474+        # self.fec. TODO: Does it know how to do the offset? Probably
14475+        # not. So do that part next.
14476+        self.setup_encoding_parameters(offset=offset)
14477+
14478+        # if we experience any surprises (writes which were rejected because
14479+        # our test vector did not match, or shares which we didn't expect to
14480+        # see), we set this flag and report an UncoordinatedWriteError at the
14481+        # end of the publish process.
14482+        self.surprised = False
14483+
14484+        # as a failsafe, refuse to iterate through self.loop more than a
14485+        # thousand times.
14486+        self.looplimit = 1000
14487+
14488+        # we keep track of three tables. The first is our goal: which share
14489+        # we want to see on which servers. This is initially populated by the
14490+        # existing servermap.
14491+        self.goal = set() # pairs of (peerid, shnum) tuples
14492+
14493+        # the second table is our list of outstanding queries: those which
14494+        # are in flight and may or may not be delivered, accepted, or
14495+        # acknowledged. Items are added to this table when the request is
14496+        # sent, and removed when the response returns (or errbacks).
14497+        self.outstanding = set() # (peerid, shnum) tuples
14498+
14499+        # the third is a table of successes: share which have actually been
14500+        # placed. These are populated when responses come back with success.
14501+        # When self.placed == self.goal, we're done.
14502+        self.placed = set() # (peerid, shnum) tuples
14503+
14504+        # we also keep a mapping from peerid to RemoteReference. Each time we
14505+        # pull a connection out of the full peerlist, we add it to this for
14506+        # use later.
14507+        self.connections = {}
14508+
14509+        self.bad_share_checkstrings = {}
14510+
14511+        # This is set at the last step of the publishing process.
14512+        self.versioninfo = ""
14513+
14514+        # we use the servermap to populate the initial goal: this way we will
14515+        # try to update each existing share in place. Since we're
14516+        # updating, we ignore damaged and missing shares -- callers must
14517+        # do a repair to repair and recreate these.
14518+        for (peerid, shnum) in self._servermap.servermap:
14519+            self.goal.add( (peerid, shnum) )
14520+            self.connections[peerid] = self._servermap.connections[peerid]
14521+        self.writers = {}
14522+        if self._version == MDMF_VERSION:
14523+            writer_class = MDMFSlotWriteProxy
14524+        else:
14525+            writer_class = SDMFSlotWriteProxy
14526+
14527+        # For each (peerid, shnum) in self.goal, we make a
14528+        # write proxy for that peer. We'll use this to write
14529+        # shares to the peer.
14530+        for key in self.goal:
14531+            peerid, shnum = key
14532+            write_enabler = self._node.get_write_enabler(peerid)
14533+            renew_secret = self._node.get_renewal_secret(peerid)
14534+            cancel_secret = self._node.get_cancel_secret(peerid)
14535+            secrets = (write_enabler, renew_secret, cancel_secret)
14536+
14537+            self.writers[shnum] =  writer_class(shnum,
14538+                                                self.connections[peerid],
14539+                                                self._storage_index,
14540+                                                secrets,
14541+                                                self._new_seqnum,
14542+                                                self.required_shares,
14543+                                                self.total_shares,
14544+                                                self.segment_size,
14545+                                                self.datalength)
14546+            self.writers[shnum].peerid = peerid
14547+            assert (peerid, shnum) in self._servermap.servermap
14548+            old_versionid, old_timestamp = self._servermap.servermap[key]
14549+            (old_seqnum, old_root_hash, old_salt, old_segsize,
14550+             old_datalength, old_k, old_N, old_prefix,
14551+             old_offsets_tuple) = old_versionid
14552+            self.writers[shnum].set_checkstring(old_seqnum,
14553+                                                old_root_hash,
14554+                                                old_salt)
14555+
14556+        # Our remote shares will not have a complete checkstring until
14557+        # after we are done writing share data and have started to write
14558+        # blocks. In the meantime, we need to know what to look for when
14559+        # writing, so that we can detect UncoordinatedWriteErrors.
14560+        self._checkstring = self.writers.values()[0].get_checkstring()
14561+
14562+        # Now, we start pushing shares.
14563+        self._status.timings["setup"] = time.time() - self._started
14564+        # First, we encrypt, encode, and publish the shares that we need
14565+        # to encrypt, encode, and publish.
14566+
14567+        # Our update process fetched these for us. We need to update
14568+        # them in place as publishing happens.
14569+        self.blockhashes = {} # (shnum, [blochashes])
14570+        for (i, bht) in blockhashes.iteritems():
14571+            self.blockhashes[i] = bht
14572+
14573+        # These are filled in later, after we've modified the block hash
14574+        # tree suitably.
14575+        self.sharehash_leaves = None # eventually [sharehashes]
14576+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
14577+                              # validate the share]
14578+
14579+        d = defer.succeed(None)
14580+        self.log("Starting push")
14581+
14582+        self._state = PUSHING_BLOCKS_STATE
14583+        self._push()
14584+
14585+        return self.done_deferred
14586+
14587+
14588     def publish(self, newdata):
14589         """Publish the filenode's current contents.  Returns a Deferred that
14590         fires (with None) when the publish has done as much work as it's ever
14591hunk ./src/allmydata/mutable/publish.py 502
14592         # that we publish. We define it this way so that empty publishes
14593         # will still have something to write to the remote slot.
14594         self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
14595+        for i in xrange(self.total_shares):
14596+            blocks = self.blockhashes[i]
14597+            for j in xrange(self.num_segments):
14598+                blocks.append(None)
14599         self.sharehash_leaves = None # eventually [sharehashes]
14600         self.sharehashes = {} # shnum -> [sharehash leaves necessary to
14601                               # validate the share]
14602hunk ./src/allmydata/mutable/publish.py 519
14603         return self.done_deferred
14604 
14605 
14606-    def setup_encoding_parameters(self):
14607+    def setup_encoding_parameters(self, offset=0):
14608         if self._version == MDMF_VERSION:
14609             segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
14610         else:
14611hunk ./src/allmydata/mutable/publish.py 528
14612         segment_size = mathutil.next_multiple(segment_size,
14613                                               self.required_shares)
14614         self.segment_size = segment_size
14615+
14616+        # Calculate the starting segment for the upload.
14617         if segment_size:
14618             self.num_segments = mathutil.div_ceil(self.datalength,
14619                                                   segment_size)
14620hunk ./src/allmydata/mutable/publish.py 533
14621+            self.starting_segment = mathutil.div_ceil(offset,
14622+                                                      segment_size)
14623+            if offset == 0:
14624+                self.starting_segment = 0
14625+
14626         else:
14627             self.num_segments = 0
14628hunk ./src/allmydata/mutable/publish.py 540
14629+            self.starting_segment = 0
14630+
14631 
14632         self.log("building encoding parameters for file")
14633         self.log("got segsize %d" % self.segment_size)
14634hunk ./src/allmydata/mutable/publish.py 576
14635                                 self.total_shares)
14636             self.tail_fec = tail_fec
14637 
14638-        self._current_segment = 0
14639+        self._current_segment = self.starting_segment
14640+        self.end_segment = self.num_segments - 1
14641+        # Now figure out where the last segment should be.
14642+        if self.data.get_size() != self.datalength:
14643+            end = offset + self.data.get_size()
14644+            self.end_segment = mathutil.div_ceil(end,
14645+                                                 segment_size)
14646+            self.end_segment -= 1
14647+        self.log("got start segment %d" % self.starting_segment)
14648+        self.log("got end segment %d" % self.end_segment)
14649 
14650 
14651     def _push(self, ignored=None):
14652hunk ./src/allmydata/mutable/publish.py 618
14653         if self.num_segments == 0 and self._version == SDMF_VERSION:
14654             self._add_dummy_salts()
14655 
14656-        if segnum == self.num_segments:
14657+        if segnum > self.end_segment:
14658             # We don't have any more segments to push.
14659             self._state = PUSHING_EVERYTHING_ELSE_STATE
14660             return self._push()
14661hunk ./src/allmydata/mutable/publish.py 715
14662             else:
14663                 hashed = sharedata
14664             block_hash = hashutil.block_hash(hashed)
14665-            self.blockhashes[shareid].append(block_hash)
14666+            self.blockhashes[shareid][segnum] = block_hash
14667 
14668             # find the writer for this share
14669             writer = self.writers[shareid]
14670hunk ./src/allmydata/mutable/publish.py 1227
14671         assert isinstance(s, str)
14672 
14673         MutableFileHandle.__init__(self, StringIO(s))
14674+
14675+
14676+class TransformingUploadable:
14677+    """
14678+    I am an IMutableUploadable that wraps another IMutableUploadable,
14679+    and some segments that are already on the grid. When I am called to
14680+    read, I handle merging of boundary segments.
14681+    """
14682+    implements(IMutableUploadable)
14683+
14684+
14685+    def __init__(self, data, offset, segment_size, start, end):
14686+        assert IMutableUploadable.providedBy(data)
14687+        # offset == data.get_size() means that we're appending.
14688+        assert offset <= data.get_size()
14689+
14690+        self._newdata = data
14691+        self._offset = offset
14692+        self._segment_size = segment_size
14693+        self._start = start
14694+        self._end = end
14695+
14696+        self._read_marker = 0
14697+        self._first_segment_offset = offset % segment_size
14698+
14699+
14700+    def get_size(self):
14701+        # TODO
14702+        pass
14703+
14704+
14705+    def read(self, length):
14706+        # We can be in three states here:
14707+        #   0. In the first segment of data. In this segment, we need to
14708+        #      return the original data until we get to the new data.
14709+        #      This is so that replacing data in the middle of an
14710+        #      existing segment works as expected.
14711+        #   1. After the first segment, before the last segment. In this
14712+        #      state, we delegate all reads to the underlying
14713+        #      IMutableUploadable.
14714+        #   2. Reading the last segment of data. If our replacement ends
14715+        #      in the middle of an existing segment, we need to pad the
14716+        #      replacement data with enough data from the end of segment
14717+        #      so that the replacement can happen.
14718+
14719+        # are we in state 0?
14720+        if self._read_marker < self._first_segment_offset:
14721+            # We need to read at least some data from the first segment
14722+            # to satisfy this read.
14723+            old_data_length = self._first_segment_offset - self._read_marker
14724+            if old_data_length > length:
14725+                old_data_length = length
14726+
14727+            new_data_length = length - old_data_length
14728+            old_data_end = old_data_length + self._read_marker
14729+            old_data = self._start[self._read_marker:old_data_end]
14730+            new_data = self._newdata.read(new_data_length)
14731+            new_data = "".join(new_data)
14732+            data = old_data + new_data
14733+
14734+        # are we in state 3?
14735+        elif self._read_marker + self._length > \
14736+                self._offset + self._newdata.get_size():
14737+            # We need to pad this read (for the last segment) with an
14738+            # appropriate amount of data from the old segment.
14739+            new_data_length = self._newdata.get_size() - self._read_marker
14740+            new_data = self._newdata.read(new_data_length)
14741+            new_data = "".join(data)
14742+            old_data_length = length - new_data_length
14743+            old_data_offset = new_data_length
14744+            old_data = self._end[old_data_offset:old_data_offset +
14745+                                 old_data_length]
14746+            data = new_data + old_data
14747+        else:
14748+            data = self._newdata.read(length)
14749+            data = "".join(data)
14750+
14751+        assert len(data) == length
14752+        self._read_marker += length
14753+        return data
14754+
14755+
14756+    def close(self):
14757+        pass
14758}
14759[mutable/retrieve.py: expose decoding and decrypting methods to callers
14760Kevan Carstensen <kevan@isnotajoke.com>**20100730234141
14761 Ignore-this: db66ee9019755f49388fe74049f26982
14762] hunk ./src/allmydata/mutable/retrieve.py 291
14763         return self._done_deferred
14764 
14765 
14766+    def decode(blocks_and_salts, segnum):
14767+        """
14768+        I am a helper method that the mutable file update process uses
14769+        as a shortcut to decode and decrypt the segments that it needs
14770+        to fetch in order to perform a file update. I take in a
14771+        collection of blocks and salts, and pick some of those to make a
14772+        segment with. I return the plaintext associated with that
14773+        segment.
14774+        """
14775+        self._setup_encoding_parameters()
14776+        d = self._decode_blocks(blocks_and_salts, segnum)
14777+        d.addCallback(self._decrypt_segment)
14778+        return d
14779+
14780+
14781     def _setup_encoding_parameters(self):
14782         """
14783         I set up the encoding parameters, including k, n, the number
14784[mutable/servermap.py: lay some groundwork for IWritable
14785Kevan Carstensen <kevan@isnotajoke.com>**20100730234229
14786 Ignore-this: c9ef2cace79db1e15937d030bcad20d9
14787] {
14788hunk ./src/allmydata/mutable/servermap.py 9
14789 from twisted.python import failure
14790 from foolscap.api import DeadReferenceError, RemoteException, eventually, \
14791                          fireEventually
14792-from allmydata.util import base32, hashutil, idlib, log
14793+from allmydata.util import base32, hashutil, idlib, log, deferredutil
14794 from allmydata.storage.server import si_b2a
14795 from allmydata.interfaces import IServermapUpdaterStatus
14796 from pycryptopp.publickey import rsa
14797hunk ./src/allmydata/mutable/servermap.py 124
14798         self.bad_shares = {} # maps (peerid,shnum) to old checkstring
14799         self.last_update_mode = None
14800         self.last_update_time = 0
14801+        self.update_data = {} # (verinfo,shnum) => data
14802 
14803     def copy(self):
14804         s = ServerMap()
14805hunk ./src/allmydata/mutable/servermap.py 340
14806         return False
14807 
14808 
14809+    def get_update_data_for_share_and_verinfo(self, shnum, verinfo):
14810+        """
14811+        I return the update data for the given shnum
14812+        """
14813+        update_data = self.update_data[shnum]
14814+        update_datum = [i[1] for i in update_data if i[0] == verinfo][0]
14815+        return update_datum
14816+
14817+
14818+    def set_update_data_for_share_and_verinfo(self, shnum, verinfo, data):
14819+        """
14820+        I record the block hash tree for the given shnum.
14821+        """
14822+        self.update_data.setdefault(shnum , []).append((verinfo, data))
14823+
14824+
14825 class ServermapUpdater:
14826     def __init__(self, filenode, storage_broker, monitor, servermap,
14827hunk ./src/allmydata/mutable/servermap.py 358
14828-                 mode=MODE_READ, add_lease=False):
14829+                 mode=MODE_READ, add_lease=False, update_range=None):
14830         """I update a servermap, locating a sufficient number of useful
14831         shares and remembering where they are located.
14832 
14833hunk ./src/allmydata/mutable/servermap.py 405
14834             # we use unpack_prefix_and_signature, so we need 1k
14835             self._read_size = 1000
14836         self._need_privkey = False
14837+
14838         if mode == MODE_WRITE and not self._node.get_privkey():
14839             self._need_privkey = True
14840         # check+repair: repair requires the privkey, so if we didn't happen
14841hunk ./src/allmydata/mutable/servermap.py 412
14842         # to ask for it during the check, we'll have problems doing the
14843         # publish.
14844 
14845+        self.fetch_update_data = False
14846+        if mode == MODE_WRITE and update_range:
14847+            # We're updating the servermap in preparation for an
14848+            # in-place file update, so we need to fetch some additional
14849+            # data from each share that we find.
14850+            assert len(update_range) == 2
14851+
14852+            self.start_segment = update_range[0]
14853+            self.end_segment = update_range[1]
14854+            self.fetch_update_data = True
14855+
14856         prefix = si_b2a(self._storage_index)[:5]
14857         self._log_number = log.msg(format="SharemapUpdater(%(si)s): starting (%(mode)s)",
14858                                    si=prefix, mode=mode)
14859hunk ./src/allmydata/mutable/servermap.py 643
14860                       level=log.NOISY)
14861         now = time.time()
14862         elapsed = now - started
14863-        self._queries_outstanding.discard(peerid)
14864-        self._servermap.reachable_peers.add(peerid)
14865-        self._must_query.discard(peerid)
14866-        self._queries_completed += 1
14867+        def _done_processing(ignored=None):
14868+            self._queries_outstanding.discard(peerid)
14869+            self._servermap.reachable_peers.add(peerid)
14870+            self._must_query.discard(peerid)
14871+            self._queries_completed += 1
14872         if not self._running:
14873             self.log("but we're not running, so we'll ignore it", parent=lp,
14874                      level=log.NOISY)
14875hunk ./src/allmydata/mutable/servermap.py 651
14876+            _done_processing()
14877             self._status.add_per_server_time(peerid, "late", started, elapsed)
14878             return
14879         self._status.add_per_server_time(peerid, "query", started, elapsed)
14880hunk ./src/allmydata/mutable/servermap.py 679
14881             #     public key. We use this to validate the signature.
14882             if not self._node.get_pubkey():
14883                 # fetch and set the public key.
14884-                d = reader.get_verification_key()
14885+                d = reader.get_verification_key(queue=True)
14886                 d.addCallback(lambda results, shnum=shnum, peerid=peerid:
14887                     self._try_to_set_pubkey(results, peerid, shnum, lp))
14888                 # XXX: Make self._pubkey_query_failed?
14889hunk ./src/allmydata/mutable/servermap.py 688
14890             else:
14891                 # we already have the public key.
14892                 d = defer.succeed(None)
14893+
14894             # Neither of these two branches return anything of
14895             # consequence, so the first entry in our deferredlist will
14896             # be None.
14897hunk ./src/allmydata/mutable/servermap.py 705
14898             #   to get the version information. In MDMF, this lives at
14899             #   the end of the share, so unless the file is quite small,
14900             #   we'll need to do a remote fetch to get it.
14901-            d3 = reader.get_signature()
14902+            d3 = reader.get_signature(queue=True)
14903             d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
14904                 self._got_corrupt_share(error, shnum, peerid, data, lp))
14905             #  Once we have all three of these responses, we can move on
14906hunk ./src/allmydata/mutable/servermap.py 714
14907             # Does the node already have a privkey? If not, we'll try to
14908             # fetch it here.
14909             if self._need_privkey:
14910-                d4 = reader.get_encprivkey()
14911+                d4 = reader.get_encprivkey(queue=True)
14912                 d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
14913                     self._try_to_validate_privkey(results, peerid, shnum, lp))
14914                 d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
14915hunk ./src/allmydata/mutable/servermap.py 722
14916             else:
14917                 d4 = defer.succeed(None)
14918 
14919-            dl = defer.DeferredList([d, d2, d3, d4])
14920+
14921+            if self.fetch_update_data:
14922+                # fetch the block hash tree and first + last segment, as
14923+                # configured earlier.
14924+                # Then set them in wherever we happen to want to set
14925+                # them.
14926+                ds = []
14927+                # XXX: We do this above, too. Is there a good way to
14928+                # make the two routines share the value without
14929+                # introducing more roundtrips?
14930+                ds.append(reader.get_verinfo())
14931+                ds.append(reader.get_blockhashes(queue=True))
14932+                ds.append(reader.get_block_and_salt(self.start_segment,
14933+                                                    queue=True))
14934+                ds.append(reader.get_block_and_salt(self.end_segment,
14935+                                                    queue=True))
14936+                d5 = deferredutil.gatherResults(ds)
14937+                d5.addCallback(self._got_update_results_one_share, shnum)
14938+            else:
14939+                d5 = defer.succeed(None)
14940+
14941+            dl = defer.DeferredList([d, d2, d3, d4, d5])
14942+            reader.flush()
14943             dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
14944                 self._got_signature_one_share(results, shnum, peerid, lp))
14945             dl.addErrback(lambda error, shnum=shnum, data=data:
14946hunk ./src/allmydata/mutable/servermap.py 764
14947         # that we returned to our caller to fire, which tells them that
14948         # they have a complete servermap, and that we won't be touching
14949         # the servermap anymore.
14950+        dl.addCallback(_done_processing)
14951         dl.addCallback(self._check_for_done)
14952         dl.addErrback(self._fatal_error)
14953         # all done!
14954hunk ./src/allmydata/mutable/servermap.py 799
14955                  peerid=idlib.shortnodeid_b2a(peerid),
14956                  level=log.NOISY,
14957                  parent=lp)
14958-        _, verinfo, signature, __ = results
14959+        _, verinfo, signature, __, ___ = results
14960         (seqnum,
14961          root_hash,
14962          saltish,
14963hunk ./src/allmydata/mutable/servermap.py 864
14964         return verinfo
14965 
14966 
14967+    def _got_update_results_one_share(self, results, share):
14968+        """
14969+        I record the update results in results.
14970+        """
14971+        assert len(results) == 4
14972+        verinfo, blockhashes, start, end = results
14973+        update_data = (blockhashes, start, end)
14974+        self._servermap.set_update_data_for_share_and_verinfo(share,
14975+                                                              verinfo,
14976+                                                              update_data)
14977+
14978+
14979     def _deserialize_pubkey(self, pubkey_s):
14980         verifier = rsa.create_verifying_key_from_string(pubkey_s)
14981         return verifier
14982}
14983[test/test_mutable.py: add tests for updating behavior
14984Kevan Carstensen <kevan@isnotajoke.com>**20100802224801
14985 Ignore-this: 8b82bedc99cfab2f46bca3d461f1804a
14986] {
14987hunk ./src/allmydata/test/test_mutable.py 8
14988 from twisted.internet import defer, reactor
14989 from allmydata import uri, client
14990 from allmydata.nodemaker import NodeMaker
14991-from allmydata.util import base32, consumer
14992+from allmydata.util import base32, consumer, mathutil
14993 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
14994      ssk_pubkey_fingerprint_hash
14995 from allmydata.util.deferredutil import gatherResults
14996hunk ./src/allmydata/test/test_mutable.py 28
14997      NotEnoughServersError, CorruptShareError
14998 from allmydata.mutable.retrieve import Retrieve
14999 from allmydata.mutable.publish import Publish, MutableFileHandle, \
15000-                                      MutableData
15001+                                      MutableData, \
15002+                                      DEFAULT_MAX_SEGMENT_SIZE
15003 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
15004 from allmydata.mutable.layout import unpack_header, unpack_share, \
15005                                      MDMFSlotReadProxy
15006hunk ./src/allmydata/test/test_mutable.py 2868
15007         d.addCallback(lambda data:
15008             self.failUnlessEqual(data, self.small_data))
15009         return d
15010+
15011+
15012+class Update(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
15013+    def setUp(self):
15014+        GridTestMixin.setUp(self)
15015+        self.basedir = self.mktemp()
15016+        self.set_up_grid()
15017+        self.c = self.g.clients[0]
15018+        self.nm = self.c.nodemaker
15019+        self.data = "test data" * 100000 # about 900 KiB; MDMF
15020+        self.small_data = "test data" * 10 # about 90 B; SDMF
15021+        return self.do_upload()
15022+
15023+
15024+    def do_upload(self):
15025+        d1 = self.nm.create_mutable_file(MutableData(self.data),
15026+                                         version=MDMF_VERSION)
15027+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
15028+        dl = gatherResults([d1, d2])
15029+        def _then((n1, n2)):
15030+            assert isinstance(n1, MutableFileNode)
15031+            assert isinstance(n2, MutableFileNode)
15032+
15033+            self.mdmf_node = n1
15034+            self.sdmf_node = n2
15035+        dl.addCallback(_then)
15036+        return dl
15037+
15038+
15039+    def test_append(self):
15040+        # We should be able to append data to the middle of a mutable
15041+        # file and get what we expect.
15042+        new_data = self.data + "appended"
15043+        d = self.mdmf_node.get_best_mutable_version()
15044+        d.addCallback(lambda mv:
15045+            mv.update(MutableData(new_data), len(self.data)))
15046+        d.addCallback(lambda ignored:
15047+            self.mdmf_node.download_best_version())
15048+        d.addCallback(lambda results:
15049+            self.failUnlessEqual(results, new_data))
15050+        return d
15051+
15052+
15053+    def test_replace(self):
15054+        # We should be able to replace data in the middle of a mutable
15055+        # file and get what we expect back.
15056+        new_data = self.data[:100]
15057+        new_data += "appended"
15058+        new_data += self.data[108:]
15059+        d = self.mdmf_node.get_best_mutable_version()
15060+        d.addCallback(lambda mv:
15061+            mv.update(MutableData("appended"), 100))
15062+        d.addCallback(lambda ignored:
15063+            self.mdmf_node.download_best_version())
15064+        d.addCallback(lambda results:
15065+            self.failUnlessEqual(results, new_data))
15066+        return d
15067+
15068+
15069+    def test_replace_and_extend(self):
15070+        # We should be able to replace data in the middle of a mutable
15071+        # file and extend that mutable file and get what we expect.
15072+        new_data = self.data[:100]
15073+        new_data += "modified " * 100000
15074+        d = self.mdmf_node.get_best_mutable_version()
15075+        d.addCallback(lambda mv:
15076+            mv.update(MutableData("modified " * 100000), 100))
15077+        d.addCallback(lambda ignored:
15078+            self.mdmf_node.download_best_version())
15079+        d.addCallback(lambda results:
15080+            self.failUnlessEqual(results, new_data))
15081+        return d
15082+
15083+
15084+    def test_append_power_of_two(self):
15085+        # If we attempt to extend a mutable file so that its segment
15086+        # count crosses a power-of-two boundary, the update operation
15087+        # should know how to reencode the file.
15088+
15089+        # Note that the data populating self.mdmf_node is about 900 KiB
15090+        # long -- this is 7 segments in the default segment size. So we
15091+        # need to add 2 segments worth of data to push it over a
15092+        # power-of-two boundary.
15093+        segment = "a" * DEFAULT_MAX_SEGMENT_SIZE
15094+        new_data = self.data + segment * 2
15095+        d = self.mdmf_node.get_best_mutable_version()
15096+        d.addCallback(lambda mv:
15097+            mv.update(MutableData(new_data), len(self.data)))
15098+        d.addCallback(lambda ignored:
15099+            self.mdmf_node.download_best_version())
15100+        d.addCallback(lambda results:
15101+            self.failUnlessEqual(results, new_data))
15102+        return d
15103+
15104+
15105+    def test_update_sdmf(self):
15106+        # Running update on a single-segment file should still work.
15107+        new_data = self.small_data + "appended"
15108+        d = self.sdmf_node.get_best_mutable_version()
15109+        d.addCallback(lambda mv:
15110+            mv.update(MutableData("appended"), len(self.small_data)))
15111+        d.addCallback(lambda ignored:
15112+            mv.download_best_version())
15113+        d.addCallback(lambda results:
15114+            self.failUnlessEqual(results, new_data))
15115+        return d
15116}
15117[mutable: fix bugs that prevented the update tests from working
15118Kevan Carstensen <kevan@isnotajoke.com>**20100802224821
15119 Ignore-this: e87a1c81f7ecf9643248554a820dc62d
15120] {
15121hunk ./src/allmydata/mutable/filenode.py 10
15122 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
15123      NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
15124      IMutableFileVersion, IWritable
15125-from allmydata.util import hashutil, log, consumer, deferredutil
15126+from allmydata.util import hashutil, log, consumer, deferredutil, mathutil
15127 from allmydata.util.assertutil import precondition
15128 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
15129 from allmydata.monitor import Monitor
15130hunk ./src/allmydata/mutable/filenode.py 961
15131         self._size = size
15132         return res
15133 
15134-    def _update(self, data, offset):
15135+    def update(self, data, offset):
15136         """
15137         Do an update of this mutable file version by inserting data at
15138         offset within the file. If offset is the EOF, this is an append
15139hunk ./src/allmydata/mutable/filenode.py 986
15140         completed.
15141         """
15142         d = self._do_update_update(data, offset)
15143-        d.addCallback(self._decode_and_decrypt_segments)
15144-        d.addCallback(self._build_uploadable_and_finish)
15145+        d.addCallback(self._decode_and_decrypt_segments, data, offset)
15146+        d.addCallback(self._build_uploadable_and_finish, data, offset)
15147         return d
15148 
15149 
15150hunk ./src/allmydata/mutable/filenode.py 1016
15151             end_data = offset + data.get_size()
15152             end_segment = mathutil.div_ceil(end_data, DEFAULT_MAX_SEGMENT_SIZE)
15153             end_segment -= 1
15154+        self._start_segment = start_segment
15155+        self._end_segment = end_segment
15156 
15157         # Now ask for the servermap to be updated in MODE_WRITE with
15158         # this update range.
15159hunk ./src/allmydata/mutable/filenode.py 1021
15160-        u = ServermapUpdater(self, self._storage_broker, Monitor(),
15161+        u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
15162                              self._servermap,
15163                              mode=MODE_WRITE,
15164                              update_range=(start_segment, end_segment))
15165hunk ./src/allmydata/mutable/filenode.py 1036
15166         used by the new uploadable. I return a Deferred that fires with
15167         the segments.
15168         """
15169-        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
15170+        r = Retrieve(self._node, self._servermap, self._version)
15171         # decode: takes in our blocks and salts from the servermap,
15172         # returns a Deferred that fires with the corresponding plaintext
15173         # segments. Does not download -- simply takes advantage of
15174hunk ./src/allmydata/mutable/filenode.py 1046
15175         # XXX: If the methods in the servermap don't work as
15176         # abstractions, you should rewrite them instead of going around
15177         # them.
15178-        data = sm.update_data
15179-        data = [data[1] for i in update_data if i[0] == self._verinfo]
15180-        start_seg_data = [d[0] for d in data]
15181-        end_seg_data = [d[1] for d in data]
15182-        d1 = r.decode(start_seg_data)
15183-        d2 = r.decode(end_seg_data)
15184-        return deferredutil.gatherResults([d1, d2])
15185+        update_data = sm.update_data
15186+        start_segments = {} # shnum -> start segment
15187+        end_segments = {} # shnum -> end segment
15188+        blockhashes = {} # shnum -> blockhash tree
15189+        for (shnum, data) in update_data.iteritems():
15190+            data = [d[1] for d in data if d[0] == self._version]
15191+
15192+            # Every data entry in our list should now be share shnum for
15193+            # a particular version of the mutable file, so all of the
15194+            # entries should be identical.
15195+            datum = data[0]
15196+            assert filter(lambda x: x != datum, data) == []
15197+
15198+            blockhashes[shnum] = datum[0]
15199+            start_segments[shnum] = datum[1]
15200+            end_segments[shnum] = datum[2]
15201+
15202+        d1 = r.decode(start_segments, self._start_segment)
15203+        d2 = r.decode(end_segments, self._end_segment)
15204+        d3 = defer.succeed(blockhashes)
15205+        return deferredutil.gatherResults([d1, d2, d3])
15206 
15207 
15208hunk ./src/allmydata/mutable/filenode.py 1069
15209-    def _build_uploadable_and_finish(self, segments, data, offset):
15210+    def _build_uploadable_and_finish(self, segments_and_bht, data, offset):
15211         """
15212         After the process has the plaintext segments, I build the
15213         TransformingUploadable that the publisher will eventually
15214hunk ./src/allmydata/mutable/filenode.py 1079
15215         """
15216         u = TransformingUploadable(data, offset,
15217                                    DEFAULT_MAX_SEGMENT_SIZE,
15218-                                   segments[0],
15219-                                   segments[1])
15220+                                   segments_and_bht[0],
15221+                                   segments_and_bht[1])
15222         p = Publish(self._node, self._storage_broker, self._servermap)
15223hunk ./src/allmydata/mutable/filenode.py 1082
15224-        return p.update(data, offset, blockhashes)
15225+        return p.update(data, offset, segments_and_bht[2])
15226hunk ./src/allmydata/mutable/publish.py 168
15227 
15228         self.log("starting update")
15229         self.log("adding new data of length %d at offset %d" % \
15230-                    data.get_size(), offset)
15231+                    (data.get_size(), offset))
15232         self.log("new data length is %d" % self.datalength)
15233         self._status.set_size(self.datalength)
15234         self._status.set_status("Started")
15235hunk ./src/allmydata/mutable/publish.py 1254
15236 
15237 
15238     def get_size(self):
15239-        # TODO
15240-        pass
15241+        return self._offset + self._newdata.get_size()
15242 
15243 
15244     def read(self, length):
15245hunk ./src/allmydata/mutable/retrieve.py 145
15246         self.readers = {}
15247         self._paused = False
15248         self._paused_deferred = None
15249+        self._offset = None
15250+        self._read_length = None
15251 
15252 
15253     def get_status(self):
15254hunk ./src/allmydata/mutable/retrieve.py 293
15255         return self._done_deferred
15256 
15257 
15258-    def decode(blocks_and_salts, segnum):
15259+    def decode(self, blocks_and_salts, segnum):
15260         """
15261         I am a helper method that the mutable file update process uses
15262         as a shortcut to decode and decrypt the segments that it needs
15263hunk ./src/allmydata/mutable/retrieve.py 302
15264         segment with. I return the plaintext associated with that
15265         segment.
15266         """
15267+        # shnum => block hash tree. Unusued, but setup_encoding_parameters will
15268+        # want to set this.
15269+        # XXX: Make it so that it won't set this if we're just decoding.
15270+        self._block_hash_trees = {}
15271         self._setup_encoding_parameters()
15272hunk ./src/allmydata/mutable/retrieve.py 307
15273+        # This is the form expected by decode.
15274+        blocks_and_salts = blocks_and_salts.items()
15275+        blocks_and_salts = [(True, [d]) for d in blocks_and_salts]
15276+
15277         d = self._decode_blocks(blocks_and_salts, segnum)
15278         d.addCallback(self._decrypt_segment)
15279         return d
15280hunk ./src/allmydata/mutable/servermap.py 870
15281         """
15282         assert len(results) == 4
15283         verinfo, blockhashes, start, end = results
15284+        (seqnum,
15285+         root_hash,
15286+         saltish,
15287+         segsize,
15288+         datalen,
15289+         k,
15290+         n,
15291+         prefix,
15292+         offsets) = verinfo
15293+        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
15294+
15295+        # XXX: This should be done for us in the method, so
15296+        # presumably you can go in there and fix it.
15297+        verinfo = (seqnum,
15298+                   root_hash,
15299+                   saltish,
15300+                   segsize,
15301+                   datalen,
15302+                   k,
15303+                   n,
15304+                   prefix,
15305+                   offsets_tuple)
15306+
15307         update_data = (blockhashes, start, end)
15308         self._servermap.set_update_data_for_share_and_verinfo(share,
15309                                                               verinfo,
15310}
15311
15312Context:
15313
15314[misc/build_helpers/run-with-pythonpath.py: fix stale comment, and remove 'trial' example that is not the right way to run trial.
15315david-sarah@jacaranda.org**20100726225729
15316 Ignore-this: a61f55557ad69a1633bfb2b8172cce97
15317] 
15318[docs/specifications/dirnodes.txt: 'mesh'->'grid'.
15319david-sarah@jacaranda.org**20100723061616
15320 Ignore-this: 887bcf921ef00afba8e05e9239035bca
15321] 
15322[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'.
15323david-sarah@jacaranda.org**20100723054703
15324 Ignore-this: f3b98183e7d0a0f391225b8b93ac6c37
15325] 
15326[docs: use current cap to Zooko's wiki page in example text
15327zooko@zooko.com**20100721010543
15328 Ignore-this: 4f36f36758f9fdbaf9eb73eac23b6652
15329 fixes #1134
15330] 
15331[__init__.py: silence DeprecationWarning about BaseException.message globally. fixes #1129
15332david-sarah@jacaranda.org**20100720011939
15333 Ignore-this: 38808986ba79cb2786b010504a22f89
15334] 
15335[test_runner: test that 'tahoe --version' outputs no noise (e.g. DeprecationWarnings).
15336david-sarah@jacaranda.org**20100720011345
15337 Ignore-this: dd358b7b2e5d57282cbe133e8069702e
15338] 
15339[TAG allmydata-tahoe-1.7.1
15340zooko@zooko.com**20100719131352
15341 Ignore-this: 6942056548433dc653a746703819ad8c
15342] 
15343[relnotes.txt: updated for v1.7.1 release!
15344zooko@zooko.com**20100719083059
15345 Ignore-this: 9f10eb19b65a39d652b546c57481da45
15346] 
15347[immutable: add test case of #1128, fix test case of #1118
15348zooko@zooko.com**20100719081612
15349 Ignore-this: 8f9f742e7dac2bd9b49c19bd10f1c204
15350] 
15351[NEWS: add #1118 and reflow
15352zooko@zooko.com**20100719081248
15353 Ignore-this: 37a2e39d58c7b584b3c7f193bc1b30df
15354] 
15355[immutable: fix bug in which preexisting_shares and merged were shallowly referencing the same sets
15356zooko@zooko.com**20100719075426
15357 Ignore-this: 90827f8ce7ff0fc0c3c7f819399b8cf0
15358 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.
15359] 
15360[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.
15361david-sarah@jacaranda.org**20100719044655
15362 Ignore-this: 142d182c0739986812140bb8387077d5
15363] 
15364[docs/known_issues.txt: update release version and date.
15365david-sarah@jacaranda.org**20100718235940
15366 Ignore-this: dbbb42dbfa6c0d205a0b8e6e58eee9c7
15367] 
15368[relnotes.txt, docs/quickstart.html: prepare for 1.7.1 release. Don't claim to work on Cygwin (this might work but is untested).
15369david-sarah@jacaranda.org**20100718235437
15370 Ignore-this: dfc7334ee4bb76c04ee19304a7f1024b
15371] 
15372[immutable: extend the tests to check that the shares that got uploaded really do make a sufficiently Happy distribution
15373zooko@zooko.com**20100719045047
15374 Ignore-this: 89c33a7b795e23018667351045a8d5d0
15375 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.
15376] 
15377[immutable: test for #1118
15378zooko@zooko.com**20100718221537
15379 Ignore-this: 8882aabe2aaec6a0148c87e735d817ad
15380] 
15381[immutable: test for #1124
15382zooko@zooko.com**20100718222907
15383 Ignore-this: 1766e3cbab92ea2a9e246f40eb6e770b
15384] 
15385[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>.
15386david-sarah@jacaranda.org**20100718230420
15387 Ignore-this: aef40f2e74ddeabee5e122e8d80893a1
15388] 
15389[trivial: fix unused import (sorry about that, pyflakes)
15390zooko@zooko.com**20100718215133
15391 Ignore-this: c2414e443405072b51d552295f2c0e8c
15392] 
15393[tests, NEWS, CREDITS re: #1117
15394zooko@zooko.com**20100718203225
15395 Ignore-this: 1f08be2c692fb72cc0dd023259f11354
15396 Give Brian and Kevan promotions, move release date in NEWS to the 18th, commit Brian's test for #1117.
15397 fixes #1117
15398] 
15399[test/test_upload.py: test to see that aborted buckets are ignored by the storage server
15400Kevan Carstensen <kevan@isnotajoke.com>**20100716001046
15401 Ignore-this: cc075c24b1c86d737f3199af894cc780
15402] 
15403[test/test_storage.py: test for the new remote_abort semantics.
15404Kevan Carstensen <kevan@isnotajoke.com>**20100715232148
15405 Ignore-this: d3d6491f17bf670e770ca4b385007515
15406] 
15407[storage/immutable.py: make remote_abort btell the storage server about aborted buckets.
15408Kevan Carstensen <kevan@isnotajoke.com>**20100715232105
15409 Ignore-this: 16ab0090676355abdd5600ed44ff19c9
15410] 
15411[test/test_upload.py: changes to test plumbing for #1117 tests
15412Kevan Carstensen <kevan@isnotajoke.com>**20100715231820
15413 Ignore-this: 78a6d359d7bf8529d283e2815bf1e2de
15414 
15415     - Add a callRemoteOnly method to FakeBucketWriter.
15416     - Change the abort method in FakeBucketWriter to not return a
15417       RuntimeError.
15418] 
15419[immutable/upload.py: abort buckets if peer selection fails
15420Kevan Carstensen <kevan@isnotajoke.com>**20100715231714
15421 Ignore-this: 2a0b643a22284df292d8ed9d91b1fd37
15422] 
15423[test_encodingutil: correct an error in the previous patch to StdlibUnicode.test_open_representable.
15424david-sarah@jacaranda.org**20100718151420
15425 Ignore-this: af050955f623fbc0e4d78e15a0a8a144
15426] 
15427[NEWS: Forward-compatibility improvements for non-ASCII caps (#1051).
15428david-sarah@jacaranda.org**20100718143622
15429 Ignore-this: 1edfebc4bd38a3b5c35e75c99588153f
15430] 
15431[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.
15432david-sarah@jacaranda.org**20100718142915
15433 Ignore-this: c4e78ef4b1478dd400da71cf077ffa4a
15434] 
15435[test_encodingutil: StdlibUnicode.test_open_representable no longer uses a mock.
15436david-sarah@jacaranda.org**20100718125412
15437 Ignore-this: 4bf373a5e2dfe4209e5e364124af29a3
15438] 
15439[docs: add comment clarifying #1051
15440zooko@zooko.com**20100718053250
15441 Ignore-this: 6cfc0930434cbdbbc262dabb58f1505d
15442] 
15443[docs: update NEWS
15444zooko@zooko.com**20100718053225
15445 Ignore-this: 63d5c782ef84812e6d010f0590866831
15446] 
15447[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.
15448david-sarah@jacaranda.org**20100711200252
15449 Ignore-this: c2f193352369d32e06865f8f3e951894
15450] 
15451[Debian documentation update
15452jacob@appelbaum.net**20100305003004] 
15453[debian-docs-patch-final
15454jacob@appelbaum.net**20100304085955] 
15455[M-x whitespace-cleanup
15456zooko@zooko.com**20100718032739
15457 Ignore-this: babfd4af6ad2fc885c957fd5c8b10c3f
15458] 
15459[docs: tidy up NEWS a little
15460zooko@zooko.com**20100718032434
15461 Ignore-this: 54f2820fd1a37c8967609f6bfc4e5e18
15462] 
15463[benchmarking: update bench_dirnode.py to reflect the new directory interfaces
15464zooko@zooko.com**20100718031710
15465 Ignore-this: 368ba523dd3de80d9da29cd58afbe827
15466] 
15467[test_encodingutil: fix test_open_representable, which is only valid when run on a platform for which we know an unrepresentable filename.
15468david-sarah@jacaranda.org**20100718030333
15469 Ignore-this: c114d92c17714a5d4ae005c15267d60c
15470] 
15471[iputil.py: Add support for FreeBSD 7,8 and 9
15472francois@ctrlaltdel.ch**20100718022832
15473 Ignore-this: 1829b4cf4b91107f4cf87841e6167e99
15474 committed by: zooko@zooko.com
15475 date: 2010-07-17
15476 and I also patched: NEWS and CREDITS
15477] 
15478[NEWS: add snippet about #1083
15479zooko@zooko.com**20100718020653
15480 Ignore-this: d353a9d93cbc5a5e6ba4671f78d1e22b
15481] 
15482[fileutil: docstrings for non-obvious usage restrictions on methods of EncryptedTemporaryFile.
15483david-sarah@jacaranda.org**20100717054647
15484 Ignore-this: 46d8fc10782fa8ec2b6c5b168c841943
15485] 
15486[Move EncryptedTemporaryFile from SFTP frontend to allmydata.util.fileutil, and make the FTP frontend also use it (fixing #1083).
15487david-sarah@jacaranda.org**20100711213721
15488 Ignore-this: e452e8ca66391aa2a1a49afe0114f317
15489] 
15490[NEWS: reorder NEWS snippets to be in descending order of interestingness
15491zooko@zooko.com**20100718015929
15492 Ignore-this: 146c42e88a9555a868a04a69dd0e5326
15493] 
15494[Correct stringutils->encodingutil patch to be the newer version, rather than the old version that was committed in error.
15495david-sarah@jacaranda.org**20100718013435
15496 Ignore-this: c8940c4e1aa2e9acc80cd4fe54753cd8
15497] 
15498[test_cli.py: fix error that crept in when rebasing the patch for #1072.
15499david-sarah@jacaranda.org**20100718000123
15500 Ignore-this: 3e8f6cc3a27b747c708221dd581934f4
15501] 
15502[stringutils: add test for when sys.stdout has no encoding attribute (fixes #1099).
15503david-sarah@jacaranda.org**20100717045816
15504 Ignore-this: f28dce6940e909f12f354086d17db54f
15505] 
15506[CLI: add 'tahoe unlink' as an alias to 'tahoe rm', for forward-compatibility.
15507david-sarah@jacaranda.org**20100717220411
15508 Ignore-this: 3ecdde7f2d0498514cef32e118e0b855
15509] 
15510[minor code clean-up in dirnode.py
15511zooko@zooko.com**20100714060255
15512 Ignore-this: bb0ab2783203e605024b3e2f798256a1
15513 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.
15514 fixes #967
15515] 
15516[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.
15517david-sarah@jacaranda.org**20100712003015
15518 Ignore-this: 103b809d180df17a7283077c3104c7be
15519] 
15520[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.
15521david-sarah@jacaranda.org**20100711195525
15522 Ignore-this: deac32d8b91ba26ede18905d3f7d2b93
15523] 
15524[docs: CREDITS and NEWS
15525zooko@zooko.com**20100714060150
15526 Ignore-this: dc83e612f77d69e50ee975f07f6b16fe
15527] 
15528[CREDITS: more creds for Kevan, plus utf-8 BOM
15529zooko@zooko.com**20100619045503
15530 Ignore-this: 72d02bdd7a0f324f1cee8cd399c7c6de
15531] 
15532[cli.py: make command descriptions consistently end with a full stop.
15533david-sarah@jacaranda.org**20100714014538
15534 Ignore-this: 9ee7fa29ca2d1631db4049c2a389a97a
15535] 
15536[SFTP: address some of the comments in zooko's review (#1106).
15537david-sarah@jacaranda.org**20100712025537
15538 Ignore-this: c3921638a2d4f1de2a776ae78e4dc37e
15539] 
15540[docs/logging.txt: note that setting flogging vars might affect tests with race conditions.
15541david-sarah@jacaranda.org**20100712050721
15542 Ignore-this: fc1609d215fcd5561a57fd1226206f27
15543] 
15544[test_storage.py: potential fix for failures when logging is enabled.
15545david-sarah@jacaranda.org**19700713040546
15546 Ignore-this: 5815693a0df3e64c52c3c6b7be2846c7
15547] 
15548[upcase_since_on_welcome
15549terrellrussell@gmail.com**20100708193903] 
15550[server_version_on_welcome_page.dpatch.txt
15551freestorm77@gmail.com**20100605191721
15552 Ignore-this: b450c76dc875f5ac8cca229a666cbd0a
15553 
15554 
15555 - The storage server version is 0 for all storage nodes in the Welcome Page
15556 
15557 
15558] 
15559[NEWS: add NEWS snippets about two recent patches
15560zooko@zooko.com**20100708162058
15561 Ignore-this: 6c9da6a0ad7351a960bdd60f81532899
15562] 
15563[directory_html_top_banner.dpatch
15564freestorm77@gmail.com**20100622205301
15565 Ignore-this: 1d770d975e0c414c996564774f049bca
15566 
15567 The div tag with the link "Return to Welcome page" on the directory.xhtml page is not correct
15568 
15569] 
15570[tahoe_css_toolbar.dpatch
15571freestorm77@gmail.com**20100622210046
15572 Ignore-this: 5b3ebb2e0f52bbba718a932f80c246c0
15573 
15574 CSS modification to be correctly diplayed with Internet Explorer 8
15575 
15576 The links on the top of page directory.xhtml are not diplayed in the same line as display with Firefox.
15577 
15578] 
15579[runnin_test_tahoe_css.dpatch
15580freestorm77@gmail.com**20100622214714
15581 Ignore-this: e0db73d68740aad09a7b9ae60a08c05c
15582 
15583 Runnin test for changes in tahoe.css file
15584 
15585] 
15586[runnin_test_directory_xhtml.dpatch
15587freestorm77@gmail.com**20100622201403
15588 Ignore-this: f8962463fce50b9466405cb59fe11d43
15589 
15590 Runnin test for diretory.xhtml top banner
15591 
15592] 
15593[stringutils.py: tolerate sys.stdout having no 'encoding' attribute.
15594david-sarah@jacaranda.org**20100626040817
15595 Ignore-this: f42cad81cef645ee38ac1df4660cc850
15596] 
15597[quickstart.html: python 2.5 -> 2.6 as recommended version
15598david-sarah@jacaranda.org**20100705175858
15599 Ignore-this: bc3a14645ea1d5435002966ae903199f
15600] 
15601[SFTP: don't call .stopProducing on the producer registered with OverwriteableFileConsumer (which breaks with warner's new downloader).
15602david-sarah@jacaranda.org**20100628231926
15603 Ignore-this: 131b7a5787bc85a9a356b5740d9d996f
15604] 
15605[docs/how_to_make_a_tahoe-lafs_release.txt: trivial correction, install.html should now be quickstart.html.
15606david-sarah@jacaranda.org**20100625223929
15607 Ignore-this: 99a5459cac51bd867cc11ad06927ff30
15608] 
15609[setup: in the Makefile, refuse to upload tarballs unless someone has passed the environment variable "BB_BRANCH" with value "trunk"
15610zooko@zooko.com**20100619034928
15611 Ignore-this: 276ddf9b6ad7ec79e27474862e0f7d6
15612] 
15613[trivial: tiny update to in-line comment
15614zooko@zooko.com**20100614045715
15615 Ignore-this: 10851b0ed2abfed542c97749e5d280bc
15616 (I'm actually committing this patch as a test of the new eager-annotation-computation of trac-darcs.)
15617] 
15618[docs: about.html link to home page early on, and be decentralized storage instead of cloud storage this time around
15619zooko@zooko.com**20100619065318
15620 Ignore-this: dc6db03f696e5b6d2848699e754d8053
15621] 
15622[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"
15623zooko@zooko.com**20100619065124
15624 Ignore-this: e292c7f51c337a84ebfeb366fbd24d6c
15625] 
15626[TAG allmydata-tahoe-1.7.0
15627zooko@zooko.com**20100619052631
15628 Ignore-this: d21e27afe6d85e2e3ba6a3292ba2be1
15629] 
15630Patch bundle hash:
15631940f3788e1d48b33291c9e2150dcce67d4d2ebe2