Ticket #393: 393status27.dpatch

File 393status27.dpatch, 679.7 KB (added by kevan, at 2010-08-06T00:23:43Z)
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:21 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
184  * mutable: fix bugs that prevented the update tests from working
185
186Thu Aug  5 17:03:38 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
187  * mutable: fix some bugs in update logic.
188
189Thu Aug  5 17:04:08 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
190  * test/test_mutable.py: add tests for updating behavior
191
192New patches:
193
194[Misc. changes to support the work I'm doing
195Kevan Carstensen <kevan@isnotajoke.com>**20100624234637
196 Ignore-this: fdd18fa8cc05f4b4b15ff53ee24a1819
197 
198     - Add a notion of file version number to interfaces.py
199     - Alter mutable file node interfaces to have a notion of version,
200       though this may be changed later.
201     - Alter mutable/filenode.py to conform to these changes.
202     - Add a salt hasher to util/hashutil.py
203] {
204hunk ./src/allmydata/interfaces.py 7
205      ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
206 
207 HASH_SIZE=32
208+SALT_SIZE=16
209+
210+SDMF_VERSION=0
211+MDMF_VERSION=1
212 
213 Hash = StringConstraint(maxLength=HASH_SIZE,
214                         minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
215hunk ./src/allmydata/interfaces.py 811
216         writer-visible data using this writekey.
217         """
218 
219+    def set_version(version):
220+        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
221+        we upload in SDMF for reasons of compatibility. If you want to
222+        change this, set_version will let you do that.
223+
224+        To say that this file should be uploaded in SDMF, pass in a 0. To
225+        say that the file should be uploaded as MDMF, pass in a 1.
226+        """
227+
228+    def get_version():
229+        """Returns the mutable file protocol version."""
230+
231 class NotEnoughSharesError(Exception):
232     """Download was unable to get enough shares"""
233 
234hunk ./src/allmydata/mutable/filenode.py 8
235 from twisted.internet import defer, reactor
236 from foolscap.api import eventually
237 from allmydata.interfaces import IMutableFileNode, \
238-     ICheckable, ICheckResults, NotEnoughSharesError
239+     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
240 from allmydata.util import hashutil, log
241 from allmydata.util.assertutil import precondition
242 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
243hunk ./src/allmydata/mutable/filenode.py 67
244         self._sharemap = {} # known shares, shnum-to-[nodeids]
245         self._cache = ResponseCache()
246         self._most_recent_size = None
247+        # filled in after __init__ if we're being created for the first time;
248+        # filled in by the servermap updater before publishing, otherwise.
249+        # set to this default value in case neither of those things happen,
250+        # or in case the servermap can't find any shares to tell us what
251+        # to publish as.
252+        # TODO: Set this back to None, and find out why the tests fail
253+        #       with it set to None.
254+        self._protocol_version = SDMF_VERSION
255 
256         # all users of this MutableFileNode go through the serializer. This
257         # takes advantage of the fact that Deferreds discard the callbacks
258hunk ./src/allmydata/mutable/filenode.py 472
259     def _did_upload(self, res, size):
260         self._most_recent_size = size
261         return res
262+
263+
264+    def set_version(self, version):
265+        # I can be set in two ways:
266+        #  1. When the node is created.
267+        #  2. (for an existing share) when the Servermap is updated
268+        #     before I am read.
269+        assert version in (MDMF_VERSION, SDMF_VERSION)
270+        self._protocol_version = version
271+
272+
273+    def get_version(self):
274+        return self._protocol_version
275hunk ./src/allmydata/util/hashutil.py 90
276 MUTABLE_READKEY_TAG = "allmydata_mutable_writekey_to_readkey_v1"
277 MUTABLE_DATAKEY_TAG = "allmydata_mutable_readkey_to_datakey_v1"
278 MUTABLE_STORAGEINDEX_TAG = "allmydata_mutable_readkey_to_storage_index_v1"
279+MUTABLE_SALT_TAG = "allmydata_mutable_segment_salt_v1"
280 
281 # dirnodes
282 DIRNODE_CHILD_WRITECAP_TAG = "allmydata_mutable_writekey_and_salt_to_dirnode_child_capkey_v1"
283hunk ./src/allmydata/util/hashutil.py 134
284 def plaintext_segment_hasher():
285     return tagged_hasher(PLAINTEXT_SEGMENT_TAG)
286 
287+def mutable_salt_hash(data):
288+    return tagged_hash(MUTABLE_SALT_TAG, data)
289+def mutable_salt_hasher():
290+    return tagged_hasher(MUTABLE_SALT_TAG)
291+
292 KEYLEN = 16
293 IVLEN = 16
294 
295}
296[nodemaker.py: create MDMF files when asked to
297Kevan Carstensen <kevan@isnotajoke.com>**20100624234833
298 Ignore-this: 26c16aaca9ddab7a7ce37a4530bc970
299] {
300hunk ./src/allmydata/nodemaker.py 3
301 import weakref
302 from zope.interface import implements
303-from allmydata.interfaces import INodeMaker
304+from allmydata.util.assertutil import precondition
305+from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError, \
306+                                 SDMF_VERSION, MDMF_VERSION
307 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
308 from allmydata.immutable.upload import Data
309 from allmydata.mutable.filenode import MutableFileNode
310hunk ./src/allmydata/nodemaker.py 88
311             return self._create_dirnode(filenode)
312         return None
313 
314-    def create_mutable_file(self, contents=None, keysize=None):
315+    def create_mutable_file(self, contents=None, keysize=None,
316+                            version=SDMF_VERSION):
317         n = MutableFileNode(self.storage_broker, self.secret_holder,
318                             self.default_encoding_parameters, self.history)
319hunk ./src/allmydata/nodemaker.py 92
320+        n.set_version(version)
321         d = self.key_generator.generate(keysize)
322         d.addCallback(n.create_with_keys, contents)
323         d.addCallback(lambda res: n)
324hunk ./src/allmydata/nodemaker.py 98
325         return d
326 
327-    def create_new_mutable_directory(self, initial_children={}):
328+    def create_new_mutable_directory(self, initial_children={},
329+                                     version=SDMF_VERSION):
330+        # initial_children must have metadata (i.e. {} instead of None)
331+        for (name, (node, metadata)) in initial_children.iteritems():
332+            precondition(isinstance(metadata, dict),
333+                         "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
334+            node.raise_error()
335         d = self.create_mutable_file(lambda n:
336                                      pack_children(initial_children, n.get_writekey()))
337         d.addCallback(self._create_dirnode)
338merger 0.0 (
339hunk ./src/allmydata/nodemaker.py 106
340-                                     pack_children(n, initial_children))
341+                                     pack_children(initial_children, n.get_writekey()))
342hunk ./src/allmydata/nodemaker.py 106
343-                                     pack_children(n, initial_children))
344+                                     pack_children(n, initial_children),
345+                                     version)
346)
347}
348[storage/server.py: minor code cleanup
349Kevan Carstensen <kevan@isnotajoke.com>**20100624234905
350 Ignore-this: 2358c531c39e48d3c8e56b62b5768228
351] {
352hunk ./src/allmydata/storage/server.py 569
353                                          self)
354         return share
355 
356-    def remote_slot_readv(self, storage_index, shares, readv):
357+    def remote_slot_readv(self, storage_index, shares, readvs):
358         start = time.time()
359         self.count("readv")
360         si_s = si_b2a(storage_index)
361hunk ./src/allmydata/storage/server.py 590
362             if sharenum in shares or not shares:
363                 filename = os.path.join(bucketdir, sharenum_s)
364                 msf = MutableShareFile(filename, self)
365-                datavs[sharenum] = msf.readv(readv)
366+                datavs[sharenum] = msf.readv(readvs)
367         log.msg("returning shares %s" % (datavs.keys(),),
368                 facility="tahoe.storage", level=log.NOISY, parent=lp)
369         self.add_latency("readv", time.time() - start)
370}
371[test/test_mutable.py: alter some tests that were failing due to MDMF; minor code cleanup.
372Kevan Carstensen <kevan@isnotajoke.com>**20100624234924
373 Ignore-this: afb86ec1fbdbfe1a5ef6f46f350273c0
374] {
375hunk ./src/allmydata/test/test_mutable.py 151
376             chr(ord(original[byte_offset]) ^ 0x01) +
377             original[byte_offset+1:])
378 
379+def add_two(original, byte_offset):
380+    # It isn't enough to simply flip the bit for the version number,
381+    # because 1 is a valid version number. So we add two instead.
382+    return (original[:byte_offset] +
383+            chr(ord(original[byte_offset]) ^ 0x02) +
384+            original[byte_offset+1:])
385+
386 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
387     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
388     # list of shnums to corrupt.
389hunk ./src/allmydata/test/test_mutable.py 187
390                 real_offset = offset1
391             real_offset = int(real_offset) + offset2 + offset_offset
392             assert isinstance(real_offset, int), offset
393-            shares[shnum] = flip_bit(data, real_offset)
394+            if offset1 == 0: # verbyte
395+                f = add_two
396+            else:
397+                f = flip_bit
398+            shares[shnum] = f(data, real_offset)
399     return res
400 
401 def make_storagebroker(s=None, num_peers=10):
402hunk ./src/allmydata/test/test_mutable.py 423
403         d.addCallback(_created)
404         return d
405 
406+
407     def test_modify_backoffer(self):
408         def _modifier(old_contents, servermap, first_time):
409             return old_contents + "line2"
410hunk ./src/allmydata/test/test_mutable.py 658
411         d.addCallback(_created)
412         return d
413 
414+
415     def _copy_shares(self, ignored, index):
416         shares = self._storage._peers
417         # we need a deep copy
418}
419[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
420Kevan Carstensen <kevan@isnotajoke.com>**20100626003520
421 Ignore-this: 836e59e2fde0535f6b4bea3468dc8244
422] {
423hunk ./src/allmydata/test/test_mutable.py 168
424                 and shnum not in shnums_to_corrupt):
425                 continue
426             data = shares[shnum]
427-            (version,
428-             seqnum,
429-             root_hash,
430-             IV,
431-             k, N, segsize, datalen,
432-             o) = unpack_header(data)
433-            if isinstance(offset, tuple):
434-                offset1, offset2 = offset
435-            else:
436-                offset1 = offset
437-                offset2 = 0
438-            if offset1 == "pubkey":
439-                real_offset = 107
440-            elif offset1 in o:
441-                real_offset = o[offset1]
442-            else:
443-                real_offset = offset1
444-            real_offset = int(real_offset) + offset2 + offset_offset
445-            assert isinstance(real_offset, int), offset
446-            if offset1 == 0: # verbyte
447-                f = add_two
448-            else:
449-                f = flip_bit
450-            shares[shnum] = f(data, real_offset)
451-    return res
452+            # We're feeding the reader all of the share data, so it
453+            # won't need to use the rref that we didn't provide, nor the
454+            # storage index that we didn't provide. We do this because
455+            # the reader will work for both MDMF and SDMF.
456+            reader = MDMFSlotReadProxy(None, None, shnum, data)
457+            # We need to get the offsets for the next part.
458+            d = reader.get_verinfo()
459+            def _do_corruption(verinfo, data, shnum):
460+                (seqnum,
461+                 root_hash,
462+                 IV,
463+                 segsize,
464+                 datalen,
465+                 k, n, prefix, o) = verinfo
466+                if isinstance(offset, tuple):
467+                    offset1, offset2 = offset
468+                else:
469+                    offset1 = offset
470+                    offset2 = 0
471+                if offset1 == "pubkey":
472+                    real_offset = 107
473+                elif offset1 in o:
474+                    real_offset = o[offset1]
475+                else:
476+                    real_offset = offset1
477+                real_offset = int(real_offset) + offset2 + offset_offset
478+                assert isinstance(real_offset, int), offset
479+                if offset1 == 0: # verbyte
480+                    f = add_two
481+                else:
482+                    f = flip_bit
483+                shares[shnum] = f(data, real_offset)
484+            d.addCallback(_do_corruption, data, shnum)
485+            ds.append(d)
486+    dl = defer.DeferredList(ds)
487+    dl.addCallback(lambda ignored: res)
488+    return dl
489 
490 def make_storagebroker(s=None, num_peers=10):
491     if not s:
492hunk ./src/allmydata/test/test_mutable.py 1177
493         return d
494 
495     def test_download_fails(self):
496-        corrupt(None, self._storage, "signature")
497-        d = self.shouldFail(UnrecoverableFileError, "test_download_anyway",
498+        d = corrupt(None, self._storage, "signature")
499+        d.addCallback(lambda ignored:
500+            self.shouldFail(UnrecoverableFileError, "test_download_anyway",
501                             "no recoverable versions",
502                             self._fn.download_best_version)
503         return d
504hunk ./src/allmydata/test/test_mutable.py 1232
505         return d
506 
507     def test_check_all_bad_sig(self):
508-        corrupt(None, self._storage, 1) # bad sig
509-        d = self._fn.check(Monitor())
510+        d = corrupt(None, self._storage, 1) # bad sig
511+        d.addCallback(lambda ignored:
512+            self._fn.check(Monitor()))
513         d.addCallback(self.check_bad, "test_check_all_bad_sig")
514         return d
515 
516hunk ./src/allmydata/test/test_mutable.py 1239
517     def test_check_all_bad_blocks(self):
518-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
519+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
520         # the Checker won't notice this.. it doesn't look at actual data
521hunk ./src/allmydata/test/test_mutable.py 1241
522-        d = self._fn.check(Monitor())
523+        d.addCallback(lambda ignored:
524+            self._fn.check(Monitor()))
525         d.addCallback(self.check_good, "test_check_all_bad_blocks")
526         return d
527 
528hunk ./src/allmydata/test/test_mutable.py 1252
529         return d
530 
531     def test_verify_all_bad_sig(self):
532-        corrupt(None, self._storage, 1) # bad sig
533-        d = self._fn.check(Monitor(), verify=True)
534+        d = corrupt(None, self._storage, 1) # bad sig
535+        d.addCallback(lambda ignored:
536+            self._fn.check(Monitor(), verify=True))
537         d.addCallback(self.check_bad, "test_verify_all_bad_sig")
538         return d
539 
540hunk ./src/allmydata/test/test_mutable.py 1259
541     def test_verify_one_bad_sig(self):
542-        corrupt(None, self._storage, 1, [9]) # bad sig
543-        d = self._fn.check(Monitor(), verify=True)
544+        d = corrupt(None, self._storage, 1, [9]) # bad sig
545+        d.addCallback(lambda ignored:
546+            self._fn.check(Monitor(), verify=True))
547         d.addCallback(self.check_bad, "test_verify_one_bad_sig")
548         return d
549 
550hunk ./src/allmydata/test/test_mutable.py 1266
551     def test_verify_one_bad_block(self):
552-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
553+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
554         # the Verifier *will* notice this, since it examines every byte
555hunk ./src/allmydata/test/test_mutable.py 1268
556-        d = self._fn.check(Monitor(), verify=True)
557+        d.addCallback(lambda ignored:
558+            self._fn.check(Monitor(), verify=True))
559         d.addCallback(self.check_bad, "test_verify_one_bad_block")
560         d.addCallback(self.check_expected_failure,
561                       CorruptShareError, "block hash tree failure",
562hunk ./src/allmydata/test/test_mutable.py 1277
563         return d
564 
565     def test_verify_one_bad_sharehash(self):
566-        corrupt(None, self._storage, "share_hash_chain", [9], 5)
567-        d = self._fn.check(Monitor(), verify=True)
568+        d = corrupt(None, self._storage, "share_hash_chain", [9], 5)
569+        d.addCallback(lambda ignored:
570+            self._fn.check(Monitor(), verify=True))
571         d.addCallback(self.check_bad, "test_verify_one_bad_sharehash")
572         d.addCallback(self.check_expected_failure,
573                       CorruptShareError, "corrupt hashes",
574hunk ./src/allmydata/test/test_mutable.py 1287
575         return d
576 
577     def test_verify_one_bad_encprivkey(self):
578-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
579-        d = self._fn.check(Monitor(), verify=True)
580+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
581+        d.addCallback(lambda ignored:
582+            self._fn.check(Monitor(), verify=True))
583         d.addCallback(self.check_bad, "test_verify_one_bad_encprivkey")
584         d.addCallback(self.check_expected_failure,
585                       CorruptShareError, "invalid privkey",
586hunk ./src/allmydata/test/test_mutable.py 1297
587         return d
588 
589     def test_verify_one_bad_encprivkey_uncheckable(self):
590-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
591+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
592         readonly_fn = self._fn.get_readonly()
593         # a read-only node has no way to validate the privkey
594hunk ./src/allmydata/test/test_mutable.py 1300
595-        d = readonly_fn.check(Monitor(), verify=True)
596+        d.addCallback(lambda ignored:
597+            readonly_fn.check(Monitor(), verify=True))
598         d.addCallback(self.check_good,
599                       "test_verify_one_bad_encprivkey_uncheckable")
600         return d
601}
602[Alter the ServermapUpdater to find MDMF files
603Kevan Carstensen <kevan@isnotajoke.com>**20100626234118
604 Ignore-this: 25f6278209c2983ba8f307cfe0fde0
605 
606 The servermapupdater should find MDMF files on a grid in the same way
607 that it finds SDMF files. This patch makes it do that.
608] {
609hunk ./src/allmydata/mutable/servermap.py 7
610 from itertools import count
611 from twisted.internet import defer
612 from twisted.python import failure
613-from foolscap.api import DeadReferenceError, RemoteException, eventually
614+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
615+                         fireEventually
616 from allmydata.util import base32, hashutil, idlib, log
617 from allmydata.storage.server import si_b2a
618 from allmydata.interfaces import IServermapUpdaterStatus
619hunk ./src/allmydata/mutable/servermap.py 17
620 from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \
621      DictOfSets, CorruptShareError, NeedMoreDataError
622 from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
623-     SIGNED_PREFIX_LENGTH
624+     SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
625 
626 class UpdateStatus:
627     implements(IServermapUpdaterStatus)
628hunk ./src/allmydata/mutable/servermap.py 254
629         """Return a set of versionids, one for each version that is currently
630         recoverable."""
631         versionmap = self.make_versionmap()
632-
633         recoverable_versions = set()
634         for (verinfo, shares) in versionmap.items():
635             (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
636hunk ./src/allmydata/mutable/servermap.py 366
637         self._servers_responded = set()
638 
639         # how much data should we read?
640+        # SDMF:
641         #  * if we only need the checkstring, then [0:75]
642         #  * if we need to validate the checkstring sig, then [543ish:799ish]
643         #  * if we need the verification key, then [107:436ish]
644hunk ./src/allmydata/mutable/servermap.py 374
645         #  * if we need the encrypted private key, we want [-1216ish:]
646         #   * but we can't read from negative offsets
647         #   * the offset table tells us the 'ish', also the positive offset
648-        # A future version of the SMDF slot format should consider using
649-        # fixed-size slots so we can retrieve less data. For now, we'll just
650-        # read 2000 bytes, which also happens to read enough actual data to
651-        # pre-fetch a 9-entry dirnode.
652+        # MDMF:
653+        #  * Checkstring? [0:72]
654+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
655+        #    the offset table will tell us for sure.
656+        #  * If we need the verification key, we have to consult the offset
657+        #    table as well.
658+        # At this point, we don't know which we are. Our filenode can
659+        # tell us, but it might be lying -- in some cases, we're
660+        # responsible for telling it which kind of file it is.
661         self._read_size = 4000
662         if mode == MODE_CHECK:
663             # we use unpack_prefix_and_signature, so we need 1k
664hunk ./src/allmydata/mutable/servermap.py 432
665         self._queries_completed = 0
666 
667         sb = self._storage_broker
668+        # All of the peers, permuted by the storage index, as usual.
669         full_peerlist = sb.get_servers_for_index(self._storage_index)
670         self.full_peerlist = full_peerlist # for use later, immutable
671         self.extra_peers = full_peerlist[:] # peers are removed as we use them
672hunk ./src/allmydata/mutable/servermap.py 439
673         self._good_peers = set() # peers who had some shares
674         self._empty_peers = set() # peers who don't have any shares
675         self._bad_peers = set() # peers to whom our queries failed
676+        self._readers = {} # peerid -> dict(sharewriters), filled in
677+                           # after responses come in.
678 
679         k = self._node.get_required_shares()
680hunk ./src/allmydata/mutable/servermap.py 443
681+        # For what cases can these conditions work?
682         if k is None:
683             # make a guess
684             k = 3
685hunk ./src/allmydata/mutable/servermap.py 456
686         self.num_peers_to_query = k + self.EPSILON
687 
688         if self.mode == MODE_CHECK:
689+            # We want to query all of the peers.
690             initial_peers_to_query = dict(full_peerlist)
691             must_query = set(initial_peers_to_query.keys())
692             self.extra_peers = []
693hunk ./src/allmydata/mutable/servermap.py 464
694             # we're planning to replace all the shares, so we want a good
695             # chance of finding them all. We will keep searching until we've
696             # seen epsilon that don't have a share.
697+            # We don't query all of the peers because that could take a while.
698             self.num_peers_to_query = N + self.EPSILON
699             initial_peers_to_query, must_query = self._build_initial_querylist()
700             self.required_num_empty_peers = self.EPSILON
701hunk ./src/allmydata/mutable/servermap.py 474
702             # might also avoid the round trip required to read the encrypted
703             # private key.
704 
705-        else:
706+        else: # MODE_READ, MODE_ANYTHING
707+            # 2k peers is good enough.
708             initial_peers_to_query, must_query = self._build_initial_querylist()
709 
710         # this is a set of peers that we are required to get responses from:
711hunk ./src/allmydata/mutable/servermap.py 490
712         # before we can consider ourselves finished, and self.extra_peers
713         # contains the overflow (peers that we should tap if we don't get
714         # enough responses)
715+        # I guess that self._must_query is a subset of
716+        # initial_peers_to_query?
717+        assert set(must_query).issubset(set(initial_peers_to_query))
718 
719         self._send_initial_requests(initial_peers_to_query)
720         self._status.timings["initial_queries"] = time.time() - self._started
721hunk ./src/allmydata/mutable/servermap.py 549
722         # errors that aren't handled by _query_failed (and errors caused by
723         # _query_failed) get logged, but we still want to check for doneness.
724         d.addErrback(log.err)
725-        d.addBoth(self._check_for_done)
726         d.addErrback(self._fatal_error)
727hunk ./src/allmydata/mutable/servermap.py 550
728+        d.addCallback(self._check_for_done)
729         return d
730 
731     def _do_read(self, ss, peerid, storage_index, shnums, readv):
732hunk ./src/allmydata/mutable/servermap.py 569
733         d = ss.callRemote("slot_readv", storage_index, shnums, readv)
734         return d
735 
736+
737+    def _got_corrupt_share(self, e, shnum, peerid, data, lp):
738+        """
739+        I am called when a remote server returns a corrupt share in
740+        response to one of our queries. By corrupt, I mean a share
741+        without a valid signature. I then record the failure, notify the
742+        server of the corruption, and record the share as bad.
743+        """
744+        f = failure.Failure(e)
745+        self.log(format="bad share: %(f_value)s", f_value=str(f),
746+                 failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
747+        # Notify the server that its share is corrupt.
748+        self.notify_server_corruption(peerid, shnum, str(e))
749+        # By flagging this as a bad peer, we won't count any of
750+        # the other shares on that peer as valid, though if we
751+        # happen to find a valid version string amongst those
752+        # shares, we'll keep track of it so that we don't need
753+        # to validate the signature on those again.
754+        self._bad_peers.add(peerid)
755+        self._last_failure = f
756+        # XXX: Use the reader for this?
757+        checkstring = data[:SIGNED_PREFIX_LENGTH]
758+        self._servermap.mark_bad_share(peerid, shnum, checkstring)
759+        self._servermap.problems.append(f)
760+
761+
762+    def _cache_good_sharedata(self, verinfo, shnum, now, data):
763+        """
764+        If one of my queries returns successfully (which means that we
765+        were able to and successfully did validate the signature), I
766+        cache the data that we initially fetched from the storage
767+        server. This will help reduce the number of roundtrips that need
768+        to occur when the file is downloaded, or when the file is
769+        updated.
770+        """
771+        self._node._add_to_cache(verinfo, shnum, 0, data, now)
772+
773+
774     def _got_results(self, datavs, peerid, readsize, stuff, started):
775         lp = self.log(format="got result from [%(peerid)s], %(numshares)d shares",
776                       peerid=idlib.shortnodeid_b2a(peerid),
777hunk ./src/allmydata/mutable/servermap.py 630
778         else:
779             self._empty_peers.add(peerid)
780 
781-        last_verinfo = None
782-        last_shnum = None
783+        ss, storage_index = stuff
784+        ds = []
785+
786         for shnum,datav in datavs.items():
787             data = datav[0]
788hunk ./src/allmydata/mutable/servermap.py 635
789-            try:
790-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
791-                last_verinfo = verinfo
792-                last_shnum = shnum
793-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
794-            except CorruptShareError, e:
795-                # log it and give the other shares a chance to be processed
796-                f = failure.Failure()
797-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
798-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
799-                self.notify_server_corruption(peerid, shnum, str(e))
800-                self._bad_peers.add(peerid)
801-                self._last_failure = f
802-                checkstring = data[:SIGNED_PREFIX_LENGTH]
803-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
804-                self._servermap.problems.append(f)
805-                pass
806-
807-        self._status.timings["cumulative_verify"] += (time.time() - now)
808+            reader = MDMFSlotReadProxy(ss,
809+                                       storage_index,
810+                                       shnum,
811+                                       data)
812+            self._readers.setdefault(peerid, dict())[shnum] = reader
813+            # our goal, with each response, is to validate the version
814+            # information and share data as best we can at this point --
815+            # we do this by validating the signature. To do this, we
816+            # need to do the following:
817+            #   - If we don't already have the public key, fetch the
818+            #     public key. We use this to validate the signature.
819+            if not self._node.get_pubkey():
820+                # fetch and set the public key.
821+                d = reader.get_verification_key()
822+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
823+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
824+                # XXX: Make self._pubkey_query_failed?
825+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
826+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
827+            else:
828+                # we already have the public key.
829+                d = defer.succeed(None)
830+            # Neither of these two branches return anything of
831+            # consequence, so the first entry in our deferredlist will
832+            # be None.
833 
834hunk ./src/allmydata/mutable/servermap.py 661
835-        if self._need_privkey and last_verinfo:
836-            # send them a request for the privkey. We send one request per
837-            # server.
838-            lp2 = self.log("sending privkey request",
839-                           parent=lp, level=log.NOISY)
840-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
841-             offsets_tuple) = last_verinfo
842-            o = dict(offsets_tuple)
843+            # - Next, we need the version information. We almost
844+            #   certainly got this by reading the first thousand or so
845+            #   bytes of the share on the storage server, so we
846+            #   shouldn't need to fetch anything at this step.
847+            d2 = reader.get_verinfo()
848+            d2.addErrback(lambda error, shnum=shnum, peerid=peerid:
849+                self._got_corrupt_share(error, shnum, peerid, data, lp))
850+            # - Next, we need the signature. For an SDMF share, it is
851+            #   likely that we fetched this when doing our initial fetch
852+            #   to get the version information. In MDMF, this lives at
853+            #   the end of the share, so unless the file is quite small,
854+            #   we'll need to do a remote fetch to get it.
855+            d3 = reader.get_signature()
856+            d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
857+                self._got_corrupt_share(error, shnum, peerid, data, lp))
858+            #  Once we have all three of these responses, we can move on
859+            #  to validating the signature
860 
861hunk ./src/allmydata/mutable/servermap.py 679
862-            self._queries_outstanding.add(peerid)
863-            readv = [ (o['enc_privkey'], (o['EOF'] - o['enc_privkey'])) ]
864-            ss = self._servermap.connections[peerid]
865-            privkey_started = time.time()
866-            d = self._do_read(ss, peerid, self._storage_index,
867-                              [last_shnum], readv)
868-            d.addCallback(self._got_privkey_results, peerid, last_shnum,
869-                          privkey_started, lp2)
870-            d.addErrback(self._privkey_query_failed, peerid, last_shnum, lp2)
871-            d.addErrback(log.err)
872-            d.addCallback(self._check_for_done)
873-            d.addErrback(self._fatal_error)
874+            # Does the node already have a privkey? If not, we'll try to
875+            # fetch it here.
876+            if self._need_privkey:
877+                d4 = reader.get_encprivkey()
878+                d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
879+                    self._try_to_validate_privkey(results, peerid, shnum, lp))
880+                d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
881+                    self._privkey_query_failed(error, shnum, data, lp))
882+            else:
883+                d4 = defer.succeed(None)
884 
885hunk ./src/allmydata/mutable/servermap.py 690
886+            dl = defer.DeferredList([d, d2, d3, d4])
887+            dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
888+                self._got_signature_one_share(results, shnum, peerid, lp))
889+            dl.addErrback(lambda error, shnum=shnum, data=data:
890+               self._got_corrupt_share(error, shnum, peerid, data, lp))
891+            dl.addCallback(lambda verinfo, shnum=shnum, peerid=peerid, data=data:
892+                self._cache_good_sharedata(verinfo, shnum, now, data))
893+            ds.append(dl)
894+        # dl is a deferred list that will fire when all of the shares
895+        # that we found on this peer are done processing. When dl fires,
896+        # we know that processing is done, so we can decrement the
897+        # semaphore-like thing that we incremented earlier.
898+        dl = defer.DeferredList(ds, fireOnOneErrback=True)
899+        # Are we done? Done means that there are no more queries to
900+        # send, that there are no outstanding queries, and that we
901+        # haven't received any queries that are still processing. If we
902+        # are done, self._check_for_done will cause the done deferred
903+        # that we returned to our caller to fire, which tells them that
904+        # they have a complete servermap, and that we won't be touching
905+        # the servermap anymore.
906+        dl.addCallback(self._check_for_done)
907+        dl.addErrback(self._fatal_error)
908         # all done!
909         self.log("_got_results done", parent=lp, level=log.NOISY)
910hunk ./src/allmydata/mutable/servermap.py 714
911+        return dl
912+
913+
914+    def _try_to_set_pubkey(self, pubkey_s, peerid, shnum, lp):
915+        if self._node.get_pubkey():
916+            return # don't go through this again if we don't have to
917+        fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
918+        assert len(fingerprint) == 32
919+        if fingerprint != self._node.get_fingerprint():
920+            raise CorruptShareError(peerid, shnum,
921+                                "pubkey doesn't match fingerprint")
922+        self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
923+        assert self._node.get_pubkey()
924+
925 
926     def notify_server_corruption(self, peerid, shnum, reason):
927         ss = self._servermap.connections[peerid]
928hunk ./src/allmydata/mutable/servermap.py 734
929         ss.callRemoteOnly("advise_corrupt_share",
930                           "mutable", self._storage_index, shnum, reason)
931 
932-    def _got_results_one_share(self, shnum, data, peerid, lp):
933+
934+    def _got_signature_one_share(self, results, shnum, peerid, lp):
935+        # It is our job to give versioninfo to our caller. We need to
936+        # raise CorruptShareError if the share is corrupt for any
937+        # reason, something that our caller will handle.
938         self.log(format="_got_results: got shnum #%(shnum)d from peerid %(peerid)s",
939                  shnum=shnum,
940                  peerid=idlib.shortnodeid_b2a(peerid),
941hunk ./src/allmydata/mutable/servermap.py 744
942                  level=log.NOISY,
943                  parent=lp)
944-
945-        # this might raise NeedMoreDataError, if the pubkey and signature
946-        # live at some weird offset. That shouldn't happen, so I'm going to
947-        # treat it as a bad share.
948-        (seqnum, root_hash, IV, k, N, segsize, datalength,
949-         pubkey_s, signature, prefix) = unpack_prefix_and_signature(data)
950-
951-        if not self._node.get_pubkey():
952-            fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
953-            assert len(fingerprint) == 32
954-            if fingerprint != self._node.get_fingerprint():
955-                raise CorruptShareError(peerid, shnum,
956-                                        "pubkey doesn't match fingerprint")
957-            self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
958-
959-        if self._need_privkey:
960-            self._try_to_extract_privkey(data, peerid, shnum, lp)
961-
962-        (ig_version, ig_seqnum, ig_root_hash, ig_IV, ig_k, ig_N,
963-         ig_segsize, ig_datalen, offsets) = unpack_header(data)
964+        _, verinfo, signature, __ = results
965+        (seqnum,
966+         root_hash,
967+         saltish,
968+         segsize,
969+         datalen,
970+         k,
971+         n,
972+         prefix,
973+         offsets) = verinfo[1]
974         offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
975 
976hunk ./src/allmydata/mutable/servermap.py 756
977-        verinfo = (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
978+        # XXX: This should be done for us in the method, so
979+        # presumably you can go in there and fix it.
980+        verinfo = (seqnum,
981+                   root_hash,
982+                   saltish,
983+                   segsize,
984+                   datalen,
985+                   k,
986+                   n,
987+                   prefix,
988                    offsets_tuple)
989hunk ./src/allmydata/mutable/servermap.py 767
990+        # This tuple uniquely identifies a share on the grid; we use it
991+        # to keep track of the ones that we've already seen.
992 
993         if verinfo not in self._valid_versions:
994hunk ./src/allmydata/mutable/servermap.py 771
995-            # it's a new pair. Verify the signature.
996-            valid = self._node.get_pubkey().verify(prefix, signature)
997+            # This is a new version tuple, and we need to validate it
998+            # against the public key before keeping track of it.
999+            assert self._node.get_pubkey()
1000+            valid = self._node.get_pubkey().verify(prefix, signature[1])
1001             if not valid:
1002hunk ./src/allmydata/mutable/servermap.py 776
1003-                raise CorruptShareError(peerid, shnum, "signature is invalid")
1004+                raise CorruptShareError(peerid, shnum,
1005+                                        "signature is invalid")
1006 
1007hunk ./src/allmydata/mutable/servermap.py 779
1008-            # ok, it's a valid verinfo. Add it to the list of validated
1009-            # versions.
1010-            self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
1011-                     % (seqnum, base32.b2a(root_hash)[:4],
1012-                        idlib.shortnodeid_b2a(peerid), shnum,
1013-                        k, N, segsize, datalength),
1014-                     parent=lp)
1015-            self._valid_versions.add(verinfo)
1016-        # We now know that this is a valid candidate verinfo.
1017+        # ok, it's a valid verinfo. Add it to the list of validated
1018+        # versions.
1019+        self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
1020+                 % (seqnum, base32.b2a(root_hash)[:4],
1021+                    idlib.shortnodeid_b2a(peerid), shnum,
1022+                    k, n, segsize, datalen),
1023+                    parent=lp)
1024+        self._valid_versions.add(verinfo)
1025+        # We now know that this is a valid candidate verinfo. Whether or
1026+        # not this instance of it is valid is a matter for the next
1027+        # statement; at this point, we just know that if we see this
1028+        # version info again, that its signature checks out and that
1029+        # we're okay to skip the signature-checking step.
1030 
1031hunk ./src/allmydata/mutable/servermap.py 793
1032+        # (peerid, shnum) are bound in the method invocation.
1033         if (peerid, shnum) in self._servermap.bad_shares:
1034             # we've been told that the rest of the data in this share is
1035             # unusable, so don't add it to the servermap.
1036hunk ./src/allmydata/mutable/servermap.py 808
1037         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
1038         return verinfo
1039 
1040+
1041     def _deserialize_pubkey(self, pubkey_s):
1042         verifier = rsa.create_verifying_key_from_string(pubkey_s)
1043         return verifier
1044hunk ./src/allmydata/mutable/servermap.py 813
1045 
1046-    def _try_to_extract_privkey(self, data, peerid, shnum, lp):
1047-        try:
1048-            r = unpack_share(data)
1049-        except NeedMoreDataError, e:
1050-            # this share won't help us. oh well.
1051-            offset = e.encprivkey_offset
1052-            length = e.encprivkey_length
1053-            self.log("shnum %d on peerid %s: share was too short (%dB) "
1054-                     "to get the encprivkey; [%d:%d] ought to hold it" %
1055-                     (shnum, idlib.shortnodeid_b2a(peerid), len(data),
1056-                      offset, offset+length),
1057-                     parent=lp)
1058-            # NOTE: if uncoordinated writes are taking place, someone might
1059-            # change the share (and most probably move the encprivkey) before
1060-            # we get a chance to do one of these reads and fetch it. This
1061-            # will cause us to see a NotEnoughSharesError(unable to fetch
1062-            # privkey) instead of an UncoordinatedWriteError . This is a
1063-            # nuisance, but it will go away when we move to DSA-based mutable
1064-            # files (since the privkey will be small enough to fit in the
1065-            # write cap).
1066-
1067-            return
1068-
1069-        (seqnum, root_hash, IV, k, N, segsize, datalen,
1070-         pubkey, signature, share_hash_chain, block_hash_tree,
1071-         share_data, enc_privkey) = r
1072-
1073-        return self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1074 
1075     def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
1076hunk ./src/allmydata/mutable/servermap.py 815
1077-
1078+        """
1079+        Given a writekey from a remote server, I validate it against the
1080+        writekey stored in my node. If it is valid, then I set the
1081+        privkey and encprivkey properties of the node.
1082+        """
1083         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
1084         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
1085         if alleged_writekey != self._node.get_writekey():
1086hunk ./src/allmydata/mutable/servermap.py 892
1087         self._queries_completed += 1
1088         self._last_failure = f
1089 
1090-    def _got_privkey_results(self, datavs, peerid, shnum, started, lp):
1091-        now = time.time()
1092-        elapsed = now - started
1093-        self._status.add_per_server_time(peerid, "privkey", started, elapsed)
1094-        self._queries_outstanding.discard(peerid)
1095-        if not self._need_privkey:
1096-            return
1097-        if shnum not in datavs:
1098-            self.log("privkey wasn't there when we asked it",
1099-                     level=log.WEIRD, umid="VA9uDQ")
1100-            return
1101-        datav = datavs[shnum]
1102-        enc_privkey = datav[0]
1103-        self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1104 
1105     def _privkey_query_failed(self, f, peerid, shnum, lp):
1106         self._queries_outstanding.discard(peerid)
1107hunk ./src/allmydata/mutable/servermap.py 906
1108         self._servermap.problems.append(f)
1109         self._last_failure = f
1110 
1111+
1112     def _check_for_done(self, res):
1113         # exit paths:
1114         #  return self._send_more_queries(outstanding) : send some more queries
1115hunk ./src/allmydata/mutable/servermap.py 912
1116         #  return self._done() : all done
1117         #  return : keep waiting, no new queries
1118-
1119         lp = self.log(format=("_check_for_done, mode is '%(mode)s', "
1120                               "%(outstanding)d queries outstanding, "
1121                               "%(extra)d extra peers available, "
1122hunk ./src/allmydata/mutable/servermap.py 1117
1123         self._servermap.last_update_time = self._started
1124         # the servermap will not be touched after this
1125         self.log("servermap: %s" % self._servermap.summarize_versions())
1126+
1127         eventually(self._done_deferred.callback, self._servermap)
1128 
1129     def _fatal_error(self, f):
1130hunk ./src/allmydata/test/test_mutable.py 637
1131         d.addCallback(_created)
1132         return d
1133 
1134-    def publish_multiple(self):
1135+    def publish_mdmf(self):
1136+        # like publish_one, except that the result is guaranteed to be
1137+        # an MDMF file.
1138+        # self.CONTENTS should have more than one segment.
1139+        self.CONTENTS = "This is an MDMF file" * 100000
1140+        self._storage = FakeStorage()
1141+        self._nodemaker = make_nodemaker(self._storage)
1142+        self._storage_broker = self._nodemaker.storage_broker
1143+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
1144+        def _created(node):
1145+            self._fn = node
1146+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1147+        d.addCallback(_created)
1148+        return d
1149+
1150+
1151+    def publish_sdmf(self):
1152+        # like publish_one, except that the result is guaranteed to be
1153+        # an SDMF file
1154+        self.CONTENTS = "This is an SDMF file" * 1000
1155+        self._storage = FakeStorage()
1156+        self._nodemaker = make_nodemaker(self._storage)
1157+        self._storage_broker = self._nodemaker.storage_broker
1158+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
1159+        def _created(node):
1160+            self._fn = node
1161+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1162+        d.addCallback(_created)
1163+        return d
1164+
1165+
1166+    def publish_multiple(self, version=0):
1167         self.CONTENTS = ["Contents 0",
1168                          "Contents 1",
1169                          "Contents 2",
1170hunk ./src/allmydata/test/test_mutable.py 677
1171         self._copied_shares = {}
1172         self._storage = FakeStorage()
1173         self._nodemaker = make_nodemaker(self._storage)
1174-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0]) # seqnum=1
1175+        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
1176         def _created(node):
1177             self._fn = node
1178             # now create multiple versions of the same file, and accumulate
1179hunk ./src/allmydata/test/test_mutable.py 906
1180         return d
1181 
1182 
1183+    def test_servermapupdater_finds_mdmf_files(self):
1184+        # setUp already published an MDMF file for us. We just need to
1185+        # make sure that when we run the ServermapUpdater, the file is
1186+        # reported to have one recoverable version.
1187+        d = defer.succeed(None)
1188+        d.addCallback(lambda ignored:
1189+            self.publish_mdmf())
1190+        d.addCallback(lambda ignored:
1191+            self.make_servermap(mode=MODE_CHECK))
1192+        # Calling make_servermap also updates the servermap in the mode
1193+        # that we specify, so we just need to see what it says.
1194+        def _check_servermap(sm):
1195+            self.failUnlessEqual(len(sm.recoverable_versions()), 1)
1196+        d.addCallback(_check_servermap)
1197+        return d
1198+
1199+
1200+    def test_servermapupdater_finds_sdmf_files(self):
1201+        d = defer.succeed(None)
1202+        d.addCallback(lambda ignored:
1203+            self.publish_sdmf())
1204+        d.addCallback(lambda ignored:
1205+            self.make_servermap(mode=MODE_CHECK))
1206+        d.addCallback(lambda servermap:
1207+            self.failUnlessEqual(len(servermap.recoverable_versions()), 1))
1208+        return d
1209+
1210 
1211 class Roundtrip(unittest.TestCase, testutil.ShouldFailMixin, PublishMixin):
1212     def setUp(self):
1213hunk ./src/allmydata/test/test_mutable.py 1050
1214         return d
1215     test_no_servers_download.timeout = 15
1216 
1217+
1218     def _test_corrupt_all(self, offset, substring,
1219                           should_succeed=False, corrupt_early=True,
1220                           failure_checker=None):
1221}
1222[Make a segmented mutable uploader
1223Kevan Carstensen <kevan@isnotajoke.com>**20100626234204
1224 Ignore-this: d199af8ab0bc64d8ed2bc19c5437bfba
1225 
1226 The mutable file uploader should be able to publish files with one
1227 segment and files with multiple segments. This patch makes it do that.
1228 This is still incomplete, and rather ugly -- I need to flesh out error
1229 handling, I need to write tests, and I need to remove some of the uglier
1230 kludges in the process before I can call this done.
1231] {
1232hunk ./src/allmydata/mutable/publish.py 8
1233 from zope.interface import implements
1234 from twisted.internet import defer
1235 from twisted.python import failure
1236-from allmydata.interfaces import IPublishStatus
1237+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
1238 from allmydata.util import base32, hashutil, mathutil, idlib, log
1239 from allmydata import hashtree, codec
1240 from allmydata.storage.server import si_b2a
1241hunk ./src/allmydata/mutable/publish.py 19
1242      UncoordinatedWriteError, NotEnoughServersError
1243 from allmydata.mutable.servermap import ServerMap
1244 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
1245-     unpack_checkstring, SIGNED_PREFIX
1246+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
1247+
1248+KiB = 1024
1249+DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
1250 
1251 class PublishStatus:
1252     implements(IPublishStatus)
1253hunk ./src/allmydata/mutable/publish.py 112
1254         self._status.set_helper(False)
1255         self._status.set_progress(0.0)
1256         self._status.set_active(True)
1257+        # We use this to control how the file is written.
1258+        version = self._node.get_version()
1259+        assert version in (SDMF_VERSION, MDMF_VERSION)
1260+        self._version = version
1261 
1262     def get_status(self):
1263         return self._status
1264hunk ./src/allmydata/mutable/publish.py 134
1265         simultaneous write.
1266         """
1267 
1268-        # 1: generate shares (SDMF: files are small, so we can do it in RAM)
1269-        # 2: perform peer selection, get candidate servers
1270-        #  2a: send queries to n+epsilon servers, to determine current shares
1271-        #  2b: based upon responses, create target map
1272-        # 3: send slot_testv_and_readv_and_writev messages
1273-        # 4: as responses return, update share-dispatch table
1274-        # 4a: may need to run recovery algorithm
1275-        # 5: when enough responses are back, we're done
1276+        # 0. Setup encoding parameters, encoder, and other such things.
1277+        # 1. Encrypt, encode, and publish segments.
1278 
1279         self.log("starting publish, datalen is %s" % len(newdata))
1280         self._status.set_size(len(newdata))
1281hunk ./src/allmydata/mutable/publish.py 187
1282         self.bad_peers = set() # peerids who have errbacked/refused requests
1283 
1284         self.newdata = newdata
1285-        self.salt = os.urandom(16)
1286 
1287hunk ./src/allmydata/mutable/publish.py 188
1288+        # This will set self.segment_size, self.num_segments, and
1289+        # self.fec.
1290         self.setup_encoding_parameters()
1291 
1292         # if we experience any surprises (writes which were rejected because
1293hunk ./src/allmydata/mutable/publish.py 238
1294             self.bad_share_checkstrings[key] = old_checkstring
1295             self.connections[peerid] = self._servermap.connections[peerid]
1296 
1297-        # create the shares. We'll discard these as they are delivered. SDMF:
1298-        # we're allowed to hold everything in memory.
1299+        # Now, the process dovetails -- if this is an SDMF file, we need
1300+        # to write an SDMF file. Otherwise, we need to write an MDMF
1301+        # file.
1302+        if self._version == MDMF_VERSION:
1303+            return self._publish_mdmf()
1304+        else:
1305+            return self._publish_sdmf()
1306+        #return self.done_deferred
1307+
1308+    def _publish_mdmf(self):
1309+        # Next, we find homes for all of the shares that we don't have
1310+        # homes for yet.
1311+        # TODO: Make this part do peer selection.
1312+        self.update_goal()
1313+        self.writers = {}
1314+        # For each (peerid, shnum) in self.goal, we make an
1315+        # MDMFSlotWriteProxy for that peer. We'll use this to write
1316+        # shares to the peer.
1317+        for key in self.goal:
1318+            peerid, shnum = key
1319+            write_enabler = self._node.get_write_enabler(peerid)
1320+            renew_secret = self._node.get_renewal_secret(peerid)
1321+            cancel_secret = self._node.get_cancel_secret(peerid)
1322+            secrets = (write_enabler, renew_secret, cancel_secret)
1323+
1324+            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
1325+                                                      self.connections[peerid],
1326+                                                      self._storage_index,
1327+                                                      secrets,
1328+                                                      self._new_seqnum,
1329+                                                      self.required_shares,
1330+                                                      self.total_shares,
1331+                                                      self.segment_size,
1332+                                                      len(self.newdata))
1333+            if (peerid, shnum) in self._servermap.servermap:
1334+                old_versionid, old_timestamp = self._servermap.servermap[key]
1335+                (old_seqnum, old_root_hash, old_salt, old_segsize,
1336+                 old_datalength, old_k, old_N, old_prefix,
1337+                 old_offsets_tuple) = old_versionid
1338+                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
1339+
1340+        # Now, we start pushing shares.
1341+        self._status.timings["setup"] = time.time() - self._started
1342+        def _start_pushing(res):
1343+            self._started_pushing = time.time()
1344+            return res
1345+
1346+        # First, we encrypt, encode, and publish the shares that we need
1347+        # to encrypt, encode, and publish.
1348+
1349+        # This will eventually hold the block hash chain for each share
1350+        # that we publish. We define it this way so that empty publishes
1351+        # will still have something to write to the remote slot.
1352+        self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
1353+        self.sharehash_leaves = None # eventually [sharehashes]
1354+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
1355+                              # validate the share]
1356 
1357hunk ./src/allmydata/mutable/publish.py 296
1358+        d = defer.succeed(None)
1359+        self.log("Starting push")
1360+        for i in xrange(self.num_segments - 1):
1361+            d.addCallback(lambda ignored, i=i:
1362+                self.push_segment(i))
1363+            d.addCallback(self._turn_barrier)
1364+        # We have at least one segment, so we will have a tail segment
1365+        if self.num_segments > 0:
1366+            d.addCallback(lambda ignored:
1367+                self.push_tail_segment())
1368+
1369+        d.addCallback(lambda ignored:
1370+            self.push_encprivkey())
1371+        d.addCallback(lambda ignored:
1372+            self.push_blockhashes())
1373+        d.addCallback(lambda ignored:
1374+            self.push_sharehashes())
1375+        d.addCallback(lambda ignored:
1376+            self.push_toplevel_hashes_and_signature())
1377+        d.addCallback(lambda ignored:
1378+            self.finish_publishing())
1379+        return d
1380+
1381+
1382+    def _publish_sdmf(self):
1383         self._status.timings["setup"] = time.time() - self._started
1384hunk ./src/allmydata/mutable/publish.py 322
1385+        self.salt = os.urandom(16)
1386+
1387         d = self._encrypt_and_encode()
1388         d.addCallback(self._generate_shares)
1389         def _start_pushing(res):
1390hunk ./src/allmydata/mutable/publish.py 335
1391 
1392         return self.done_deferred
1393 
1394+
1395     def setup_encoding_parameters(self):
1396hunk ./src/allmydata/mutable/publish.py 337
1397-        segment_size = len(self.newdata)
1398+        if self._version == MDMF_VERSION:
1399+            segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
1400+        else:
1401+            segment_size = len(self.newdata) # SDMF is only one segment
1402         # this must be a multiple of self.required_shares
1403         segment_size = mathutil.next_multiple(segment_size,
1404                                               self.required_shares)
1405hunk ./src/allmydata/mutable/publish.py 350
1406                                                   segment_size)
1407         else:
1408             self.num_segments = 0
1409-        assert self.num_segments in [0, 1,] # SDMF restrictions
1410+        if self._version == SDMF_VERSION:
1411+            assert self.num_segments in (0, 1) # SDMF
1412+            return
1413+        # calculate the tail segment size.
1414+        self.tail_segment_size = len(self.newdata) % segment_size
1415+
1416+        if self.tail_segment_size == 0:
1417+            # The tail segment is the same size as the other segments.
1418+            self.tail_segment_size = segment_size
1419+
1420+        # We'll make an encoder ahead-of-time for the normal-sized
1421+        # segments (defined as any segment of segment_size size.
1422+        # (the part of the code that puts the tail segment will make its
1423+        #  own encoder for that part)
1424+        fec = codec.CRSEncoder()
1425+        fec.set_params(self.segment_size,
1426+                       self.required_shares, self.total_shares)
1427+        self.piece_size = fec.get_block_size()
1428+        self.fec = fec
1429+
1430+
1431+    def push_segment(self, segnum):
1432+        started = time.time()
1433+        segsize = self.segment_size
1434+        self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
1435+        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
1436+        assert len(data) == segsize
1437+
1438+        salt = os.urandom(16)
1439+
1440+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1441+        enc = AES(key)
1442+        crypttext = enc.process(data)
1443+        assert len(crypttext) == len(data)
1444+
1445+        now = time.time()
1446+        self._status.timings["encrypt"] = now - started
1447+        started = now
1448+
1449+        # now apply FEC
1450+
1451+        self._status.set_status("Encoding")
1452+        crypttext_pieces = [None] * self.required_shares
1453+        piece_size = self.piece_size
1454+        for i in range(len(crypttext_pieces)):
1455+            offset = i * piece_size
1456+            piece = crypttext[offset:offset+piece_size]
1457+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1458+            crypttext_pieces[i] = piece
1459+            assert len(piece) == piece_size
1460+        d = self.fec.encode(crypttext_pieces)
1461+        def _done_encoding(res):
1462+            elapsed = time.time() - started
1463+            self._status.timings["encode"] = elapsed
1464+            return res
1465+        d.addCallback(_done_encoding)
1466+
1467+        def _push_shares_and_salt(results):
1468+            shares, shareids = results
1469+            dl = []
1470+            for i in xrange(len(shares)):
1471+                sharedata = shares[i]
1472+                shareid = shareids[i]
1473+                block_hash = hashutil.block_hash(salt + sharedata)
1474+                self.blockhashes[shareid].append(block_hash)
1475+
1476+                # find the writer for this share
1477+                d = self.writers[shareid].put_block(sharedata, segnum, salt)
1478+                dl.append(d)
1479+            # TODO: Naturally, we need to check on the results of these.
1480+            return defer.DeferredList(dl)
1481+        d.addCallback(_push_shares_and_salt)
1482+        return d
1483+
1484+
1485+    def push_tail_segment(self):
1486+        # This is essentially the same as push_segment, except that we
1487+        # don't use the cached encoder that we use elsewhere.
1488+        self.log("Pushing tail segment")
1489+        started = time.time()
1490+        segsize = self.segment_size
1491+        data = self.newdata[segsize * (self.num_segments-1):]
1492+        assert len(data) == self.tail_segment_size
1493+        salt = os.urandom(16)
1494+
1495+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1496+        enc = AES(key)
1497+        crypttext = enc.process(data)
1498+        assert len(crypttext) == len(data)
1499+
1500+        now = time.time()
1501+        self._status.timings['encrypt'] = now - started
1502+        started = now
1503+
1504+        self._status.set_status("Encoding")
1505+        tail_fec = codec.CRSEncoder()
1506+        tail_fec.set_params(self.tail_segment_size,
1507+                            self.required_shares,
1508+                            self.total_shares)
1509+
1510+        crypttext_pieces = [None] * self.required_shares
1511+        piece_size = tail_fec.get_block_size()
1512+        for i in range(len(crypttext_pieces)):
1513+            offset = i * piece_size
1514+            piece = crypttext[offset:offset+piece_size]
1515+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1516+            crypttext_pieces[i] = piece
1517+            assert len(piece) == piece_size
1518+        d = tail_fec.encode(crypttext_pieces)
1519+        def _push_shares_and_salt(results):
1520+            shares, shareids = results
1521+            dl = []
1522+            for i in xrange(len(shares)):
1523+                sharedata = shares[i]
1524+                shareid = shareids[i]
1525+                block_hash = hashutil.block_hash(salt + sharedata)
1526+                self.blockhashes[shareid].append(block_hash)
1527+                # find the writer for this share
1528+                d = self.writers[shareid].put_block(sharedata,
1529+                                                    self.num_segments - 1,
1530+                                                    salt)
1531+                dl.append(d)
1532+            # TODO: Naturally, we need to check on the results of these.
1533+            return defer.DeferredList(dl)
1534+        d.addCallback(_push_shares_and_salt)
1535+        return d
1536+
1537+
1538+    def push_encprivkey(self):
1539+        started = time.time()
1540+        encprivkey = self._encprivkey
1541+        dl = []
1542+        def _spy_on_writer(results):
1543+            print results
1544+            return results
1545+        for shnum, writer in self.writers.iteritems():
1546+            d = writer.put_encprivkey(encprivkey)
1547+            dl.append(d)
1548+        d = defer.DeferredList(dl)
1549+        return d
1550+
1551+
1552+    def push_blockhashes(self):
1553+        started = time.time()
1554+        dl = []
1555+        def _spy_on_results(results):
1556+            print results
1557+            return results
1558+        self.sharehash_leaves = [None] * len(self.blockhashes)
1559+        for shnum, blockhashes in self.blockhashes.iteritems():
1560+            t = hashtree.HashTree(blockhashes)
1561+            self.blockhashes[shnum] = list(t)
1562+            # set the leaf for future use.
1563+            self.sharehash_leaves[shnum] = t[0]
1564+            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
1565+            dl.append(d)
1566+        d = defer.DeferredList(dl)
1567+        return d
1568+
1569+
1570+    def push_sharehashes(self):
1571+        share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
1572+        share_hash_chain = {}
1573+        ds = []
1574+        def _spy_on_results(results):
1575+            print results
1576+            return results
1577+        for shnum in xrange(len(self.sharehash_leaves)):
1578+            needed_indices = share_hash_tree.needed_hashes(shnum)
1579+            self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
1580+                                             for i in needed_indices] )
1581+            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
1582+            ds.append(d)
1583+        self.root_hash = share_hash_tree[0]
1584+        d = defer.DeferredList(ds)
1585+        return d
1586+
1587+
1588+    def push_toplevel_hashes_and_signature(self):
1589+        # We need to to three things here:
1590+        #   - Push the root hash and salt hash
1591+        #   - Get the checkstring of the resulting layout; sign that.
1592+        #   - Push the signature
1593+        ds = []
1594+        def _spy_on_results(results):
1595+            print results
1596+            return results
1597+        for shnum in xrange(self.total_shares):
1598+            d = self.writers[shnum].put_root_hash(self.root_hash)
1599+            ds.append(d)
1600+        d = defer.DeferredList(ds)
1601+        def _make_and_place_signature(ignored):
1602+            signable = self.writers[0].get_signable()
1603+            self.signature = self._privkey.sign(signable)
1604+
1605+            ds = []
1606+            for (shnum, writer) in self.writers.iteritems():
1607+                d = writer.put_signature(self.signature)
1608+                ds.append(d)
1609+            return defer.DeferredList(ds)
1610+        d.addCallback(_make_and_place_signature)
1611+        return d
1612+
1613+
1614+    def finish_publishing(self):
1615+        # We're almost done -- we just need to put the verification key
1616+        # and the offsets
1617+        ds = []
1618+        verification_key = self._pubkey.serialize()
1619+
1620+        def _spy_on_results(results):
1621+            print results
1622+            return results
1623+        for (shnum, writer) in self.writers.iteritems():
1624+            d = writer.put_verification_key(verification_key)
1625+            d.addCallback(lambda ignored, writer=writer:
1626+                writer.finish_publishing())
1627+            ds.append(d)
1628+        return defer.DeferredList(ds)
1629+
1630+
1631+    def _turn_barrier(self, res):
1632+        # putting this method in a Deferred chain imposes a guaranteed
1633+        # reactor turn between the pre- and post- portions of that chain.
1634+        # This can be useful to limit memory consumption: since Deferreds do
1635+        # not do tail recursion, code which uses defer.succeed(result) for
1636+        # consistency will cause objects to live for longer than you might
1637+        # normally expect.
1638+        return fireEventually(res)
1639+
1640 
1641     def _fatal_error(self, f):
1642         self.log("error during loop", failure=f, level=log.UNUSUAL)
1643hunk ./src/allmydata/mutable/publish.py 716
1644             self.log_goal(self.goal, "after update: ")
1645 
1646 
1647-
1648     def _encrypt_and_encode(self):
1649         # this returns a Deferred that fires with a list of (sharedata,
1650         # sharenum) tuples. TODO: cache the ciphertext, only produce the
1651hunk ./src/allmydata/mutable/publish.py 757
1652         d.addCallback(_done_encoding)
1653         return d
1654 
1655+
1656     def _generate_shares(self, shares_and_shareids):
1657         # this sets self.shares and self.root_hash
1658         self.log("_generate_shares")
1659hunk ./src/allmydata/mutable/publish.py 1145
1660             self._status.set_progress(1.0)
1661         eventually(self.done_deferred.callback, res)
1662 
1663-
1664hunk ./src/allmydata/test/test_mutable.py 248
1665         d.addCallback(_created)
1666         return d
1667 
1668+
1669+    def test_create_mdmf(self):
1670+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
1671+        def _created(n):
1672+            self.failUnless(isinstance(n, MutableFileNode))
1673+            self.failUnlessEqual(n.get_storage_index(), n._storage_index)
1674+            sb = self.nodemaker.storage_broker
1675+            peer0 = sorted(sb.get_all_serverids())[0]
1676+            shnums = self._storage._peers[peer0].keys()
1677+            self.failUnlessEqual(len(shnums), 1)
1678+        d.addCallback(_created)
1679+        return d
1680+
1681+
1682     def test_serialize(self):
1683         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
1684         calls = []
1685hunk ./src/allmydata/test/test_mutable.py 334
1686         d.addCallback(_created)
1687         return d
1688 
1689+
1690+    def test_create_mdmf_with_initial_contents(self):
1691+        initial_contents = "foobarbaz" * 131072 # 900KiB
1692+        d = self.nodemaker.create_mutable_file(initial_contents,
1693+                                               version=MDMF_VERSION)
1694+        def _created(n):
1695+            d = n.download_best_version()
1696+            d.addCallback(lambda data:
1697+                self.failUnlessEqual(data, initial_contents))
1698+            d.addCallback(lambda ignored:
1699+                n.overwrite(initial_contents + "foobarbaz"))
1700+            d.addCallback(lambda ignored:
1701+                n.download_best_version())
1702+            d.addCallback(lambda data:
1703+                self.failUnlessEqual(data, initial_contents +
1704+                                           "foobarbaz"))
1705+            return d
1706+        d.addCallback(_created)
1707+        return d
1708+
1709+
1710     def test_create_with_initial_contents_function(self):
1711         data = "initial contents"
1712         def _make_contents(n):
1713hunk ./src/allmydata/test/test_mutable.py 370
1714         d.addCallback(lambda data2: self.failUnlessEqual(data2, data))
1715         return d
1716 
1717+
1718+    def test_create_mdmf_with_initial_contents_function(self):
1719+        data = "initial contents" * 100000
1720+        def _make_contents(n):
1721+            self.failUnless(isinstance(n, MutableFileNode))
1722+            key = n.get_writekey()
1723+            self.failUnless(isinstance(key, str), key)
1724+            self.failUnlessEqual(len(key), 16)
1725+            return data
1726+        d = self.nodemaker.create_mutable_file(_make_contents,
1727+                                               version=MDMF_VERSION)
1728+        d.addCallback(lambda n:
1729+            n.download_best_version())
1730+        d.addCallback(lambda data2:
1731+            self.failUnlessEqual(data2, data))
1732+        return d
1733+
1734+
1735     def test_create_with_too_large_contents(self):
1736         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
1737         d = self.nodemaker.create_mutable_file(BIG)
1738}
1739[Write a segmented mutable downloader
1740Kevan Carstensen <kevan@isnotajoke.com>**20100626234314
1741 Ignore-this: d2bef531cde1b5c38f2eb28afdd4b17c
1742 
1743 The segmented mutable downloader can deal with MDMF files (files with
1744 one or more segments in MDMF format) and SDMF files (files with one
1745 segment in SDMF format). It is backwards compatible with the old
1746 file format.
1747 
1748 This patch also contains tests for the segmented mutable downloader.
1749] {
1750hunk ./src/allmydata/mutable/retrieve.py 8
1751 from twisted.internet import defer
1752 from twisted.python import failure
1753 from foolscap.api import DeadReferenceError, eventually, fireEventually
1754-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
1755-from allmydata.util import hashutil, idlib, log
1756+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
1757+                                 MDMF_VERSION, SDMF_VERSION
1758+from allmydata.util import hashutil, idlib, log, mathutil
1759 from allmydata import hashtree, codec
1760 from allmydata.storage.server import si_b2a
1761 from pycryptopp.cipher.aes import AES
1762hunk ./src/allmydata/mutable/retrieve.py 17
1763 from pycryptopp.publickey import rsa
1764 
1765 from allmydata.mutable.common import DictOfSets, CorruptShareError, UncoordinatedWriteError
1766-from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data
1767+from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data, \
1768+                                     MDMFSlotReadProxy
1769 
1770 class RetrieveStatus:
1771     implements(IRetrieveStatus)
1772hunk ./src/allmydata/mutable/retrieve.py 104
1773         self.verinfo = verinfo
1774         # during repair, we may be called upon to grab the private key, since
1775         # it wasn't picked up during a verify=False checker run, and we'll
1776-        # need it for repair to generate the a new version.
1777+        # need it for repair to generate a new version.
1778         self._need_privkey = fetch_privkey
1779         if self._node.get_privkey():
1780             self._need_privkey = False
1781hunk ./src/allmydata/mutable/retrieve.py 109
1782 
1783+        if self._need_privkey:
1784+            # TODO: Evaluate the need for this. We'll use it if we want
1785+            # to limit how many queries are on the wire for the privkey
1786+            # at once.
1787+            self._privkey_query_markers = [] # one Marker for each time we've
1788+                                             # tried to get the privkey.
1789+
1790         self._status = RetrieveStatus()
1791         self._status.set_storage_index(self._storage_index)
1792         self._status.set_helper(False)
1793hunk ./src/allmydata/mutable/retrieve.py 125
1794          offsets_tuple) = self.verinfo
1795         self._status.set_size(datalength)
1796         self._status.set_encoding(k, N)
1797+        self.readers = {}
1798 
1799     def get_status(self):
1800         return self._status
1801hunk ./src/allmydata/mutable/retrieve.py 149
1802         self.remaining_sharemap = DictOfSets()
1803         for (shnum, peerid, timestamp) in shares:
1804             self.remaining_sharemap.add(shnum, peerid)
1805+            # If the servermap update fetched anything, it fetched at least 1
1806+            # KiB, so we ask for that much.
1807+            # TODO: Change the cache methods to allow us to fetch all of the
1808+            # data that they have, then change this method to do that.
1809+            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
1810+                                                               shnum,
1811+                                                               0,
1812+                                                               1000)
1813+            ss = self.servermap.connections[peerid]
1814+            reader = MDMFSlotReadProxy(ss,
1815+                                       self._storage_index,
1816+                                       shnum,
1817+                                       any_cache)
1818+            reader.peerid = peerid
1819+            self.readers[shnum] = reader
1820+
1821 
1822         self.shares = {} # maps shnum to validated blocks
1823hunk ./src/allmydata/mutable/retrieve.py 167
1824+        self._active_readers = [] # list of active readers for this dl.
1825+        self._validated_readers = set() # set of readers that we have
1826+                                        # validated the prefix of
1827+        self._block_hash_trees = {} # shnum => hashtree
1828+        # TODO: Make this into a file-backed consumer or something to
1829+        # conserve memory.
1830+        self._plaintext = ""
1831 
1832         # how many shares do we need?
1833hunk ./src/allmydata/mutable/retrieve.py 176
1834-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1835+        (seqnum,
1836+         root_hash,
1837+         IV,
1838+         segsize,
1839+         datalength,
1840+         k,
1841+         N,
1842+         prefix,
1843          offsets_tuple) = self.verinfo
1844hunk ./src/allmydata/mutable/retrieve.py 185
1845-        assert len(self.remaining_sharemap) >= k
1846-        # we start with the lowest shnums we have available, since FEC is
1847-        # faster if we're using "primary shares"
1848-        self.active_shnums = set(sorted(self.remaining_sharemap.keys())[:k])
1849-        for shnum in self.active_shnums:
1850-            # we use an arbitrary peer who has the share. If shares are
1851-            # doubled up (more than one share per peer), we could make this
1852-            # run faster by spreading the load among multiple peers. But the
1853-            # algorithm to do that is more complicated than I want to write
1854-            # right now, and a well-provisioned grid shouldn't have multiple
1855-            # shares per peer.
1856-            peerid = list(self.remaining_sharemap[shnum])[0]
1857-            self.get_data(shnum, peerid)
1858 
1859hunk ./src/allmydata/mutable/retrieve.py 186
1860-        # control flow beyond this point: state machine. Receiving responses
1861-        # from queries is the input. We might send out more queries, or we
1862-        # might produce a result.
1863 
1864hunk ./src/allmydata/mutable/retrieve.py 187
1865+        # We need one share hash tree for the entire file; its leaves
1866+        # are the roots of the block hash trees for the shares that
1867+        # comprise it, and its root is in the verinfo.
1868+        self.share_hash_tree = hashtree.IncompleteHashTree(N)
1869+        self.share_hash_tree.set_hashes({0: root_hash})
1870+
1871+        # This will set up both the segment decoder and the tail segment
1872+        # decoder, as well as a variety of other instance variables that
1873+        # the download process will use.
1874+        self._setup_encoding_parameters()
1875+        assert len(self.remaining_sharemap) >= k
1876+
1877+        self.log("starting download")
1878+        self._add_active_peers()
1879+        # The download process beyond this is a state machine.
1880+        # _add_active_peers will select the peers that we want to use
1881+        # for the download, and then attempt to start downloading. After
1882+        # each segment, it will check for doneness, reacting to broken
1883+        # peers and corrupt shares as necessary. If it runs out of good
1884+        # peers before downloading all of the segments, _done_deferred
1885+        # will errback.  Otherwise, it will eventually callback with the
1886+        # contents of the mutable file.
1887         return self._done_deferred
1888 
1889hunk ./src/allmydata/mutable/retrieve.py 211
1890-    def get_data(self, shnum, peerid):
1891-        self.log(format="sending sh#%(shnum)d request to [%(peerid)s]",
1892-                 shnum=shnum,
1893-                 peerid=idlib.shortnodeid_b2a(peerid),
1894-                 level=log.NOISY)
1895-        ss = self.servermap.connections[peerid]
1896-        started = time.time()
1897-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1898+
1899+    def _setup_encoding_parameters(self):
1900+        """
1901+        I set up the encoding parameters, including k, n, the number
1902+        of segments associated with this file, and the segment decoder.
1903+        """
1904+        (seqnum,
1905+         root_hash,
1906+         IV,
1907+         segsize,
1908+         datalength,
1909+         k,
1910+         n,
1911+         known_prefix,
1912          offsets_tuple) = self.verinfo
1913hunk ./src/allmydata/mutable/retrieve.py 226
1914-        offsets = dict(offsets_tuple)
1915+        self._required_shares = k
1916+        self._total_shares = n
1917+        self._segment_size = segsize
1918+        self._data_length = datalength
1919+
1920+        if not IV:
1921+            self._version = MDMF_VERSION
1922+        else:
1923+            self._version = SDMF_VERSION
1924+
1925+        if datalength and segsize:
1926+            self._num_segments = mathutil.div_ceil(datalength, segsize)
1927+            self._tail_data_size = datalength % segsize
1928+        else:
1929+            self._num_segments = 0
1930+            self._tail_data_size = 0
1931 
1932hunk ./src/allmydata/mutable/retrieve.py 243
1933-        # we read the checkstring, to make sure that the data we grab is from
1934-        # the right version.
1935-        readv = [ (0, struct.calcsize(SIGNED_PREFIX)) ]
1936+        self._segment_decoder = codec.CRSDecoder()
1937+        self._segment_decoder.set_params(segsize, k, n)
1938+        self._current_segment = 0
1939 
1940hunk ./src/allmydata/mutable/retrieve.py 247
1941-        # We also read the data, and the hashes necessary to validate them
1942-        # (share_hash_chain, block_hash_tree, share_data). We don't read the
1943-        # signature or the pubkey, since that was handled during the
1944-        # servermap phase, and we'll be comparing the share hash chain
1945-        # against the roothash that was validated back then.
1946+        if  not self._tail_data_size:
1947+            self._tail_data_size = segsize
1948 
1949hunk ./src/allmydata/mutable/retrieve.py 250
1950-        readv.append( (offsets['share_hash_chain'],
1951-                       offsets['enc_privkey'] - offsets['share_hash_chain'] ) )
1952+        self._tail_segment_size = mathutil.next_multiple(self._tail_data_size,
1953+                                                         self._required_shares)
1954+        if self._tail_segment_size == self._segment_size:
1955+            self._tail_decoder = self._segment_decoder
1956+        else:
1957+            self._tail_decoder = codec.CRSDecoder()
1958+            self._tail_decoder.set_params(self._tail_segment_size,
1959+                                          self._required_shares,
1960+                                          self._total_shares)
1961 
1962hunk ./src/allmydata/mutable/retrieve.py 260
1963-        # if we need the private key (for repair), we also fetch that
1964-        if self._need_privkey:
1965-            readv.append( (offsets['enc_privkey'],
1966-                           offsets['EOF'] - offsets['enc_privkey']) )
1967+        self.log("got encoding parameters: "
1968+                 "k: %d "
1969+                 "n: %d "
1970+                 "%d segments of %d bytes each (%d byte tail segment)" % \
1971+                 (k, n, self._num_segments, self._segment_size,
1972+                  self._tail_segment_size))
1973 
1974hunk ./src/allmydata/mutable/retrieve.py 267
1975-        m = Marker()
1976-        self._outstanding_queries[m] = (peerid, shnum, started)
1977+        for i in xrange(self._total_shares):
1978+            # So we don't have to do this later.
1979+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
1980 
1981hunk ./src/allmydata/mutable/retrieve.py 271
1982-        # ask the cache first
1983-        got_from_cache = False
1984-        datavs = []
1985-        for (offset, length) in readv:
1986-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
1987-                                                            offset, length)
1988-            if data is not None:
1989-                datavs.append(data)
1990-        if len(datavs) == len(readv):
1991-            self.log("got data from cache")
1992-            got_from_cache = True
1993-            d = fireEventually({shnum: datavs})
1994-            # datavs is a dict mapping shnum to a pair of strings
1995-        else:
1996-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
1997-        self.remaining_sharemap.discard(shnum, peerid)
1998+        # If we have more than one segment, we are an SDMF file, which
1999+        # means that we need to validate the salts as we receive them.
2000+        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
2001+        self._salt_hash_tree[0] = IV # from the prefix.
2002 
2003hunk ./src/allmydata/mutable/retrieve.py 276
2004-        d.addCallback(self._got_results, m, peerid, started, got_from_cache)
2005-        d.addErrback(self._query_failed, m, peerid)
2006-        # errors that aren't handled by _query_failed (and errors caused by
2007-        # _query_failed) get logged, but we still want to check for doneness.
2008-        def _oops(f):
2009-            self.log(format="problem in _query_failed for sh#%(shnum)d to %(peerid)s",
2010-                     shnum=shnum,
2011-                     peerid=idlib.shortnodeid_b2a(peerid),
2012-                     failure=f,
2013-                     level=log.WEIRD, umid="W0xnQA")
2014-        d.addErrback(_oops)
2015-        d.addBoth(self._check_for_done)
2016-        # any error during _check_for_done means the download fails. If the
2017-        # download is successful, _check_for_done will fire _done by itself.
2018-        d.addErrback(self._done)
2019-        d.addErrback(log.err)
2020-        return d # purely for testing convenience
2021 
2022hunk ./src/allmydata/mutable/retrieve.py 277
2023-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
2024-        # isolate the callRemote to a separate method, so tests can subclass
2025-        # Publish and override it
2026-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
2027-        return d
2028+    def _add_active_peers(self):
2029+        """
2030+        I populate self._active_readers with enough active readers to
2031+        retrieve the contents of this mutable file. I am called before
2032+        downloading starts, and (eventually) after each validation
2033+        error, connection error, or other problem in the download.
2034+        """
2035+        # TODO: It would be cool to investigate other heuristics for
2036+        # reader selection. For instance, the cost (in time the user
2037+        # spends waiting for their file) of selecting a really slow peer
2038+        # that happens to have a primary share is probably more than
2039+        # selecting a really fast peer that doesn't have a primary
2040+        # share. Maybe the servermap could be extended to provide this
2041+        # information; it could keep track of latency information while
2042+        # it gathers more important data, and then this routine could
2043+        # use that to select active readers.
2044+        #
2045+        # (these and other questions would be easier to answer with a
2046+        #  robust, configurable tahoe-lafs simulator, which modeled node
2047+        #  failures, differences in node speed, and other characteristics
2048+        #  that we expect storage servers to have.  You could have
2049+        #  presets for really stable grids (like allmydata.com),
2050+        #  friendnets, make it easy to configure your own settings, and
2051+        #  then simulate the effect of big changes on these use cases
2052+        #  instead of just reasoning about what the effect might be. Out
2053+        #  of scope for MDMF, though.)
2054 
2055hunk ./src/allmydata/mutable/retrieve.py 304
2056-    def remove_peer(self, peerid):
2057-        for shnum in list(self.remaining_sharemap.keys()):
2058-            self.remaining_sharemap.discard(shnum, peerid)
2059+        # We need at least self._required_shares readers to download a
2060+        # segment.
2061+        needed = self._required_shares - len(self._active_readers)
2062+        # XXX: Why don't format= log messages work here?
2063+        self.log("adding %d peers to the active peers list" % needed)
2064 
2065hunk ./src/allmydata/mutable/retrieve.py 310
2066-    def _got_results(self, datavs, marker, peerid, started, got_from_cache):
2067-        now = time.time()
2068-        elapsed = now - started
2069-        if not got_from_cache:
2070-            self._status.add_fetch_timing(peerid, elapsed)
2071-        self.log(format="got results (%(shares)d shares) from [%(peerid)s]",
2072-                 shares=len(datavs),
2073-                 peerid=idlib.shortnodeid_b2a(peerid),
2074-                 level=log.NOISY)
2075-        self._outstanding_queries.pop(marker, None)
2076-        if not self._running:
2077-            return
2078+        # We favor lower numbered shares, since FEC is faster with
2079+        # primary shares than with other shares, and lower-numbered
2080+        # shares are more likely to be primary than higher numbered
2081+        # shares.
2082+        active_shnums = set(sorted(self.remaining_sharemap.keys()))
2083+        # We shouldn't consider adding shares that we already have; this
2084+        # will cause problems later.
2085+        active_shnums -= set([reader.shnum for reader in self._active_readers])
2086+        active_shnums = list(active_shnums)[:needed]
2087+        if len(active_shnums) < needed:
2088+            # We don't have enough readers to retrieve the file; fail.
2089+            return self._failed()
2090 
2091hunk ./src/allmydata/mutable/retrieve.py 323
2092-        # note that we only ask for a single share per query, so we only
2093-        # expect a single share back. On the other hand, we use the extra
2094-        # shares if we get them.. seems better than an assert().
2095+        for shnum in active_shnums:
2096+            self._active_readers.append(self.readers[shnum])
2097+            self.log("added reader for share %d" % shnum)
2098+        assert len(self._active_readers) == self._required_shares
2099+        # Conceptually, this is part of the _add_active_peers step. It
2100+        # validates the prefixes of newly added readers to make sure
2101+        # that they match what we are expecting for self.verinfo. If
2102+        # validation is successful, _validate_active_prefixes will call
2103+        # _download_current_segment for us. If validation is
2104+        # unsuccessful, then _validate_prefixes will remove the peer and
2105+        # call _add_active_peers again, where we will attempt to rectify
2106+        # the problem by choosing another peer.
2107+        return self._validate_active_prefixes()
2108 
2109hunk ./src/allmydata/mutable/retrieve.py 337
2110-        for shnum,datav in datavs.items():
2111-            (prefix, hash_and_data) = datav[:2]
2112-            try:
2113-                self._got_results_one_share(shnum, peerid,
2114-                                            prefix, hash_and_data)
2115-            except CorruptShareError, e:
2116-                # log it and give the other shares a chance to be processed
2117-                f = failure.Failure()
2118-                self.log(format="bad share: %(f_value)s",
2119-                         f_value=str(f.value), failure=f,
2120-                         level=log.WEIRD, umid="7fzWZw")
2121-                self.notify_server_corruption(peerid, shnum, str(e))
2122-                self.remove_peer(peerid)
2123-                self.servermap.mark_bad_share(peerid, shnum, prefix)
2124-                self._bad_shares.add( (peerid, shnum) )
2125-                self._status.problems[peerid] = f
2126-                self._last_failure = f
2127-                pass
2128-            if self._need_privkey and len(datav) > 2:
2129-                lp = None
2130-                self._try_to_validate_privkey(datav[2], peerid, shnum, lp)
2131-        # all done!
2132 
2133hunk ./src/allmydata/mutable/retrieve.py 338
2134-    def notify_server_corruption(self, peerid, shnum, reason):
2135-        ss = self.servermap.connections[peerid]
2136-        ss.callRemoteOnly("advise_corrupt_share",
2137-                          "mutable", self._storage_index, shnum, reason)
2138+    def _validate_active_prefixes(self):
2139+        """
2140+        I check to make sure that the prefixes on the peers that I am
2141+        currently reading from match the prefix that we want to see, as
2142+        said in self.verinfo.
2143 
2144hunk ./src/allmydata/mutable/retrieve.py 344
2145-    def _got_results_one_share(self, shnum, peerid,
2146-                               got_prefix, got_hash_and_data):
2147-        self.log("_got_results: got shnum #%d from peerid %s"
2148-                 % (shnum, idlib.shortnodeid_b2a(peerid)))
2149-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2150+        If I find that all of the active peers have acceptable prefixes,
2151+        I pass control to _download_current_segment, which will use
2152+        those peers to do cool things. If I find that some of the active
2153+        peers have unacceptable prefixes, I will remove them from active
2154+        peers (and from further consideration) and call
2155+        _add_active_peers to attempt to rectify the situation. I keep
2156+        track of which peers I have already validated so that I don't
2157+        need to do so again.
2158+        """
2159+        assert self._active_readers, "No more active readers"
2160+
2161+        ds = []
2162+        new_readers = set(self._active_readers) - self._validated_readers
2163+        self.log('validating %d newly-added active readers' % len(new_readers))
2164+
2165+        for reader in new_readers:
2166+            # We force a remote read here -- otherwise, we are relying
2167+            # on cached data that we already verified as valid, and we
2168+            # won't detect an uncoordinated write that has occurred
2169+            # since the last servermap update.
2170+            d = reader.get_prefix(force_remote=True)
2171+            d.addCallback(self._try_to_validate_prefix, reader)
2172+            ds.append(d)
2173+        dl = defer.DeferredList(ds, consumeErrors=True)
2174+        def _check_results(results):
2175+            # Each result in results will be of the form (success, msg).
2176+            # We don't care about msg, but success will tell us whether
2177+            # or not the checkstring validated. If it didn't, we need to
2178+            # remove the offending (peer,share) from our active readers,
2179+            # and ensure that active readers is again populated.
2180+            bad_readers = []
2181+            for i, result in enumerate(results):
2182+                if not result[0]:
2183+                    reader = self._active_readers[i]
2184+                    f = result[1]
2185+                    assert isinstance(f, failure.Failure)
2186+
2187+                    self.log("The reader %s failed to "
2188+                             "properly validate: %s" % \
2189+                             (reader, str(f.value)))
2190+                    bad_readers.append((reader, f))
2191+                else:
2192+                    reader = self._active_readers[i]
2193+                    self.log("the reader %s checks out, so we'll use it" % \
2194+                             reader)
2195+                    self._validated_readers.add(reader)
2196+                    # Each time we validate a reader, we check to see if
2197+                    # we need the private key. If we do, we politely ask
2198+                    # for it and then continue computing. If we find
2199+                    # that we haven't gotten it at the end of
2200+                    # segment decoding, then we'll take more drastic
2201+                    # measures.
2202+                    if self._need_privkey:
2203+                        d = reader.get_encprivkey()
2204+                        d.addCallback(self._try_to_validate_privkey, reader)
2205+            if bad_readers:
2206+                # We do them all at once, or else we screw up list indexing.
2207+                for (reader, f) in bad_readers:
2208+                    self._mark_bad_share(reader, f)
2209+                return self._add_active_peers()
2210+            else:
2211+                return self._download_current_segment()
2212+            # The next step will assert that it has enough active
2213+            # readers to fetch shares; we just need to remove it.
2214+        dl.addCallback(_check_results)
2215+        return dl
2216+
2217+
2218+    def _try_to_validate_prefix(self, prefix, reader):
2219+        """
2220+        I check that the prefix returned by a candidate server for
2221+        retrieval matches the prefix that the servermap knows about
2222+        (and, hence, the prefix that was validated earlier). If it does,
2223+        I return True, which means that I approve of the use of the
2224+        candidate server for segment retrieval. If it doesn't, I return
2225+        False, which means that another server must be chosen.
2226+        """
2227+        (seqnum,
2228+         root_hash,
2229+         IV,
2230+         segsize,
2231+         datalength,
2232+         k,
2233+         N,
2234+         known_prefix,
2235          offsets_tuple) = self.verinfo
2236hunk ./src/allmydata/mutable/retrieve.py 430
2237-        assert len(got_prefix) == len(prefix), (len(got_prefix), len(prefix))
2238-        if got_prefix != prefix:
2239-            msg = "someone wrote to the data since we read the servermap: prefix changed"
2240-            raise UncoordinatedWriteError(msg)
2241-        (share_hash_chain, block_hash_tree,
2242-         share_data) = unpack_share_data(self.verinfo, got_hash_and_data)
2243+        if known_prefix != prefix:
2244+            self.log("prefix from share %d doesn't match" % reader.shnum)
2245+            raise UncoordinatedWriteError("Mismatched prefix -- this could "
2246+                                          "indicate an uncoordinated write")
2247+        # Otherwise, we're okay -- no issues.
2248 
2249hunk ./src/allmydata/mutable/retrieve.py 436
2250-        assert isinstance(share_data, str)
2251-        # build the block hash tree. SDMF has only one leaf.
2252-        leaves = [hashutil.block_hash(share_data)]
2253-        t = hashtree.HashTree(leaves)
2254-        if list(t) != block_hash_tree:
2255-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
2256-        share_hash_leaf = t[0]
2257-        t2 = hashtree.IncompleteHashTree(N)
2258-        # root_hash was checked by the signature
2259-        t2.set_hashes({0: root_hash})
2260-        try:
2261-            t2.set_hashes(hashes=share_hash_chain,
2262-                          leaves={shnum: share_hash_leaf})
2263-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
2264-                IndexError), e:
2265-            msg = "corrupt hashes: %s" % (e,)
2266-            raise CorruptShareError(peerid, shnum, msg)
2267-        self.log(" data valid! len=%d" % len(share_data))
2268-        # each query comes down to this: placing validated share data into
2269-        # self.shares
2270-        self.shares[shnum] = share_data
2271 
2272hunk ./src/allmydata/mutable/retrieve.py 437
2273-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
2274+    def _remove_reader(self, reader):
2275+        """
2276+        At various points, we will wish to remove a peer from
2277+        consideration and/or use. These include, but are not necessarily
2278+        limited to:
2279 
2280hunk ./src/allmydata/mutable/retrieve.py 443
2281-        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2282-        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2283-        if alleged_writekey != self._node.get_writekey():
2284-            self.log("invalid privkey from %s shnum %d" %
2285-                     (idlib.nodeid_b2a(peerid)[:8], shnum),
2286-                     parent=lp, level=log.WEIRD, umid="YIw4tA")
2287-            return
2288+            - A connection error.
2289+            - A mismatched prefix (that is, a prefix that does not match
2290+              our conception of the version information string).
2291+            - A failing block hash, salt hash, or share hash, which can
2292+              indicate disk failure/bit flips, or network trouble.
2293 
2294hunk ./src/allmydata/mutable/retrieve.py 449
2295-        # it's good
2296-        self.log("got valid privkey from shnum %d on peerid %s" %
2297-                 (shnum, idlib.shortnodeid_b2a(peerid)),
2298-                 parent=lp)
2299-        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2300-        self._node._populate_encprivkey(enc_privkey)
2301-        self._node._populate_privkey(privkey)
2302-        self._need_privkey = False
2303+        This method will do that. I will make sure that the
2304+        (shnum,reader) combination represented by my reader argument is
2305+        not used for anything else during this download. I will not
2306+        advise the reader of any corruption, something that my callers
2307+        may wish to do on their own.
2308+        """
2309+        # TODO: When you're done writing this, see if this is ever
2310+        # actually used for something that _mark_bad_share isn't. I have
2311+        # a feeling that they will be used for very similar things, and
2312+        # that having them both here is just going to be an epic amount
2313+        # of code duplication.
2314+        #
2315+        # (well, okay, not epic, but meaningful)
2316+        self.log("removing reader %s" % reader)
2317+        # Remove the reader from _active_readers
2318+        self._active_readers.remove(reader)
2319+        # TODO: self.readers.remove(reader)?
2320+        for shnum in list(self.remaining_sharemap.keys()):
2321+            self.remaining_sharemap.discard(shnum, reader.peerid)
2322 
2323hunk ./src/allmydata/mutable/retrieve.py 469
2324-    def _query_failed(self, f, marker, peerid):
2325-        self.log(format="query to [%(peerid)s] failed",
2326-                 peerid=idlib.shortnodeid_b2a(peerid),
2327-                 level=log.NOISY)
2328-        self._status.problems[peerid] = f
2329-        self._outstanding_queries.pop(marker, None)
2330-        if not self._running:
2331-            return
2332-        self._last_failure = f
2333-        self.remove_peer(peerid)
2334-        level = log.WEIRD
2335-        if f.check(DeadReferenceError):
2336-            level = log.UNUSUAL
2337-        self.log(format="error during query: %(f_value)s",
2338-                 f_value=str(f.value), failure=f, level=level, umid="gOJB5g")
2339 
2340hunk ./src/allmydata/mutable/retrieve.py 470
2341-    def _check_for_done(self, res):
2342-        # exit paths:
2343-        #  return : keep waiting, no new queries
2344-        #  return self._send_more_queries(outstanding) : send some more queries
2345-        #  fire self._done(plaintext) : download successful
2346-        #  raise exception : download fails
2347+    def _mark_bad_share(self, reader, f):
2348+        """
2349+        I mark the (peerid, shnum) encapsulated by my reader argument as
2350+        a bad share, which means that it will not be used anywhere else.
2351 
2352hunk ./src/allmydata/mutable/retrieve.py 475
2353-        self.log(format="_check_for_done: running=%(running)s, decoding=%(decoding)s",
2354-                 running=self._running, decoding=self._decoding,
2355-                 level=log.NOISY)
2356-        if not self._running:
2357-            return
2358-        if self._decoding:
2359-            return
2360-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2361-         offsets_tuple) = self.verinfo
2362+        There are several reasons to want to mark something as a bad
2363+        share. These include:
2364 
2365hunk ./src/allmydata/mutable/retrieve.py 478
2366-        if len(self.shares) < k:
2367-            # we don't have enough shares yet
2368-            return self._maybe_send_more_queries(k)
2369-        if self._need_privkey:
2370-            # we got k shares, but none of them had a valid privkey. TODO:
2371-            # look further. Adding code to do this is a bit complicated, and
2372-            # I want to avoid that complication, and this should be pretty
2373-            # rare (k shares with bitflips in the enc_privkey but not in the
2374-            # data blocks). If we actually do get here, the subsequent repair
2375-            # will fail for lack of a privkey.
2376-            self.log("got k shares but still need_privkey, bummer",
2377-                     level=log.WEIRD, umid="MdRHPA")
2378+            - A connection error to the peer.
2379+            - A mismatched prefix (that is, a prefix that does not match
2380+              our local conception of the version information string).
2381+            - A failing block hash, salt hash, share hash, or other
2382+              integrity check.
2383 
2384hunk ./src/allmydata/mutable/retrieve.py 484
2385-        # we have enough to finish. All the shares have had their hashes
2386-        # checked, so if something fails at this point, we don't know how
2387-        # to fix it, so the download will fail.
2388+        This method will ensure that readers that we wish to mark bad
2389+        (for these reasons or other reasons) are not used for the rest
2390+        of the download. Additionally, it will attempt to tell the
2391+        remote peer (with no guarantee of success) that its share is
2392+        corrupt.
2393+        """
2394+        self.log("marking share %d on server %s as bad" % \
2395+                 (reader.shnum, reader))
2396+        self._remove_reader(reader)
2397+        self._bad_shares.add((reader.peerid, reader.shnum))
2398+        self._status.problems[reader.peerid] = f
2399+        self._last_failure = f
2400+        self.notify_server_corruption(reader.peerid, reader.shnum,
2401+                                      str(f.value))
2402 
2403hunk ./src/allmydata/mutable/retrieve.py 499
2404-        self._decoding = True # avoid reentrancy
2405-        self._status.set_status("decoding")
2406-        now = time.time()
2407-        elapsed = now - self._started
2408-        self._status.timings["fetch"] = elapsed
2409 
2410hunk ./src/allmydata/mutable/retrieve.py 500
2411-        d = defer.maybeDeferred(self._decode)
2412-        d.addCallback(self._decrypt, IV, self._node.get_readkey())
2413-        d.addBoth(self._done)
2414-        return d # purely for test convenience
2415+    def _download_current_segment(self):
2416+        """
2417+        I download, validate, decode, decrypt, and assemble the segment
2418+        that this Retrieve is currently responsible for downloading.
2419+        """
2420+        assert len(self._active_readers) >= self._required_shares
2421+        if self._current_segment < self._num_segments:
2422+            d = self._process_segment(self._current_segment)
2423+        else:
2424+            d = defer.succeed(None)
2425+        d.addCallback(self._check_for_done)
2426+        return d
2427 
2428hunk ./src/allmydata/mutable/retrieve.py 513
2429-    def _maybe_send_more_queries(self, k):
2430-        # we don't have enough shares yet. Should we send out more queries?
2431-        # There are some number of queries outstanding, each for a single
2432-        # share. If we can generate 'needed_shares' additional queries, we do
2433-        # so. If we can't, then we know this file is a goner, and we raise
2434-        # NotEnoughSharesError.
2435-        self.log(format=("_maybe_send_more_queries, have=%(have)d, k=%(k)d, "
2436-                         "outstanding=%(outstanding)d"),
2437-                 have=len(self.shares), k=k,
2438-                 outstanding=len(self._outstanding_queries),
2439-                 level=log.NOISY)
2440 
2441hunk ./src/allmydata/mutable/retrieve.py 514
2442-        remaining_shares = k - len(self.shares)
2443-        needed = remaining_shares - len(self._outstanding_queries)
2444-        if not needed:
2445-            # we have enough queries in flight already
2446+    def _process_segment(self, segnum):
2447+        """
2448+        I download, validate, decode, and decrypt one segment of the
2449+        file that this Retrieve is retrieving. This means coordinating
2450+        the process of getting k blocks of that file, validating them,
2451+        assembling them into one segment with the decoder, and then
2452+        decrypting them.
2453+        """
2454+        self.log("processing segment %d" % segnum)
2455 
2456hunk ./src/allmydata/mutable/retrieve.py 524
2457-            # TODO: but if they've been in flight for a long time, and we
2458-            # have reason to believe that new queries might respond faster
2459-            # (i.e. we've seen other queries come back faster, then consider
2460-            # sending out new queries. This could help with peers which have
2461-            # silently gone away since the servermap was updated, for which
2462-            # we're still waiting for the 15-minute TCP disconnect to happen.
2463-            self.log("enough queries are in flight, no more are needed",
2464-                     level=log.NOISY)
2465-            return
2466+        # TODO: The old code uses a marker. Should this code do that
2467+        # too? What did the Marker do?
2468+        assert len(self._active_readers) >= self._required_shares
2469+
2470+        # We need to ask each of our active readers for its block and
2471+        # salt. We will then validate those. If validation is
2472+        # successful, we will assemble the results into plaintext.
2473+        ds = []
2474+        for reader in self._active_readers:
2475+            d = reader.get_block_and_salt(segnum, queue=True)
2476+            d2 = self._get_needed_hashes(reader, segnum)
2477+            dl = defer.DeferredList([d, d2], consumeErrors=True)
2478+            dl.addCallback(self._validate_block, segnum, reader)
2479+            dl.addErrback(self._validation_or_decoding_failed, [reader])
2480+            ds.append(dl)
2481+            reader.flush()
2482+        dl = defer.DeferredList(ds)
2483+        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
2484+        return dl
2485 
2486hunk ./src/allmydata/mutable/retrieve.py 544
2487-        outstanding_shnums = set([shnum
2488-                                  for (peerid, shnum, started)
2489-                                  in self._outstanding_queries.values()])
2490-        # prefer low-numbered shares, they are more likely to be primary
2491-        available_shnums = sorted(self.remaining_sharemap.keys())
2492-        for shnum in available_shnums:
2493-            if shnum in outstanding_shnums:
2494-                # skip ones that are already in transit
2495-                continue
2496-            if shnum not in self.remaining_sharemap:
2497-                # no servers for that shnum. note that DictOfSets removes
2498-                # empty sets from the dict for us.
2499-                continue
2500-            peerid = list(self.remaining_sharemap[shnum])[0]
2501-            # get_data will remove that peerid from the sharemap, and add the
2502-            # query to self._outstanding_queries
2503-            self._status.set_status("Retrieving More Shares")
2504-            self.get_data(shnum, peerid)
2505-            needed -= 1
2506-            if not needed:
2507+
2508+    def _maybe_decode_and_decrypt_segment(self, blocks_and_salts, segnum):
2509+        """
2510+        I take the results of fetching and validating the blocks from a
2511+        callback chain in another method. If the results are such that
2512+        they tell me that validation and fetching succeeded without
2513+        incident, I will proceed with decoding and decryption.
2514+        Otherwise, I will do nothing.
2515+        """
2516+        self.log("trying to decode and decrypt segment %d" % segnum)
2517+        failures = False
2518+        for block_and_salt in blocks_and_salts:
2519+            if not block_and_salt[0] or block_and_salt[1] == None:
2520+                self.log("some validation operations failed; not proceeding")
2521+                failures = True
2522                 break
2523hunk ./src/allmydata/mutable/retrieve.py 560
2524+        if not failures:
2525+            self.log("everything looks ok, building segment %d" % segnum)
2526+            d = self._decode_blocks(blocks_and_salts, segnum)
2527+            d.addCallback(self._decrypt_segment)
2528+            d.addErrback(self._validation_or_decoding_failed,
2529+                         self._active_readers)
2530+            d.addCallback(self._set_segment)
2531+            return d
2532+        else:
2533+            return defer.succeed(None)
2534+
2535+
2536+    def _set_segment(self, segment):
2537+        """
2538+        Given a plaintext segment, I register that segment with the
2539+        target that is handling the file download.
2540+        """
2541+        self.log("got plaintext for segment %d" % self._current_segment)
2542+        self._plaintext += segment
2543+        self._current_segment += 1
2544 
2545hunk ./src/allmydata/mutable/retrieve.py 581
2546-        # at this point, we have as many outstanding queries as we can. If
2547-        # needed!=0 then we might not have enough to recover the file.
2548-        if needed:
2549-            format = ("ran out of peers: "
2550-                      "have %(have)d shares (k=%(k)d), "
2551-                      "%(outstanding)d queries in flight, "
2552-                      "need %(need)d more, "
2553-                      "found %(bad)d bad shares")
2554-            args = {"have": len(self.shares),
2555-                    "k": k,
2556-                    "outstanding": len(self._outstanding_queries),
2557-                    "need": needed,
2558-                    "bad": len(self._bad_shares),
2559-                    }
2560-            self.log(format=format,
2561-                     level=log.WEIRD, umid="ezTfjw", **args)
2562-            err = NotEnoughSharesError("%s, last failure: %s" %
2563-                                      (format % args, self._last_failure))
2564-            if self._bad_shares:
2565-                self.log("We found some bad shares this pass. You should "
2566-                         "update the servermap and try again to check "
2567-                         "more peers",
2568-                         level=log.WEIRD, umid="EFkOlA")
2569-                err.servermap = self.servermap
2570-            raise err
2571 
2572hunk ./src/allmydata/mutable/retrieve.py 582
2573+    def _validation_or_decoding_failed(self, f, readers):
2574+        """
2575+        I am called when a block or a salt fails to correctly validate, or when
2576+        the decryption or decoding operation fails for some reason.  I react to
2577+        this failure by notifying the remote server of corruption, and then
2578+        removing the remote peer from further activity.
2579+        """
2580+        assert isinstance(readers, list)
2581+        bad_shnums = [reader.shnum for reader in readers]
2582+
2583+        self.log("validation or decoding failed on share(s) %s, peer(s) %s "
2584+                 ", segment %d: %s" % \
2585+                 (bad_shnums, readers, self._current_segment, str(f)))
2586+        for reader in readers:
2587+            self._mark_bad_share(reader, f)
2588         return
2589 
2590hunk ./src/allmydata/mutable/retrieve.py 599
2591-    def _decode(self):
2592-        started = time.time()
2593-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2594-         offsets_tuple) = self.verinfo
2595 
2596hunk ./src/allmydata/mutable/retrieve.py 600
2597-        # shares_dict is a dict mapping shnum to share data, but the codec
2598-        # wants two lists.
2599-        shareids = []; shares = []
2600-        for shareid, share in self.shares.items():
2601+    def _validate_block(self, results, segnum, reader):
2602+        """
2603+        I validate a block from one share on a remote server.
2604+        """
2605+        # Grab the part of the block hash tree that is necessary to
2606+        # validate this block, then generate the block hash root.
2607+        self.log("validating share %d for segment %d" % (reader.shnum,
2608+                                                             segnum))
2609+        # Did we fail to fetch either of the things that we were
2610+        # supposed to? Fail if so.
2611+        if not results[0][0] and results[1][0]:
2612+            # handled by the errback handler.
2613+
2614+            # These all get batched into one query, so the resulting
2615+            # failure should be the same for all of them, so we can just
2616+            # use the first one.
2617+            assert isinstance(results[0][1], failure.Failure)
2618+
2619+            f = results[0][1]
2620+            raise CorruptShareError(reader.peerid,
2621+                                    reader.shnum,
2622+                                    "Connection error: %s" % str(f))
2623+
2624+        block_and_salt, block_and_sharehashes = results
2625+        block, salt = block_and_salt[1]
2626+        blockhashes, sharehashes = block_and_sharehashes[1]
2627+
2628+        blockhashes = dict(enumerate(blockhashes[1]))
2629+        self.log("the reader gave me the following blockhashes: %s" % \
2630+                 blockhashes.keys())
2631+        self.log("the reader gave me the following sharehashes: %s" % \
2632+                 sharehashes[1].keys())
2633+        bht = self._block_hash_trees[reader.shnum]
2634+
2635+        if bht.needed_hashes(segnum, include_leaf=True):
2636+            try:
2637+                bht.set_hashes(blockhashes)
2638+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2639+                    IndexError), e:
2640+                raise CorruptShareError(reader.peerid,
2641+                                        reader.shnum,
2642+                                        "block hash tree failure: %s" % e)
2643+
2644+        if self._version == MDMF_VERSION:
2645+            blockhash = hashutil.block_hash(salt + block)
2646+        else:
2647+            blockhash = hashutil.block_hash(block)
2648+        # If this works without an error, then validation is
2649+        # successful.
2650+        try:
2651+           bht.set_hashes(leaves={segnum: blockhash})
2652+        except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2653+                IndexError), e:
2654+            raise CorruptShareError(reader.peerid,
2655+                                    reader.shnum,
2656+                                    "block hash tree failure: %s" % e)
2657+
2658+        # Reaching this point means that we know that this segment
2659+        # is correct. Now we need to check to see whether the share
2660+        # hash chain is also correct.
2661+        # SDMF wrote share hash chains that didn't contain the
2662+        # leaves, which would be produced from the block hash tree.
2663+        # So we need to validate the block hash tree first. If
2664+        # successful, then bht[0] will contain the root for the
2665+        # shnum, which will be a leaf in the share hash tree, which
2666+        # will allow us to validate the rest of the tree.
2667+        if self.share_hash_tree.needed_hashes(reader.shnum,
2668+                                               include_leaf=True):
2669+            try:
2670+                self.share_hash_tree.set_hashes(hashes=sharehashes[1],
2671+                                            leaves={reader.shnum: bht[0]})
2672+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2673+                    IndexError), e:
2674+                raise CorruptShareError(reader.peerid,
2675+                                        reader.shnum,
2676+                                        "corrupt hashes: %s" % e)
2677+
2678+        # TODO: Validate the salt, too.
2679+        self.log('share %d is valid for segment %d' % (reader.shnum,
2680+                                                       segnum))
2681+        return {reader.shnum: (block, salt)}
2682+
2683+
2684+    def _get_needed_hashes(self, reader, segnum):
2685+        """
2686+        I get the hashes needed to validate segnum from the reader, then return
2687+        to my caller when this is done.
2688+        """
2689+        bht = self._block_hash_trees[reader.shnum]
2690+        needed = bht.needed_hashes(segnum, include_leaf=True)
2691+        # The root of the block hash tree is also a leaf in the share
2692+        # hash tree. So we don't need to fetch it from the remote
2693+        # server. In the case of files with one segment, this means that
2694+        # we won't fetch any block hash tree from the remote server,
2695+        # since the hash of each share of the file is the entire block
2696+        # hash tree, and is a leaf in the share hash tree. This is fine,
2697+        # since any share corruption will be detected in the share hash
2698+        # tree.
2699+        #needed.discard(0)
2700+        self.log("getting blockhashes for segment %d, share %d: %s" % \
2701+                 (segnum, reader.shnum, str(needed)))
2702+        d1 = reader.get_blockhashes(needed, queue=True, force_remote=True)
2703+        if self.share_hash_tree.needed_hashes(reader.shnum):
2704+            need = self.share_hash_tree.needed_hashes(reader.shnum)
2705+            self.log("also need sharehashes for share %d: %s" % (reader.shnum,
2706+                                                                 str(need)))
2707+            d2 = reader.get_sharehashes(need, queue=True, force_remote=True)
2708+        else:
2709+            d2 = defer.succeed({}) # the logic in the next method
2710+                                   # expects a dict
2711+        dl = defer.DeferredList([d1, d2], consumeErrors=True)
2712+        return dl
2713+
2714+
2715+    def _decode_blocks(self, blocks_and_salts, segnum):
2716+        """
2717+        I take a list of k blocks and salts, and decode that into a
2718+        single encrypted segment.
2719+        """
2720+        d = {}
2721+        # We want to merge our dictionaries to the form
2722+        # {shnum: blocks_and_salts}
2723+        #
2724+        # The dictionaries come from validate block that way, so we just
2725+        # need to merge them.
2726+        for block_and_salt in blocks_and_salts:
2727+            d.update(block_and_salt[1])
2728+
2729+        # All of these blocks should have the same salt; in SDMF, it is
2730+        # the file-wide IV, while in MDMF it is the per-segment salt. In
2731+        # either case, we just need to get one of them and use it.
2732+        #
2733+        # d.items()[0] is like (shnum, (block, salt))
2734+        # d.items()[0][1] is like (block, salt)
2735+        # d.items()[0][1][1] is the salt.
2736+        salt = d.items()[0][1][1]
2737+        # Next, extract just the blocks from the dict. We'll use the
2738+        # salt in the next step.
2739+        share_and_shareids = [(k, v[0]) for k, v in d.items()]
2740+        d2 = dict(share_and_shareids)
2741+        shareids = []
2742+        shares = []
2743+        for shareid, share in d2.items():
2744             shareids.append(shareid)
2745             shares.append(share)
2746 
2747hunk ./src/allmydata/mutable/retrieve.py 746
2748-        assert len(shareids) >= k, len(shareids)
2749+        assert len(shareids) >= self._required_shares, len(shareids)
2750         # zfec really doesn't want extra shares
2751hunk ./src/allmydata/mutable/retrieve.py 748
2752-        shareids = shareids[:k]
2753-        shares = shares[:k]
2754-
2755-        fec = codec.CRSDecoder()
2756-        fec.set_params(segsize, k, N)
2757-
2758-        self.log("params %s, we have %d shares" % ((segsize, k, N), len(shares)))
2759-        self.log("about to decode, shareids=%s" % (shareids,))
2760-        d = defer.maybeDeferred(fec.decode, shares, shareids)
2761-        def _done(buffers):
2762-            self._status.timings["decode"] = time.time() - started
2763-            self.log(" decode done, %d buffers" % len(buffers))
2764+        shareids = shareids[:self._required_shares]
2765+        shares = shares[:self._required_shares]
2766+        self.log("decoding segment %d" % segnum)
2767+        if segnum == self._num_segments - 1:
2768+            d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids)
2769+        else:
2770+            d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids)
2771+        def _process(buffers):
2772             segment = "".join(buffers)
2773hunk ./src/allmydata/mutable/retrieve.py 757
2774+            self.log(format="now decoding segment %(segnum)s of %(numsegs)s",
2775+                     segnum=segnum,
2776+                     numsegs=self._num_segments,
2777+                     level=log.NOISY)
2778             self.log(" joined length %d, datalength %d" %
2779hunk ./src/allmydata/mutable/retrieve.py 762
2780-                     (len(segment), datalength))
2781-            segment = segment[:datalength]
2782+                     (len(segment), self._data_length))
2783+            if segnum == self._num_segments - 1:
2784+                size_to_use = self._tail_data_size
2785+            else:
2786+                size_to_use = self._segment_size
2787+            segment = segment[:size_to_use]
2788             self.log(" segment len=%d" % len(segment))
2789hunk ./src/allmydata/mutable/retrieve.py 769
2790-            return segment
2791-        def _err(f):
2792-            self.log(" decode failed: %s" % f)
2793-            return f
2794-        d.addCallback(_done)
2795-        d.addErrback(_err)
2796+            return segment, salt
2797+        d.addCallback(_process)
2798         return d
2799 
2800hunk ./src/allmydata/mutable/retrieve.py 773
2801-    def _decrypt(self, crypttext, IV, readkey):
2802+
2803+    def _decrypt_segment(self, segment_and_salt):
2804+        """
2805+        I take a single segment and its salt, and decrypt it. I return
2806+        the plaintext of the segment that is in my argument.
2807+        """
2808+        segment, salt = segment_and_salt
2809         self._status.set_status("decrypting")
2810hunk ./src/allmydata/mutable/retrieve.py 781
2811+        self.log("decrypting segment %d" % self._current_segment)
2812         started = time.time()
2813hunk ./src/allmydata/mutable/retrieve.py 783
2814-        key = hashutil.ssk_readkey_data_hash(IV, readkey)
2815+        key = hashutil.ssk_readkey_data_hash(salt, self._node.get_readkey())
2816         decryptor = AES(key)
2817hunk ./src/allmydata/mutable/retrieve.py 785
2818-        plaintext = decryptor.process(crypttext)
2819+        plaintext = decryptor.process(segment)
2820         self._status.timings["decrypt"] = time.time() - started
2821         return plaintext
2822 
2823hunk ./src/allmydata/mutable/retrieve.py 789
2824-    def _done(self, res):
2825-        if not self._running:
2826+
2827+    def notify_server_corruption(self, peerid, shnum, reason):
2828+        ss = self.servermap.connections[peerid]
2829+        ss.callRemoteOnly("advise_corrupt_share",
2830+                          "mutable", self._storage_index, shnum, reason)
2831+
2832+
2833+    def _try_to_validate_privkey(self, enc_privkey, reader):
2834+
2835+        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2836+        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2837+        if alleged_writekey != self._node.get_writekey():
2838+            self.log("invalid privkey from %s shnum %d" %
2839+                     (reader, reader.shnum),
2840+                     level=log.WEIRD, umid="YIw4tA")
2841             return
2842hunk ./src/allmydata/mutable/retrieve.py 805
2843-        self._running = False
2844-        self._status.set_active(False)
2845-        self._status.timings["total"] = time.time() - self._started
2846-        # res is either the new contents, or a Failure
2847-        if isinstance(res, failure.Failure):
2848-            self.log("Retrieve done, with failure", failure=res,
2849-                     level=log.UNUSUAL)
2850-            self._status.set_status("Failed")
2851-        else:
2852-            self.log("Retrieve done, success!")
2853-            self._status.set_status("Finished")
2854-            self._status.set_progress(1.0)
2855-            # remember the encoding parameters, use them again next time
2856-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2857-             offsets_tuple) = self.verinfo
2858-            self._node._populate_required_shares(k)
2859-            self._node._populate_total_shares(N)
2860-        eventually(self._done_deferred.callback, res)
2861 
2862hunk ./src/allmydata/mutable/retrieve.py 806
2863+        # it's good
2864+        self.log("got valid privkey from shnum %d on reader %s" %
2865+                 (reader.shnum, reader))
2866+        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2867+        self._node._populate_encprivkey(enc_privkey)
2868+        self._node._populate_privkey(privkey)
2869+        self._need_privkey = False
2870+
2871+
2872+    def _check_for_done(self, res):
2873+        """
2874+        I check to see if this Retrieve object has successfully finished
2875+        its work.
2876+
2877+        I can exit in the following ways:
2878+            - If there are no more segments to download, then I exit by
2879+              causing self._done_deferred to fire with the plaintext
2880+              content requested by the caller.
2881+            - If there are still segments to be downloaded, and there
2882+              are enough active readers (readers which have not broken
2883+              and have not given us corrupt data) to continue
2884+              downloading, I send control back to
2885+              _download_current_segment.
2886+            - If there are still segments to be downloaded but there are
2887+              not enough active peers to download them, I ask
2888+              _add_active_peers to add more peers. If it is successful,
2889+              it will call _download_current_segment. If there are not
2890+              enough peers to retrieve the file, then that will cause
2891+              _done_deferred to errback.
2892+        """
2893+        self.log("checking for doneness")
2894+        if self._current_segment == self._num_segments:
2895+            # No more segments to download, we're done.
2896+            self.log("got plaintext, done")
2897+            return self._done()
2898+
2899+        if len(self._active_readers) >= self._required_shares:
2900+            # More segments to download, but we have enough good peers
2901+            # in self._active_readers that we can do that without issue,
2902+            # so go nab the next segment.
2903+            self.log("not done yet: on segment %d of %d" % \
2904+                     (self._current_segment + 1, self._num_segments))
2905+            return self._download_current_segment()
2906+
2907+        self.log("not done yet: on segment %d of %d, need to add peers" % \
2908+                 (self._current_segment + 1, self._num_segments))
2909+        return self._add_active_peers()
2910+
2911+
2912+    def _done(self):
2913+        """
2914+        I am called by _check_for_done when the download process has
2915+        finished successfully. After making some useful logging
2916+        statements, I return the decrypted contents to the owner of this
2917+        Retrieve object through self._done_deferred.
2918+        """
2919+        eventually(self._done_deferred.callback, self._plaintext)
2920+
2921+
2922+    def _failed(self):
2923+        """
2924+        I am called by _add_active_peers when there are not enough
2925+        active peers left to complete the download. After making some
2926+        useful logging statements, I return an exception to that effect
2927+        to the caller of this Retrieve object through
2928+        self._done_deferred.
2929+        """
2930+        format = ("ran out of peers: "
2931+                  "have %(have)d of %(total)d segments "
2932+                  "found %(bad)d bad shares "
2933+                  "encoding %(k)d-of-%(n)d")
2934+        args = {"have": self._current_segment,
2935+                "total": self._num_segments,
2936+                "k": self._required_shares,
2937+                "n": self._total_shares,
2938+                "bad": len(self._bad_shares)}
2939+        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
2940+                                                        str(self._last_failure)))
2941+        f = failure.Failure(e)
2942+        eventually(self._done_deferred.callback, f)
2943hunk ./src/allmydata/test/test_mutable.py 12
2944 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
2945      ssk_pubkey_fingerprint_hash
2946 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
2947-     NotEnoughSharesError
2948+     NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
2949 from allmydata.monitor import Monitor
2950 from allmydata.test.common import ShouldFailMixin
2951 from allmydata.test.no_network import GridTestMixin
2952hunk ./src/allmydata/test/test_mutable.py 28
2953 from allmydata.mutable.retrieve import Retrieve
2954 from allmydata.mutable.publish import Publish
2955 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
2956-from allmydata.mutable.layout import unpack_header, unpack_share
2957+from allmydata.mutable.layout import unpack_header, unpack_share, \
2958+                                     MDMFSlotReadProxy
2959 from allmydata.mutable.repairer import MustForceRepairError
2960 
2961 import allmydata.test.common_util as testutil
2962hunk ./src/allmydata/test/test_mutable.py 104
2963         d = fireEventually()
2964         d.addCallback(lambda res: _call())
2965         return d
2966+
2967     def callRemoteOnly(self, methname, *args, **kwargs):
2968         d = self.callRemote(methname, *args, **kwargs)
2969         d.addBoth(lambda ignore: None)
2970hunk ./src/allmydata/test/test_mutable.py 163
2971 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
2972     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
2973     # list of shnums to corrupt.
2974+    ds = []
2975     for peerid in s._peers:
2976         shares = s._peers[peerid]
2977         for shnum in shares:
2978hunk ./src/allmydata/test/test_mutable.py 190
2979                 else:
2980                     offset1 = offset
2981                     offset2 = 0
2982-                if offset1 == "pubkey":
2983+                if offset1 == "pubkey" and IV:
2984                     real_offset = 107
2985hunk ./src/allmydata/test/test_mutable.py 192
2986+                elif offset1 == "share_data" and not IV:
2987+                    real_offset = 104
2988                 elif offset1 in o:
2989                     real_offset = o[offset1]
2990                 else:
2991hunk ./src/allmydata/test/test_mutable.py 327
2992         d.addCallback(_created)
2993         return d
2994 
2995+
2996+    def test_upload_and_download_mdmf(self):
2997+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
2998+        def _created(n):
2999+            d = defer.succeed(None)
3000+            d.addCallback(lambda ignored:
3001+                n.get_servermap(MODE_READ))
3002+            def _then(servermap):
3003+                dumped = servermap.dump(StringIO())
3004+                self.failUnlessIn("3-of-10", dumped.getvalue())
3005+            d.addCallback(_then)
3006+            # Now overwrite the contents with some new contents. We want
3007+            # to make them big enough to force the file to be uploaded
3008+            # in more than one segment.
3009+            big_contents = "contents1" * 100000 # about 900 KiB
3010+            d.addCallback(lambda ignored:
3011+                n.overwrite(big_contents))
3012+            d.addCallback(lambda ignored:
3013+                n.download_best_version())
3014+            d.addCallback(lambda data:
3015+                self.failUnlessEqual(data, big_contents))
3016+            # Overwrite the contents again with some new contents. As
3017+            # before, they need to be big enough to force multiple
3018+            # segments, so that we make the downloader deal with
3019+            # multiple segments.
3020+            bigger_contents = "contents2" * 1000000 # about 9MiB
3021+            d.addCallback(lambda ignored:
3022+                n.overwrite(bigger_contents))
3023+            d.addCallback(lambda ignored:
3024+                n.download_best_version())
3025+            d.addCallback(lambda data:
3026+                self.failUnlessEqual(data, bigger_contents))
3027+            return d
3028+        d.addCallback(_created)
3029+        return d
3030+
3031+
3032     def test_create_with_initial_contents(self):
3033         d = self.nodemaker.create_mutable_file("contents 1")
3034         def _created(n):
3035hunk ./src/allmydata/test/test_mutable.py 1147
3036 
3037 
3038     def _test_corrupt_all(self, offset, substring,
3039-                          should_succeed=False, corrupt_early=True,
3040-                          failure_checker=None):
3041+                          should_succeed=False,
3042+                          corrupt_early=True,
3043+                          failure_checker=None,
3044+                          fetch_privkey=False):
3045         d = defer.succeed(None)
3046         if corrupt_early:
3047             d.addCallback(corrupt, self._storage, offset)
3048hunk ./src/allmydata/test/test_mutable.py 1167
3049                     self.failUnlessIn(substring, "".join(allproblems))
3050                 return servermap
3051             if should_succeed:
3052-                d1 = self._fn.download_version(servermap, ver)
3053+                d1 = self._fn.download_version(servermap, ver,
3054+                                               fetch_privkey)
3055                 d1.addCallback(lambda new_contents:
3056                                self.failUnlessEqual(new_contents, self.CONTENTS))
3057             else:
3058hunk ./src/allmydata/test/test_mutable.py 1175
3059                 d1 = self.shouldFail(NotEnoughSharesError,
3060                                      "_corrupt_all(offset=%s)" % (offset,),
3061                                      substring,
3062-                                     self._fn.download_version, servermap, ver)
3063+                                     self._fn.download_version, servermap,
3064+                                                                ver,
3065+                                                                fetch_privkey)
3066             if failure_checker:
3067                 d1.addCallback(failure_checker)
3068             d1.addCallback(lambda res: servermap)
3069hunk ./src/allmydata/test/test_mutable.py 1186
3070         return d
3071 
3072     def test_corrupt_all_verbyte(self):
3073-        # when the version byte is not 0, we hit an UnknownVersionError error
3074-        # in unpack_share().
3075+        # when the version byte is not 0 or 1, we hit an UnknownVersionError
3076+        # error in unpack_share().
3077         d = self._test_corrupt_all(0, "UnknownVersionError")
3078         def _check_servermap(servermap):
3079             # and the dump should mention the problems
3080hunk ./src/allmydata/test/test_mutable.py 1193
3081             s = StringIO()
3082             dump = servermap.dump(s).getvalue()
3083-            self.failUnless("10 PROBLEMS" in dump, dump)
3084+            self.failUnless("30 PROBLEMS" in dump, dump)
3085         d.addCallback(_check_servermap)
3086         return d
3087 
3088hunk ./src/allmydata/test/test_mutable.py 1263
3089         return self._test_corrupt_all("enc_privkey", None, should_succeed=True)
3090 
3091 
3092+    def test_corrupt_all_encprivkey_late(self):
3093+        # this should work for the same reason as above, but we corrupt
3094+        # after the servermap update to exercise the error handling
3095+        # code.
3096+        # We need to remove the privkey from the node, or the retrieve
3097+        # process won't know to update it.
3098+        self._fn._privkey = None
3099+        return self._test_corrupt_all("enc_privkey",
3100+                                      None, # this shouldn't fail
3101+                                      should_succeed=True,
3102+                                      corrupt_early=False,
3103+                                      fetch_privkey=True)
3104+
3105+
3106     def test_corrupt_all_seqnum_late(self):
3107         # corrupting the seqnum between mapupdate and retrieve should result
3108         # in NotEnoughSharesError, since each share will look invalid
3109hunk ./src/allmydata/test/test_mutable.py 1283
3110         def _check(res):
3111             f = res[0]
3112             self.failUnless(f.check(NotEnoughSharesError))
3113-            self.failUnless("someone wrote to the data since we read the servermap" in str(f))
3114+            self.failUnless("uncoordinated write" in str(f))
3115         return self._test_corrupt_all(1, "ran out of peers",
3116                                       corrupt_early=False,
3117                                       failure_checker=_check)
3118hunk ./src/allmydata/test/test_mutable.py 1333
3119                       self.failUnlessEqual(new_contents, self.CONTENTS))
3120         return d
3121 
3122-    def test_corrupt_some(self):
3123-        # corrupt the data of first five shares (so the servermap thinks
3124-        # they're good but retrieve marks them as bad), so that the
3125-        # MODE_READ set of 6 will be insufficient, forcing node.download to
3126-        # retry with more servers.
3127-        corrupt(None, self._storage, "share_data", range(5))
3128-        d = self.make_servermap()
3129+
3130+    def _test_corrupt_some(self, offset, mdmf=False):
3131+        if mdmf:
3132+            d = self.publish_mdmf()
3133+        else:
3134+            d = defer.succeed(None)
3135+        d.addCallback(lambda ignored:
3136+            corrupt(None, self._storage, offset, range(5)))
3137+        d.addCallback(lambda ignored:
3138+            self.make_servermap())
3139         def _do_retrieve(servermap):
3140             ver = servermap.best_recoverable_version()
3141             self.failUnless(ver)
3142hunk ./src/allmydata/test/test_mutable.py 1349
3143             return self._fn.download_best_version()
3144         d.addCallback(_do_retrieve)
3145         d.addCallback(lambda new_contents:
3146-                      self.failUnlessEqual(new_contents, self.CONTENTS))
3147+            self.failUnlessEqual(new_contents, self.CONTENTS))
3148         return d
3149 
3150hunk ./src/allmydata/test/test_mutable.py 1352
3151+
3152+    def test_corrupt_some(self):
3153+        # corrupt the data of first five shares (so the servermap thinks
3154+        # they're good but retrieve marks them as bad), so that the
3155+        # MODE_READ set of 6 will be insufficient, forcing node.download to
3156+        # retry with more servers.
3157+        return self._test_corrupt_some("share_data")
3158+
3159+
3160     def test_download_fails(self):
3161         d = corrupt(None, self._storage, "signature")
3162         d.addCallback(lambda ignored:
3163hunk ./src/allmydata/test/test_mutable.py 1366
3164             self.shouldFail(UnrecoverableFileError, "test_download_anyway",
3165                             "no recoverable versions",
3166-                            self._fn.download_best_version)
3167+                            self._fn.download_best_version))
3168         return d
3169 
3170 
3171hunk ./src/allmydata/test/test_mutable.py 1370
3172+
3173+    def test_corrupt_mdmf_block_hash_tree(self):
3174+        d = self.publish_mdmf()
3175+        d.addCallback(lambda ignored:
3176+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3177+                                   "block hash tree failure",
3178+                                   corrupt_early=False,
3179+                                   should_succeed=False))
3180+        return d
3181+
3182+
3183+    def test_corrupt_mdmf_block_hash_tree_late(self):
3184+        d = self.publish_mdmf()
3185+        d.addCallback(lambda ignored:
3186+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3187+                                   "block hash tree failure",
3188+                                   corrupt_early=True,
3189+                                   should_succeed=False))
3190+        return d
3191+
3192+
3193+    def test_corrupt_mdmf_share_data(self):
3194+        d = self.publish_mdmf()
3195+        d.addCallback(lambda ignored:
3196+            # TODO: Find out what the block size is and corrupt a
3197+            # specific block, rather than just guessing.
3198+            self._test_corrupt_all(("share_data", 12 * 40),
3199+                                    "block hash tree failure",
3200+                                    corrupt_early=True,
3201+                                    should_succeed=False))
3202+        return d
3203+
3204+
3205+    def test_corrupt_some_mdmf(self):
3206+        return self._test_corrupt_some(("share_data", 12 * 40),
3207+                                       mdmf=True)
3208+
3209+
3210 class CheckerMixin:
3211     def check_good(self, r, where):
3212         self.failUnless(r.is_healthy(), where)
3213hunk ./src/allmydata/test/test_mutable.py 2116
3214             d.addCallback(lambda res:
3215                           self.shouldFail(NotEnoughSharesError,
3216                                           "test_retrieve_surprise",
3217-                                          "ran out of peers: have 0 shares (k=3)",
3218+                                          "ran out of peers: have 0 of 1",
3219                                           n.download_version,
3220                                           self.old_map,
3221                                           self.old_map.best_recoverable_version(),
3222hunk ./src/allmydata/test/test_mutable.py 2125
3223         d.addCallback(_created)
3224         return d
3225 
3226+
3227     def test_unexpected_shares(self):
3228         # upload the file, take a servermap, shut down one of the servers,
3229         # upload it again (causing shares to appear on a new server), then
3230hunk ./src/allmydata/test/test_mutable.py 2329
3231         self.basedir = "mutable/Problems/test_privkey_query_missing"
3232         self.set_up_grid(num_servers=20)
3233         nm = self.g.clients[0].nodemaker
3234-        LARGE = "These are Larger contents" * 2000 # about 50KB
3235+        LARGE = "These are Larger contents" * 2000 # about 50KiB
3236         nm._node_cache = DevNullDictionary() # disable the nodecache
3237 
3238         d = nm.create_mutable_file(LARGE)
3239hunk ./src/allmydata/test/test_mutable.py 2342
3240         d.addCallback(_created)
3241         d.addCallback(lambda res: self.n2.get_servermap(MODE_WRITE))
3242         return d
3243+
3244+
3245+    def test_block_and_hash_query_error(self):
3246+        # This tests for what happens when a query to a remote server
3247+        # fails in either the hash validation step or the block getting
3248+        # step (because of batching, this is the same actual query).
3249+        # We need to have the storage server persist up until the point
3250+        # that its prefix is validated, then suddenly die. This
3251+        # exercises some exception handling code in Retrieve.
3252+        self.basedir = "mutable/Problems/test_block_and_hash_query_error"
3253+        self.set_up_grid(num_servers=20)
3254+        nm = self.g.clients[0].nodemaker
3255+        CONTENTS = "contents" * 2000
3256+        d = nm.create_mutable_file(CONTENTS)
3257+        def _created(node):
3258+            self._node = node
3259+        d.addCallback(_created)
3260+        d.addCallback(lambda ignored:
3261+            self._node.get_servermap(MODE_READ))
3262+        def _then(servermap):
3263+            # we have our servermap. Now we set up the servers like the
3264+            # tests above -- the first one that gets a read call should
3265+            # start throwing errors, but only after returning its prefix
3266+            # for validation. Since we'll download without fetching the
3267+            # private key, the next query to the remote server will be
3268+            # for either a block and salt or for hashes, either of which
3269+            # will exercise the error handling code.
3270+            killer = FirstServerGetsKilled()
3271+            for (serverid, ss) in nm.storage_broker.get_all_servers():
3272+                ss.post_call_notifier = killer.notify
3273+            ver = servermap.best_recoverable_version()
3274+            assert ver
3275+            return self._node.download_version(servermap, ver)
3276+        d.addCallback(_then)
3277+        d.addCallback(lambda data:
3278+            self.failUnlessEqual(data, CONTENTS))
3279+        return d
3280}
3281[mutable/checker.py: check MDMF files
3282Kevan Carstensen <kevan@isnotajoke.com>**20100628225048
3283 Ignore-this: fb697b36285d60552df6ca5ac6a37629
3284 
3285 This patch adapts the mutable file checker and verifier to check and
3286 verify MDMF files. It does this by using the new segmented downloader,
3287 which is trained to perform verification operations on request. This
3288 removes some code duplication.
3289] {
3290hunk ./src/allmydata/mutable/checker.py 12
3291 from allmydata.mutable.common import MODE_CHECK, CorruptShareError
3292 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
3293 from allmydata.mutable.layout import unpack_share, SIGNED_PREFIX_LENGTH
3294+from allmydata.mutable.retrieve import Retrieve # for verifying
3295 
3296 class MutableChecker:
3297 
3298hunk ./src/allmydata/mutable/checker.py 29
3299 
3300     def check(self, verify=False, add_lease=False):
3301         servermap = ServerMap()
3302+        # Updating the servermap in MODE_CHECK will stand a good chance
3303+        # of finding all of the shares, and getting a good idea of
3304+        # recoverability, etc, without verifying.
3305         u = ServermapUpdater(self._node, self._storage_broker, self._monitor,
3306                              servermap, MODE_CHECK, add_lease=add_lease)
3307         if self._history:
3308hunk ./src/allmydata/mutable/checker.py 55
3309         if num_recoverable:
3310             self.best_version = servermap.best_recoverable_version()
3311 
3312+        # The file is unhealthy and needs to be repaired if:
3313+        # - There are unrecoverable versions.
3314         if servermap.unrecoverable_versions():
3315             self.need_repair = True
3316hunk ./src/allmydata/mutable/checker.py 59
3317+        # - There isn't a recoverable version.
3318         if num_recoverable != 1:
3319             self.need_repair = True
3320hunk ./src/allmydata/mutable/checker.py 62
3321+        # - The best recoverable version is missing some shares.
3322         if self.best_version:
3323             available_shares = servermap.shares_available()
3324             (num_distinct_shares, k, N) = available_shares[self.best_version]
3325hunk ./src/allmydata/mutable/checker.py 73
3326 
3327     def _verify_all_shares(self, servermap):
3328         # read every byte of each share
3329+        #
3330+        # This logic is going to be very nearly the same as the
3331+        # downloader. I bet we could pass the downloader a flag that
3332+        # makes it do this, and piggyback onto that instead of
3333+        # duplicating a bunch of code.
3334+        #
3335+        # Like:
3336+        #  r = Retrieve(blah, blah, blah, verify=True)
3337+        #  d = r.download()
3338+        #  (wait, wait, wait, d.callback)
3339+        # 
3340+        #  Then, when it has finished, we can check the servermap (which
3341+        #  we provided to Retrieve) to figure out which shares are bad,
3342+        #  since the Retrieve process will have updated the servermap as
3343+        #  it went along.
3344+        #
3345+        #  By passing the verify=True flag to the constructor, we are
3346+        #  telling the downloader a few things.
3347+        #
3348+        #  1. It needs to download all N shares, not just K shares.
3349+        #  2. It doesn't need to decrypt or decode the shares, only
3350+        #     verify them.
3351         if not self.best_version:
3352             return
3353hunk ./src/allmydata/mutable/checker.py 97
3354-        versionmap = servermap.make_versionmap()
3355-        shares = versionmap[self.best_version]
3356-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3357-         offsets_tuple) = self.best_version
3358-        offsets = dict(offsets_tuple)
3359-        readv = [ (0, offsets["EOF"]) ]
3360-        dl = []
3361-        for (shnum, peerid, timestamp) in shares:
3362-            ss = servermap.connections[peerid]
3363-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
3364-            d.addCallback(self._got_answer, peerid, servermap)
3365-            dl.append(d)
3366-        return defer.DeferredList(dl, fireOnOneErrback=True, consumeErrors=True)
3367 
3368hunk ./src/allmydata/mutable/checker.py 98
3369-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
3370-        # isolate the callRemote to a separate method, so tests can subclass
3371-        # Publish and override it
3372-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
3373+        r = Retrieve(self._node, servermap, self.best_version, verify=True)
3374+        d = r.download()
3375+        d.addCallback(self._process_bad_shares)
3376         return d
3377 
3378hunk ./src/allmydata/mutable/checker.py 103
3379-    def _got_answer(self, datavs, peerid, servermap):
3380-        for shnum,datav in datavs.items():
3381-            data = datav[0]
3382-            try:
3383-                self._got_results_one_share(shnum, peerid, data)
3384-            except CorruptShareError:
3385-                f = failure.Failure()
3386-                self.need_repair = True
3387-                self.bad_shares.append( (peerid, shnum, f) )
3388-                prefix = data[:SIGNED_PREFIX_LENGTH]
3389-                servermap.mark_bad_share(peerid, shnum, prefix)
3390-                ss = servermap.connections[peerid]
3391-                self.notify_server_corruption(ss, shnum, str(f.value))
3392-
3393-    def check_prefix(self, peerid, shnum, data):
3394-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3395-         offsets_tuple) = self.best_version
3396-        got_prefix = data[:SIGNED_PREFIX_LENGTH]
3397-        if got_prefix != prefix:
3398-            raise CorruptShareError(peerid, shnum,
3399-                                    "prefix mismatch: share changed while we were reading it")
3400-
3401-    def _got_results_one_share(self, shnum, peerid, data):
3402-        self.check_prefix(peerid, shnum, data)
3403-
3404-        # the [seqnum:signature] pieces are validated by _compare_prefix,
3405-        # which checks their signature against the pubkey known to be
3406-        # associated with this file.
3407 
3408hunk ./src/allmydata/mutable/checker.py 104
3409-        (seqnum, root_hash, IV, k, N, segsize, datalen, pubkey, signature,
3410-         share_hash_chain, block_hash_tree, share_data,
3411-         enc_privkey) = unpack_share(data)
3412-
3413-        # validate [share_hash_chain,block_hash_tree,share_data]
3414-
3415-        leaves = [hashutil.block_hash(share_data)]
3416-        t = hashtree.HashTree(leaves)
3417-        if list(t) != block_hash_tree:
3418-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
3419-        share_hash_leaf = t[0]
3420-        t2 = hashtree.IncompleteHashTree(N)
3421-        # root_hash was checked by the signature
3422-        t2.set_hashes({0: root_hash})
3423-        try:
3424-            t2.set_hashes(hashes=share_hash_chain,
3425-                          leaves={shnum: share_hash_leaf})
3426-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
3427-                IndexError), e:
3428-            msg = "corrupt hashes: %s" % (e,)
3429-            raise CorruptShareError(peerid, shnum, msg)
3430-
3431-        # validate enc_privkey: only possible if we have a write-cap
3432-        if not self._node.is_readonly():
3433-            alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3434-            alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3435-            if alleged_writekey != self._node.get_writekey():
3436-                raise CorruptShareError(peerid, shnum, "invalid privkey")
3437+    def _process_bad_shares(self, bad_shares):
3438+        if bad_shares:
3439+            self.need_repair = True
3440+        self.bad_shares = bad_shares
3441 
3442hunk ./src/allmydata/mutable/checker.py 109
3443-    def notify_server_corruption(self, ss, shnum, reason):
3444-        ss.callRemoteOnly("advise_corrupt_share",
3445-                          "mutable", self._storage_index, shnum, reason)
3446 
3447     def _count_shares(self, smap, version):
3448         available_shares = smap.shares_available()
3449hunk ./src/allmydata/test/test_mutable.py 193
3450                 if offset1 == "pubkey" and IV:
3451                     real_offset = 107
3452                 elif offset1 == "share_data" and not IV:
3453-                    real_offset = 104
3454+                    real_offset = 107
3455                 elif offset1 in o:
3456                     real_offset = o[offset1]
3457                 else:
3458hunk ./src/allmydata/test/test_mutable.py 395
3459             return d
3460         d.addCallback(_created)
3461         return d
3462+    test_create_mdmf_with_initial_contents.timeout = 20
3463 
3464 
3465     def test_create_with_initial_contents_function(self):
3466hunk ./src/allmydata/test/test_mutable.py 700
3467                                            k, N, segsize, datalen)
3468                 self.failUnless(p._pubkey.verify(sig_material, signature))
3469                 #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
3470-                self.failUnless(isinstance(share_hash_chain, dict))
3471-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3472+                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3473                 for shnum,share_hash in share_hash_chain.items():
3474                     self.failUnless(isinstance(shnum, int))
3475                     self.failUnless(isinstance(share_hash, str))
3476hunk ./src/allmydata/test/test_mutable.py 820
3477                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
3478 
3479 
3480+
3481+
3482 class Servermap(unittest.TestCase, PublishMixin):
3483     def setUp(self):
3484         return self.publish_one()
3485hunk ./src/allmydata/test/test_mutable.py 951
3486         self._storage._peers = {} # delete all shares
3487         ms = self.make_servermap
3488         d = defer.succeed(None)
3489-
3490+#
3491         d.addCallback(lambda res: ms(mode=MODE_CHECK))
3492         d.addCallback(lambda sm: self.failUnlessNoneRecoverable(sm))
3493 
3494hunk ./src/allmydata/test/test_mutable.py 1440
3495         d.addCallback(self.check_good, "test_check_good")
3496         return d
3497 
3498+    def test_check_mdmf_good(self):
3499+        d = self.publish_mdmf()
3500+        d.addCallback(lambda ignored:
3501+            self._fn.check(Monitor()))
3502+        d.addCallback(self.check_good, "test_check_mdmf_good")
3503+        return d
3504+
3505     def test_check_no_shares(self):
3506         for shares in self._storage._peers.values():
3507             shares.clear()
3508hunk ./src/allmydata/test/test_mutable.py 1454
3509         d.addCallback(self.check_bad, "test_check_no_shares")
3510         return d
3511 
3512+    def test_check_mdmf_no_shares(self):
3513+        d = self.publish_mdmf()
3514+        def _then(ignored):
3515+            for share in self._storage._peers.values():
3516+                share.clear()
3517+        d.addCallback(_then)
3518+        d.addCallback(lambda ignored:
3519+            self._fn.check(Monitor()))
3520+        d.addCallback(self.check_bad, "test_check_mdmf_no_shares")
3521+        return d
3522+
3523     def test_check_not_enough_shares(self):
3524         for shares in self._storage._peers.values():
3525             for shnum in shares.keys():
3526hunk ./src/allmydata/test/test_mutable.py 1474
3527         d.addCallback(self.check_bad, "test_check_not_enough_shares")
3528         return d
3529 
3530+    def test_check_mdmf_not_enough_shares(self):
3531+        d = self.publish_mdmf()
3532+        def _then(ignored):
3533+            for shares in self._storage._peers.values():
3534+                for shnum in shares.keys():
3535+                    if shnum > 0:
3536+                        del shares[shnum]
3537+        d.addCallback(_then)
3538+        d.addCallback(lambda ignored:
3539+            self._fn.check(Monitor()))
3540+        d.addCallback(self.check_bad, "test_check_mdmf_not_enougH_shares")
3541+        return d
3542+
3543+
3544     def test_check_all_bad_sig(self):
3545         d = corrupt(None, self._storage, 1) # bad sig
3546         d.addCallback(lambda ignored:
3547hunk ./src/allmydata/test/test_mutable.py 1495
3548         d.addCallback(self.check_bad, "test_check_all_bad_sig")
3549         return d
3550 
3551+    def test_check_mdmf_all_bad_sig(self):
3552+        d = self.publish_mdmf()
3553+        d.addCallback(lambda ignored:
3554+            corrupt(None, self._storage, 1))
3555+        d.addCallback(lambda ignored:
3556+            self._fn.check(Monitor()))
3557+        d.addCallback(self.check_bad, "test_check_mdmf_all_bad_sig")
3558+        return d
3559+
3560     def test_check_all_bad_blocks(self):
3561         d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
3562         # the Checker won't notice this.. it doesn't look at actual data
3563hunk ./src/allmydata/test/test_mutable.py 1512
3564         d.addCallback(self.check_good, "test_check_all_bad_blocks")
3565         return d
3566 
3567+
3568+    def test_check_mdmf_all_bad_blocks(self):
3569+        d = self.publish_mdmf()
3570+        d.addCallback(lambda ignored:
3571+            corrupt(None, self._storage, "share_data"))
3572+        d.addCallback(lambda ignored:
3573+            self._fn.check(Monitor()))
3574+        d.addCallback(self.check_good, "test_check_mdmf_all_bad_blocks")
3575+        return d
3576+
3577     def test_verify_good(self):
3578         d = self._fn.check(Monitor(), verify=True)
3579         d.addCallback(self.check_good, "test_verify_good")
3580hunk ./src/allmydata/test/test_mutable.py 1582
3581                       "test_verify_one_bad_encprivkey_uncheckable")
3582         return d
3583 
3584+
3585+    def test_verify_mdmf_good(self):
3586+        d = self.publish_mdmf()
3587+        d.addCallback(lambda ignored:
3588+            self._fn.check(Monitor(), verify=True))
3589+        d.addCallback(self.check_good, "test_verify_mdmf_good")
3590+        return d
3591+
3592+
3593+    def test_verify_mdmf_one_bad_block(self):
3594+        d = self.publish_mdmf()
3595+        d.addCallback(lambda ignored:
3596+            corrupt(None, self._storage, "share_data", [1]))
3597+        d.addCallback(lambda ignored:
3598+            self._fn.check(Monitor(), verify=True))
3599+        # We should find one bad block here
3600+        d.addCallback(self.check_bad, "test_verify_mdmf_one_bad_block")
3601+        d.addCallback(self.check_expected_failure,
3602+                      CorruptShareError, "block hash tree failure",
3603+                      "test_verify_mdmf_one_bad_block")
3604+        return d
3605+
3606+
3607+    def test_verify_mdmf_bad_encprivkey(self):
3608+        d = self.publish_mdmf()
3609+        d.addCallback(lambda ignored:
3610+            corrupt(None, self._storage, "enc_privkey", [1]))
3611+        d.addCallback(lambda ignored:
3612+            self._fn.check(Monitor(), verify=True))
3613+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
3614+        d.addCallback(self.check_expected_failure,
3615+                      CorruptShareError, "privkey",
3616+                      "test_verify_mdmf_bad_encprivkey")
3617+        return d
3618+
3619+
3620+    def test_verify_mdmf_bad_sig(self):
3621+        d = self.publish_mdmf()
3622+        d.addCallback(lambda ignored:
3623+            corrupt(None, self._storage, 1, [1]))
3624+        d.addCallback(lambda ignored:
3625+            self._fn.check(Monitor(), verify=True))
3626+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_sig")
3627+        return d
3628+
3629+
3630+    def test_verify_mdmf_bad_encprivkey_uncheckable(self):
3631+        d = self.publish_mdmf()
3632+        d.addCallback(lambda ignored:
3633+            corrupt(None, self._storage, "enc_privkey", [1]))
3634+        d.addCallback(lambda ignored:
3635+            self._fn.get_readonly())
3636+        d.addCallback(lambda fn:
3637+            fn.check(Monitor(), verify=True))
3638+        d.addCallback(self.check_good,
3639+                      "test_verify_mdmf_bad_encprivkey_uncheckable")
3640+        return d
3641+
3642+
3643 class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
3644 
3645     def get_shares(self, s):
3646hunk ./src/allmydata/test/test_mutable.py 1706
3647         current_shares = self.old_shares[-1]
3648         self.failUnlessEqual(old_shares, current_shares)
3649 
3650+
3651     def test_unrepairable_0shares(self):
3652         d = self.publish_one()
3653         def _delete_all_shares(ign):
3654hunk ./src/allmydata/test/test_mutable.py 1721
3655         d.addCallback(_check)
3656         return d
3657 
3658+    def test_mdmf_unrepairable_0shares(self):
3659+        d = self.publish_mdmf()
3660+        def _delete_all_shares(ign):
3661+            shares = self._storage._peers
3662+            for peerid in shares:
3663+                shares[peerid] = {}
3664+        d.addCallback(_delete_all_shares)
3665+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3666+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3667+        d.addCallback(lambda crr: self.failIf(crr.get_successful()))
3668+        return d
3669+
3670+
3671     def test_unrepairable_1share(self):
3672         d = self.publish_one()
3673         def _delete_all_shares(ign):
3674hunk ./src/allmydata/test/test_mutable.py 1750
3675         d.addCallback(_check)
3676         return d
3677 
3678+    def test_mdmf_unrepairable_1share(self):
3679+        d = self.publish_mdmf()
3680+        def _delete_all_shares(ign):
3681+            shares = self._storage._peers
3682+            for peerid in shares:
3683+                for shnum in list(shares[peerid]):
3684+                    if shnum > 0:
3685+                        del shares[peerid][shnum]
3686+        d.addCallback(_delete_all_shares)
3687+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3688+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3689+        def _check(crr):
3690+            self.failUnlessEqual(crr.get_successful(), False)
3691+        d.addCallback(_check)
3692+        return d
3693+
3694+    def test_repairable_5shares(self):
3695+        d = self.publish_mdmf()
3696+        def _delete_all_shares(ign):
3697+            shares = self._storage._peers
3698+            for peerid in shares:
3699+                for shnum in list(shares[peerid]):
3700+                    if shnum > 4:
3701+                        del shares[peerid][shnum]
3702+        d.addCallback(_delete_all_shares)
3703+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3704+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3705+        def _check(crr):
3706+            self.failUnlessEqual(crr.get_successful(), True)
3707+        d.addCallback(_check)
3708+        return d
3709+
3710+    def test_mdmf_repairable_5shares(self):
3711+        d = self.publish_mdmf()
3712+        def _delete_all_shares(ign):
3713+            shares = self._storage._peers
3714+            for peerid in shares:
3715+                for shnum in list(shares[peerid]):
3716+                    if shnum > 5:
3717+                        del shares[peerid][shnum]
3718+        d.addCallback(_delete_all_shares)
3719+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3720+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3721+        def _check(crr):
3722+            self.failUnlessEqual(crr.get_successful(), True)
3723+        d.addCallback(_check)
3724+        return d
3725+
3726+
3727     def test_merge(self):
3728         self.old_shares = []
3729         d = self.publish_multiple()
3730}
3731[mutable/retrieve.py: learn how to verify mutable files
3732Kevan Carstensen <kevan@isnotajoke.com>**20100628225201
3733 Ignore-this: 989af7800c47589620918461ec989483
3734] {
3735hunk ./src/allmydata/mutable/retrieve.py 86
3736     # Retrieve object will remain tied to a specific version of the file, and
3737     # will use a single ServerMap instance.
3738 
3739-    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False):
3740+    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
3741+                 verify=False):
3742         self._node = filenode
3743         assert self._node.get_pubkey()
3744         self._storage_index = filenode.get_storage_index()
3745hunk ./src/allmydata/mutable/retrieve.py 106
3746         # during repair, we may be called upon to grab the private key, since
3747         # it wasn't picked up during a verify=False checker run, and we'll
3748         # need it for repair to generate a new version.
3749-        self._need_privkey = fetch_privkey
3750-        if self._node.get_privkey():
3751+        self._need_privkey = fetch_privkey or verify
3752+        if self._node.get_privkey() and not verify:
3753             self._need_privkey = False
3754 
3755         if self._need_privkey:
3756hunk ./src/allmydata/mutable/retrieve.py 117
3757             self._privkey_query_markers = [] # one Marker for each time we've
3758                                              # tried to get the privkey.
3759 
3760+        # verify means that we are using the downloader logic to verify all
3761+        # of our shares. This tells the downloader a few things.
3762+        #
3763+        # 1. We need to download all of the shares.
3764+        # 2. We don't need to decode or decrypt the shares, since our
3765+        #    caller doesn't care about the plaintext, only the
3766+        #    information about which shares are or are not valid.
3767+        # 3. When we are validating readers, we need to validate the
3768+        #    signature on the prefix. Do we? We already do this in the
3769+        #    servermap update?
3770+        #
3771+        # (just work on 1 and 2 for now, I guess)
3772+        self._verify = False
3773+        if verify:
3774+            self._verify = True
3775+
3776         self._status = RetrieveStatus()
3777         self._status.set_storage_index(self._storage_index)
3778         self._status.set_helper(False)
3779hunk ./src/allmydata/mutable/retrieve.py 323
3780 
3781         # We need at least self._required_shares readers to download a
3782         # segment.
3783-        needed = self._required_shares - len(self._active_readers)
3784+        if self._verify:
3785+            needed = self._total_shares
3786+        else:
3787+            needed = self._required_shares - len(self._active_readers)
3788         # XXX: Why don't format= log messages work here?
3789         self.log("adding %d peers to the active peers list" % needed)
3790 
3791hunk ./src/allmydata/mutable/retrieve.py 339
3792         # will cause problems later.
3793         active_shnums -= set([reader.shnum for reader in self._active_readers])
3794         active_shnums = list(active_shnums)[:needed]
3795-        if len(active_shnums) < needed:
3796+        if len(active_shnums) < needed and not self._verify:
3797             # We don't have enough readers to retrieve the file; fail.
3798             return self._failed()
3799 
3800hunk ./src/allmydata/mutable/retrieve.py 346
3801         for shnum in active_shnums:
3802             self._active_readers.append(self.readers[shnum])
3803             self.log("added reader for share %d" % shnum)
3804-        assert len(self._active_readers) == self._required_shares
3805+        assert len(self._active_readers) >= self._required_shares
3806         # Conceptually, this is part of the _add_active_peers step. It
3807         # validates the prefixes of newly added readers to make sure
3808         # that they match what we are expecting for self.verinfo. If
3809hunk ./src/allmydata/mutable/retrieve.py 416
3810                     # that we haven't gotten it at the end of
3811                     # segment decoding, then we'll take more drastic
3812                     # measures.
3813-                    if self._need_privkey:
3814+                    if self._need_privkey and not self._node.is_readonly():
3815                         d = reader.get_encprivkey()
3816                         d.addCallback(self._try_to_validate_privkey, reader)
3817             if bad_readers:
3818hunk ./src/allmydata/mutable/retrieve.py 423
3819                 # We do them all at once, or else we screw up list indexing.
3820                 for (reader, f) in bad_readers:
3821                     self._mark_bad_share(reader, f)
3822-                return self._add_active_peers()
3823+                if self._verify:
3824+                    if len(self._active_readers) >= self._required_shares:
3825+                        return self._download_current_segment()
3826+                    else:
3827+                        return self._failed()
3828+                else:
3829+                    return self._add_active_peers()
3830             else:
3831                 return self._download_current_segment()
3832             # The next step will assert that it has enough active
3833hunk ./src/allmydata/mutable/retrieve.py 518
3834         """
3835         self.log("marking share %d on server %s as bad" % \
3836                  (reader.shnum, reader))
3837+        prefix = self.verinfo[-2]
3838+        self.servermap.mark_bad_share(reader.peerid,
3839+                                      reader.shnum,
3840+                                      prefix)
3841         self._remove_reader(reader)
3842hunk ./src/allmydata/mutable/retrieve.py 523
3843-        self._bad_shares.add((reader.peerid, reader.shnum))
3844+        self._bad_shares.add((reader.peerid, reader.shnum, f))
3845         self._status.problems[reader.peerid] = f
3846         self._last_failure = f
3847         self.notify_server_corruption(reader.peerid, reader.shnum,
3848hunk ./src/allmydata/mutable/retrieve.py 571
3849             ds.append(dl)
3850             reader.flush()
3851         dl = defer.DeferredList(ds)
3852-        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3853+        if self._verify:
3854+            dl.addCallback(lambda ignored: "")
3855+            dl.addCallback(self._set_segment)
3856+        else:
3857+            dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3858         return dl
3859 
3860 
3861hunk ./src/allmydata/mutable/retrieve.py 701
3862         # shnum, which will be a leaf in the share hash tree, which
3863         # will allow us to validate the rest of the tree.
3864         if self.share_hash_tree.needed_hashes(reader.shnum,
3865-                                               include_leaf=True):
3866+                                              include_leaf=True) or \
3867+                                              self._verify:
3868             try:
3869                 self.share_hash_tree.set_hashes(hashes=sharehashes[1],
3870                                             leaves={reader.shnum: bht[0]})
3871hunk ./src/allmydata/mutable/retrieve.py 832
3872 
3873 
3874     def _try_to_validate_privkey(self, enc_privkey, reader):
3875-
3876         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3877         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3878         if alleged_writekey != self._node.get_writekey():
3879hunk ./src/allmydata/mutable/retrieve.py 838
3880             self.log("invalid privkey from %s shnum %d" %
3881                      (reader, reader.shnum),
3882                      level=log.WEIRD, umid="YIw4tA")
3883+            if self._verify:
3884+                self.servermap.mark_bad_share(reader.peerid, reader.shnum,
3885+                                              self.verinfo[-2])
3886+                e = CorruptShareError(reader.peerid,
3887+                                      reader.shnum,
3888+                                      "invalid privkey")
3889+                f = failure.Failure(e)
3890+                self._bad_shares.add((reader.peerid, reader.shnum, f))
3891             return
3892 
3893         # it's good
3894hunk ./src/allmydata/mutable/retrieve.py 904
3895         statements, I return the decrypted contents to the owner of this
3896         Retrieve object through self._done_deferred.
3897         """
3898-        eventually(self._done_deferred.callback, self._plaintext)
3899+        if self._verify:
3900+            ret = list(self._bad_shares)
3901+            self.log("done verifying, found %d bad shares" % len(ret))
3902+        else:
3903+            ret = self._plaintext
3904+        eventually(self._done_deferred.callback, ret)
3905 
3906 
3907     def _failed(self):
3908hunk ./src/allmydata/mutable/retrieve.py 920
3909         to the caller of this Retrieve object through
3910         self._done_deferred.
3911         """
3912-        format = ("ran out of peers: "
3913-                  "have %(have)d of %(total)d segments "
3914-                  "found %(bad)d bad shares "
3915-                  "encoding %(k)d-of-%(n)d")
3916-        args = {"have": self._current_segment,
3917-                "total": self._num_segments,
3918-                "k": self._required_shares,
3919-                "n": self._total_shares,
3920-                "bad": len(self._bad_shares)}
3921-        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
3922-                                                        str(self._last_failure)))
3923-        f = failure.Failure(e)
3924-        eventually(self._done_deferred.callback, f)
3925+        if self._verify:
3926+            ret = list(self._bad_shares)
3927+        else:
3928+            format = ("ran out of peers: "
3929+                      "have %(have)d of %(total)d segments "
3930+                      "found %(bad)d bad shares "
3931+                      "encoding %(k)d-of-%(n)d")
3932+            args = {"have": self._current_segment,
3933+                    "total": self._num_segments,
3934+                    "k": self._required_shares,
3935+                    "n": self._total_shares,
3936+                    "bad": len(self._bad_shares)}
3937+            e = NotEnoughSharesError("%s, last failure: %s" % \
3938+                                     (format % args, str(self._last_failure)))
3939+            f = failure.Failure(e)
3940+            ret = f
3941+        eventually(self._done_deferred.callback, ret)
3942}
3943[interfaces.py: add IMutableSlotWriter
3944Kevan Carstensen <kevan@isnotajoke.com>**20100630183305
3945 Ignore-this: ff9dca96ef1a009ae85485682f81ea5
3946] hunk ./src/allmydata/interfaces.py 418
3947         """
3948 
3949 
3950+class IMutableSlotWriter(Interface):
3951+    """
3952+    The interface for a writer around a mutable slot on a remote server.
3953+    """
3954+    def set_checkstring(checkstring, *args):
3955+        """
3956+        Set the checkstring that I will pass to the remote server when
3957+        writing.
3958+
3959+            @param checkstring A packed checkstring to use.
3960+
3961+        Note that implementations can differ in which semantics they
3962+        wish to support for set_checkstring -- they can, for example,
3963+        build the checkstring themselves from its constituents, or
3964+        some other thing.
3965+        """
3966+
3967+    def get_checkstring():
3968+        """
3969+        Get the checkstring that I think currently exists on the remote
3970+        server.
3971+        """
3972+
3973+    def put_block(data, segnum, salt):
3974+        """
3975+        Add a block and salt to the share.
3976+        """
3977+
3978+    def put_encprivey(encprivkey):
3979+        """
3980+        Add the encrypted private key to the share.
3981+        """
3982+
3983+    def put_blockhashes(blockhashes=list):
3984+        """
3985+        Add the block hash tree to the share.
3986+        """
3987+
3988+    def put_sharehashes(sharehashes=dict):
3989+        """
3990+        Add the share hash chain to the share.
3991+        """
3992+
3993+    def get_signable():
3994+        """
3995+        Return the part of the share that needs to be signed.
3996+        """
3997+
3998+    def put_signature(signature):
3999+        """
4000+        Add the signature to the share.
4001+        """
4002+
4003+    def put_verification_key(verification_key):
4004+        """
4005+        Add the verification key to the share.
4006+        """
4007+
4008+    def finish_publishing():
4009+        """
4010+        Do anything necessary to finish writing the share to a remote
4011+        server. I require that no further publishing needs to take place
4012+        after this method has been called.
4013+        """
4014+
4015+
4016 class IURI(Interface):
4017     def init_from_string(uri):
4018         """Accept a string (as created by my to_string() method) and populate
4019[test/test_mutable.py: temporarily disable two tests that are now irrelevant
4020Kevan Carstensen <kevan@isnotajoke.com>**20100701232806
4021 Ignore-this: 701e143567f3954812ca6960af1d6ac7
4022] {
4023hunk ./src/allmydata/test/test_mutable.py 651
4024             self.failUnlessEqual(len(share_ids), 10)
4025         d.addCallback(_done)
4026         return d
4027+    test_encrypt.todo = "Write an equivalent of this for the new uploader"
4028 
4029     def test_generate(self):
4030         nm = make_nodemaker()
4031hunk ./src/allmydata/test/test_mutable.py 713
4032                 self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
4033         d.addCallback(_generated)
4034         return d
4035+    test_generate.todo = "Write an equivalent of this for the new uploader"
4036 
4037     # TODO: when we publish to 20 peers, we should get one share per peer on 10
4038     # when we publish to 3 peers, we should get either 3 or 4 shares per peer
4039}
4040[Add MDMF reader and writer, and SDMF writer
4041Kevan Carstensen <kevan@isnotajoke.com>**20100702225531
4042 Ignore-this: bf6276a91d27dcb4e779b0eb82ea1843
4043 
4044 The MDMF/SDMF reader MDMF writer, and SDMF writer are similar to the
4045 object proxies that exist for immutable files. They abstract away
4046 details of connection, state, and caching from their callers (in this
4047 case, the download, servermap updater, and uploader), and expose methods
4048 to get and set information on the remote server.
4049 
4050 MDMFSlotReadProxy reads a mutable file from the server, doing the right
4051 thing (in most cases) regardless of whether the file is MDMF or SDMF. It
4052 allows callers to tell it how to batch and flush reads.
4053 
4054 MDMFSlotWriteProxy writes an MDMF mutable file to a server.
4055 
4056 SDMFSlotWriteProxy writes an SDMF mutable file to a server.
4057 
4058 This patch also includes tests for MDMFSlotReadProxy,
4059 SDMFSlotWriteProxy, and MDMFSlotWriteProxy.
4060] {
4061hunk ./src/allmydata/mutable/layout.py 4
4062 
4063 import struct
4064 from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError
4065+from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \
4066+                                 MDMF_VERSION, IMutableSlotWriter
4067+from allmydata.util import mathutil, observer
4068+from twisted.python import failure
4069+from twisted.internet import defer
4070+from zope.interface import implements
4071+
4072+
4073+# These strings describe the format of the packed structs they help process
4074+# Here's what they mean:
4075+#
4076+#  PREFIX:
4077+#    >: Big-endian byte order; the most significant byte is first (leftmost).
4078+#    B: The version information; an 8 bit version identifier. Stored as
4079+#       an unsigned char. This is currently 00 00 00 00; our modifications
4080+#       will turn it into 00 00 00 01.
4081+#    Q: The sequence number; this is sort of like a revision history for
4082+#       mutable files; they start at 1 and increase as they are changed after
4083+#       being uploaded. Stored as an unsigned long long, which is 8 bytes in
4084+#       length.
4085+#  32s: The root hash of the share hash tree. We use sha-256d, so we use 32
4086+#       characters = 32 bytes to store the value.
4087+#  16s: The salt for the readkey. This is a 16-byte random value, stored as
4088+#       16 characters.
4089+#
4090+#  SIGNED_PREFIX additions, things that are covered by the signature:
4091+#    B: The "k" encoding parameter. We store this as an 8-bit character,
4092+#       which is convenient because our erasure coding scheme cannot
4093+#       encode if you ask for more than 255 pieces.
4094+#    B: The "N" encoding parameter. Stored as an 8-bit character for the
4095+#       same reasons as above.
4096+#    Q: The segment size of the uploaded file. This will essentially be the
4097+#       length of the file in SDMF. An unsigned long long, so we can store
4098+#       files of quite large size.
4099+#    Q: The data length of the uploaded file. Modulo padding, this will be
4100+#       the same of the data length field. Like the data length field, it is
4101+#       an unsigned long long and can be quite large.
4102+#
4103+#   HEADER additions:
4104+#     L: The offset of the signature of this. An unsigned long.
4105+#     L: The offset of the share hash chain. An unsigned long.
4106+#     L: The offset of the block hash tree. An unsigned long.
4107+#     L: The offset of the share data. An unsigned long.
4108+#     Q: The offset of the encrypted private key. An unsigned long long, to
4109+#        account for the possibility of a lot of share data.
4110+#     Q: The offset of the EOF. An unsigned long long, to account for the
4111+#        possibility of a lot of share data.
4112+#
4113+#  After all of these, we have the following:
4114+#    - The verification key: Occupies the space between the end of the header
4115+#      and the start of the signature (i.e.: data[HEADER_LENGTH:o['signature']].
4116+#    - The signature, which goes from the signature offset to the share hash
4117+#      chain offset.
4118+#    - The share hash chain, which goes from the share hash chain offset to
4119+#      the block hash tree offset.
4120+#    - The share data, which goes from the share data offset to the encrypted
4121+#      private key offset.
4122+#    - The encrypted private key offset, which goes until the end of the file.
4123+#
4124+#  The block hash tree in this encoding has only one share, so the offset of
4125+#  the share data will be 32 bits more than the offset of the block hash tree.
4126+#  Given this, we may need to check to see how many bytes a reasonably sized
4127+#  block hash tree will take up.
4128 
4129 PREFIX = ">BQ32s16s" # each version has a different prefix
4130 SIGNED_PREFIX = ">BQ32s16s BBQQ" # this is covered by the signature
4131hunk ./src/allmydata/mutable/layout.py 73
4132 SIGNED_PREFIX_LENGTH = struct.calcsize(SIGNED_PREFIX)
4133 HEADER = ">BQ32s16s BBQQ LLLLQQ" # includes offsets
4134 HEADER_LENGTH = struct.calcsize(HEADER)
4135+OFFSETS = ">LLLLQQ"
4136+OFFSETS_LENGTH = struct.calcsize(OFFSETS)
4137 
4138 def unpack_header(data):
4139     o = {}
4140hunk ./src/allmydata/mutable/layout.py 194
4141     return (share_hash_chain, block_hash_tree, share_data)
4142 
4143 
4144-def pack_checkstring(seqnum, root_hash, IV):
4145+def pack_checkstring(seqnum, root_hash, IV, version=0):
4146     return struct.pack(PREFIX,
4147hunk ./src/allmydata/mutable/layout.py 196
4148-                       0, # version,
4149+                       version,
4150                        seqnum,
4151                        root_hash,
4152                        IV)
4153hunk ./src/allmydata/mutable/layout.py 269
4154                            encprivkey])
4155     return final_share
4156 
4157+def pack_prefix(seqnum, root_hash, IV,
4158+                required_shares, total_shares,
4159+                segment_size, data_length):
4160+    prefix = struct.pack(SIGNED_PREFIX,
4161+                         0, # version,
4162+                         seqnum,
4163+                         root_hash,
4164+                         IV,
4165+                         required_shares,
4166+                         total_shares,
4167+                         segment_size,
4168+                         data_length,
4169+                         )
4170+    return prefix
4171+
4172+
4173+class SDMFSlotWriteProxy:
4174+    implements(IMutableSlotWriter)
4175+    """
4176+    I represent a remote write slot for an SDMF mutable file. I build a
4177+    share in memory, and then write it in one piece to the remote
4178+    server. This mimics how SDMF shares were built before MDMF (and the
4179+    new MDMF uploader), but provides that functionality in a way that
4180+    allows the MDMF uploader to be built without much special-casing for
4181+    file format, which makes the uploader code more readable.
4182+    """
4183+    def __init__(self,
4184+                 shnum,
4185+                 rref, # a remote reference to a storage server
4186+                 storage_index,
4187+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4188+                 seqnum, # the sequence number of the mutable file
4189+                 required_shares,
4190+                 total_shares,
4191+                 segment_size,
4192+                 data_length): # the length of the original file
4193+        self.shnum = shnum
4194+        self._rref = rref
4195+        self._storage_index = storage_index
4196+        self._secrets = secrets
4197+        self._seqnum = seqnum
4198+        self._required_shares = required_shares
4199+        self._total_shares = total_shares
4200+        self._segment_size = segment_size
4201+        self._data_length = data_length
4202+
4203+        # This is an SDMF file, so it should have only one segment, so,
4204+        # modulo padding of the data length, the segment size and the
4205+        # data length should be the same.
4206+        expected_segment_size = mathutil.next_multiple(data_length,
4207+                                                       self._required_shares)
4208+        assert expected_segment_size == segment_size
4209+
4210+        self._block_size = self._segment_size / self._required_shares
4211+
4212+        # This is meant to mimic how SDMF files were built before MDMF
4213+        # entered the picture: we generate each share in its entirety,
4214+        # then push it off to the storage server in one write. When
4215+        # callers call set_*, they are just populating this dict.
4216+        # finish_publishing will stitch these pieces together into a
4217+        # coherent share, and then write the coherent share to the
4218+        # storage server.
4219+        self._share_pieces = {}
4220+
4221+        # This tells the write logic what checkstring to use when
4222+        # writing remote shares.
4223+        self._testvs = []
4224+
4225+        self._readvs = [(0, struct.calcsize(PREFIX))]
4226+
4227+
4228+    def set_checkstring(self, checkstring_or_seqnum,
4229+                              root_hash=None,
4230+                              salt=None):
4231+        """
4232+        Set the checkstring that I will pass to the remote server when
4233+        writing.
4234+
4235+            @param checkstring_or_seqnum: A packed checkstring to use,
4236+                   or a sequence number. I will treat this as a checkstr
4237+
4238+        Note that implementations can differ in which semantics they
4239+        wish to support for set_checkstring -- they can, for example,
4240+        build the checkstring themselves from its constituents, or
4241+        some other thing.
4242+        """
4243+        if root_hash and salt:
4244+            checkstring = struct.pack(PREFIX,
4245+                                      0,
4246+                                      checkstring_or_seqnum,
4247+                                      root_hash,
4248+                                      salt)
4249+        else:
4250+            checkstring = checkstring_or_seqnum
4251+        self._testvs = [(0, len(checkstring), "eq", checkstring)]
4252+
4253+
4254+    def get_checkstring(self):
4255+        """
4256+        Get the checkstring that I think currently exists on the remote
4257+        server.
4258+        """
4259+        if self._testvs:
4260+            return self._testvs[0][3]
4261+        return ""
4262+
4263+
4264+    def put_block(self, data, segnum, salt):
4265+        """
4266+        Add a block and salt to the share.
4267+        """
4268+        # SDMF files have only one segment
4269+        assert segnum == 0
4270+        assert len(data) == self._block_size
4271+        assert len(salt) == SALT_SIZE
4272+
4273+        self._share_pieces['sharedata'] = data
4274+        self._share_pieces['salt'] = salt
4275+
4276+        # TODO: Figure out something intelligent to return.
4277+        return defer.succeed(None)
4278+
4279+
4280+    def put_encprivkey(self, encprivkey):
4281+        """
4282+        Add the encrypted private key to the share.
4283+        """
4284+        self._share_pieces['encprivkey'] = encprivkey
4285+
4286+        return defer.succeed(None)
4287+
4288+
4289+    def put_blockhashes(self, blockhashes):
4290+        """
4291+        Add the block hash tree to the share.
4292+        """
4293+        assert isinstance(blockhashes, list)
4294+        for h in blockhashes:
4295+            assert len(h) == HASH_SIZE
4296+
4297+        # serialize the blockhashes, then set them.
4298+        blockhashes_s = "".join(blockhashes)
4299+        self._share_pieces['block_hash_tree'] = blockhashes_s
4300+
4301+        return defer.succeed(None)
4302+
4303+
4304+    def put_sharehashes(self, sharehashes):
4305+        """
4306+        Add the share hash chain to the share.
4307+        """
4308+        assert isinstance(sharehashes, dict)
4309+        for h in sharehashes.itervalues():
4310+            assert len(h) == HASH_SIZE
4311+
4312+        # serialize the sharehashes, then set them.
4313+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4314+                                 for i in sorted(sharehashes.keys())])
4315+        self._share_pieces['share_hash_chain'] = sharehashes_s
4316+
4317+        return defer.succeed(None)
4318+
4319+
4320+    def put_root_hash(self, root_hash):
4321+        """
4322+        Add the root hash to the share.
4323+        """
4324+        assert len(root_hash) == HASH_SIZE
4325+
4326+        self._share_pieces['root_hash'] = root_hash
4327+
4328+        return defer.succeed(None)
4329+
4330+
4331+    def put_salt(self, salt):
4332+        """
4333+        Add a salt to an empty SDMF file.
4334+        """
4335+        assert len(salt) == SALT_SIZE
4336+
4337+        self._share_pieces['salt'] = salt
4338+        self._share_pieces['sharedata'] = ""
4339+
4340+
4341+    def get_signable(self):
4342+        """
4343+        Return the part of the share that needs to be signed.
4344+
4345+        SDMF writers need to sign the packed representation of the
4346+        first eight fields of the remote share, that is:
4347+            - version number (0)
4348+            - sequence number
4349+            - root of the share hash tree
4350+            - salt
4351+            - k
4352+            - n
4353+            - segsize
4354+            - datalen
4355+
4356+        This method is responsible for returning that to callers.
4357+        """
4358+        return struct.pack(SIGNED_PREFIX,
4359+                           0,
4360+                           self._seqnum,
4361+                           self._share_pieces['root_hash'],
4362+                           self._share_pieces['salt'],
4363+                           self._required_shares,
4364+                           self._total_shares,
4365+                           self._segment_size,
4366+                           self._data_length)
4367+
4368+
4369+    def put_signature(self, signature):
4370+        """
4371+        Add the signature to the share.
4372+        """
4373+        self._share_pieces['signature'] = signature
4374+
4375+        return defer.succeed(None)
4376+
4377+
4378+    def put_verification_key(self, verification_key):
4379+        """
4380+        Add the verification key to the share.
4381+        """
4382+        self._share_pieces['verification_key'] = verification_key
4383+
4384+        return defer.succeed(None)
4385+
4386+
4387+    def get_verinfo(self):
4388+        """
4389+        I return my verinfo tuple. This is used by the ServermapUpdater
4390+        to keep track of versions of mutable files.
4391+
4392+        The verinfo tuple for MDMF files contains:
4393+            - seqnum
4394+            - root hash
4395+            - a blank (nothing)
4396+            - segsize
4397+            - datalen
4398+            - k
4399+            - n
4400+            - prefix (the thing that you sign)
4401+            - a tuple of offsets
4402+
4403+        We include the nonce in MDMF to simplify processing of version
4404+        information tuples.
4405+
4406+        The verinfo tuple for SDMF files is the same, but contains a
4407+        16-byte IV instead of a hash of salts.
4408+        """
4409+        return (self._seqnum,
4410+                self._share_pieces['root_hash'],
4411+                self._share_pieces['salt'],
4412+                self._segment_size,
4413+                self._data_length,
4414+                self._required_shares,
4415+                self._total_shares,
4416+                self.get_signable(),
4417+                self._get_offsets_tuple())
4418+
4419+    def _get_offsets_dict(self):
4420+        post_offset = HEADER_LENGTH
4421+        offsets = {}
4422+
4423+        verification_key_length = len(self._share_pieces['verification_key'])
4424+        o1 = offsets['signature'] = post_offset + verification_key_length
4425+
4426+        signature_length = len(self._share_pieces['signature'])
4427+        o2 = offsets['share_hash_chain'] = o1 + signature_length
4428+
4429+        share_hash_chain_length = len(self._share_pieces['share_hash_chain'])
4430+        o3 = offsets['block_hash_tree'] = o2 + share_hash_chain_length
4431+
4432+        block_hash_tree_length = len(self._share_pieces['block_hash_tree'])
4433+        o4 = offsets['share_data'] = o3 + block_hash_tree_length
4434+
4435+        share_data_length = len(self._share_pieces['sharedata'])
4436+        o5 = offsets['enc_privkey'] = o4 + share_data_length
4437+
4438+        encprivkey_length = len(self._share_pieces['encprivkey'])
4439+        offsets['EOF'] = o5 + encprivkey_length
4440+        return offsets
4441+
4442+
4443+    def _get_offsets_tuple(self):
4444+        offsets = self._get_offsets_dict()
4445+        return tuple([(key, value) for key, value in offsets.items()])
4446+
4447+
4448+    def _pack_offsets(self):
4449+        offsets = self._get_offsets_dict()
4450+        return struct.pack(">LLLLQQ",
4451+                           offsets['signature'],
4452+                           offsets['share_hash_chain'],
4453+                           offsets['block_hash_tree'],
4454+                           offsets['share_data'],
4455+                           offsets['enc_privkey'],
4456+                           offsets['EOF'])
4457+
4458+
4459+    def finish_publishing(self):
4460+        """
4461+        Do anything necessary to finish writing the share to a remote
4462+        server. I require that no further publishing needs to take place
4463+        after this method has been called.
4464+        """
4465+        for k in ["sharedata", "encprivkey", "signature", "verification_key",
4466+                  "share_hash_chain", "block_hash_tree"]:
4467+            assert k in self._share_pieces
4468+        # This is the only method that actually writes something to the
4469+        # remote server.
4470+        # First, we need to pack the share into data that we can write
4471+        # to the remote server in one write.
4472+        offsets = self._pack_offsets()
4473+        prefix = self.get_signable()
4474+        final_share = "".join([prefix,
4475+                               offsets,
4476+                               self._share_pieces['verification_key'],
4477+                               self._share_pieces['signature'],
4478+                               self._share_pieces['share_hash_chain'],
4479+                               self._share_pieces['block_hash_tree'],
4480+                               self._share_pieces['sharedata'],
4481+                               self._share_pieces['encprivkey']])
4482+
4483+        # Our only data vector is going to be writing the final share,
4484+        # in its entirely.
4485+        datavs = [(0, final_share)]
4486+
4487+        if not self._testvs:
4488+            # Our caller has not provided us with another checkstring
4489+            # yet, so we assume that we are writing a new share, and set
4490+            # a test vector that will allow a new share to be written.
4491+            self._testvs = []
4492+            self._testvs.append(tuple([0, 1, "eq", ""]))
4493+            new_share = True
4494+
4495+        tw_vectors = {}
4496+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
4497+        return self._rref.callRemote("slot_testv_and_readv_and_writev",
4498+                                     self._storage_index,
4499+                                     self._secrets,
4500+                                     tw_vectors,
4501+                                     # TODO is it useful to read something?
4502+                                     self._readvs)
4503+
4504+
4505+MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
4506+MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
4507+MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
4508+MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
4509+MDMFCHECKSTRING = ">BQ32s"
4510+MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
4511+MDMFOFFSETS = ">QQQQQQ"
4512+MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
4513+
4514+class MDMFSlotWriteProxy:
4515+    implements(IMutableSlotWriter)
4516+
4517+    """
4518+    I represent a remote write slot for an MDMF mutable file.
4519+
4520+    I abstract away from my caller the details of block and salt
4521+    management, and the implementation of the on-disk format for MDMF
4522+    shares.
4523+    """
4524+    # Expected layout, MDMF:
4525+    # offset:     size:       name:
4526+    #-- signed part --
4527+    # 0           1           version number (01)
4528+    # 1           8           sequence number
4529+    # 9           32          share tree root hash
4530+    # 41          1           The "k" encoding parameter
4531+    # 42          1           The "N" encoding parameter
4532+    # 43          8           The segment size of the uploaded file
4533+    # 51          8           The data length of the original plaintext
4534+    #-- end signed part --
4535+    # 59          8           The offset of the encrypted private key
4536+    # 67          8           The offset of the block hash tree
4537+    # 75          8           The offset of the share hash chain
4538+    # 83          8           The offset of the signature
4539+    # 91          8           The offset of the verification key
4540+    # 99          8           The offset of the EOF
4541+    #
4542+    # followed by salts and share data, the encrypted private key, the
4543+    # block hash tree, the salt hash tree, the share hash chain, a
4544+    # signature over the first eight fields, and a verification key.
4545+    #
4546+    # The checkstring is the first three fields -- the version number,
4547+    # sequence number, root hash and root salt hash. This is consistent
4548+    # in meaning to what we have with SDMF files, except now instead of
4549+    # using the literal salt, we use a value derived from all of the
4550+    # salts -- the share hash root.
4551+    #
4552+    # The salt is stored before the block for each segment. The block
4553+    # hash tree is computed over the combination of block and salt for
4554+    # each segment. In this way, we get integrity checking for both
4555+    # block and salt with the current block hash tree arrangement.
4556+    #
4557+    # The ordering of the offsets is different to reflect the dependencies
4558+    # that we'll run into with an MDMF file. The expected write flow is
4559+    # something like this:
4560+    #
4561+    #   0: Initialize with the sequence number, encoding parameters and
4562+    #      data length. From this, we can deduce the number of segments,
4563+    #      and where they should go.. We can also figure out where the
4564+    #      encrypted private key should go, because we can figure out how
4565+    #      big the share data will be.
4566+    #
4567+    #   1: Encrypt, encode, and upload the file in chunks. Do something
4568+    #      like
4569+    #
4570+    #       put_block(data, segnum, salt)
4571+    #
4572+    #      to write a block and a salt to the disk. We can do both of
4573+    #      these operations now because we have enough of the offsets to
4574+    #      know where to put them.
4575+    #
4576+    #   2: Put the encrypted private key. Use:
4577+    #
4578+    #        put_encprivkey(encprivkey)
4579+    #
4580+    #      Now that we know the length of the private key, we can fill
4581+    #      in the offset for the block hash tree.
4582+    #
4583+    #   3: We're now in a position to upload the block hash tree for
4584+    #      a share. Put that using something like:
4585+    #       
4586+    #        put_blockhashes(block_hash_tree)
4587+    #
4588+    #      Note that block_hash_tree is a list of hashes -- we'll take
4589+    #      care of the details of serializing that appropriately. When
4590+    #      we get the block hash tree, we are also in a position to
4591+    #      calculate the offset for the share hash chain, and fill that
4592+    #      into the offsets table.
4593+    #
4594+    #   4: At the same time, we're in a position to upload the salt hash
4595+    #      tree. This is a Merkle tree over all of the salts. We use a
4596+    #      Merkle tree so that we can validate each block,salt pair as
4597+    #      we download them later. We do this using
4598+    #
4599+    #        put_salthashes(salt_hash_tree)
4600+    #
4601+    #      When you do this, I automatically put the root of the tree
4602+    #      (the hash at index 0 of the list) in its appropriate slot in
4603+    #      the signed prefix of the share.
4604+    #
4605+    #   5: We're now in a position to upload the share hash chain for
4606+    #      a share. Do that with something like:
4607+    #     
4608+    #        put_sharehashes(share_hash_chain)
4609+    #
4610+    #      share_hash_chain should be a dictionary mapping shnums to
4611+    #      32-byte hashes -- the wrapper handles serialization.
4612+    #      We'll know where to put the signature at this point, also.
4613+    #      The root of this tree will be put explicitly in the next
4614+    #      step.
4615+    #
4616+    #      TODO: Why? Why not just include it in the tree here?
4617+    #
4618+    #   6: Before putting the signature, we must first put the
4619+    #      root_hash. Do this with:
4620+    #
4621+    #        put_root_hash(root_hash).
4622+    #     
4623+    #      In terms of knowing where to put this value, it was always
4624+    #      possible to place it, but it makes sense semantically to
4625+    #      place it after the share hash tree, so that's why you do it
4626+    #      in this order.
4627+    #
4628+    #   6: With the root hash put, we can now sign the header. Use:
4629+    #
4630+    #        get_signable()
4631+    #
4632+    #      to get the part of the header that you want to sign, and use:
4633+    #       
4634+    #        put_signature(signature)
4635+    #
4636+    #      to write your signature to the remote server.
4637+    #
4638+    #   6: Add the verification key, and finish. Do:
4639+    #
4640+    #        put_verification_key(key)
4641+    #
4642+    #      and
4643+    #
4644+    #        finish_publish()
4645+    #
4646+    # Checkstring management:
4647+    #
4648+    # To write to a mutable slot, we have to provide test vectors to ensure
4649+    # that we are writing to the same data that we think we are. These
4650+    # vectors allow us to detect uncoordinated writes; that is, writes
4651+    # where both we and some other shareholder are writing to the
4652+    # mutable slot, and to report those back to the parts of the program
4653+    # doing the writing.
4654+    #
4655+    # With SDMF, this was easy -- all of the share data was written in
4656+    # one go, so it was easy to detect uncoordinated writes, and we only
4657+    # had to do it once. With MDMF, not all of the file is written at
4658+    # once.
4659+    #
4660+    # If a share is new, we write out as much of the header as we can
4661+    # before writing out anything else. This gives other writers a
4662+    # canary that they can use to detect uncoordinated writes, and, if
4663+    # they do the same thing, gives us the same canary. We them update
4664+    # the share. We won't be able to write out two fields of the header
4665+    # -- the share tree hash and the salt hash -- until we finish
4666+    # writing out the share. We only require the writer to provide the
4667+    # initial checkstring, and keep track of what it should be after
4668+    # updates ourselves.
4669+    #
4670+    # If we haven't written anything yet, then on the first write (which
4671+    # will probably be a block + salt of a share), we'll also write out
4672+    # the header. On subsequent passes, we'll expect to see the header.
4673+    # This changes in two places:
4674+    #
4675+    #   - When we write out the salt hash
4676+    #   - When we write out the root of the share hash tree
4677+    #
4678+    # since these values will change the header. It is possible that we
4679+    # can just make those be written in one operation to minimize
4680+    # disruption.
4681+    def __init__(self,
4682+                 shnum,
4683+                 rref, # a remote reference to a storage server
4684+                 storage_index,
4685+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4686+                 seqnum, # the sequence number of the mutable file
4687+                 required_shares,
4688+                 total_shares,
4689+                 segment_size,
4690+                 data_length): # the length of the original file
4691+        self.shnum = shnum
4692+        self._rref = rref
4693+        self._storage_index = storage_index
4694+        self._seqnum = seqnum
4695+        self._required_shares = required_shares
4696+        assert self.shnum >= 0 and self.shnum < total_shares
4697+        self._total_shares = total_shares
4698+        # We build up the offset table as we write things. It is the
4699+        # last thing we write to the remote server.
4700+        self._offsets = {}
4701+        self._testvs = []
4702+        self._secrets = secrets
4703+        # The segment size needs to be a multiple of the k parameter --
4704+        # any padding should have been carried out by the publisher
4705+        # already.
4706+        assert segment_size % required_shares == 0
4707+        self._segment_size = segment_size
4708+        self._data_length = data_length
4709+
4710+        # These are set later -- we define them here so that we can
4711+        # check for their existence easily
4712+
4713+        # This is the root of the share hash tree -- the Merkle tree
4714+        # over the roots of the block hash trees computed for shares in
4715+        # this upload.
4716+        self._root_hash = None
4717+
4718+        # We haven't yet written anything to the remote bucket. By
4719+        # setting this, we tell the _write method as much. The write
4720+        # method will then know that it also needs to add a write vector
4721+        # for the checkstring (or what we have of it) to the first write
4722+        # request. We'll then record that value for future use.  If
4723+        # we're expecting something to be there already, we need to call
4724+        # set_checkstring before we write anything to tell the first
4725+        # write about that.
4726+        self._written = False
4727+
4728+        # When writing data to the storage servers, we get a read vector
4729+        # for free. We'll read the checkstring, which will help us
4730+        # figure out what's gone wrong if a write fails.
4731+        self._readv = [(0, struct.calcsize(MDMFCHECKSTRING))]
4732+
4733+        # We calculate the number of segments because it tells us
4734+        # where the salt part of the file ends/share segment begins,
4735+        # and also because it provides a useful amount of bounds checking.
4736+        self._num_segments = mathutil.div_ceil(self._data_length,
4737+                                               self._segment_size)
4738+        self._block_size = self._segment_size / self._required_shares
4739+        # We also calculate the share size, to help us with block
4740+        # constraints later.
4741+        tail_size = self._data_length % self._segment_size
4742+        if not tail_size:
4743+            self._tail_block_size = self._block_size
4744+        else:
4745+            self._tail_block_size = mathutil.next_multiple(tail_size,
4746+                                                           self._required_shares)
4747+            self._tail_block_size /= self._required_shares
4748+
4749+        # We already know where the sharedata starts; right after the end
4750+        # of the header (which is defined as the signable part + the offsets)
4751+        # We can also calculate where the encrypted private key begins
4752+        # from what we know know.
4753+        self._actual_block_size = self._block_size + SALT_SIZE
4754+        data_size = self._actual_block_size * (self._num_segments - 1)
4755+        data_size += self._tail_block_size
4756+        data_size += SALT_SIZE
4757+        self._offsets['enc_privkey'] = MDMFHEADERSIZE
4758+        self._offsets['enc_privkey'] += data_size
4759+        # We'll wait for the rest. Callers can now call my "put_block" and
4760+        # "set_checkstring" methods.
4761+
4762+
4763+    def set_checkstring(self,
4764+                        seqnum_or_checkstring,
4765+                        root_hash=None,
4766+                        salt=None):
4767+        """
4768+        Set checkstring checkstring for the given shnum.
4769+
4770+        This can be invoked in one of two ways.
4771+
4772+        With one argument, I assume that you are giving me a literal
4773+        checkstring -- e.g., the output of get_checkstring. I will then
4774+        set that checkstring as it is. This form is used by unit tests.
4775+
4776+        With two arguments, I assume that you are giving me a sequence
4777+        number and root hash to make a checkstring from. In that case, I
4778+        will build a checkstring and set it for you. This form is used
4779+        by the publisher.
4780+
4781+        By default, I assume that I am writing new shares to the grid.
4782+        If you don't explcitly set your own checkstring, I will use
4783+        one that requires that the remote share not exist. You will want
4784+        to use this method if you are updating a share in-place;
4785+        otherwise, writes will fail.
4786+        """
4787+        # You're allowed to overwrite checkstrings with this method;
4788+        # I assume that users know what they are doing when they call
4789+        # it.
4790+        if root_hash:
4791+            checkstring = struct.pack(MDMFCHECKSTRING,
4792+                                      1,
4793+                                      seqnum_or_checkstring,
4794+                                      root_hash)
4795+        else:
4796+            checkstring = seqnum_or_checkstring
4797+
4798+        if checkstring == "":
4799+            # We special-case this, since len("") = 0, but we need
4800+            # length of 1 for the case of an empty share to work on the
4801+            # storage server, which is what a checkstring that is the
4802+            # empty string means.
4803+            self._testvs = []
4804+        else:
4805+            self._testvs = []
4806+            self._testvs.append((0, len(checkstring), "eq", checkstring))
4807+
4808+
4809+    def __repr__(self):
4810+        return "MDMFSlotWriteProxy for share %d" % self.shnum
4811+
4812+
4813+    def get_checkstring(self):
4814+        """
4815+        Given a share number, I return a representation of what the
4816+        checkstring for that share on the server will look like.
4817+
4818+        I am mostly used for tests.
4819+        """
4820+        if self._root_hash:
4821+            roothash = self._root_hash
4822+        else:
4823+            roothash = "\x00" * 32
4824+        return struct.pack(MDMFCHECKSTRING,
4825+                           1,
4826+                           self._seqnum,
4827+                           roothash)
4828+
4829+
4830+    def put_block(self, data, segnum, salt):
4831+        """
4832+        Put the encrypted-and-encoded data segment in the slot, along
4833+        with the salt.
4834+        """
4835+        if segnum >= self._num_segments:
4836+            raise LayoutInvalid("I won't overwrite the private key")
4837+        if len(salt) != SALT_SIZE:
4838+            raise LayoutInvalid("I was given a salt of size %d, but "
4839+                                "I wanted a salt of size %d")
4840+        if segnum + 1 == self._num_segments:
4841+            if len(data) != self._tail_block_size:
4842+                raise LayoutInvalid("I was given the wrong size block to write")
4843+        elif len(data) != self._block_size:
4844+            raise LayoutInvalid("I was given the wrong size block to write")
4845+
4846+        # We want to write at len(MDMFHEADER) + segnum * block_size.
4847+
4848+        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
4849+        data = salt + data
4850+
4851+        datavs = [tuple([offset, data])]
4852+        return self._write(datavs)
4853+
4854+
4855+    def put_encprivkey(self, encprivkey):
4856+        """
4857+        Put the encrypted private key in the remote slot.
4858+        """
4859+        assert self._offsets
4860+        assert self._offsets['enc_privkey']
4861+        # You shouldn't re-write the encprivkey after the block hash
4862+        # tree is written, since that could cause the private key to run
4863+        # into the block hash tree. Before it writes the block hash
4864+        # tree, the block hash tree writing method writes the offset of
4865+        # the salt hash tree. So that's a good indicator of whether or
4866+        # not the block hash tree has been written.
4867+        if "share_hash_chain" in self._offsets:
4868+            raise LayoutInvalid("You must write this before the block hash tree")
4869+
4870+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
4871+        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
4872+        def _on_failure():
4873+            del(self._offsets['block_hash_tree'])
4874+        return self._write(datavs, on_failure=_on_failure)
4875+
4876+
4877+    def put_blockhashes(self, blockhashes):
4878+        """
4879+        Put the block hash tree in the remote slot.
4880+
4881+        The encrypted private key must be put before the block hash
4882+        tree, since we need to know how large it is to know where the
4883+        block hash tree should go. The block hash tree must be put
4884+        before the salt hash tree, since its size determines the
4885+        offset of the share hash chain.
4886+        """
4887+        assert self._offsets
4888+        assert isinstance(blockhashes, list)
4889+        if "block_hash_tree" not in self._offsets:
4890+            raise LayoutInvalid("You must put the encrypted private key "
4891+                                "before you put the block hash tree")
4892+        # If written, the share hash chain causes the signature offset
4893+        # to be defined.
4894+        if "signature" in self._offsets:
4895+            raise LayoutInvalid("You must put the block hash tree before "
4896+                                "you put the share hash chain")
4897+        blockhashes_s = "".join(blockhashes)
4898+        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
4899+        datavs = []
4900+        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
4901+        def _on_failure():
4902+            del(self._offsets['share_hash_chain'])
4903+        return self._write(datavs, on_failure=_on_failure)
4904+
4905+
4906+    def put_sharehashes(self, sharehashes):
4907+        """
4908+        Put the share hash chain in the remote slot.
4909+
4910+        The salt hash tree must be put before the share hash chain,
4911+        since we need to know where the salt hash tree ends before we
4912+        can know where the share hash chain starts. The share hash chain
4913+        must be put before the signature, since the length of the packed
4914+        share hash chain determines the offset of the signature. Also,
4915+        semantically, you must know what the root of the salt hash tree
4916+        is before you can generate a valid signature.
4917+        """
4918+        assert isinstance(sharehashes, dict)
4919+        if "share_hash_chain" not in self._offsets:
4920+            raise LayoutInvalid("You need to put the salt hash tree before "
4921+                                "you can put the share hash chain")
4922+        # The signature comes after the share hash chain. If the
4923+        # signature has already been written, we must not write another
4924+        # share hash chain. The signature writes the verification key
4925+        # offset when it gets sent to the remote server, so we look for
4926+        # that.
4927+        if "verification_key" in self._offsets:
4928+            raise LayoutInvalid("You must write the share hash chain "
4929+                                "before you write the signature")
4930+        datavs = []
4931+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4932+                                  for i in sorted(sharehashes.keys())])
4933+        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
4934+        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
4935+        def _on_failure():
4936+            del(self._offsets['signature'])
4937+        return self._write(datavs, on_failure=_on_failure)
4938+
4939+
4940+    def put_root_hash(self, roothash):
4941+        """
4942+        Put the root hash (the root of the share hash tree) in the
4943+        remote slot.
4944+        """
4945+        # It does not make sense to be able to put the root
4946+        # hash without first putting the share hashes, since you need
4947+        # the share hashes to generate the root hash.
4948+        #
4949+        # Signature is defined by the routine that places the share hash
4950+        # chain, so it's a good thing to look for in finding out whether
4951+        # or not the share hash chain exists on the remote server.
4952+        if "signature" not in self._offsets:
4953+            raise LayoutInvalid("You need to put the share hash chain "
4954+                                "before you can put the root share hash")
4955+        if len(roothash) != HASH_SIZE:
4956+            raise LayoutInvalid("hashes and salts must be exactly %d bytes"
4957+                                 % HASH_SIZE)
4958+        datavs = []
4959+        self._root_hash = roothash
4960+        # To write both of these values, we update the checkstring on
4961+        # the remote server, which includes them
4962+        checkstring = self.get_checkstring()
4963+        datavs.append(tuple([0, checkstring]))
4964+        # This write, if successful, changes the checkstring, so we need
4965+        # to update our internal checkstring to be consistent with the
4966+        # one on the server.
4967+        def _on_success():
4968+            self._testvs = [(0, len(checkstring), "eq", checkstring)]
4969+        def _on_failure():
4970+            self._root_hash = None
4971+        return self._write(datavs,
4972+                           on_success=_on_success,
4973+                           on_failure=_on_failure)
4974+
4975+
4976+    def get_signable(self):
4977+        """
4978+        Get the first seven fields of the mutable file; the parts that
4979+        are signed.
4980+        """
4981+        if not self._root_hash:
4982+            raise LayoutInvalid("You need to set the root hash "
4983+                                "before getting something to "
4984+                                "sign")
4985+        return struct.pack(MDMFSIGNABLEHEADER,
4986+                           1,
4987+                           self._seqnum,
4988+                           self._root_hash,
4989+                           self._required_shares,
4990+                           self._total_shares,
4991+                           self._segment_size,
4992+                           self._data_length)
4993+
4994+
4995+    def put_signature(self, signature):
4996+        """
4997+        Put the signature field to the remote slot.
4998+
4999+        I require that the root hash and share hash chain have been put
5000+        to the grid before I will write the signature to the grid.
5001+        """
5002+        if "signature" not in self._offsets:
5003+            raise LayoutInvalid("You must put the share hash chain "
5004+        # It does not make sense to put a signature without first
5005+        # putting the root hash and the salt hash (since otherwise
5006+        # the signature would be incomplete), so we don't allow that.
5007+                       "before putting the signature")
5008+        if not self._root_hash:
5009+            raise LayoutInvalid("You must complete the signed prefix "
5010+                                "before computing a signature")
5011+        # If we put the signature after we put the verification key, we
5012+        # could end up running into the verification key, and will
5013+        # probably screw up the offsets as well. So we don't allow that.
5014+        # The method that writes the verification key defines the EOF
5015+        # offset before writing the verification key, so look for that.
5016+        if "EOF" in self._offsets:
5017+            raise LayoutInvalid("You must write the signature before the verification key")
5018+
5019+        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
5020+        datavs = []
5021+        datavs.append(tuple([self._offsets['signature'], signature]))
5022+        def _on_failure():
5023+            del(self._offsets['verification_key'])
5024+        return self._write(datavs, on_failure=_on_failure)
5025+
5026+
5027+    def put_verification_key(self, verification_key):
5028+        """
5029+        Put the verification key into the remote slot.
5030+
5031+        I require that the signature have been written to the storage
5032+        server before I allow the verification key to be written to the
5033+        remote server.
5034+        """
5035+        if "verification_key" not in self._offsets:
5036+            raise LayoutInvalid("You must put the signature before you "
5037+                                "can put the verification key")
5038+        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
5039+        datavs = []
5040+        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
5041+        def _on_failure():
5042+            del(self._offsets['EOF'])
5043+        return self._write(datavs, on_failure=_on_failure)
5044+
5045+    def _get_offsets_tuple(self):
5046+        return tuple([(key, value) for key, value in self._offsets.items()])
5047+
5048+    def get_verinfo(self):
5049+        return (self._seqnum,
5050+                self._root_hash,
5051+                self._required_shares,
5052+                self._total_shares,
5053+                self._segment_size,
5054+                self._data_length,
5055+                self.get_signable(),
5056+                self._get_offsets_tuple())
5057+
5058+
5059+    def finish_publishing(self):
5060+        """
5061+        Write the offset table and encoding parameters to the remote
5062+        slot, since that's the only thing we have yet to publish at this
5063+        point.
5064+        """
5065+        if "EOF" not in self._offsets:
5066+            raise LayoutInvalid("You must put the verification key before "
5067+                                "you can publish the offsets")
5068+        offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
5069+        offsets = struct.pack(MDMFOFFSETS,
5070+                              self._offsets['enc_privkey'],
5071+                              self._offsets['block_hash_tree'],
5072+                              self._offsets['share_hash_chain'],
5073+                              self._offsets['signature'],
5074+                              self._offsets['verification_key'],
5075+                              self._offsets['EOF'])
5076+        datavs = []
5077+        datavs.append(tuple([offsets_offset, offsets]))
5078+        encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
5079+        params = struct.pack(">BBQQ",
5080+                             self._required_shares,
5081+                             self._total_shares,
5082+                             self._segment_size,
5083+                             self._data_length)
5084+        datavs.append(tuple([encoding_parameters_offset, params]))
5085+        return self._write(datavs)
5086+
5087+
5088+    def _write(self, datavs, on_failure=None, on_success=None):
5089+        """I write the data vectors in datavs to the remote slot."""
5090+        tw_vectors = {}
5091+        new_share = False
5092+        if not self._testvs:
5093+            self._testvs = []
5094+            self._testvs.append(tuple([0, 1, "eq", ""]))
5095+            new_share = True
5096+        if not self._written:
5097+            # Write a new checkstring to the share when we write it, so
5098+            # that we have something to check later.
5099+            new_checkstring = self.get_checkstring()
5100+            datavs.append((0, new_checkstring))
5101+            def _first_write():
5102+                self._written = True
5103+                self._testvs = [(0, len(new_checkstring), "eq", new_checkstring)]
5104+            on_success = _first_write
5105+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
5106+        datalength = sum([len(x[1]) for x in datavs])
5107+        d = self._rref.callRemote("slot_testv_and_readv_and_writev",
5108+                                  self._storage_index,
5109+                                  self._secrets,
5110+                                  tw_vectors,
5111+                                  self._readv)
5112+        def _result(results):
5113+            if isinstance(results, failure.Failure) or not results[0]:
5114+                # Do nothing; the write was unsuccessful.
5115+                if on_failure: on_failure()
5116+            else:
5117+                if on_success: on_success()
5118+            return results
5119+        d.addCallback(_result)
5120+        return d
5121+
5122+
5123+class MDMFSlotReadProxy:
5124+    """
5125+    I read from a mutable slot filled with data written in the MDMF data
5126+    format (which is described above).
5127+
5128+    I can be initialized with some amount of data, which I will use (if
5129+    it is valid) to eliminate some of the need to fetch it from servers.
5130+    """
5131+    def __init__(self,
5132+                 rref,
5133+                 storage_index,
5134+                 shnum,
5135+                 data=""):
5136+        # Start the initialization process.
5137+        self._rref = rref
5138+        self._storage_index = storage_index
5139+        self.shnum = shnum
5140+
5141+        # Before doing anything, the reader is probably going to want to
5142+        # verify that the signature is correct. To do that, they'll need
5143+        # the verification key, and the signature. To get those, we'll
5144+        # need the offset table. So fetch the offset table on the
5145+        # assumption that that will be the first thing that a reader is
5146+        # going to do.
5147+
5148+        # The fact that these encoding parameters are None tells us
5149+        # that we haven't yet fetched them from the remote share, so we
5150+        # should. We could just not set them, but the checks will be
5151+        # easier to read if we don't have to use hasattr.
5152+        self._version_number = None
5153+        self._sequence_number = None
5154+        self._root_hash = None
5155+        # Filled in if we're dealing with an SDMF file. Unused
5156+        # otherwise.
5157+        self._salt = None
5158+        self._required_shares = None
5159+        self._total_shares = None
5160+        self._segment_size = None
5161+        self._data_length = None
5162+        self._offsets = None
5163+
5164+        # If the user has chosen to initialize us with some data, we'll
5165+        # try to satisfy subsequent data requests with that data before
5166+        # asking the storage server for it. If
5167+        self._data = data
5168+        # The way callers interact with cache in the filenode returns
5169+        # None if there isn't any cached data, but the way we index the
5170+        # cached data requires a string, so convert None to "".
5171+        if self._data == None:
5172+            self._data = ""
5173+
5174+        self._queue_observers = observer.ObserverList()
5175+        self._queue_errbacks = observer.ObserverList()
5176+        self._readvs = []
5177+
5178+
5179+    def _maybe_fetch_offsets_and_header(self, force_remote=False):
5180+        """
5181+        I fetch the offset table and the header from the remote slot if
5182+        I don't already have them. If I do have them, I do nothing and
5183+        return an empty Deferred.
5184+        """
5185+        if self._offsets:
5186+            return defer.succeed(None)
5187+        # At this point, we may be either SDMF or MDMF. Fetching 107
5188+        # bytes will be enough to get header and offsets for both SDMF and
5189+        # MDMF, though we'll be left with 4 more bytes than we
5190+        # need if this ends up being MDMF. This is probably less
5191+        # expensive than the cost of a second roundtrip.
5192+        readvs = [(0, 107)]
5193+        d = self._read(readvs, force_remote)
5194+        d.addCallback(self._process_encoding_parameters)
5195+        d.addCallback(self._process_offsets)
5196+        return d
5197+
5198+
5199+    def _process_encoding_parameters(self, encoding_parameters):
5200+        assert self.shnum in encoding_parameters
5201+        encoding_parameters = encoding_parameters[self.shnum][0]
5202+        # The first byte is the version number. It will tell us what
5203+        # to do next.
5204+        (verno,) = struct.unpack(">B", encoding_parameters[:1])
5205+        if verno == MDMF_VERSION:
5206+            read_size = MDMFHEADERWITHOUTOFFSETSSIZE
5207+            (verno,
5208+             seqnum,
5209+             root_hash,
5210+             k,
5211+             n,
5212+             segsize,
5213+             datalen) = struct.unpack(MDMFHEADERWITHOUTOFFSETS,
5214+                                      encoding_parameters[:read_size])
5215+            if segsize == 0 and datalen == 0:
5216+                # Empty file, no segments.
5217+                self._num_segments = 0
5218+            else:
5219+                self._num_segments = mathutil.div_ceil(datalen, segsize)
5220+
5221+        elif verno == SDMF_VERSION:
5222+            read_size = SIGNED_PREFIX_LENGTH
5223+            (verno,
5224+             seqnum,
5225+             root_hash,
5226+             salt,
5227+             k,
5228+             n,
5229+             segsize,
5230+             datalen) = struct.unpack(">BQ32s16s BBQQ",
5231+                                encoding_parameters[:SIGNED_PREFIX_LENGTH])
5232+            self._salt = salt
5233+            if segsize == 0 and datalen == 0:
5234+                # empty file
5235+                self._num_segments = 0
5236+            else:
5237+                # non-empty SDMF files have one segment.
5238+                self._num_segments = 1
5239+        else:
5240+            raise UnknownVersionError("You asked me to read mutable file "
5241+                                      "version %d, but I only understand "
5242+                                      "%d and %d" % (verno, SDMF_VERSION,
5243+                                                     MDMF_VERSION))
5244+
5245+        self._version_number = verno
5246+        self._sequence_number = seqnum
5247+        self._root_hash = root_hash
5248+        self._required_shares = k
5249+        self._total_shares = n
5250+        self._segment_size = segsize
5251+        self._data_length = datalen
5252+
5253+        self._block_size = self._segment_size / self._required_shares
5254+        # We can upload empty files, and need to account for this fact
5255+        # so as to avoid zero-division and zero-modulo errors.
5256+        if datalen > 0:
5257+            tail_size = self._data_length % self._segment_size
5258+        else:
5259+            tail_size = 0
5260+        if not tail_size:
5261+            self._tail_block_size = self._block_size
5262+        else:
5263+            self._tail_block_size = mathutil.next_multiple(tail_size,
5264+                                                    self._required_shares)
5265+            self._tail_block_size /= self._required_shares
5266+
5267+        return encoding_parameters
5268+
5269+
5270+    def _process_offsets(self, offsets):
5271+        if self._version_number == 0:
5272+            read_size = OFFSETS_LENGTH
5273+            read_offset = SIGNED_PREFIX_LENGTH
5274+            end = read_size + read_offset
5275+            (signature,
5276+             share_hash_chain,
5277+             block_hash_tree,
5278+             share_data,
5279+             enc_privkey,
5280+             EOF) = struct.unpack(">LLLLQQ",
5281+                                  offsets[read_offset:end])
5282+            self._offsets = {}
5283+            self._offsets['signature'] = signature
5284+            self._offsets['share_data'] = share_data
5285+            self._offsets['block_hash_tree'] = block_hash_tree
5286+            self._offsets['share_hash_chain'] = share_hash_chain
5287+            self._offsets['enc_privkey'] = enc_privkey
5288+            self._offsets['EOF'] = EOF
5289+
5290+        elif self._version_number == 1:
5291+            read_offset = MDMFHEADERWITHOUTOFFSETSSIZE
5292+            read_length = MDMFOFFSETS_LENGTH
5293+            end = read_offset + read_length
5294+            (encprivkey,
5295+             blockhashes,
5296+             sharehashes,
5297+             signature,
5298+             verification_key,
5299+             eof) = struct.unpack(MDMFOFFSETS,
5300+                                  offsets[read_offset:end])
5301+            self._offsets = {}
5302+            self._offsets['enc_privkey'] = encprivkey
5303+            self._offsets['block_hash_tree'] = blockhashes
5304+            self._offsets['share_hash_chain'] = sharehashes
5305+            self._offsets['signature'] = signature
5306+            self._offsets['verification_key'] = verification_key
5307+            self._offsets['EOF'] = eof
5308+
5309+
5310+    def get_block_and_salt(self, segnum, queue=False):
5311+        """
5312+        I return (block, salt), where block is the block data and
5313+        salt is the salt used to encrypt that segment.
5314+        """
5315+        d = self._maybe_fetch_offsets_and_header()
5316+        def _then(ignored):
5317+            if self._version_number == 1:
5318+                base_share_offset = MDMFHEADERSIZE
5319+            else:
5320+                base_share_offset = self._offsets['share_data']
5321+
5322+            if segnum + 1 > self._num_segments:
5323+                raise LayoutInvalid("Not a valid segment number")
5324+
5325+            if self._version_number == 0:
5326+                share_offset = base_share_offset + self._block_size * segnum
5327+            else:
5328+                share_offset = base_share_offset + (self._block_size + \
5329+                                                    SALT_SIZE) * segnum
5330+            if segnum + 1 == self._num_segments:
5331+                data = self._tail_block_size
5332+            else:
5333+                data = self._block_size
5334+
5335+            if self._version_number == 1:
5336+                data += SALT_SIZE
5337+
5338+            readvs = [(share_offset, data)]
5339+            return readvs
5340+        d.addCallback(_then)
5341+        d.addCallback(lambda readvs:
5342+            self._read(readvs, queue=queue))
5343+        def _process_results(results):
5344+            assert self.shnum in results
5345+            if self._version_number == 0:
5346+                # We only read the share data, but we know the salt from
5347+                # when we fetched the header
5348+                data = results[self.shnum]
5349+                if not data:
5350+                    data = ""
5351+                else:
5352+                    assert len(data) == 1
5353+                    data = data[0]
5354+                salt = self._salt
5355+            else:
5356+                data = results[self.shnum]
5357+                if not data:
5358+                    salt = data = ""
5359+                else:
5360+                    salt_and_data = results[self.shnum][0]
5361+                    salt = salt_and_data[:SALT_SIZE]
5362+                    data = salt_and_data[SALT_SIZE:]
5363+            return data, salt
5364+        d.addCallback(_process_results)
5365+        return d
5366+
5367+
5368+    def get_blockhashes(self, needed=None, queue=False, force_remote=False):
5369+        """
5370+        I return the block hash tree
5371+
5372+        I take an optional argument, needed, which is a set of indices
5373+        correspond to hashes that I should fetch. If this argument is
5374+        missing, I will fetch the entire block hash tree; otherwise, I
5375+        may attempt to fetch fewer hashes, based on what needed says
5376+        that I should do. Note that I may fetch as many hashes as I
5377+        want, so long as the set of hashes that I do fetch is a superset
5378+        of the ones that I am asked for, so callers should be prepared
5379+        to tolerate additional hashes.
5380+        """
5381+        # TODO: Return only the parts of the block hash tree necessary
5382+        # to validate the blocknum provided?
5383+        # This is a good idea, but it is hard to implement correctly. It
5384+        # is bad to fetch any one block hash more than once, so we
5385+        # probably just want to fetch the whole thing at once and then
5386+        # serve it.
5387+        if needed == set([]):
5388+            return defer.succeed([])
5389+        d = self._maybe_fetch_offsets_and_header()
5390+        def _then(ignored):
5391+            blockhashes_offset = self._offsets['block_hash_tree']
5392+            if self._version_number == 1:
5393+                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
5394+            else:
5395+                blockhashes_length = self._offsets['share_data'] - blockhashes_offset
5396+            readvs = [(blockhashes_offset, blockhashes_length)]
5397+            return readvs
5398+        d.addCallback(_then)
5399+        d.addCallback(lambda readvs:
5400+            self._read(readvs, queue=queue, force_remote=force_remote))
5401+        def _build_block_hash_tree(results):
5402+            assert self.shnum in results
5403+
5404+            rawhashes = results[self.shnum][0]
5405+            results = [rawhashes[i:i+HASH_SIZE]
5406+                       for i in range(0, len(rawhashes), HASH_SIZE)]
5407+            return results
5408+        d.addCallback(_build_block_hash_tree)
5409+        return d
5410+
5411+
5412+    def get_sharehashes(self, needed=None, queue=False, force_remote=False):
5413+        """
5414+        I return the part of the share hash chain placed to validate
5415+        this share.
5416+
5417+        I take an optional argument, needed. Needed is a set of indices
5418+        that correspond to the hashes that I should fetch. If needed is
5419+        not present, I will fetch and return the entire share hash
5420+        chain. Otherwise, I may fetch and return any part of the share
5421+        hash chain that is a superset of the part that I am asked to
5422+        fetch. Callers should be prepared to deal with more hashes than
5423+        they've asked for.
5424+        """
5425+        if needed == set([]):
5426+            return defer.succeed([])
5427+        d = self._maybe_fetch_offsets_and_header()
5428+
5429+        def _make_readvs(ignored):
5430+            sharehashes_offset = self._offsets['share_hash_chain']
5431+            if self._version_number == 0:
5432+                sharehashes_length = self._offsets['block_hash_tree'] - sharehashes_offset
5433+            else:
5434+                sharehashes_length = self._offsets['signature'] - sharehashes_offset
5435+            readvs = [(sharehashes_offset, sharehashes_length)]
5436+            return readvs
5437+        d.addCallback(_make_readvs)
5438+        d.addCallback(lambda readvs:
5439+            self._read(readvs, queue=queue, force_remote=force_remote))
5440+        def _build_share_hash_chain(results):
5441+            assert self.shnum in results
5442+
5443+            sharehashes = results[self.shnum][0]
5444+            results = [sharehashes[i:i+(HASH_SIZE + 2)]
5445+                       for i in range(0, len(sharehashes), HASH_SIZE + 2)]
5446+            results = dict([struct.unpack(">H32s", data)
5447+                            for data in results])
5448+            return results
5449+        d.addCallback(_build_share_hash_chain)
5450+        return d
5451+
5452+
5453+    def get_encprivkey(self, queue=False):
5454+        """
5455+        I return the encrypted private key.
5456+        """
5457+        d = self._maybe_fetch_offsets_and_header()
5458+
5459+        def _make_readvs(ignored):
5460+            privkey_offset = self._offsets['enc_privkey']
5461+            if self._version_number == 0:
5462+                privkey_length = self._offsets['EOF'] - privkey_offset
5463+            else:
5464+                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
5465+            readvs = [(privkey_offset, privkey_length)]
5466+            return readvs
5467+        d.addCallback(_make_readvs)
5468+        d.addCallback(lambda readvs:
5469+            self._read(readvs, queue=queue))
5470+        def _process_results(results):
5471+            assert self.shnum in results
5472+            privkey = results[self.shnum][0]
5473+            return privkey
5474+        d.addCallback(_process_results)
5475+        return d
5476+
5477+
5478+    def get_signature(self, queue=False):
5479+        """
5480+        I return the signature of my share.
5481+        """
5482+        d = self._maybe_fetch_offsets_and_header()
5483+
5484+        def _make_readvs(ignored):
5485+            signature_offset = self._offsets['signature']
5486+            if self._version_number == 1:
5487+                signature_length = self._offsets['verification_key'] - signature_offset
5488+            else:
5489+                signature_length = self._offsets['share_hash_chain'] - signature_offset
5490+            readvs = [(signature_offset, signature_length)]
5491+            return readvs
5492+        d.addCallback(_make_readvs)
5493+        d.addCallback(lambda readvs:
5494+            self._read(readvs, queue=queue))
5495+        def _process_results(results):
5496+            assert self.shnum in results
5497+            signature = results[self.shnum][0]
5498+            return signature
5499+        d.addCallback(_process_results)
5500+        return d
5501+
5502+
5503+    def get_verification_key(self, queue=False):
5504+        """
5505+        I return the verification key.
5506+        """
5507+        d = self._maybe_fetch_offsets_and_header()
5508+
5509+        def _make_readvs(ignored):
5510+            if self._version_number == 1:
5511+                vk_offset = self._offsets['verification_key']
5512+                vk_length = self._offsets['EOF'] - vk_offset
5513+            else:
5514+                vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5515+                vk_length = self._offsets['signature'] - vk_offset
5516+            readvs = [(vk_offset, vk_length)]
5517+            return readvs
5518+        d.addCallback(_make_readvs)
5519+        d.addCallback(lambda readvs:
5520+            self._read(readvs, queue=queue))
5521+        def _process_results(results):
5522+            assert self.shnum in results
5523+            verification_key = results[self.shnum][0]
5524+            return verification_key
5525+        d.addCallback(_process_results)
5526+        return d
5527+
5528+
5529+    def get_encoding_parameters(self):
5530+        """
5531+        I return (k, n, segsize, datalen)
5532+        """
5533+        d = self._maybe_fetch_offsets_and_header()
5534+        d.addCallback(lambda ignored:
5535+            (self._required_shares,
5536+             self._total_shares,
5537+             self._segment_size,
5538+             self._data_length))
5539+        return d
5540+
5541+
5542+    def get_seqnum(self):
5543+        """
5544+        I return the sequence number for this share.
5545+        """
5546+        d = self._maybe_fetch_offsets_and_header()
5547+        d.addCallback(lambda ignored:
5548+            self._sequence_number)
5549+        return d
5550+
5551+
5552+    def get_root_hash(self):
5553+        """
5554+        I return the root of the block hash tree
5555+        """
5556+        d = self._maybe_fetch_offsets_and_header()
5557+        d.addCallback(lambda ignored: self._root_hash)
5558+        return d
5559+
5560+
5561+    def get_checkstring(self):
5562+        """
5563+        I return the packed representation of the following:
5564+
5565+            - version number
5566+            - sequence number
5567+            - root hash
5568+            - salt hash
5569+
5570+        which my users use as a checkstring to detect other writers.
5571+        """
5572+        d = self._maybe_fetch_offsets_and_header()
5573+        def _build_checkstring(ignored):
5574+            if self._salt:
5575+                checkstring = strut.pack(PREFIX,
5576+                                         self._version_number,
5577+                                         self._sequence_number,
5578+                                         self._root_hash,
5579+                                         self._salt)
5580+            else:
5581+                checkstring = struct.pack(MDMFCHECKSTRING,
5582+                                          self._version_number,
5583+                                          self._sequence_number,
5584+                                          self._root_hash)
5585+
5586+            return checkstring
5587+        d.addCallback(_build_checkstring)
5588+        return d
5589+
5590+
5591+    def get_prefix(self, force_remote):
5592+        d = self._maybe_fetch_offsets_and_header(force_remote)
5593+        d.addCallback(lambda ignored:
5594+            self._build_prefix())
5595+        return d
5596+
5597+
5598+    def _build_prefix(self):
5599+        # The prefix is another name for the part of the remote share
5600+        # that gets signed. It consists of everything up to and
5601+        # including the datalength, packed by struct.
5602+        if self._version_number == SDMF_VERSION:
5603+            return struct.pack(SIGNED_PREFIX,
5604+                           self._version_number,
5605+                           self._sequence_number,
5606+                           self._root_hash,
5607+                           self._salt,
5608+                           self._required_shares,
5609+                           self._total_shares,
5610+                           self._segment_size,
5611+                           self._data_length)
5612+
5613+        else:
5614+            return struct.pack(MDMFSIGNABLEHEADER,
5615+                           self._version_number,
5616+                           self._sequence_number,
5617+                           self._root_hash,
5618+                           self._required_shares,
5619+                           self._total_shares,
5620+                           self._segment_size,
5621+                           self._data_length)
5622+
5623+
5624+    def _get_offsets_tuple(self):
5625+        # The offsets tuple is another component of the version
5626+        # information tuple. It is basically our offsets dictionary,
5627+        # itemized and in a tuple.
5628+        return self._offsets.copy()
5629+
5630+
5631+    def get_verinfo(self):
5632+        """
5633+        I return my verinfo tuple. This is used by the ServermapUpdater
5634+        to keep track of versions of mutable files.
5635+
5636+        The verinfo tuple for MDMF files contains:
5637+            - seqnum
5638+            - root hash
5639+            - a blank (nothing)
5640+            - segsize
5641+            - datalen
5642+            - k
5643+            - n
5644+            - prefix (the thing that you sign)
5645+            - a tuple of offsets
5646+
5647+        We include the nonce in MDMF to simplify processing of version
5648+        information tuples.
5649+
5650+        The verinfo tuple for SDMF files is the same, but contains a
5651+        16-byte IV instead of a hash of salts.
5652+        """
5653+        d = self._maybe_fetch_offsets_and_header()
5654+        def _build_verinfo(ignored):
5655+            if self._version_number == SDMF_VERSION:
5656+                salt_to_use = self._salt
5657+            else:
5658+                salt_to_use = None
5659+            return (self._sequence_number,
5660+                    self._root_hash,
5661+                    salt_to_use,
5662+                    self._segment_size,
5663+                    self._data_length,
5664+                    self._required_shares,
5665+                    self._total_shares,
5666+                    self._build_prefix(),
5667+                    self._get_offsets_tuple())
5668+        d.addCallback(_build_verinfo)
5669+        return d
5670+
5671+
5672+    def flush(self):
5673+        """
5674+        I flush my queue of read vectors.
5675+        """
5676+        d = self._read(self._readvs)
5677+        def _then(results):
5678+            self._readvs = []
5679+            if isinstance(results, failure.Failure):
5680+                self._queue_errbacks.notify(results)
5681+            else:
5682+                self._queue_observers.notify(results)
5683+            self._queue_observers = observer.ObserverList()
5684+            self._queue_errbacks = observer.ObserverList()
5685+        d.addBoth(_then)
5686+
5687+
5688+    def _read(self, readvs, force_remote=False, queue=False):
5689+        unsatisfiable = filter(lambda x: x[0] + x[1] > len(self._data), readvs)
5690+        # TODO: It's entirely possible to tweak this so that it just
5691+        # fulfills the requests that it can, and not demand that all
5692+        # requests are satisfiable before running it.
5693+        if not unsatisfiable and not force_remote:
5694+            results = [self._data[offset:offset+length]
5695+                       for (offset, length) in readvs]
5696+            results = {self.shnum: results}
5697+            return defer.succeed(results)
5698+        else:
5699+            if queue:
5700+                start = len(self._readvs)
5701+                self._readvs += readvs
5702+                end = len(self._readvs)
5703+                def _get_results(results, start, end):
5704+                    if not self.shnum in results:
5705+                        return {self._shnum: [""]}
5706+                    return {self.shnum: results[self.shnum][start:end]}
5707+                d = defer.Deferred()
5708+                d.addCallback(_get_results, start, end)
5709+                self._queue_observers.subscribe(d.callback)
5710+                self._queue_errbacks.subscribe(d.errback)
5711+                return d
5712+            return self._rref.callRemote("slot_readv",
5713+                                         self._storage_index,
5714+                                         [self.shnum],
5715+                                         readvs)
5716+
5717+
5718+    def is_sdmf(self):
5719+        """I tell my caller whether or not my remote file is SDMF or MDMF
5720+        """
5721+        d = self._maybe_fetch_offsets_and_header()
5722+        d.addCallback(lambda ignored:
5723+            self._version_number == 0)
5724+        return d
5725+
5726+
5727+class LayoutInvalid(Exception):
5728+    """
5729+    This isn't a valid MDMF mutable file
5730+    """
5731hunk ./src/allmydata/test/test_storage.py 2
5732 
5733-import time, os.path, stat, re, simplejson, struct
5734+import time, os.path, stat, re, simplejson, struct, shutil
5735 
5736 from twisted.trial import unittest
5737 
5738hunk ./src/allmydata/test/test_storage.py 22
5739 from allmydata.storage.expirer import LeaseCheckingCrawler
5740 from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \
5741      ReadBucketProxy
5742-from allmydata.interfaces import BadWriteEnablerError
5743-from allmydata.test.common import LoggingServiceParent
5744+from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
5745+                                     LayoutInvalid, MDMFSIGNABLEHEADER, \
5746+                                     SIGNED_PREFIX, MDMFHEADER, \
5747+                                     MDMFOFFSETS, SDMFSlotWriteProxy
5748+from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
5749+                                 SDMF_VERSION
5750+from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
5751 from allmydata.test.common_web import WebRenderingMixin
5752 from allmydata.web.storage import StorageStatus, remove_prefix
5753 
5754hunk ./src/allmydata/test/test_storage.py 106
5755 
5756 class RemoteBucket:
5757 
5758+    def __init__(self):
5759+        self.read_count = 0
5760+        self.write_count = 0
5761+
5762     def callRemote(self, methname, *args, **kwargs):
5763         def _call():
5764             meth = getattr(self.target, "remote_" + methname)
5765hunk ./src/allmydata/test/test_storage.py 114
5766             return meth(*args, **kwargs)
5767+
5768+        if methname == "slot_readv":
5769+            self.read_count += 1
5770+        if "writev" in methname:
5771+            self.write_count += 1
5772+
5773         return defer.maybeDeferred(_call)
5774 
5775hunk ./src/allmydata/test/test_storage.py 122
5776+
5777 class BucketProxy(unittest.TestCase):
5778     def make_bucket(self, name, size):
5779         basedir = os.path.join("storage", "BucketProxy", name)
5780hunk ./src/allmydata/test/test_storage.py 1313
5781         self.failUnless(os.path.exists(prefixdir), prefixdir)
5782         self.failIf(os.path.exists(bucketdir), bucketdir)
5783 
5784+
5785+class MDMFProxies(unittest.TestCase, ShouldFailMixin):
5786+    def setUp(self):
5787+        self.sparent = LoggingServiceParent()
5788+        self._lease_secret = itertools.count()
5789+        self.ss = self.create("MDMFProxies storage test server")
5790+        self.rref = RemoteBucket()
5791+        self.rref.target = self.ss
5792+        self.secrets = (self.write_enabler("we_secret"),
5793+                        self.renew_secret("renew_secret"),
5794+                        self.cancel_secret("cancel_secret"))
5795+        self.segment = "aaaaaa"
5796+        self.block = "aa"
5797+        self.salt = "a" * 16
5798+        self.block_hash = "a" * 32
5799+        self.block_hash_tree = [self.block_hash for i in xrange(6)]
5800+        self.share_hash = self.block_hash
5801+        self.share_hash_chain = dict([(i, self.share_hash) for i in xrange(6)])
5802+        self.signature = "foobarbaz"
5803+        self.verification_key = "vvvvvv"
5804+        self.encprivkey = "private"
5805+        self.root_hash = self.block_hash
5806+        self.salt_hash = self.root_hash
5807+        self.salt_hash_tree = [self.salt_hash for i in xrange(6)]
5808+        self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree)
5809+        self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain)
5810+        # blockhashes and salt hashes are serialized in the same way,
5811+        # only we lop off the first element and store that in the
5812+        # header.
5813+        self.salt_hash_tree_s = self.serialize_blockhashes(self.salt_hash_tree[1:])
5814+
5815+
5816+    def tearDown(self):
5817+        self.sparent.stopService()
5818+        shutil.rmtree(self.workdir("MDMFProxies storage test server"))
5819+
5820+
5821+    def write_enabler(self, we_tag):
5822+        return hashutil.tagged_hash("we_blah", we_tag)
5823+
5824+
5825+    def renew_secret(self, tag):
5826+        return hashutil.tagged_hash("renew_blah", str(tag))
5827+
5828+
5829+    def cancel_secret(self, tag):
5830+        return hashutil.tagged_hash("cancel_blah", str(tag))
5831+
5832+
5833+    def workdir(self, name):
5834+        basedir = os.path.join("storage", "MutableServer", name)
5835+        return basedir
5836+
5837+
5838+    def create(self, name):
5839+        workdir = self.workdir(name)
5840+        ss = StorageServer(workdir, "\x00" * 20)
5841+        ss.setServiceParent(self.sparent)
5842+        return ss
5843+
5844+
5845+    def build_test_mdmf_share(self, tail_segment=False, empty=False):
5846+        # Start with the checkstring
5847+        data = struct.pack(">BQ32s",
5848+                           1,
5849+                           0,
5850+                           self.root_hash)
5851+        self.checkstring = data
5852+        # Next, the encoding parameters
5853+        if tail_segment:
5854+            data += struct.pack(">BBQQ",
5855+                                3,
5856+                                10,
5857+                                6,
5858+                                33)
5859+        elif empty:
5860+            data += struct.pack(">BBQQ",
5861+                                3,
5862+                                10,
5863+                                0,
5864+                                0)
5865+        else:
5866+            data += struct.pack(">BBQQ",
5867+                                3,
5868+                                10,
5869+                                6,
5870+                                36)
5871+        # Now we'll build the offsets.
5872+        sharedata = ""
5873+        if not tail_segment and not empty:
5874+            for i in xrange(6):
5875+                sharedata += self.salt + self.block
5876+        elif tail_segment:
5877+            for i in xrange(5):
5878+                sharedata += self.salt + self.block
5879+            sharedata += self.salt + "a"
5880+
5881+        # The encrypted private key comes after the shares + salts
5882+        offset_size = struct.calcsize(MDMFOFFSETS)
5883+        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
5884+        # The blockhashes come after the private key
5885+        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
5886+        # The sharehashes come after the salt hashes
5887+        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
5888+        # The signature comes after the share hash chain
5889+        signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
5890+        # The verification key comes after the signature
5891+        verification_offset = signature_offset + len(self.signature)
5892+        # The EOF comes after the verification key
5893+        eof_offset = verification_offset + len(self.verification_key)
5894+        data += struct.pack(MDMFOFFSETS,
5895+                            encrypted_private_key_offset,
5896+                            blockhashes_offset,
5897+                            sharehashes_offset,
5898+                            signature_offset,
5899+                            verification_offset,
5900+                            eof_offset)
5901+        self.offsets = {}
5902+        self.offsets['enc_privkey'] = encrypted_private_key_offset
5903+        self.offsets['block_hash_tree'] = blockhashes_offset
5904+        self.offsets['share_hash_chain'] = sharehashes_offset
5905+        self.offsets['signature'] = signature_offset
5906+        self.offsets['verification_key'] = verification_offset
5907+        self.offsets['EOF'] = eof_offset
5908+        # Next, we'll add in the salts and share data,
5909+        data += sharedata
5910+        # the private key,
5911+        data += self.encprivkey
5912+        # the block hash tree,
5913+        data += self.block_hash_tree_s
5914+        # the share hash chain,
5915+        data += self.share_hash_chain_s
5916+        # the signature,
5917+        data += self.signature
5918+        # and the verification key
5919+        data += self.verification_key
5920+        return data
5921+
5922+
5923+    def write_test_share_to_server(self,
5924+                                   storage_index,
5925+                                   tail_segment=False,
5926+                                   empty=False):
5927+        """
5928+        I write some data for the read tests to read to self.ss
5929+
5930+        If tail_segment=True, then I will write a share that has a
5931+        smaller tail segment than other segments.
5932+        """
5933+        write = self.ss.remote_slot_testv_and_readv_and_writev
5934+        data = self.build_test_mdmf_share(tail_segment, empty)
5935+        # Finally, we write the whole thing to the storage server in one
5936+        # pass.
5937+        testvs = [(0, 1, "eq", "")]
5938+        tws = {}
5939+        tws[0] = (testvs, [(0, data)], None)
5940+        readv = [(0, 1)]
5941+        results = write(storage_index, self.secrets, tws, readv)
5942+        self.failUnless(results[0])
5943+
5944+
5945+    def build_test_sdmf_share(self, empty=False):
5946+        if empty:
5947+            sharedata = ""
5948+        else:
5949+            sharedata = self.segment * 6
5950+        self.sharedata = sharedata
5951+        blocksize = len(sharedata) / 3
5952+        block = sharedata[:blocksize]
5953+        self.blockdata = block
5954+        prefix = struct.pack(">BQ32s16s BBQQ",
5955+                             0, # version,
5956+                             0,
5957+                             self.root_hash,
5958+                             self.salt,
5959+                             3,
5960+                             10,
5961+                             len(sharedata),
5962+                             len(sharedata),
5963+                            )
5964+        post_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5965+        signature_offset = post_offset + len(self.verification_key)
5966+        sharehashes_offset = signature_offset + len(self.signature)
5967+        blockhashes_offset = sharehashes_offset + len(self.share_hash_chain_s)
5968+        sharedata_offset = blockhashes_offset + len(self.block_hash_tree_s)
5969+        encprivkey_offset = sharedata_offset + len(block)
5970+        eof_offset = encprivkey_offset + len(self.encprivkey)
5971+        offsets = struct.pack(">LLLLQQ",
5972+                              signature_offset,
5973+                              sharehashes_offset,
5974+                              blockhashes_offset,
5975+                              sharedata_offset,
5976+                              encprivkey_offset,
5977+                              eof_offset)
5978+        final_share = "".join([prefix,
5979+                           offsets,
5980+                           self.verification_key,
5981+                           self.signature,
5982+                           self.share_hash_chain_s,
5983+                           self.block_hash_tree_s,
5984+                           block,
5985+                           self.encprivkey])
5986+        self.offsets = {}
5987+        self.offsets['signature'] = signature_offset
5988+        self.offsets['share_hash_chain'] = sharehashes_offset
5989+        self.offsets['block_hash_tree'] = blockhashes_offset
5990+        self.offsets['share_data'] = sharedata_offset
5991+        self.offsets['enc_privkey'] = encprivkey_offset
5992+        self.offsets['EOF'] = eof_offset
5993+        return final_share
5994+
5995+
5996+    def write_sdmf_share_to_server(self,
5997+                                   storage_index,
5998+                                   empty=False):
5999+        # Some tests need SDMF shares to verify that we can still
6000+        # read them. This method writes one, which resembles but is not
6001+        assert self.rref
6002+        write = self.ss.remote_slot_testv_and_readv_and_writev
6003+        share = self.build_test_sdmf_share(empty)
6004+        testvs = [(0, 1, "eq", "")]
6005+        tws = {}
6006+        tws[0] = (testvs, [(0, share)], None)
6007+        readv = []
6008+        results = write(storage_index, self.secrets, tws, readv)
6009+        self.failUnless(results[0])
6010+
6011+
6012+    def test_read(self):
6013+        self.write_test_share_to_server("si1")
6014+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6015+        # Check that every method equals what we expect it to.
6016+        d = defer.succeed(None)
6017+        def _check_block_and_salt((block, salt)):
6018+            self.failUnlessEqual(block, self.block)
6019+            self.failUnlessEqual(salt, self.salt)
6020+
6021+        for i in xrange(6):
6022+            d.addCallback(lambda ignored, i=i:
6023+                mr.get_block_and_salt(i))
6024+            d.addCallback(_check_block_and_salt)
6025+
6026+        d.addCallback(lambda ignored:
6027+            mr.get_encprivkey())
6028+        d.addCallback(lambda encprivkey:
6029+            self.failUnlessEqual(self.encprivkey, encprivkey))
6030+
6031+        d.addCallback(lambda ignored:
6032+            mr.get_blockhashes())
6033+        d.addCallback(lambda blockhashes:
6034+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6035+
6036+        d.addCallback(lambda ignored:
6037+            mr.get_sharehashes())
6038+        d.addCallback(lambda sharehashes:
6039+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6040+
6041+        d.addCallback(lambda ignored:
6042+            mr.get_signature())
6043+        d.addCallback(lambda signature:
6044+            self.failUnlessEqual(signature, self.signature))
6045+
6046+        d.addCallback(lambda ignored:
6047+            mr.get_verification_key())
6048+        d.addCallback(lambda verification_key:
6049+            self.failUnlessEqual(verification_key, self.verification_key))
6050+
6051+        d.addCallback(lambda ignored:
6052+            mr.get_seqnum())
6053+        d.addCallback(lambda seqnum:
6054+            self.failUnlessEqual(seqnum, 0))
6055+
6056+        d.addCallback(lambda ignored:
6057+            mr.get_root_hash())
6058+        d.addCallback(lambda root_hash:
6059+            self.failUnlessEqual(self.root_hash, root_hash))
6060+
6061+        d.addCallback(lambda ignored:
6062+            mr.get_seqnum())
6063+        d.addCallback(lambda seqnum:
6064+            self.failUnlessEqual(0, seqnum))
6065+
6066+        d.addCallback(lambda ignored:
6067+            mr.get_encoding_parameters())
6068+        def _check_encoding_parameters((k, n, segsize, datalen)):
6069+            self.failUnlessEqual(k, 3)
6070+            self.failUnlessEqual(n, 10)
6071+            self.failUnlessEqual(segsize, 6)
6072+            self.failUnlessEqual(datalen, 36)
6073+        d.addCallback(_check_encoding_parameters)
6074+
6075+        d.addCallback(lambda ignored:
6076+            mr.get_checkstring())
6077+        d.addCallback(lambda checkstring:
6078+            self.failUnlessEqual(checkstring, checkstring))
6079+        return d
6080+
6081+
6082+    def test_read_with_different_tail_segment_size(self):
6083+        self.write_test_share_to_server("si1", tail_segment=True)
6084+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6085+        d = mr.get_block_and_salt(5)
6086+        def _check_tail_segment(results):
6087+            block, salt = results
6088+            self.failUnlessEqual(len(block), 1)
6089+            self.failUnlessEqual(block, "a")
6090+        d.addCallback(_check_tail_segment)
6091+        return d
6092+
6093+
6094+    def test_get_block_with_invalid_segnum(self):
6095+        self.write_test_share_to_server("si1")
6096+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6097+        d = defer.succeed(None)
6098+        d.addCallback(lambda ignored:
6099+            self.shouldFail(LayoutInvalid, "test invalid segnum",
6100+                            None,
6101+                            mr.get_block_and_salt, 7))
6102+        return d
6103+
6104+
6105+    def test_get_encoding_parameters_first(self):
6106+        self.write_test_share_to_server("si1")
6107+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6108+        d = mr.get_encoding_parameters()
6109+        def _check_encoding_parameters((k, n, segment_size, datalen)):
6110+            self.failUnlessEqual(k, 3)
6111+            self.failUnlessEqual(n, 10)
6112+            self.failUnlessEqual(segment_size, 6)
6113+            self.failUnlessEqual(datalen, 36)
6114+        d.addCallback(_check_encoding_parameters)
6115+        return d
6116+
6117+
6118+    def test_get_seqnum_first(self):
6119+        self.write_test_share_to_server("si1")
6120+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6121+        d = mr.get_seqnum()
6122+        d.addCallback(lambda seqnum:
6123+            self.failUnlessEqual(seqnum, 0))
6124+        return d
6125+
6126+
6127+    def test_get_root_hash_first(self):
6128+        self.write_test_share_to_server("si1")
6129+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6130+        d = mr.get_root_hash()
6131+        d.addCallback(lambda root_hash:
6132+            self.failUnlessEqual(root_hash, self.root_hash))
6133+        return d
6134+
6135+
6136+    def test_get_checkstring_first(self):
6137+        self.write_test_share_to_server("si1")
6138+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6139+        d = mr.get_checkstring()
6140+        d.addCallback(lambda checkstring:
6141+            self.failUnlessEqual(checkstring, self.checkstring))
6142+        return d
6143+
6144+
6145+    def test_write_read_vectors(self):
6146+        # When writing for us, the storage server will return to us a
6147+        # read vector, along with its result. If a write fails because
6148+        # the test vectors failed, this read vector can help us to
6149+        # diagnose the problem. This test ensures that the read vector
6150+        # is working appropriately.
6151+        mw = self._make_new_mw("si1", 0)
6152+        d = defer.succeed(None)
6153+
6154+        # Write one share. This should return a checkstring of nothing,
6155+        # since there is no data there.
6156+        d.addCallback(lambda ignored:
6157+            mw.put_block(self.block, 0, self.salt))
6158+        def _check_first_write(results):
6159+            result, readvs = results
6160+            self.failUnless(result)
6161+            self.failIf(readvs)
6162+        d.addCallback(_check_first_write)
6163+        # Now, there should be a different checkstring returned when
6164+        # we write other shares
6165+        d.addCallback(lambda ignored:
6166+            mw.put_block(self.block, 1, self.salt))
6167+        def _check_next_write(results):
6168+            result, readvs = results
6169+            self.failUnless(result)
6170+            self.expected_checkstring = mw.get_checkstring()
6171+            self.failUnlessIn(0, readvs)
6172+            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
6173+        d.addCallback(_check_next_write)
6174+        # Add the other four shares
6175+        for i in xrange(2, 6):
6176+            d.addCallback(lambda ignored, i=i:
6177+                mw.put_block(self.block, i, self.salt))
6178+            d.addCallback(_check_next_write)
6179+        # Add the encrypted private key
6180+        d.addCallback(lambda ignored:
6181+            mw.put_encprivkey(self.encprivkey))
6182+        d.addCallback(_check_next_write)
6183+        # Add the block hash tree and share hash tree
6184+        d.addCallback(lambda ignored:
6185+            mw.put_blockhashes(self.block_hash_tree))
6186+        d.addCallback(_check_next_write)
6187+        d.addCallback(lambda ignored:
6188+            mw.put_sharehashes(self.share_hash_chain))
6189+        d.addCallback(_check_next_write)
6190+        # Add the root hash and the salt hash. This should change the
6191+        # checkstring, but not in a way that we'll be able to see right
6192+        # now, since the read vectors are applied before the write
6193+        # vectors.
6194+        d.addCallback(lambda ignored:
6195+            mw.put_root_hash(self.root_hash))
6196+        def _check_old_testv_after_new_one_is_written(results):
6197+            result, readvs = results
6198+            self.failUnless(result)
6199+            self.failUnlessIn(0, readvs)
6200+            self.failUnlessEqual(self.expected_checkstring,
6201+                                 readvs[0][0])
6202+            new_checkstring = mw.get_checkstring()
6203+            self.failIfEqual(new_checkstring,
6204+                             readvs[0][0])
6205+        d.addCallback(_check_old_testv_after_new_one_is_written)
6206+        # Now add the signature. This should succeed, meaning that the
6207+        # data gets written and the read vector matches what the writer
6208+        # thinks should be there.
6209+        d.addCallback(lambda ignored:
6210+            mw.put_signature(self.signature))
6211+        d.addCallback(_check_next_write)
6212+        # The checkstring remains the same for the rest of the process.
6213+        return d
6214+
6215+
6216+    def test_blockhashes_after_share_hash_chain(self):
6217+        mw = self._make_new_mw("si1", 0)
6218+        d = defer.succeed(None)
6219+        # Put everything up to and including the share hash chain
6220+        for i in xrange(6):
6221+            d.addCallback(lambda ignored, i=i:
6222+                mw.put_block(self.block, i, self.salt))
6223+        d.addCallback(lambda ignored:
6224+            mw.put_encprivkey(self.encprivkey))
6225+        d.addCallback(lambda ignored:
6226+            mw.put_blockhashes(self.block_hash_tree))
6227+        d.addCallback(lambda ignored:
6228+            mw.put_sharehashes(self.share_hash_chain))
6229+
6230+        # Now try to put the block hash tree again.
6231+        d.addCallback(lambda ignored:
6232+            self.shouldFail(LayoutInvalid, "test repeat salthashes",
6233+                            None,
6234+                            mw.put_blockhashes, self.block_hash_tree))
6235+        return d
6236+
6237+
6238+    def test_encprivkey_after_blockhashes(self):
6239+        mw = self._make_new_mw("si1", 0)
6240+        d = defer.succeed(None)
6241+        # Put everything up to and including the block hash tree
6242+        for i in xrange(6):
6243+            d.addCallback(lambda ignored, i=i:
6244+                mw.put_block(self.block, i, self.salt))
6245+        d.addCallback(lambda ignored:
6246+            mw.put_encprivkey(self.encprivkey))
6247+        d.addCallback(lambda ignored:
6248+            mw.put_blockhashes(self.block_hash_tree))
6249+        d.addCallback(lambda ignored:
6250+            self.shouldFail(LayoutInvalid, "out of order private key",
6251+                            None,
6252+                            mw.put_encprivkey, self.encprivkey))
6253+        return d
6254+
6255+
6256+    def test_share_hash_chain_after_signature(self):
6257+        mw = self._make_new_mw("si1", 0)
6258+        d = defer.succeed(None)
6259+        # Put everything up to and including the signature
6260+        for i in xrange(6):
6261+            d.addCallback(lambda ignored, i=i:
6262+                mw.put_block(self.block, i, self.salt))
6263+        d.addCallback(lambda ignored:
6264+            mw.put_encprivkey(self.encprivkey))
6265+        d.addCallback(lambda ignored:
6266+            mw.put_blockhashes(self.block_hash_tree))
6267+        d.addCallback(lambda ignored:
6268+            mw.put_sharehashes(self.share_hash_chain))
6269+        d.addCallback(lambda ignored:
6270+            mw.put_root_hash(self.root_hash))
6271+        d.addCallback(lambda ignored:
6272+            mw.put_signature(self.signature))
6273+        # Now try to put the share hash chain again. This should fail
6274+        d.addCallback(lambda ignored:
6275+            self.shouldFail(LayoutInvalid, "out of order share hash chain",
6276+                            None,
6277+                            mw.put_sharehashes, self.share_hash_chain))
6278+        return d
6279+
6280+
6281+    def test_signature_after_verification_key(self):
6282+        mw = self._make_new_mw("si1", 0)
6283+        d = defer.succeed(None)
6284+        # Put everything up to and including the verification key.
6285+        for i in xrange(6):
6286+            d.addCallback(lambda ignored, i=i:
6287+                mw.put_block(self.block, i, self.salt))
6288+        d.addCallback(lambda ignored:
6289+            mw.put_encprivkey(self.encprivkey))
6290+        d.addCallback(lambda ignored:
6291+            mw.put_blockhashes(self.block_hash_tree))
6292+        d.addCallback(lambda ignored:
6293+            mw.put_sharehashes(self.share_hash_chain))
6294+        d.addCallback(lambda ignored:
6295+            mw.put_root_hash(self.root_hash))
6296+        d.addCallback(lambda ignored:
6297+            mw.put_signature(self.signature))
6298+        d.addCallback(lambda ignored:
6299+            mw.put_verification_key(self.verification_key))
6300+        # Now try to put the signature again. This should fail
6301+        d.addCallback(lambda ignored:
6302+            self.shouldFail(LayoutInvalid, "signature after verification",
6303+                            None,
6304+                            mw.put_signature, self.signature))
6305+        return d
6306+
6307+
6308+    def test_uncoordinated_write(self):
6309+        # Make two mutable writers, both pointing to the same storage
6310+        # server, both at the same storage index, and try writing to the
6311+        # same share.
6312+        mw1 = self._make_new_mw("si1", 0)
6313+        mw2 = self._make_new_mw("si1", 0)
6314+        d = defer.succeed(None)
6315+        def _check_success(results):
6316+            result, readvs = results
6317+            self.failUnless(result)
6318+
6319+        def _check_failure(results):
6320+            result, readvs = results
6321+            self.failIf(result)
6322+
6323+        d.addCallback(lambda ignored:
6324+            mw1.put_block(self.block, 0, self.salt))
6325+        d.addCallback(_check_success)
6326+        d.addCallback(lambda ignored:
6327+            mw2.put_block(self.block, 0, self.salt))
6328+        d.addCallback(_check_failure)
6329+        return d
6330+
6331+
6332+    def test_invalid_salt_size(self):
6333+        # Salts need to be 16 bytes in size. Writes that attempt to
6334+        # write more or less than this should be rejected.
6335+        mw = self._make_new_mw("si1", 0)
6336+        invalid_salt = "a" * 17 # 17 bytes
6337+        another_invalid_salt = "b" * 15 # 15 bytes
6338+        d = defer.succeed(None)
6339+        d.addCallback(lambda ignored:
6340+            self.shouldFail(LayoutInvalid, "salt too big",
6341+                            None,
6342+                            mw.put_block, self.block, 0, invalid_salt))
6343+        d.addCallback(lambda ignored:
6344+            self.shouldFail(LayoutInvalid, "salt too small",
6345+                            None,
6346+                            mw.put_block, self.block, 0,
6347+                            another_invalid_salt))
6348+        return d
6349+
6350+
6351+    def test_write_test_vectors(self):
6352+        # If we give the write proxy a bogus test vector at
6353+        # any point during the process, it should fail to write.
6354+        mw = self._make_new_mw("si1", 0)
6355+        mw.set_checkstring("this is a lie")
6356+        # The initial write should be expecting to find the improbable
6357+        # checkstring above in place; finding nothing, it should fail.
6358+        d = defer.succeed(None)
6359+        d.addCallback(lambda ignored:
6360+            mw.put_block(self.block, 0, self.salt))
6361+        def _check_failure(results):
6362+            result, readv = results
6363+            self.failIf(result)
6364+        d.addCallback(_check_failure)
6365+        # Now set the checkstring to the empty string, which
6366+        # indicates that no share is there.
6367+        d.addCallback(lambda ignored:
6368+            mw.set_checkstring(""))
6369+        d.addCallback(lambda ignored:
6370+            mw.put_block(self.block, 0, self.salt))
6371+        def _check_success(results):
6372+            result, readv = results
6373+            self.failUnless(result)
6374+        d.addCallback(_check_success)
6375+        # Now set the checkstring to something wrong
6376+        d.addCallback(lambda ignored:
6377+            mw.set_checkstring("something wrong"))
6378+        # This should fail to do anything
6379+        d.addCallback(lambda ignored:
6380+            mw.put_block(self.block, 1, self.salt))
6381+        d.addCallback(_check_failure)
6382+        # Now set it back to what it should be.
6383+        d.addCallback(lambda ignored:
6384+            mw.set_checkstring(mw.get_checkstring()))
6385+        for i in xrange(1, 6):
6386+            d.addCallback(lambda ignored, i=i:
6387+                mw.put_block(self.block, i, self.salt))
6388+            d.addCallback(_check_success)
6389+        d.addCallback(lambda ignored:
6390+            mw.put_encprivkey(self.encprivkey))
6391+        d.addCallback(_check_success)
6392+        d.addCallback(lambda ignored:
6393+            mw.put_blockhashes(self.block_hash_tree))
6394+        d.addCallback(_check_success)
6395+        d.addCallback(lambda ignored:
6396+            mw.put_sharehashes(self.share_hash_chain))
6397+        d.addCallback(_check_success)
6398+        def _keep_old_checkstring(ignored):
6399+            self.old_checkstring = mw.get_checkstring()
6400+            mw.set_checkstring("foobarbaz")
6401+        d.addCallback(_keep_old_checkstring)
6402+        d.addCallback(lambda ignored:
6403+            mw.put_root_hash(self.root_hash))
6404+        d.addCallback(_check_failure)
6405+        d.addCallback(lambda ignored:
6406+            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
6407+        def _restore_old_checkstring(ignored):
6408+            mw.set_checkstring(self.old_checkstring)
6409+        d.addCallback(_restore_old_checkstring)
6410+        d.addCallback(lambda ignored:
6411+            mw.put_root_hash(self.root_hash))
6412+        d.addCallback(_check_success)
6413+        # The checkstring should have been set appropriately for us on
6414+        # the last write; if we try to change it to something else,
6415+        # that change should cause the verification key step to fail.
6416+        d.addCallback(lambda ignored:
6417+            mw.set_checkstring("something else"))
6418+        d.addCallback(lambda ignored:
6419+            mw.put_signature(self.signature))
6420+        d.addCallback(_check_failure)
6421+        d.addCallback(lambda ignored:
6422+            mw.set_checkstring(mw.get_checkstring()))
6423+        d.addCallback(lambda ignored:
6424+            mw.put_signature(self.signature))
6425+        d.addCallback(_check_success)
6426+        d.addCallback(lambda ignored:
6427+            mw.put_verification_key(self.verification_key))
6428+        d.addCallback(_check_success)
6429+        return d
6430+
6431+
6432+    def test_offset_only_set_on_success(self):
6433+        # The write proxy should be smart enough to detect when a write
6434+        # has failed, and to temper its definition of progress based on
6435+        # that.
6436+        mw = self._make_new_mw("si1", 0)
6437+        d = defer.succeed(None)
6438+        for i in xrange(1, 6):
6439+            d.addCallback(lambda ignored, i=i:
6440+                mw.put_block(self.block, i, self.salt))
6441+        def _break_checkstring(ignored):
6442+            self._old_checkstring = mw.get_checkstring()
6443+            mw.set_checkstring("foobarbaz")
6444+
6445+        def _fix_checkstring(ignored):
6446+            mw.set_checkstring(self._old_checkstring)
6447+
6448+        d.addCallback(_break_checkstring)
6449+
6450+        # Setting the encrypted private key shouldn't work now, which is
6451+        # to be expected and is tested elsewhere. We also want to make
6452+        # sure that we can't add the block hash tree after a failed
6453+        # write of this sort.
6454+        d.addCallback(lambda ignored:
6455+            mw.put_encprivkey(self.encprivkey))
6456+        d.addCallback(lambda ignored:
6457+            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
6458+                            None,
6459+                            mw.put_blockhashes, self.block_hash_tree))
6460+        d.addCallback(_fix_checkstring)
6461+        d.addCallback(lambda ignored:
6462+            mw.put_encprivkey(self.encprivkey))
6463+        d.addCallback(_break_checkstring)
6464+        d.addCallback(lambda ignored:
6465+            mw.put_blockhashes(self.block_hash_tree))
6466+        d.addCallback(lambda ignored:
6467+            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
6468+                            None,
6469+                            mw.put_sharehashes, self.share_hash_chain))
6470+        d.addCallback(_fix_checkstring)
6471+        d.addCallback(lambda ignored:
6472+            mw.put_blockhashes(self.block_hash_tree))
6473+        d.addCallback(_break_checkstring)
6474+        d.addCallback(lambda ignored:
6475+            mw.put_sharehashes(self.share_hash_chain))
6476+        d.addCallback(lambda ignored:
6477+            self.shouldFail(LayoutInvalid, "out-of-order root hash",
6478+                            None,
6479+                            mw.put_root_hash, self.root_hash))
6480+        d.addCallback(_fix_checkstring)
6481+        d.addCallback(lambda ignored:
6482+            mw.put_sharehashes(self.share_hash_chain))
6483+        d.addCallback(_break_checkstring)
6484+        d.addCallback(lambda ignored:
6485+            mw.put_root_hash(self.root_hash))
6486+        d.addCallback(lambda ignored:
6487+            self.shouldFail(LayoutInvalid, "out-of-order signature",
6488+                            None,
6489+                            mw.put_signature, self.signature))
6490+        d.addCallback(_fix_checkstring)
6491+        d.addCallback(lambda ignored:
6492+            mw.put_root_hash(self.root_hash))
6493+        d.addCallback(_break_checkstring)
6494+        d.addCallback(lambda ignored:
6495+            mw.put_signature(self.signature))
6496+        d.addCallback(lambda ignored:
6497+            self.shouldFail(LayoutInvalid, "out-of-order verification key",
6498+                            None,
6499+                            mw.put_verification_key,
6500+                            self.verification_key))
6501+        d.addCallback(_fix_checkstring)
6502+        d.addCallback(lambda ignored:
6503+            mw.put_signature(self.signature))
6504+        d.addCallback(_break_checkstring)
6505+        d.addCallback(lambda ignored:
6506+            mw.put_verification_key(self.verification_key))
6507+        d.addCallback(lambda ignored:
6508+            self.shouldFail(LayoutInvalid, "out-of-order finish",
6509+                            None,
6510+                            mw.finish_publishing))
6511+        return d
6512+
6513+
6514+    def serialize_blockhashes(self, blockhashes):
6515+        return "".join(blockhashes)
6516+
6517+
6518+    def serialize_sharehashes(self, sharehashes):
6519+        ret = "".join([struct.pack(">H32s", i, sharehashes[i])
6520+                        for i in sorted(sharehashes.keys())])
6521+        return ret
6522+
6523+
6524+    def test_write(self):
6525+        # This translates to a file with 6 6-byte segments, and with 2-byte
6526+        # blocks.
6527+        mw = self._make_new_mw("si1", 0)
6528+        mw2 = self._make_new_mw("si1", 1)
6529+        # Test writing some blocks.
6530+        read = self.ss.remote_slot_readv
6531+        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
6532+        written_block_size = 2 + len(self.salt)
6533+        written_block = self.block + self.salt
6534+        def _check_block_write(i, share):
6535+            self.failUnlessEqual(read("si1", [share], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
6536+                                {share: [written_block]})
6537+        d = defer.succeed(None)
6538+        for i in xrange(6):
6539+            d.addCallback(lambda ignored, i=i:
6540+                mw.put_block(self.block, i, self.salt))
6541+            d.addCallback(lambda ignored, i=i:
6542+                _check_block_write(i, 0))
6543+        # Now try the same thing, but with share 1 instead of share 0.
6544+        for i in xrange(6):
6545+            d.addCallback(lambda ignored, i=i:
6546+                mw2.put_block(self.block, i, self.salt))
6547+            d.addCallback(lambda ignored, i=i:
6548+                _check_block_write(i, 1))
6549+
6550+        # Next, we make a fake encrypted private key, and put it onto the
6551+        # storage server.
6552+        d.addCallback(lambda ignored:
6553+            mw.put_encprivkey(self.encprivkey))
6554+        expected_private_key_offset = expected_sharedata_offset + \
6555+                                      len(written_block) * 6
6556+        self.failUnlessEqual(len(self.encprivkey), 7)
6557+        d.addCallback(lambda ignored:
6558+            self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
6559+                                 {0: [self.encprivkey]}))
6560+
6561+        # Next, we put a fake block hash tree.
6562+        d.addCallback(lambda ignored:
6563+            mw.put_blockhashes(self.block_hash_tree))
6564+        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
6565+        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
6566+        d.addCallback(lambda ignored:
6567+            self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
6568+                                 {0: [self.block_hash_tree_s]}))
6569+
6570+        # Next, put a fake share hash chain
6571+        d.addCallback(lambda ignored:
6572+            mw.put_sharehashes(self.share_hash_chain))
6573+        expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
6574+        d.addCallback(lambda ignored:
6575+            self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
6576+                                 {0: [self.share_hash_chain_s]}))
6577+
6578+        # Next, we put what is supposed to be the root hash of
6579+        # our share hash tree but isn't       
6580+        d.addCallback(lambda ignored:
6581+            mw.put_root_hash(self.root_hash))
6582+        # The root hash gets inserted at byte 9 (its position is in the header,
6583+        # and is fixed).
6584+        def _check(ignored):
6585+            self.failUnlessEqual(read("si1", [0], [(9, 32)]),
6586+                                 {0: [self.root_hash]})
6587+        d.addCallback(_check)
6588+
6589+        # Next, we put a signature of the header block.
6590+        d.addCallback(lambda ignored:
6591+            mw.put_signature(self.signature))
6592+        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
6593+        self.failUnlessEqual(len(self.signature), 9)
6594+        d.addCallback(lambda ignored:
6595+            self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
6596+                                 {0: [self.signature]}))
6597+
6598+        # Next, we put the verification key
6599+        d.addCallback(lambda ignored:
6600+            mw.put_verification_key(self.verification_key))
6601+        expected_verification_key_offset = expected_signature_offset + len(self.signature)
6602+        self.failUnlessEqual(len(self.verification_key), 6)
6603+        d.addCallback(lambda ignored:
6604+            self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
6605+                                 {0: [self.verification_key]}))
6606+
6607+        def _check_signable(ignored):
6608+            # Make sure that the signable is what we think it should be.
6609+            signable = mw.get_signable()
6610+            verno, seq, roothash, k, n, segsize, datalen = \
6611+                                            struct.unpack(">BQ32sBBQQ",
6612+                                                          signable)
6613+            self.failUnlessEqual(verno, 1)
6614+            self.failUnlessEqual(seq, 0)
6615+            self.failUnlessEqual(roothash, self.root_hash)
6616+            self.failUnlessEqual(k, 3)
6617+            self.failUnlessEqual(n, 10)
6618+            self.failUnlessEqual(segsize, 6)
6619+            self.failUnlessEqual(datalen, 36)
6620+        d.addCallback(_check_signable)
6621+        # Next, we cause the offset table to be published.
6622+        d.addCallback(lambda ignored:
6623+            mw.finish_publishing())
6624+        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
6625+
6626+        def _check_offsets(ignored):
6627+            # Check the version number to make sure that it is correct.
6628+            expected_version_number = struct.pack(">B", 1)
6629+            self.failUnlessEqual(read("si1", [0], [(0, 1)]),
6630+                                 {0: [expected_version_number]})
6631+            # Check the sequence number to make sure that it is correct
6632+            expected_sequence_number = struct.pack(">Q", 0)
6633+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
6634+                                 {0: [expected_sequence_number]})
6635+            # Check that the encoding parameters (k, N, segement size, data
6636+            # length) are what they should be. These are  3, 10, 6, 36
6637+            expected_k = struct.pack(">B", 3)
6638+            self.failUnlessEqual(read("si1", [0], [(41, 1)]),
6639+                                 {0: [expected_k]})
6640+            expected_n = struct.pack(">B", 10)
6641+            self.failUnlessEqual(read("si1", [0], [(42, 1)]),
6642+                                 {0: [expected_n]})
6643+            expected_segment_size = struct.pack(">Q", 6)
6644+            self.failUnlessEqual(read("si1", [0], [(43, 8)]),
6645+                                 {0: [expected_segment_size]})
6646+            expected_data_length = struct.pack(">Q", 36)
6647+            self.failUnlessEqual(read("si1", [0], [(51, 8)]),
6648+                                 {0: [expected_data_length]})
6649+            expected_offset = struct.pack(">Q", expected_private_key_offset)
6650+            self.failUnlessEqual(read("si1", [0], [(59, 8)]),
6651+                                 {0: [expected_offset]})
6652+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
6653+            self.failUnlessEqual(read("si1", [0], [(67, 8)]),
6654+                                 {0: [expected_offset]})
6655+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
6656+            self.failUnlessEqual(read("si1", [0], [(75, 8)]),
6657+                                 {0: [expected_offset]})
6658+            expected_offset = struct.pack(">Q", expected_signature_offset)
6659+            self.failUnlessEqual(read("si1", [0], [(83, 8)]),
6660+                                 {0: [expected_offset]})
6661+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
6662+            self.failUnlessEqual(read("si1", [0], [(91, 8)]),
6663+                                 {0: [expected_offset]})
6664+            expected_offset = struct.pack(">Q", expected_eof_offset)
6665+            self.failUnlessEqual(read("si1", [0], [(99, 8)]),
6666+                                 {0: [expected_offset]})
6667+        d.addCallback(_check_offsets)
6668+        return d
6669+
6670+    def _make_new_mw(self, si, share, datalength=36):
6671+        # This is a file of size 36 bytes. Since it has a segment
6672+        # size of 6, we know that it has 6 byte segments, which will
6673+        # be split into blocks of 2 bytes because our FEC k
6674+        # parameter is 3.
6675+        mw = MDMFSlotWriteProxy(share, self.rref, si, self.secrets, 0, 3, 10,
6676+                                6, datalength)
6677+        return mw
6678+
6679+
6680+    def test_write_rejected_with_too_many_blocks(self):
6681+        mw = self._make_new_mw("si0", 0)
6682+
6683+        # Try writing too many blocks. We should not be able to write
6684+        # more than 6
6685+        # blocks into each share.
6686+        d = defer.succeed(None)
6687+        for i in xrange(6):
6688+            d.addCallback(lambda ignored, i=i:
6689+                mw.put_block(self.block, i, self.salt))
6690+        d.addCallback(lambda ignored:
6691+            self.shouldFail(LayoutInvalid, "too many blocks",
6692+                            None,
6693+                            mw.put_block, self.block, 7, self.salt))
6694+        return d
6695+
6696+
6697+    def test_write_rejected_with_invalid_salt(self):
6698+        # Try writing an invalid salt. Salts are 16 bytes -- any more or
6699+        # less should cause an error.
6700+        mw = self._make_new_mw("si1", 0)
6701+        bad_salt = "a" * 17 # 17 bytes
6702+        d = defer.succeed(None)
6703+        d.addCallback(lambda ignored:
6704+            self.shouldFail(LayoutInvalid, "test_invalid_salt",
6705+                            None, mw.put_block, self.block, 7, bad_salt))
6706+        return d
6707+
6708+
6709+    def test_write_rejected_with_invalid_root_hash(self):
6710+        # Try writing an invalid root hash. This should be SHA256d, and
6711+        # 32 bytes long as a result.
6712+        mw = self._make_new_mw("si2", 0)
6713+        # 17 bytes != 32 bytes
6714+        invalid_root_hash = "a" * 17
6715+        d = defer.succeed(None)
6716+        # Before this test can work, we need to put some blocks + salts,
6717+        # a block hash tree, and a share hash tree. Otherwise, we'll see
6718+        # failures that match what we are looking for, but are caused by
6719+        # the constraints imposed on operation ordering.
6720+        for i in xrange(6):
6721+            d.addCallback(lambda ignored, i=i:
6722+                mw.put_block(self.block, i, self.salt))
6723+        d.addCallback(lambda ignored:
6724+            mw.put_encprivkey(self.encprivkey))
6725+        d.addCallback(lambda ignored:
6726+            mw.put_blockhashes(self.block_hash_tree))
6727+        d.addCallback(lambda ignored:
6728+            mw.put_sharehashes(self.share_hash_chain))
6729+        d.addCallback(lambda ignored:
6730+            self.shouldFail(LayoutInvalid, "invalid root hash",
6731+                            None, mw.put_root_hash, invalid_root_hash))
6732+        return d
6733+
6734+
6735+    def test_write_rejected_with_invalid_blocksize(self):
6736+        # The blocksize implied by the writer that we get from
6737+        # _make_new_mw is 2bytes -- any more or any less than this
6738+        # should be cause for failure, unless it is the tail segment, in
6739+        # which case it may not be failure.
6740+        invalid_block = "a"
6741+        mw = self._make_new_mw("si3", 0, 33) # implies a tail segment with
6742+                                             # one byte blocks
6743+        # 1 bytes != 2 bytes
6744+        d = defer.succeed(None)
6745+        d.addCallback(lambda ignored, invalid_block=invalid_block:
6746+            self.shouldFail(LayoutInvalid, "test blocksize too small",
6747+                            None, mw.put_block, invalid_block, 0,
6748+                            self.salt))
6749+        invalid_block = invalid_block * 3
6750+        # 3 bytes != 2 bytes
6751+        d.addCallback(lambda ignored:
6752+            self.shouldFail(LayoutInvalid, "test blocksize too large",
6753+                            None,
6754+                            mw.put_block, invalid_block, 0, self.salt))
6755+        for i in xrange(5):
6756+            d.addCallback(lambda ignored, i=i:
6757+                mw.put_block(self.block, i, self.salt))
6758+        # Try to put an invalid tail segment
6759+        d.addCallback(lambda ignored:
6760+            self.shouldFail(LayoutInvalid, "test invalid tail segment",
6761+                            None,
6762+                            mw.put_block, self.block, 5, self.salt))
6763+        valid_block = "a"
6764+        d.addCallback(lambda ignored:
6765+            mw.put_block(valid_block, 5, self.salt))
6766+        return d
6767+
6768+
6769+    def test_write_enforces_order_constraints(self):
6770+        # We require that the MDMFSlotWriteProxy be interacted with in a
6771+        # specific way.
6772+        # That way is:
6773+        # 0: __init__
6774+        # 1: write blocks and salts
6775+        # 2: Write the encrypted private key
6776+        # 3: Write the block hashes
6777+        # 4: Write the share hashes
6778+        # 5: Write the root hash and salt hash
6779+        # 6: Write the signature and verification key
6780+        # 7: Write the file.
6781+        #
6782+        # Some of these can be performed out-of-order, and some can't.
6783+        # The dependencies that I want to test here are:
6784+        #  - Private key before block hashes
6785+        #  - share hashes and block hashes before root hash
6786+        #  - root hash before signature
6787+        #  - signature before verification key
6788+        mw0 = self._make_new_mw("si0", 0)
6789+        # Write some shares
6790+        d = defer.succeed(None)
6791+        for i in xrange(6):
6792+            d.addCallback(lambda ignored, i=i:
6793+                mw0.put_block(self.block, i, self.salt))
6794+        # Try to write the block hashes before writing the encrypted
6795+        # private key
6796+        d.addCallback(lambda ignored:
6797+            self.shouldFail(LayoutInvalid, "block hashes before key",
6798+                            None, mw0.put_blockhashes,
6799+                            self.block_hash_tree))
6800+
6801+        # Write the private key.
6802+        d.addCallback(lambda ignored:
6803+            mw0.put_encprivkey(self.encprivkey))
6804+
6805+
6806+        # Try to write the share hash chain without writing the block
6807+        # hash tree
6808+        d.addCallback(lambda ignored:
6809+            self.shouldFail(LayoutInvalid, "share hash chain before "
6810+                                           "salt hash tree",
6811+                            None,
6812+                            mw0.put_sharehashes, self.share_hash_chain))
6813+
6814+        # Try to write the root hash and without writing either the
6815+        # block hashes or the or the share hashes
6816+        d.addCallback(lambda ignored:
6817+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6818+                            None,
6819+                            mw0.put_root_hash, self.root_hash))
6820+
6821+        # Now write the block hashes and try again
6822+        d.addCallback(lambda ignored:
6823+            mw0.put_blockhashes(self.block_hash_tree))
6824+
6825+        d.addCallback(lambda ignored:
6826+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6827+                            None, mw0.put_root_hash, self.root_hash))
6828+
6829+        # We haven't yet put the root hash on the share, so we shouldn't
6830+        # be able to sign it.
6831+        d.addCallback(lambda ignored:
6832+            self.shouldFail(LayoutInvalid, "signature before root hash",
6833+                            None, mw0.put_signature, self.signature))
6834+
6835+        d.addCallback(lambda ignored:
6836+            self.failUnlessRaises(LayoutInvalid, mw0.get_signable))
6837+
6838+        # ..and, since that fails, we also shouldn't be able to put the
6839+        # verification key.
6840+        d.addCallback(lambda ignored:
6841+            self.shouldFail(LayoutInvalid, "key before signature",
6842+                            None, mw0.put_verification_key,
6843+                            self.verification_key))
6844+
6845+        # Now write the share hashes.
6846+        d.addCallback(lambda ignored:
6847+            mw0.put_sharehashes(self.share_hash_chain))
6848+        # We should be able to write the root hash now too
6849+        d.addCallback(lambda ignored:
6850+            mw0.put_root_hash(self.root_hash))
6851+
6852+        # We should still be unable to put the verification key
6853+        d.addCallback(lambda ignored:
6854+            self.shouldFail(LayoutInvalid, "key before signature",
6855+                            None, mw0.put_verification_key,
6856+                            self.verification_key))
6857+
6858+        d.addCallback(lambda ignored:
6859+            mw0.put_signature(self.signature))
6860+
6861+        # We shouldn't be able to write the offsets to the remote server
6862+        # until the offset table is finished; IOW, until we have written
6863+        # the verification key.
6864+        d.addCallback(lambda ignored:
6865+            self.shouldFail(LayoutInvalid, "offsets before verification key",
6866+                            None,
6867+                            mw0.finish_publishing))
6868+
6869+        d.addCallback(lambda ignored:
6870+            mw0.put_verification_key(self.verification_key))
6871+        return d
6872+
6873+
6874+    def test_end_to_end(self):
6875+        mw = self._make_new_mw("si1", 0)
6876+        # Write a share using the mutable writer, and make sure that the
6877+        # reader knows how to read everything back to us.
6878+        d = defer.succeed(None)
6879+        for i in xrange(6):
6880+            d.addCallback(lambda ignored, i=i:
6881+                mw.put_block(self.block, i, self.salt))
6882+        d.addCallback(lambda ignored:
6883+            mw.put_encprivkey(self.encprivkey))
6884+        d.addCallback(lambda ignored:
6885+            mw.put_blockhashes(self.block_hash_tree))
6886+        d.addCallback(lambda ignored:
6887+            mw.put_sharehashes(self.share_hash_chain))
6888+        d.addCallback(lambda ignored:
6889+            mw.put_root_hash(self.root_hash))
6890+        d.addCallback(lambda ignored:
6891+            mw.put_signature(self.signature))
6892+        d.addCallback(lambda ignored:
6893+            mw.put_verification_key(self.verification_key))
6894+        d.addCallback(lambda ignored:
6895+            mw.finish_publishing())
6896+
6897+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6898+        def _check_block_and_salt((block, salt)):
6899+            self.failUnlessEqual(block, self.block)
6900+            self.failUnlessEqual(salt, self.salt)
6901+
6902+        for i in xrange(6):
6903+            d.addCallback(lambda ignored, i=i:
6904+                mr.get_block_and_salt(i))
6905+            d.addCallback(_check_block_and_salt)
6906+
6907+        d.addCallback(lambda ignored:
6908+            mr.get_encprivkey())
6909+        d.addCallback(lambda encprivkey:
6910+            self.failUnlessEqual(self.encprivkey, encprivkey))
6911+
6912+        d.addCallback(lambda ignored:
6913+            mr.get_blockhashes())
6914+        d.addCallback(lambda blockhashes:
6915+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6916+
6917+        d.addCallback(lambda ignored:
6918+            mr.get_sharehashes())
6919+        d.addCallback(lambda sharehashes:
6920+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6921+
6922+        d.addCallback(lambda ignored:
6923+            mr.get_signature())
6924+        d.addCallback(lambda signature:
6925+            self.failUnlessEqual(signature, self.signature))
6926+
6927+        d.addCallback(lambda ignored:
6928+            mr.get_verification_key())
6929+        d.addCallback(lambda verification_key:
6930+            self.failUnlessEqual(verification_key, self.verification_key))
6931+
6932+        d.addCallback(lambda ignored:
6933+            mr.get_seqnum())
6934+        d.addCallback(lambda seqnum:
6935+            self.failUnlessEqual(seqnum, 0))
6936+
6937+        d.addCallback(lambda ignored:
6938+            mr.get_root_hash())
6939+        d.addCallback(lambda root_hash:
6940+            self.failUnlessEqual(self.root_hash, root_hash))
6941+
6942+        d.addCallback(lambda ignored:
6943+            mr.get_encoding_parameters())
6944+        def _check_encoding_parameters((k, n, segsize, datalen)):
6945+            self.failUnlessEqual(k, 3)
6946+            self.failUnlessEqual(n, 10)
6947+            self.failUnlessEqual(segsize, 6)
6948+            self.failUnlessEqual(datalen, 36)
6949+        d.addCallback(_check_encoding_parameters)
6950+
6951+        d.addCallback(lambda ignored:
6952+            mr.get_checkstring())
6953+        d.addCallback(lambda checkstring:
6954+            self.failUnlessEqual(checkstring, mw.get_checkstring()))
6955+        return d
6956+
6957+
6958+    def test_is_sdmf(self):
6959+        # The MDMFSlotReadProxy should also know how to read SDMF files,
6960+        # since it will encounter them on the grid. Callers use the
6961+        # is_sdmf method to test this.
6962+        self.write_sdmf_share_to_server("si1")
6963+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6964+        d = mr.is_sdmf()
6965+        d.addCallback(lambda issdmf:
6966+            self.failUnless(issdmf))
6967+        return d
6968+
6969+
6970+    def test_reads_sdmf(self):
6971+        # The slot read proxy should, naturally, know how to tell us
6972+        # about data in the SDMF format
6973+        self.write_sdmf_share_to_server("si1")
6974+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6975+        d = defer.succeed(None)
6976+        d.addCallback(lambda ignored:
6977+            mr.is_sdmf())
6978+        d.addCallback(lambda issdmf:
6979+            self.failUnless(issdmf))
6980+
6981+        # What do we need to read?
6982+        #  - The sharedata
6983+        #  - The salt
6984+        d.addCallback(lambda ignored:
6985+            mr.get_block_and_salt(0))
6986+        def _check_block_and_salt(results):
6987+            block, salt = results
6988+            # Our original file is 36 bytes long. Then each share is 12
6989+            # bytes in size. The share is composed entirely of the
6990+            # letter a. self.block contains 2 as, so 6 * self.block is
6991+            # what we are looking for.
6992+            self.failUnlessEqual(block, self.block * 6)
6993+            self.failUnlessEqual(salt, self.salt)
6994+        d.addCallback(_check_block_and_salt)
6995+
6996+        #  - The blockhashes
6997+        d.addCallback(lambda ignored:
6998+            mr.get_blockhashes())
6999+        d.addCallback(lambda blockhashes:
7000+            self.failUnlessEqual(self.block_hash_tree,
7001+                                 blockhashes,
7002+                                 blockhashes))
7003+        #  - The sharehashes
7004+        d.addCallback(lambda ignored:
7005+            mr.get_sharehashes())
7006+        d.addCallback(lambda sharehashes:
7007+            self.failUnlessEqual(self.share_hash_chain,
7008+                                 sharehashes))
7009+        #  - The keys
7010+        d.addCallback(lambda ignored:
7011+            mr.get_encprivkey())
7012+        d.addCallback(lambda encprivkey:
7013+            self.failUnlessEqual(encprivkey, self.encprivkey, encprivkey))
7014+        d.addCallback(lambda ignored:
7015+            mr.get_verification_key())
7016+        d.addCallback(lambda verification_key:
7017+            self.failUnlessEqual(verification_key,
7018+                                 self.verification_key,
7019+                                 verification_key))
7020+        #  - The signature
7021+        d.addCallback(lambda ignored:
7022+            mr.get_signature())
7023+        d.addCallback(lambda signature:
7024+            self.failUnlessEqual(signature, self.signature, signature))
7025+
7026+        #  - The sequence number
7027+        d.addCallback(lambda ignored:
7028+            mr.get_seqnum())
7029+        d.addCallback(lambda seqnum:
7030+            self.failUnlessEqual(seqnum, 0, seqnum))
7031+
7032+        #  - The root hash
7033+        d.addCallback(lambda ignored:
7034+            mr.get_root_hash())
7035+        d.addCallback(lambda root_hash:
7036+            self.failUnlessEqual(root_hash, self.root_hash, root_hash))
7037+        return d
7038+
7039+
7040+    def test_only_reads_one_segment_sdmf(self):
7041+        # SDMF shares have only one segment, so it doesn't make sense to
7042+        # read more segments than that. The reader should know this and
7043+        # complain if we try to do that.
7044+        self.write_sdmf_share_to_server("si1")
7045+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7046+        d = defer.succeed(None)
7047+        d.addCallback(lambda ignored:
7048+            mr.is_sdmf())
7049+        d.addCallback(lambda issdmf:
7050+            self.failUnless(issdmf))
7051+        d.addCallback(lambda ignored:
7052+            self.shouldFail(LayoutInvalid, "test bad segment",
7053+                            None,
7054+                            mr.get_block_and_salt, 1))
7055+        return d
7056+
7057+
7058+    def test_read_with_prefetched_mdmf_data(self):
7059+        # The MDMFSlotReadProxy will prefill certain fields if you pass
7060+        # it data that you have already fetched. This is useful for
7061+        # cases like the Servermap, which prefetches ~2kb of data while
7062+        # finding out which shares are on the remote peer so that it
7063+        # doesn't waste round trips.
7064+        mdmf_data = self.build_test_mdmf_share()
7065+        self.write_test_share_to_server("si1")
7066+        def _make_mr(ignored, length):
7067+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:length])
7068+            return mr
7069+
7070+        d = defer.succeed(None)
7071+        # This should be enough to fill in both the encoding parameters
7072+        # and the table of offsets, which will complete the version
7073+        # information tuple.
7074+        d.addCallback(_make_mr, 107)
7075+        d.addCallback(lambda mr:
7076+            mr.get_verinfo())
7077+        def _check_verinfo(verinfo):
7078+            self.failUnless(verinfo)
7079+            self.failUnlessEqual(len(verinfo), 9)
7080+            (seqnum,
7081+             root_hash,
7082+             salt_hash,
7083+             segsize,
7084+             datalen,
7085+             k,
7086+             n,
7087+             prefix,
7088+             offsets) = verinfo
7089+            self.failUnlessEqual(seqnum, 0)
7090+            self.failUnlessEqual(root_hash, self.root_hash)
7091+            self.failUnlessEqual(segsize, 6)
7092+            self.failUnlessEqual(datalen, 36)
7093+            self.failUnlessEqual(k, 3)
7094+            self.failUnlessEqual(n, 10)
7095+            expected_prefix = struct.pack(MDMFSIGNABLEHEADER,
7096+                                          1,
7097+                                          seqnum,
7098+                                          root_hash,
7099+                                          k,
7100+                                          n,
7101+                                          segsize,
7102+                                          datalen)
7103+            self.failUnlessEqual(expected_prefix, prefix)
7104+            self.failUnlessEqual(self.rref.read_count, 0)
7105+        d.addCallback(_check_verinfo)
7106+        # This is not enough data to read a block and a share, so the
7107+        # wrapper should attempt to read this from the remote server.
7108+        d.addCallback(_make_mr, 107)
7109+        d.addCallback(lambda mr:
7110+            mr.get_block_and_salt(0))
7111+        def _check_block_and_salt((block, salt)):
7112+            self.failUnlessEqual(block, self.block)
7113+            self.failUnlessEqual(salt, self.salt)
7114+            self.failUnlessEqual(self.rref.read_count, 1)
7115+        # This should be enough data to read one block.
7116+        d.addCallback(_make_mr, 249)
7117+        d.addCallback(lambda mr:
7118+            mr.get_block_and_salt(0))
7119+        d.addCallback(_check_block_and_salt)
7120+        return d
7121+
7122+
7123+    def test_read_with_prefetched_sdmf_data(self):
7124+        sdmf_data = self.build_test_sdmf_share()
7125+        self.write_sdmf_share_to_server("si1")
7126+        def _make_mr(ignored, length):
7127+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:length])
7128+            return mr
7129+
7130+        d = defer.succeed(None)
7131+        # This should be enough to get us the encoding parameters,
7132+        # offset table, and everything else we need to build a verinfo
7133+        # string.
7134+        d.addCallback(_make_mr, 107)
7135+        d.addCallback(lambda mr:
7136+            mr.get_verinfo())
7137+        def _check_verinfo(verinfo):
7138+            self.failUnless(verinfo)
7139+            self.failUnlessEqual(len(verinfo), 9)
7140+            (seqnum,
7141+             root_hash,
7142+             salt,
7143+             segsize,
7144+             datalen,
7145+             k,
7146+             n,
7147+             prefix,
7148+             offsets) = verinfo
7149+            self.failUnlessEqual(seqnum, 0)
7150+            self.failUnlessEqual(root_hash, self.root_hash)
7151+            self.failUnlessEqual(salt, self.salt)
7152+            self.failUnlessEqual(segsize, 36)
7153+            self.failUnlessEqual(datalen, 36)
7154+            self.failUnlessEqual(k, 3)
7155+            self.failUnlessEqual(n, 10)
7156+            expected_prefix = struct.pack(SIGNED_PREFIX,
7157+                                          0,
7158+                                          seqnum,
7159+                                          root_hash,
7160+                                          salt,
7161+                                          k,
7162+                                          n,
7163+                                          segsize,
7164+                                          datalen)
7165+            self.failUnlessEqual(expected_prefix, prefix)
7166+            self.failUnlessEqual(self.rref.read_count, 0)
7167+        d.addCallback(_check_verinfo)
7168+        # This shouldn't be enough to read any share data.
7169+        d.addCallback(_make_mr, 107)
7170+        d.addCallback(lambda mr:
7171+            mr.get_block_and_salt(0))
7172+        def _check_block_and_salt((block, salt)):
7173+            self.failUnlessEqual(block, self.block * 6)
7174+            self.failUnlessEqual(salt, self.salt)
7175+            # TODO: Fix the read routine so that it reads only the data
7176+            #       that it has cached if it can't read all of it.
7177+            self.failUnlessEqual(self.rref.read_count, 2)
7178+
7179+        # This should be enough to read share data.
7180+        d.addCallback(_make_mr, self.offsets['share_data'])
7181+        d.addCallback(lambda mr:
7182+            mr.get_block_and_salt(0))
7183+        d.addCallback(_check_block_and_salt)
7184+        return d
7185+
7186+
7187+    def test_read_with_empty_mdmf_file(self):
7188+        # Some tests upload a file with no contents to test things
7189+        # unrelated to the actual handling of the content of the file.
7190+        # The reader should behave intelligently in these cases.
7191+        self.write_test_share_to_server("si1", empty=True)
7192+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7193+        # We should be able to get the encoding parameters, and they
7194+        # should be correct.
7195+        d = defer.succeed(None)
7196+        d.addCallback(lambda ignored:
7197+            mr.get_encoding_parameters())
7198+        def _check_encoding_parameters(params):
7199+            self.failUnlessEqual(len(params), 4)
7200+            k, n, segsize, datalen = params
7201+            self.failUnlessEqual(k, 3)
7202+            self.failUnlessEqual(n, 10)
7203+            self.failUnlessEqual(segsize, 0)
7204+            self.failUnlessEqual(datalen, 0)
7205+        d.addCallback(_check_encoding_parameters)
7206+
7207+        # We should not be able to fetch a block, since there are no
7208+        # blocks to fetch
7209+        d.addCallback(lambda ignored:
7210+            self.shouldFail(LayoutInvalid, "get block on empty file",
7211+                            None,
7212+                            mr.get_block_and_salt, 0))
7213+        return d
7214+
7215+
7216+    def test_read_with_empty_sdmf_file(self):
7217+        self.write_sdmf_share_to_server("si1", empty=True)
7218+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7219+        # We should be able to get the encoding parameters, and they
7220+        # should be correct
7221+        d = defer.succeed(None)
7222+        d.addCallback(lambda ignored:
7223+            mr.get_encoding_parameters())
7224+        def _check_encoding_parameters(params):
7225+            self.failUnlessEqual(len(params), 4)
7226+            k, n, segsize, datalen = params
7227+            self.failUnlessEqual(k, 3)
7228+            self.failUnlessEqual(n, 10)
7229+            self.failUnlessEqual(segsize, 0)
7230+            self.failUnlessEqual(datalen, 0)
7231+        d.addCallback(_check_encoding_parameters)
7232+
7233+        # It does not make sense to get a block in this format, so we
7234+        # should not be able to.
7235+        d.addCallback(lambda ignored:
7236+            self.shouldFail(LayoutInvalid, "get block on an empty file",
7237+                            None,
7238+                            mr.get_block_and_salt, 0))
7239+        return d
7240+
7241+
7242+    def test_verinfo_with_sdmf_file(self):
7243+        self.write_sdmf_share_to_server("si1")
7244+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7245+        # We should be able to get the version information.
7246+        d = defer.succeed(None)
7247+        d.addCallback(lambda ignored:
7248+            mr.get_verinfo())
7249+        def _check_verinfo(verinfo):
7250+            self.failUnless(verinfo)
7251+            self.failUnlessEqual(len(verinfo), 9)
7252+            (seqnum,
7253+             root_hash,
7254+             salt,
7255+             segsize,
7256+             datalen,
7257+             k,
7258+             n,
7259+             prefix,
7260+             offsets) = verinfo
7261+            self.failUnlessEqual(seqnum, 0)
7262+            self.failUnlessEqual(root_hash, self.root_hash)
7263+            self.failUnlessEqual(salt, self.salt)
7264+            self.failUnlessEqual(segsize, 36)
7265+            self.failUnlessEqual(datalen, 36)
7266+            self.failUnlessEqual(k, 3)
7267+            self.failUnlessEqual(n, 10)
7268+            expected_prefix = struct.pack(">BQ32s16s BBQQ",
7269+                                          0,
7270+                                          seqnum,
7271+                                          root_hash,
7272+                                          salt,
7273+                                          k,
7274+                                          n,
7275+                                          segsize,
7276+                                          datalen)
7277+            self.failUnlessEqual(prefix, expected_prefix)
7278+            self.failUnlessEqual(offsets, self.offsets)
7279+        d.addCallback(_check_verinfo)
7280+        return d
7281+
7282+
7283+    def test_verinfo_with_mdmf_file(self):
7284+        self.write_test_share_to_server("si1")
7285+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7286+        d = defer.succeed(None)
7287+        d.addCallback(lambda ignored:
7288+            mr.get_verinfo())
7289+        def _check_verinfo(verinfo):
7290+            self.failUnless(verinfo)
7291+            self.failUnlessEqual(len(verinfo), 9)
7292+            (seqnum,
7293+             root_hash,
7294+             IV,
7295+             segsize,
7296+             datalen,
7297+             k,
7298+             n,
7299+             prefix,
7300+             offsets) = verinfo
7301+            self.failUnlessEqual(seqnum, 0)
7302+            self.failUnlessEqual(root_hash, self.root_hash)
7303+            self.failIf(IV)
7304+            self.failUnlessEqual(segsize, 6)
7305+            self.failUnlessEqual(datalen, 36)
7306+            self.failUnlessEqual(k, 3)
7307+            self.failUnlessEqual(n, 10)
7308+            expected_prefix = struct.pack(">BQ32s BBQQ",
7309+                                          1,
7310+                                          seqnum,
7311+                                          root_hash,
7312+                                          k,
7313+                                          n,
7314+                                          segsize,
7315+                                          datalen)
7316+            self.failUnlessEqual(prefix, expected_prefix)
7317+            self.failUnlessEqual(offsets, self.offsets)
7318+        d.addCallback(_check_verinfo)
7319+        return d
7320+
7321+
7322+    def test_reader_queue(self):
7323+        self.write_test_share_to_server('si1')
7324+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7325+        d1 = mr.get_block_and_salt(0, queue=True)
7326+        d2 = mr.get_blockhashes(queue=True)
7327+        d3 = mr.get_sharehashes(queue=True)
7328+        d4 = mr.get_signature(queue=True)
7329+        d5 = mr.get_verification_key(queue=True)
7330+        dl = defer.DeferredList([d1, d2, d3, d4, d5])
7331+        mr.flush()
7332+        def _print(results):
7333+            self.failUnlessEqual(len(results), 5)
7334+            # We have one read for version information and offsets, and
7335+            # one for everything else.
7336+            self.failUnlessEqual(self.rref.read_count, 2)
7337+            block, salt = results[0][1] # results[0] is a boolean that says
7338+                                           # whether or not the operation
7339+                                           # worked.
7340+            self.failUnlessEqual(self.block, block)
7341+            self.failUnlessEqual(self.salt, salt)
7342+
7343+            blockhashes = results[1][1]
7344+            self.failUnlessEqual(self.block_hash_tree, blockhashes)
7345+
7346+            sharehashes = results[2][1]
7347+            self.failUnlessEqual(self.share_hash_chain, sharehashes)
7348+
7349+            signature = results[3][1]
7350+            self.failUnlessEqual(self.signature, signature)
7351+
7352+            verification_key = results[4][1]
7353+            self.failUnlessEqual(self.verification_key, verification_key)
7354+        dl.addCallback(_print)
7355+        return dl
7356+
7357+
7358+    def test_sdmf_writer(self):
7359+        # Go through the motions of writing an SDMF share to the storage
7360+        # server. Then read the storage server to see that the share got
7361+        # written in the way that we think it should have.
7362+
7363+        # We do this first so that the necessary instance variables get
7364+        # set the way we want them for the tests below.
7365+        data = self.build_test_sdmf_share()
7366+        sdmfr = SDMFSlotWriteProxy(0,
7367+                                   self.rref,
7368+                                   "si1",
7369+                                   self.secrets,
7370+                                   0, 3, 10, 36, 36)
7371+        # Put the block and salt.
7372+        sdmfr.put_block(self.blockdata, 0, self.salt)
7373+
7374+        # Put the encprivkey
7375+        sdmfr.put_encprivkey(self.encprivkey)
7376+
7377+        # Put the block and share hash chains
7378+        sdmfr.put_blockhashes(self.block_hash_tree)
7379+        sdmfr.put_sharehashes(self.share_hash_chain)
7380+        sdmfr.put_root_hash(self.root_hash)
7381+
7382+        # Put the signature
7383+        sdmfr.put_signature(self.signature)
7384+
7385+        # Put the verification key
7386+        sdmfr.put_verification_key(self.verification_key)
7387+
7388+        # Now check to make sure that nothing has been written yet.
7389+        self.failUnlessEqual(self.rref.write_count, 0)
7390+
7391+        # Now finish publishing
7392+        d = sdmfr.finish_publishing()
7393+        def _then(ignored):
7394+            self.failUnlessEqual(self.rref.write_count, 1)
7395+            read = self.ss.remote_slot_readv
7396+            self.failUnlessEqual(read("si1", [0], [(0, len(data))]),
7397+                                 {0: [data]})
7398+        d.addCallback(_then)
7399+        return d
7400+
7401+
7402+    def test_sdmf_writer_preexisting_share(self):
7403+        data = self.build_test_sdmf_share()
7404+        self.write_sdmf_share_to_server("si1")
7405+
7406+        # Now there is a share on the storage server. To successfully
7407+        # write, we need to set the checkstring correctly. When we
7408+        # don't, no write should occur.
7409+        sdmfw = SDMFSlotWriteProxy(0,
7410+                                   self.rref,
7411+                                   "si1",
7412+                                   self.secrets,
7413+                                   1, 3, 10, 36, 36)
7414+        sdmfw.put_block(self.blockdata, 0, self.salt)
7415+
7416+        # Put the encprivkey
7417+        sdmfw.put_encprivkey(self.encprivkey)
7418+
7419+        # Put the block and share hash chains
7420+        sdmfw.put_blockhashes(self.block_hash_tree)
7421+        sdmfw.put_sharehashes(self.share_hash_chain)
7422+
7423+        # Put the root hash
7424+        sdmfw.put_root_hash(self.root_hash)
7425+
7426+        # Put the signature
7427+        sdmfw.put_signature(self.signature)
7428+
7429+        # Put the verification key
7430+        sdmfw.put_verification_key(self.verification_key)
7431+
7432+        # We shouldn't have a checkstring yet
7433+        self.failUnlessEqual(sdmfw.get_checkstring(), "")
7434+
7435+        d = sdmfw.finish_publishing()
7436+        def _then(results):
7437+            self.failIf(results[0])
7438+            # this is the correct checkstring
7439+            self._expected_checkstring = results[1][0][0]
7440+            return self._expected_checkstring
7441+
7442+        d.addCallback(_then)
7443+        d.addCallback(sdmfw.set_checkstring)
7444+        d.addCallback(lambda ignored:
7445+            sdmfw.get_checkstring())
7446+        d.addCallback(lambda checkstring:
7447+            self.failUnlessEqual(checkstring, self._expected_checkstring))
7448+        d.addCallback(lambda ignored:
7449+            sdmfw.finish_publishing())
7450+        def _then_again(results):
7451+            self.failUnless(results[0])
7452+            read = self.ss.remote_slot_readv
7453+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
7454+                                 {0: [struct.pack(">Q", 1)]})
7455+            self.failUnlessEqual(read("si1", [0], [(9, len(data) - 9)]),
7456+                                 {0: [data[9:]]})
7457+        d.addCallback(_then_again)
7458+        return d
7459+
7460+
7461 class Stats(unittest.TestCase):
7462 
7463     def setUp(self):
7464}
7465[mutable/publish.py: cleanup + simplification
7466Kevan Carstensen <kevan@isnotajoke.com>**20100702225554
7467 Ignore-this: 36a58424ceceffb1ddc55cc5934399e2
7468] {
7469hunk ./src/allmydata/mutable/publish.py 19
7470      UncoordinatedWriteError, NotEnoughServersError
7471 from allmydata.mutable.servermap import ServerMap
7472 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
7473-     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
7474+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy, \
7475+     SDMFSlotWriteProxy
7476 
7477 KiB = 1024
7478 DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
7479hunk ./src/allmydata/mutable/publish.py 24
7480+PUSHING_BLOCKS_STATE = 0
7481+PUSHING_EVERYTHING_ELSE_STATE = 1
7482+DONE_STATE = 2
7483 
7484 class PublishStatus:
7485     implements(IPublishStatus)
7486hunk ./src/allmydata/mutable/publish.py 229
7487 
7488         self.bad_share_checkstrings = {}
7489 
7490+        # This is set at the last step of the publishing process.
7491+        self.versioninfo = ""
7492+
7493         # we use the servermap to populate the initial goal: this way we will
7494         # try to update each existing share in place.
7495         for (peerid, shnum) in self._servermap.servermap:
7496hunk ./src/allmydata/mutable/publish.py 245
7497             self.bad_share_checkstrings[key] = old_checkstring
7498             self.connections[peerid] = self._servermap.connections[peerid]
7499 
7500-        # Now, the process dovetails -- if this is an SDMF file, we need
7501-        # to write an SDMF file. Otherwise, we need to write an MDMF
7502-        # file.
7503-        if self._version == MDMF_VERSION:
7504-            return self._publish_mdmf()
7505-        else:
7506-            return self._publish_sdmf()
7507-        #return self.done_deferred
7508-
7509-    def _publish_mdmf(self):
7510-        # Next, we find homes for all of the shares that we don't have
7511-        # homes for yet.
7512         # TODO: Make this part do peer selection.
7513         self.update_goal()
7514         self.writers = {}
7515hunk ./src/allmydata/mutable/publish.py 248
7516-        # For each (peerid, shnum) in self.goal, we make an
7517-        # MDMFSlotWriteProxy for that peer. We'll use this to write
7518+        if self._version == MDMF_VERSION:
7519+            writer_class = MDMFSlotWriteProxy
7520+        else:
7521+            writer_class = SDMFSlotWriteProxy
7522+
7523+        # For each (peerid, shnum) in self.goal, we make a
7524+        # write proxy for that peer. We'll use this to write
7525         # shares to the peer.
7526         for key in self.goal:
7527             peerid, shnum = key
7528hunk ./src/allmydata/mutable/publish.py 263
7529             cancel_secret = self._node.get_cancel_secret(peerid)
7530             secrets = (write_enabler, renew_secret, cancel_secret)
7531 
7532-            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
7533-                                                      self.connections[peerid],
7534-                                                      self._storage_index,
7535-                                                      secrets,
7536-                                                      self._new_seqnum,
7537-                                                      self.required_shares,
7538-                                                      self.total_shares,
7539-                                                      self.segment_size,
7540-                                                      len(self.newdata))
7541+            self.writers[shnum] =  writer_class(shnum,
7542+                                                self.connections[peerid],
7543+                                                self._storage_index,
7544+                                                secrets,
7545+                                                self._new_seqnum,
7546+                                                self.required_shares,
7547+                                                self.total_shares,
7548+                                                self.segment_size,
7549+                                                len(self.newdata))
7550+            self.writers[shnum].peerid = peerid
7551             if (peerid, shnum) in self._servermap.servermap:
7552                 old_versionid, old_timestamp = self._servermap.servermap[key]
7553                 (old_seqnum, old_root_hash, old_salt, old_segsize,
7554hunk ./src/allmydata/mutable/publish.py 278
7555                  old_datalength, old_k, old_N, old_prefix,
7556                  old_offsets_tuple) = old_versionid
7557-                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
7558+                self.writers[shnum].set_checkstring(old_seqnum,
7559+                                                    old_root_hash,
7560+                                                    old_salt)
7561+            elif (peerid, shnum) in self.bad_share_checkstrings:
7562+                old_checkstring = self.bad_share_checkstrings[(peerid, shnum)]
7563+                self.writers[shnum].set_checkstring(old_checkstring)
7564+
7565+        # Our remote shares will not have a complete checkstring until
7566+        # after we are done writing share data and have started to write
7567+        # blocks. In the meantime, we need to know what to look for when
7568+        # writing, so that we can detect UncoordinatedWriteErrors.
7569+        self._checkstring = self.writers.values()[0].get_checkstring()
7570 
7571         # Now, we start pushing shares.
7572         self._status.timings["setup"] = time.time() - self._started
7573hunk ./src/allmydata/mutable/publish.py 293
7574-        def _start_pushing(res):
7575-            self._started_pushing = time.time()
7576-            return res
7577-
7578         # First, we encrypt, encode, and publish the shares that we need
7579         # to encrypt, encode, and publish.
7580 
7581hunk ./src/allmydata/mutable/publish.py 306
7582 
7583         d = defer.succeed(None)
7584         self.log("Starting push")
7585-        for i in xrange(self.num_segments - 1):
7586-            d.addCallback(lambda ignored, i=i:
7587-                self.push_segment(i))
7588-            d.addCallback(self._turn_barrier)
7589-        # We have at least one segment, so we will have a tail segment
7590-        if self.num_segments > 0:
7591-            d.addCallback(lambda ignored:
7592-                self.push_tail_segment())
7593-
7594-        d.addCallback(lambda ignored:
7595-            self.push_encprivkey())
7596-        d.addCallback(lambda ignored:
7597-            self.push_blockhashes())
7598-        d.addCallback(lambda ignored:
7599-            self.push_sharehashes())
7600-        d.addCallback(lambda ignored:
7601-            self.push_toplevel_hashes_and_signature())
7602-        d.addCallback(lambda ignored:
7603-            self.finish_publishing())
7604-        return d
7605-
7606-
7607-    def _publish_sdmf(self):
7608-        self._status.timings["setup"] = time.time() - self._started
7609-        self.salt = os.urandom(16)
7610 
7611hunk ./src/allmydata/mutable/publish.py 307
7612-        d = self._encrypt_and_encode()
7613-        d.addCallback(self._generate_shares)
7614-        def _start_pushing(res):
7615-            self._started_pushing = time.time()
7616-            return res
7617-        d.addCallback(_start_pushing)
7618-        d.addCallback(self.loop) # trigger delivery
7619-        d.addErrback(self._fatal_error)
7620+        self._state = PUSHING_BLOCKS_STATE
7621+        self._push()
7622 
7623         return self.done_deferred
7624 
7625hunk ./src/allmydata/mutable/publish.py 327
7626                                                   segment_size)
7627         else:
7628             self.num_segments = 0
7629+
7630+        self.log("building encoding parameters for file")
7631+        self.log("got segsize %d" % self.segment_size)
7632+        self.log("got %d segments" % self.num_segments)
7633+
7634         if self._version == SDMF_VERSION:
7635             assert self.num_segments in (0, 1) # SDMF
7636hunk ./src/allmydata/mutable/publish.py 334
7637-            return
7638         # calculate the tail segment size.
7639hunk ./src/allmydata/mutable/publish.py 335
7640-        self.tail_segment_size = len(self.newdata) % segment_size
7641 
7642hunk ./src/allmydata/mutable/publish.py 336
7643-        if self.tail_segment_size == 0:
7644+        if segment_size and self.newdata:
7645+            self.tail_segment_size = len(self.newdata) % segment_size
7646+        else:
7647+            self.tail_segment_size = 0
7648+
7649+        if self.tail_segment_size == 0 and segment_size:
7650             # The tail segment is the same size as the other segments.
7651             self.tail_segment_size = segment_size
7652 
7653hunk ./src/allmydata/mutable/publish.py 345
7654-        # We'll make an encoder ahead-of-time for the normal-sized
7655-        # segments (defined as any segment of segment_size size.
7656-        # (the part of the code that puts the tail segment will make its
7657-        #  own encoder for that part)
7658+        # Make FEC encoders
7659         fec = codec.CRSEncoder()
7660         fec.set_params(self.segment_size,
7661                        self.required_shares, self.total_shares)
7662hunk ./src/allmydata/mutable/publish.py 352
7663         self.piece_size = fec.get_block_size()
7664         self.fec = fec
7665 
7666+        if self.tail_segment_size == self.segment_size:
7667+            self.tail_fec = self.fec
7668+        else:
7669+            tail_fec = codec.CRSEncoder()
7670+            tail_fec.set_params(self.tail_segment_size,
7671+                                self.required_shares,
7672+                                self.total_shares)
7673+            self.tail_fec = tail_fec
7674+
7675+        self._current_segment = 0
7676+
7677+
7678+    def _push(self, ignored=None):
7679+        """
7680+        I manage state transitions. In particular, I see that we still
7681+        have a good enough number of writers to complete the upload
7682+        successfully.
7683+        """
7684+        # Can we still successfully publish this file?
7685+        # TODO: Keep track of outstanding queries before aborting the
7686+        #       process.
7687+        if len(self.writers) <= self.required_shares or self.surprised:
7688+            return self._failure()
7689+
7690+        # Figure out what we need to do next. Each of these needs to
7691+        # return a deferred so that we don't block execution when this
7692+        # is first called in the upload method.
7693+        if self._state == PUSHING_BLOCKS_STATE:
7694+            return self.push_segment(self._current_segment)
7695+
7696+        # XXX: Do we want more granularity in states? Is that useful at
7697+        #      all?
7698+        #      Yes -- quicker reaction to UCW.
7699+        elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
7700+            return self.push_everything_else()
7701+
7702+        # If we make it to this point, we were successful in placing the
7703+        # file.
7704+        return self._done(None)
7705+
7706 
7707     def push_segment(self, segnum):
7708hunk ./src/allmydata/mutable/publish.py 394
7709+        if self.num_segments == 0 and self._version == SDMF_VERSION:
7710+            self._add_dummy_salts()
7711+
7712+        if segnum == self.num_segments:
7713+            # We don't have any more segments to push.
7714+            self._state = PUSHING_EVERYTHING_ELSE_STATE
7715+            return self._push()
7716+
7717+        d = self._encode_segment(segnum)
7718+        d.addCallback(self._push_segment, segnum)
7719+        def _increment_segnum(ign):
7720+            self._current_segment += 1
7721+        # XXX: I don't think we need to do addBoth here -- any errBacks
7722+        # should be handled within push_segment.
7723+        d.addBoth(_increment_segnum)
7724+        d.addBoth(self._push)
7725+
7726+
7727+    def _add_dummy_salts(self):
7728+        """
7729+        SDMF files need a salt even if they're empty, or the signature
7730+        won't make sense. This method adds a dummy salt to each of our
7731+        SDMF writers so that they can write the signature later.
7732+        """
7733+        salt = os.urandom(16)
7734+        assert self._version == SDMF_VERSION
7735+
7736+        for writer in self.writers.itervalues():
7737+            writer.put_salt(salt)
7738+
7739+
7740+    def _encode_segment(self, segnum):
7741+        """
7742+        I encrypt and encode the segment segnum.
7743+        """
7744         started = time.time()
7745hunk ./src/allmydata/mutable/publish.py 430
7746-        segsize = self.segment_size
7747+
7748+        if segnum + 1 == self.num_segments:
7749+            segsize = self.tail_segment_size
7750+        else:
7751+            segsize = self.segment_size
7752+
7753+
7754+        offset = self.segment_size * segnum
7755+        length = segsize + offset
7756         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
7757hunk ./src/allmydata/mutable/publish.py 440
7758-        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
7759+        data = self.newdata[offset:length]
7760         assert len(data) == segsize
7761 
7762         salt = os.urandom(16)
7763hunk ./src/allmydata/mutable/publish.py 455
7764         started = now
7765 
7766         # now apply FEC
7767+        if segnum + 1 == self.num_segments:
7768+            fec = self.tail_fec
7769+        else:
7770+            fec = self.fec
7771 
7772         self._status.set_status("Encoding")
7773         crypttext_pieces = [None] * self.required_shares
7774hunk ./src/allmydata/mutable/publish.py 462
7775-        piece_size = self.piece_size
7776+        piece_size = fec.get_block_size()
7777         for i in range(len(crypttext_pieces)):
7778             offset = i * piece_size
7779             piece = crypttext[offset:offset+piece_size]
7780hunk ./src/allmydata/mutable/publish.py 469
7781             piece = piece + "\x00"*(piece_size - len(piece)) # padding
7782             crypttext_pieces[i] = piece
7783             assert len(piece) == piece_size
7784-        d = self.fec.encode(crypttext_pieces)
7785+        d = fec.encode(crypttext_pieces)
7786         def _done_encoding(res):
7787             elapsed = time.time() - started
7788             self._status.timings["encode"] = elapsed
7789hunk ./src/allmydata/mutable/publish.py 473
7790-            return res
7791+            return (res, salt)
7792         d.addCallback(_done_encoding)
7793hunk ./src/allmydata/mutable/publish.py 475
7794-
7795-        def _push_shares_and_salt(results):
7796-            shares, shareids = results
7797-            dl = []
7798-            for i in xrange(len(shares)):
7799-                sharedata = shares[i]
7800-                shareid = shareids[i]
7801-                block_hash = hashutil.block_hash(salt + sharedata)
7802-                self.blockhashes[shareid].append(block_hash)
7803-
7804-                # find the writer for this share
7805-                d = self.writers[shareid].put_block(sharedata, segnum, salt)
7806-                dl.append(d)
7807-            # TODO: Naturally, we need to check on the results of these.
7808-            return defer.DeferredList(dl)
7809-        d.addCallback(_push_shares_and_salt)
7810         return d
7811 
7812 
7813hunk ./src/allmydata/mutable/publish.py 478
7814-    def push_tail_segment(self):
7815-        # This is essentially the same as push_segment, except that we
7816-        # don't use the cached encoder that we use elsewhere.
7817-        self.log("Pushing tail segment")
7818+    def _push_segment(self, encoded_and_salt, segnum):
7819+        """
7820+        I push (data, salt) as segment number segnum.
7821+        """
7822+        results, salt = encoded_and_salt
7823+        shares, shareids = results
7824         started = time.time()
7825hunk ./src/allmydata/mutable/publish.py 485
7826-        segsize = self.segment_size
7827-        data = self.newdata[segsize * (self.num_segments-1):]
7828-        assert len(data) == self.tail_segment_size
7829-        salt = os.urandom(16)
7830-
7831-        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
7832-        enc = AES(key)
7833-        crypttext = enc.process(data)
7834-        assert len(crypttext) == len(data)
7835+        dl = []
7836+        for i in xrange(len(shares)):
7837+            sharedata = shares[i]
7838+            shareid = shareids[i]
7839+            if self._version == MDMF_VERSION:
7840+                hashed = salt + sharedata
7841+            else:
7842+                hashed = sharedata
7843+            block_hash = hashutil.block_hash(hashed)
7844+            self.blockhashes[shareid].append(block_hash)
7845 
7846hunk ./src/allmydata/mutable/publish.py 496
7847-        now = time.time()
7848-        self._status.timings['encrypt'] = now - started
7849-        started = now
7850+            # find the writer for this share
7851+            writer = self.writers[shareid]
7852+            d = writer.put_block(sharedata, segnum, salt)
7853+            d.addCallback(self._got_write_answer, writer, started)
7854+            d.addErrback(self._connection_problem, writer)
7855+            dl.append(d)
7856+            # TODO: Naturally, we need to check on the results of these.
7857+        return defer.DeferredList(dl)
7858 
7859hunk ./src/allmydata/mutable/publish.py 505
7860-        self._status.set_status("Encoding")
7861-        tail_fec = codec.CRSEncoder()
7862-        tail_fec.set_params(self.tail_segment_size,
7863-                            self.required_shares,
7864-                            self.total_shares)
7865 
7866hunk ./src/allmydata/mutable/publish.py 506
7867-        crypttext_pieces = [None] * self.required_shares
7868-        piece_size = tail_fec.get_block_size()
7869-        for i in range(len(crypttext_pieces)):
7870-            offset = i * piece_size
7871-            piece = crypttext[offset:offset+piece_size]
7872-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
7873-            crypttext_pieces[i] = piece
7874-            assert len(piece) == piece_size
7875-        d = tail_fec.encode(crypttext_pieces)
7876-        def _push_shares_and_salt(results):
7877-            shares, shareids = results
7878-            dl = []
7879-            for i in xrange(len(shares)):
7880-                sharedata = shares[i]
7881-                shareid = shareids[i]
7882-                block_hash = hashutil.block_hash(salt + sharedata)
7883-                self.blockhashes[shareid].append(block_hash)
7884-                # find the writer for this share
7885-                d = self.writers[shareid].put_block(sharedata,
7886-                                                    self.num_segments - 1,
7887-                                                    salt)
7888-                dl.append(d)
7889-            # TODO: Naturally, we need to check on the results of these.
7890-            return defer.DeferredList(dl)
7891-        d.addCallback(_push_shares_and_salt)
7892+    def push_everything_else(self):
7893+        """
7894+        I put everything else associated with a share.
7895+        """
7896+        encprivkey = self._encprivkey
7897+        d = self.push_encprivkey()
7898+        d.addCallback(self.push_blockhashes)
7899+        d.addCallback(self.push_sharehashes)
7900+        d.addCallback(self.push_toplevel_hashes_and_signature)
7901+        d.addCallback(self.finish_publishing)
7902+        def _change_state(ignored):
7903+            self._state = DONE_STATE
7904+        d.addCallback(_change_state)
7905+        d.addCallback(self._push)
7906         return d
7907 
7908 
7909hunk ./src/allmydata/mutable/publish.py 527
7910         started = time.time()
7911         encprivkey = self._encprivkey
7912         dl = []
7913-        def _spy_on_writer(results):
7914-            print results
7915-            return results
7916-        for shnum, writer in self.writers.iteritems():
7917+        for writer in self.writers.itervalues():
7918             d = writer.put_encprivkey(encprivkey)
7919hunk ./src/allmydata/mutable/publish.py 529
7920+            d.addCallback(self._got_write_answer, writer, started)
7921+            d.addErrback(self._connection_problem, writer)
7922             dl.append(d)
7923         d = defer.DeferredList(dl)
7924         return d
7925hunk ./src/allmydata/mutable/publish.py 536
7926 
7927 
7928-    def push_blockhashes(self):
7929+    def push_blockhashes(self, ignored):
7930         started = time.time()
7931         dl = []
7932hunk ./src/allmydata/mutable/publish.py 539
7933-        def _spy_on_results(results):
7934-            print results
7935-            return results
7936         self.sharehash_leaves = [None] * len(self.blockhashes)
7937         for shnum, blockhashes in self.blockhashes.iteritems():
7938             t = hashtree.HashTree(blockhashes)
7939hunk ./src/allmydata/mutable/publish.py 545
7940             self.blockhashes[shnum] = list(t)
7941             # set the leaf for future use.
7942             self.sharehash_leaves[shnum] = t[0]
7943-            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
7944+            writer = self.writers[shnum]
7945+            d = writer.put_blockhashes(self.blockhashes[shnum])
7946+            d.addCallback(self._got_write_answer, writer, started)
7947+            d.addErrback(self._connection_problem, self.writers[shnum])
7948             dl.append(d)
7949         d = defer.DeferredList(dl)
7950         return d
7951hunk ./src/allmydata/mutable/publish.py 554
7952 
7953 
7954-    def push_sharehashes(self):
7955+    def push_sharehashes(self, ignored):
7956+        started = time.time()
7957         share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
7958         share_hash_chain = {}
7959         ds = []
7960hunk ./src/allmydata/mutable/publish.py 559
7961-        def _spy_on_results(results):
7962-            print results
7963-            return results
7964         for shnum in xrange(len(self.sharehash_leaves)):
7965             needed_indices = share_hash_tree.needed_hashes(shnum)
7966             self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
7967hunk ./src/allmydata/mutable/publish.py 563
7968                                              for i in needed_indices] )
7969-            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
7970+            writer = self.writers[shnum]
7971+            d = writer.put_sharehashes(self.sharehashes[shnum])
7972+            d.addCallback(self._got_write_answer, writer, started)
7973+            d.addErrback(self._connection_problem, writer)
7974             ds.append(d)
7975         self.root_hash = share_hash_tree[0]
7976         d = defer.DeferredList(ds)
7977hunk ./src/allmydata/mutable/publish.py 573
7978         return d
7979 
7980 
7981-    def push_toplevel_hashes_and_signature(self):
7982+    def push_toplevel_hashes_and_signature(self, ignored):
7983         # We need to to three things here:
7984         #   - Push the root hash and salt hash
7985         #   - Get the checkstring of the resulting layout; sign that.
7986hunk ./src/allmydata/mutable/publish.py 578
7987         #   - Push the signature
7988+        started = time.time()
7989         ds = []
7990hunk ./src/allmydata/mutable/publish.py 580
7991-        def _spy_on_results(results):
7992-            print results
7993-            return results
7994         for shnum in xrange(self.total_shares):
7995hunk ./src/allmydata/mutable/publish.py 581
7996-            d = self.writers[shnum].put_root_hash(self.root_hash)
7997+            writer = self.writers[shnum]
7998+            d = writer.put_root_hash(self.root_hash)
7999+            d.addCallback(self._got_write_answer, writer, started)
8000             ds.append(d)
8001         d = defer.DeferredList(ds)
8002hunk ./src/allmydata/mutable/publish.py 586
8003-        def _make_and_place_signature(ignored):
8004-            signable = self.writers[0].get_signable()
8005-            self.signature = self._privkey.sign(signable)
8006-
8007-            ds = []
8008-            for (shnum, writer) in self.writers.iteritems():
8009-                d = writer.put_signature(self.signature)
8010-                ds.append(d)
8011-            return defer.DeferredList(ds)
8012-        d.addCallback(_make_and_place_signature)
8013+        d.addCallback(self._update_checkstring)
8014+        d.addCallback(self._make_and_place_signature)
8015         return d
8016 
8017 
8018hunk ./src/allmydata/mutable/publish.py 591
8019-    def finish_publishing(self):
8020+    def _update_checkstring(self, ignored):
8021+        """
8022+        After putting the root hash, MDMF files will have the
8023+        checkstring written to the storage server. This means that we
8024+        can update our copy of the checkstring so we can detect
8025+        uncoordinated writes. SDMF files will have the same checkstring,
8026+        so we need not do anything.
8027+        """
8028+        self._checkstring = self.writers.values()[0].get_checkstring()
8029+
8030+
8031+    def _make_and_place_signature(self, ignored):
8032+        """
8033+        I create and place the signature.
8034+        """
8035+        started = time.time()
8036+        signable = self.writers[0].get_signable()
8037+        self.signature = self._privkey.sign(signable)
8038+
8039+        ds = []
8040+        for (shnum, writer) in self.writers.iteritems():
8041+            d = writer.put_signature(self.signature)
8042+            d.addCallback(self._got_write_answer, writer, started)
8043+            d.addErrback(self._connection_problem, writer)
8044+            ds.append(d)
8045+        return defer.DeferredList(ds)
8046+
8047+
8048+    def finish_publishing(self, ignored):
8049         # We're almost done -- we just need to put the verification key
8050         # and the offsets
8051hunk ./src/allmydata/mutable/publish.py 622
8052+        started = time.time()
8053         ds = []
8054         verification_key = self._pubkey.serialize()
8055 
8056hunk ./src/allmydata/mutable/publish.py 626
8057-        def _spy_on_results(results):
8058-            print results
8059-            return results
8060+
8061+        # TODO: Bad, since we remove from this same dict. We need to
8062+        # make a copy, or just use a non-iterated value.
8063         for (shnum, writer) in self.writers.iteritems():
8064             d = writer.put_verification_key(verification_key)
8065hunk ./src/allmydata/mutable/publish.py 631
8066+            d.addCallback(self._got_write_answer, writer, started)
8067+            d.addCallback(self._record_verinfo)
8068             d.addCallback(lambda ignored, writer=writer:
8069                 writer.finish_publishing())
8070hunk ./src/allmydata/mutable/publish.py 635
8071+            d.addCallback(self._got_write_answer, writer, started)
8072+            d.addErrback(self._connection_problem, writer)
8073             ds.append(d)
8074         return defer.DeferredList(ds)
8075 
8076hunk ./src/allmydata/mutable/publish.py 641
8077 
8078-    def _turn_barrier(self, res):
8079-        # putting this method in a Deferred chain imposes a guaranteed
8080-        # reactor turn between the pre- and post- portions of that chain.
8081-        # This can be useful to limit memory consumption: since Deferreds do
8082-        # not do tail recursion, code which uses defer.succeed(result) for
8083-        # consistency will cause objects to live for longer than you might
8084-        # normally expect.
8085-        return fireEventually(res)
8086+    def _record_verinfo(self, ignored):
8087+        self.versioninfo = self.writers.values()[0].get_verinfo()
8088 
8089 
8090hunk ./src/allmydata/mutable/publish.py 645
8091-    def _fatal_error(self, f):
8092-        self.log("error during loop", failure=f, level=log.UNUSUAL)
8093-        self._done(f)
8094+    def _connection_problem(self, f, writer):
8095+        """
8096+        We ran into a connection problem while working with writer, and
8097+        need to deal with that.
8098+        """
8099+        self.log("found problem: %s" % str(f))
8100+        self._last_failure = f
8101+        del(self.writers[writer.shnum])
8102 
8103hunk ./src/allmydata/mutable/publish.py 654
8104-    def _update_status(self):
8105-        self._status.set_status("Sending Shares: %d placed out of %d, "
8106-                                "%d messages outstanding" %
8107-                                (len(self.placed),
8108-                                 len(self.goal),
8109-                                 len(self.outstanding)))
8110-        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
8111 
8112     def loop(self, ignored=None):
8113         self.log("entering loop", level=log.NOISY)
8114hunk ./src/allmydata/mutable/publish.py 778
8115             self.log_goal(self.goal, "after update: ")
8116 
8117 
8118-    def _encrypt_and_encode(self):
8119-        # this returns a Deferred that fires with a list of (sharedata,
8120-        # sharenum) tuples. TODO: cache the ciphertext, only produce the
8121-        # shares that we care about.
8122-        self.log("_encrypt_and_encode")
8123-
8124-        self._status.set_status("Encrypting")
8125-        started = time.time()
8126+    def _got_write_answer(self, answer, writer, started):
8127+        if not answer:
8128+            # SDMF writers only pretend to write when readers set their
8129+            # blocks, salts, and so on -- they actually just write once,
8130+            # at the end of the upload process. In fake writes, they
8131+            # return defer.succeed(None). If we see that, we shouldn't
8132+            # bother checking it.
8133+            return
8134 
8135hunk ./src/allmydata/mutable/publish.py 787
8136-        key = hashutil.ssk_readkey_data_hash(self.salt, self.readkey)
8137-        enc = AES(key)
8138-        crypttext = enc.process(self.newdata)
8139-        assert len(crypttext) == len(self.newdata)
8140+        peerid = writer.peerid
8141+        lp = self.log("_got_write_answer from %s, share %d" %
8142+                      (idlib.shortnodeid_b2a(peerid), writer.shnum))
8143 
8144         now = time.time()
8145hunk ./src/allmydata/mutable/publish.py 792
8146-        self._status.timings["encrypt"] = now - started
8147-        started = now
8148-
8149-        # now apply FEC
8150-
8151-        self._status.set_status("Encoding")
8152-        fec = codec.CRSEncoder()
8153-        fec.set_params(self.segment_size,
8154-                       self.required_shares, self.total_shares)
8155-        piece_size = fec.get_block_size()
8156-        crypttext_pieces = [None] * self.required_shares
8157-        for i in range(len(crypttext_pieces)):
8158-            offset = i * piece_size
8159-            piece = crypttext[offset:offset+piece_size]
8160-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
8161-            crypttext_pieces[i] = piece
8162-            assert len(piece) == piece_size
8163-
8164-        d = fec.encode(crypttext_pieces)
8165-        def _done_encoding(res):
8166-            elapsed = time.time() - started
8167-            self._status.timings["encode"] = elapsed
8168-            return res
8169-        d.addCallback(_done_encoding)
8170-        return d
8171-
8172-
8173-    def _generate_shares(self, shares_and_shareids):
8174-        # this sets self.shares and self.root_hash
8175-        self.log("_generate_shares")
8176-        self._status.set_status("Generating Shares")
8177-        started = time.time()
8178-
8179-        # we should know these by now
8180-        privkey = self._privkey
8181-        encprivkey = self._encprivkey
8182-        pubkey = self._pubkey
8183-
8184-        (shares, share_ids) = shares_and_shareids
8185-
8186-        assert len(shares) == len(share_ids)
8187-        assert len(shares) == self.total_shares
8188-        all_shares = {}
8189-        block_hash_trees = {}
8190-        share_hash_leaves = [None] * len(shares)
8191-        for i in range(len(shares)):
8192-            share_data = shares[i]
8193-            shnum = share_ids[i]
8194-            all_shares[shnum] = share_data
8195-
8196-            # build the block hash tree. SDMF has only one leaf.
8197-            leaves = [hashutil.block_hash(share_data)]
8198-            t = hashtree.HashTree(leaves)
8199-            block_hash_trees[shnum] = list(t)
8200-            share_hash_leaves[shnum] = t[0]
8201-        for leaf in share_hash_leaves:
8202-            assert leaf is not None
8203-        share_hash_tree = hashtree.HashTree(share_hash_leaves)
8204-        share_hash_chain = {}
8205-        for shnum in range(self.total_shares):
8206-            needed_hashes = share_hash_tree.needed_hashes(shnum)
8207-            share_hash_chain[shnum] = dict( [ (i, share_hash_tree[i])
8208-                                              for i in needed_hashes ] )
8209-        root_hash = share_hash_tree[0]
8210-        assert len(root_hash) == 32
8211-        self.log("my new root_hash is %s" % base32.b2a(root_hash))
8212-        self._new_version_info = (self._new_seqnum, root_hash, self.salt)
8213-
8214-        prefix = pack_prefix(self._new_seqnum, root_hash, self.salt,
8215-                             self.required_shares, self.total_shares,
8216-                             self.segment_size, len(self.newdata))
8217-
8218-        # now pack the beginning of the share. All shares are the same up
8219-        # to the signature, then they have divergent share hash chains,
8220-        # then completely different block hash trees + salt + share data,
8221-        # then they all share the same encprivkey at the end. The sizes
8222-        # of everything are the same for all shares.
8223-
8224-        sign_started = time.time()
8225-        signature = privkey.sign(prefix)
8226-        self._status.timings["sign"] = time.time() - sign_started
8227-
8228-        verification_key = pubkey.serialize()
8229-
8230-        final_shares = {}
8231-        for shnum in range(self.total_shares):
8232-            final_share = pack_share(prefix,
8233-                                     verification_key,
8234-                                     signature,
8235-                                     share_hash_chain[shnum],
8236-                                     block_hash_trees[shnum],
8237-                                     all_shares[shnum],
8238-                                     encprivkey)
8239-            final_shares[shnum] = final_share
8240-        elapsed = time.time() - started
8241-        self._status.timings["pack"] = elapsed
8242-        self.shares = final_shares
8243-        self.root_hash = root_hash
8244-
8245-        # we also need to build up the version identifier for what we're
8246-        # pushing. Extract the offsets from one of our shares.
8247-        assert final_shares
8248-        offsets = unpack_header(final_shares.values()[0])[-1]
8249-        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
8250-        verinfo = (self._new_seqnum, root_hash, self.salt,
8251-                   self.segment_size, len(self.newdata),
8252-                   self.required_shares, self.total_shares,
8253-                   prefix, offsets_tuple)
8254-        self.versioninfo = verinfo
8255-
8256-
8257-
8258-    def _send_shares(self, needed):
8259-        self.log("_send_shares")
8260-
8261-        # we're finally ready to send out our shares. If we encounter any
8262-        # surprises here, it's because somebody else is writing at the same
8263-        # time. (Note: in the future, when we remove the _query_peers() step
8264-        # and instead speculate about [or remember] which shares are where,
8265-        # surprises here are *not* indications of UncoordinatedWriteError,
8266-        # and we'll need to respond to them more gracefully.)
8267-
8268-        # needed is a set of (peerid, shnum) tuples. The first thing we do is
8269-        # organize it by peerid.
8270-
8271-        peermap = DictOfSets()
8272-        for (peerid, shnum) in needed:
8273-            peermap.add(peerid, shnum)
8274-
8275-        # the next thing is to build up a bunch of test vectors. The
8276-        # semantics of Publish are that we perform the operation if the world
8277-        # hasn't changed since the ServerMap was constructed (more or less).
8278-        # For every share we're trying to place, we create a test vector that
8279-        # tests to see if the server*share still corresponds to the
8280-        # map.
8281-
8282-        all_tw_vectors = {} # maps peerid to tw_vectors
8283-        sm = self._servermap.servermap
8284-
8285-        for key in needed:
8286-            (peerid, shnum) = key
8287-
8288-            if key in sm:
8289-                # an old version of that share already exists on the
8290-                # server, according to our servermap. We will create a
8291-                # request that attempts to replace it.
8292-                old_versionid, old_timestamp = sm[key]
8293-                (old_seqnum, old_root_hash, old_salt, old_segsize,
8294-                 old_datalength, old_k, old_N, old_prefix,
8295-                 old_offsets_tuple) = old_versionid
8296-                old_checkstring = pack_checkstring(old_seqnum,
8297-                                                   old_root_hash,
8298-                                                   old_salt)
8299-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8300-
8301-            elif key in self.bad_share_checkstrings:
8302-                old_checkstring = self.bad_share_checkstrings[key]
8303-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8304-
8305-            else:
8306-                # add a testv that requires the share not exist
8307-
8308-                # Unfortunately, foolscap-0.2.5 has a bug in the way inbound
8309-                # constraints are handled. If the same object is referenced
8310-                # multiple times inside the arguments, foolscap emits a
8311-                # 'reference' token instead of a distinct copy of the
8312-                # argument. The bug is that these 'reference' tokens are not
8313-                # accepted by the inbound constraint code. To work around
8314-                # this, we need to prevent python from interning the
8315-                # (constant) tuple, by creating a new copy of this vector
8316-                # each time.
8317-
8318-                # This bug is fixed in foolscap-0.2.6, and even though this
8319-                # version of Tahoe requires foolscap-0.3.1 or newer, we are
8320-                # supposed to be able to interoperate with older versions of
8321-                # Tahoe which are allowed to use older versions of foolscap,
8322-                # including foolscap-0.2.5 . In addition, I've seen other
8323-                # foolscap problems triggered by 'reference' tokens (see #541
8324-                # for details). So we must keep this workaround in place.
8325-
8326-                #testv = (0, 1, 'eq', "")
8327-                testv = tuple([0, 1, 'eq', ""])
8328-
8329-            testvs = [testv]
8330-            # the write vector is simply the share
8331-            writev = [(0, self.shares[shnum])]
8332-
8333-            if peerid not in all_tw_vectors:
8334-                all_tw_vectors[peerid] = {}
8335-                # maps shnum to (testvs, writevs, new_length)
8336-            assert shnum not in all_tw_vectors[peerid]
8337-
8338-            all_tw_vectors[peerid][shnum] = (testvs, writev, None)
8339-
8340-        # we read the checkstring back from each share, however we only use
8341-        # it to detect whether there was a new share that we didn't know
8342-        # about. The success or failure of the write will tell us whether
8343-        # there was a collision or not. If there is a collision, the first
8344-        # thing we'll do is update the servermap, which will find out what
8345-        # happened. We could conceivably reduce a roundtrip by using the
8346-        # readv checkstring to populate the servermap, but really we'd have
8347-        # to read enough data to validate the signatures too, so it wouldn't
8348-        # be an overall win.
8349-        read_vector = [(0, struct.calcsize(SIGNED_PREFIX))]
8350-
8351-        # ok, send the messages!
8352-        self.log("sending %d shares" % len(all_tw_vectors), level=log.NOISY)
8353-        started = time.time()
8354-        for (peerid, tw_vectors) in all_tw_vectors.items():
8355-
8356-            write_enabler = self._node.get_write_enabler(peerid)
8357-            renew_secret = self._node.get_renewal_secret(peerid)
8358-            cancel_secret = self._node.get_cancel_secret(peerid)
8359-            secrets = (write_enabler, renew_secret, cancel_secret)
8360-            shnums = tw_vectors.keys()
8361-
8362-            for shnum in shnums:
8363-                self.outstanding.add( (peerid, shnum) )
8364-
8365-            d = self._do_testreadwrite(peerid, secrets,
8366-                                       tw_vectors, read_vector)
8367-            d.addCallbacks(self._got_write_answer, self._got_write_error,
8368-                           callbackArgs=(peerid, shnums, started),
8369-                           errbackArgs=(peerid, shnums, started))
8370-            # tolerate immediate errback, like with DeadReferenceError
8371-            d.addBoth(fireEventually)
8372-            d.addCallback(self.loop)
8373-            d.addErrback(self._fatal_error)
8374-
8375-        self._update_status()
8376-        self.log("%d shares sent" % len(all_tw_vectors), level=log.NOISY)
8377+        elapsed = now - started
8378 
8379hunk ./src/allmydata/mutable/publish.py 794
8380-    def _do_testreadwrite(self, peerid, secrets,
8381-                          tw_vectors, read_vector):
8382-        storage_index = self._storage_index
8383-        ss = self.connections[peerid]
8384+        self._status.add_per_server_time(peerid, elapsed)
8385 
8386hunk ./src/allmydata/mutable/publish.py 796
8387-        #print "SS[%s] is %s" % (idlib.shortnodeid_b2a(peerid), ss), ss.tracker.interfaceName
8388-        d = ss.callRemote("slot_testv_and_readv_and_writev",
8389-                          storage_index,
8390-                          secrets,
8391-                          tw_vectors,
8392-                          read_vector)
8393-        return d
8394+        wrote, read_data = answer
8395 
8396hunk ./src/allmydata/mutable/publish.py 798
8397-    def _got_write_answer(self, answer, peerid, shnums, started):
8398-        lp = self.log("_got_write_answer from %s" %
8399-                      idlib.shortnodeid_b2a(peerid))
8400-        for shnum in shnums:
8401-            self.outstanding.discard( (peerid, shnum) )
8402+        surprise_shares = set(read_data.keys()) - set([writer.shnum])
8403 
8404hunk ./src/allmydata/mutable/publish.py 800
8405-        now = time.time()
8406-        elapsed = now - started
8407-        self._status.add_per_server_time(peerid, elapsed)
8408+        # We need to remove from surprise_shares any shares that we are
8409+        # knowingly also writing to that peer from other writers.
8410 
8411hunk ./src/allmydata/mutable/publish.py 803
8412-        wrote, read_data = answer
8413+        # TODO: Precompute this.
8414+        known_shnums = [x.shnum for x in self.writers.values()
8415+                        if x.peerid == peerid]
8416+        surprise_shares -= set(known_shnums)
8417+        self.log("found the following surprise shares: %s" %
8418+                 str(surprise_shares))
8419 
8420hunk ./src/allmydata/mutable/publish.py 810
8421-        surprise_shares = set(read_data.keys()) - set(shnums)
8422+        # Now surprise shares contains all of the shares that we did not
8423+        # expect to be there.
8424 
8425         surprised = False
8426         for shnum in surprise_shares:
8427hunk ./src/allmydata/mutable/publish.py 817
8428             # read_data is a dict mapping shnum to checkstring (SIGNED_PREFIX)
8429             checkstring = read_data[shnum][0]
8430-            their_version_info = unpack_checkstring(checkstring)
8431-            if their_version_info == self._new_version_info:
8432+            # What we want to do here is to see if their (seqnum,
8433+            # roothash, salt) is the same as our (seqnum, roothash,
8434+            # salt), or the equivalent for MDMF. The best way to do this
8435+            # is to store a packed representation of our checkstring
8436+            # somewhere, then not bother unpacking the other
8437+            # checkstring.
8438+            if checkstring == self._checkstring:
8439                 # they have the right share, somehow
8440 
8441                 if (peerid,shnum) in self.goal:
8442hunk ./src/allmydata/mutable/publish.py 902
8443             self.log("our testv failed, so the write did not happen",
8444                      parent=lp, level=log.WEIRD, umid="8sc26g")
8445             self.surprised = True
8446-            self.bad_peers.add(peerid) # don't ask them again
8447+            # TODO: This needs to
8448+            self.bad_peers.add(writer) # don't ask them again
8449             # use the checkstring to add information to the log message
8450             for (shnum,readv) in read_data.items():
8451                 checkstring = readv[0]
8452hunk ./src/allmydata/mutable/publish.py 928
8453             # self.loop() will take care of finding new homes
8454             return
8455 
8456-        for shnum in shnums:
8457-            self.placed.add( (peerid, shnum) )
8458-            # and update the servermap
8459-            self._servermap.add_new_share(peerid, shnum,
8460+        # and update the servermap
8461+        # self.versioninfo is set during the last phase of publishing.
8462+        # If we get there, we know that responses correspond to placed
8463+        # shares, and can safely execute these statements.
8464+        if self.versioninfo:
8465+            self.log("wrote successfully: adding new share to servermap")
8466+            self._servermap.add_new_share(peerid, writer.shnum,
8467                                           self.versioninfo, started)
8468hunk ./src/allmydata/mutable/publish.py 936
8469-
8470-        # self.loop() will take care of checking to see if we're done
8471-        return
8472+            self.placed.add( (peerid, writer.shnum) )
8473 
8474hunk ./src/allmydata/mutable/publish.py 938
8475-    def _got_write_error(self, f, peerid, shnums, started):
8476-        for shnum in shnums:
8477-            self.outstanding.discard( (peerid, shnum) )
8478-        self.bad_peers.add(peerid)
8479-        if self._first_write_error is None:
8480-            self._first_write_error = f
8481-        self.log(format="error while writing shares %(shnums)s to peerid %(peerid)s",
8482-                 shnums=list(shnums), peerid=idlib.shortnodeid_b2a(peerid),
8483-                 failure=f,
8484-                 level=log.UNUSUAL)
8485         # self.loop() will take care of checking to see if we're done
8486         return
8487 
8488hunk ./src/allmydata/mutable/publish.py 949
8489         now = time.time()
8490         self._status.timings["total"] = now - self._started
8491         self._status.set_active(False)
8492-        if isinstance(res, failure.Failure):
8493-            self.log("Publish done, with failure", failure=res,
8494-                     level=log.WEIRD, umid="nRsR9Q")
8495-            self._status.set_status("Failed")
8496-        elif self.surprised:
8497-            self.log("Publish done, UncoordinatedWriteError", level=log.UNUSUAL)
8498-            self._status.set_status("UncoordinatedWriteError")
8499-            # deliver a failure
8500-            res = failure.Failure(UncoordinatedWriteError())
8501-            # TODO: recovery
8502-        else:
8503-            self.log("Publish done, success")
8504-            self._status.set_status("Finished")
8505-            self._status.set_progress(1.0)
8506+        self.log("Publish done, success")
8507+        self._status.set_status("Finished")
8508+        self._status.set_progress(1.0)
8509         eventually(self.done_deferred.callback, res)
8510 
8511hunk ./src/allmydata/mutable/publish.py 954
8512+    def _failure(self):
8513+
8514+        if not self.surprised:
8515+            # We ran out of servers
8516+            self.log("Publish ran out of good servers, "
8517+                     "last failure was: %s" % str(self._last_failure))
8518+            e = NotEnoughServersError("Ran out of non-bad servers, "
8519+                                      "last failure was %s" %
8520+                                      str(self._last_failure))
8521+        else:
8522+            # We ran into shares that we didn't recognize, which means
8523+            # that we need to return an UncoordinatedWriteError.
8524+            self.log("Publish failed with UncoordinatedWriteError")
8525+            e = UncoordinatedWriteError()
8526+        f = failure.Failure(e)
8527+        eventually(self.done_deferred.callback, f)
8528}
8529[test/test_mutable.py: remove tests that are no longer relevant
8530Kevan Carstensen <kevan@isnotajoke.com>**20100702225710
8531 Ignore-this: 90a26b4cc4b2e190a635474ba7097e21
8532] hunk ./src/allmydata/test/test_mutable.py 627
8533         return d
8534 
8535 
8536-class MakeShares(unittest.TestCase):
8537-    def test_encrypt(self):
8538-        nm = make_nodemaker()
8539-        CONTENTS = "some initial contents"
8540-        d = nm.create_mutable_file(CONTENTS)
8541-        def _created(fn):
8542-            p = Publish(fn, nm.storage_broker, None)
8543-            p.salt = "SALT" * 4
8544-            p.readkey = "\x00" * 16
8545-            p.newdata = CONTENTS
8546-            p.required_shares = 3
8547-            p.total_shares = 10
8548-            p.setup_encoding_parameters()
8549-            return p._encrypt_and_encode()
8550-        d.addCallback(_created)
8551-        def _done(shares_and_shareids):
8552-            (shares, share_ids) = shares_and_shareids
8553-            self.failUnlessEqual(len(shares), 10)
8554-            for sh in shares:
8555-                self.failUnless(isinstance(sh, str))
8556-                self.failUnlessEqual(len(sh), 7)
8557-            self.failUnlessEqual(len(share_ids), 10)
8558-        d.addCallback(_done)
8559-        return d
8560-    test_encrypt.todo = "Write an equivalent of this for the new uploader"
8561-
8562-    def test_generate(self):
8563-        nm = make_nodemaker()
8564-        CONTENTS = "some initial contents"
8565-        d = nm.create_mutable_file(CONTENTS)
8566-        def _created(fn):
8567-            self._fn = fn
8568-            p = Publish(fn, nm.storage_broker, None)
8569-            self._p = p
8570-            p.newdata = CONTENTS
8571-            p.required_shares = 3
8572-            p.total_shares = 10
8573-            p.setup_encoding_parameters()
8574-            p._new_seqnum = 3
8575-            p.salt = "SALT" * 4
8576-            # make some fake shares
8577-            shares_and_ids = ( ["%07d" % i for i in range(10)], range(10) )
8578-            p._privkey = fn.get_privkey()
8579-            p._encprivkey = fn.get_encprivkey()
8580-            p._pubkey = fn.get_pubkey()
8581-            return p._generate_shares(shares_and_ids)
8582-        d.addCallback(_created)
8583-        def _generated(res):
8584-            p = self._p
8585-            final_shares = p.shares
8586-            root_hash = p.root_hash
8587-            self.failUnlessEqual(len(root_hash), 32)
8588-            self.failUnless(isinstance(final_shares, dict))
8589-            self.failUnlessEqual(len(final_shares), 10)
8590-            self.failUnlessEqual(sorted(final_shares.keys()), range(10))
8591-            for i,sh in final_shares.items():
8592-                self.failUnless(isinstance(sh, str))
8593-                # feed the share through the unpacker as a sanity-check
8594-                pieces = unpack_share(sh)
8595-                (u_seqnum, u_root_hash, IV, k, N, segsize, datalen,
8596-                 pubkey, signature, share_hash_chain, block_hash_tree,
8597-                 share_data, enc_privkey) = pieces
8598-                self.failUnlessEqual(u_seqnum, 3)
8599-                self.failUnlessEqual(u_root_hash, root_hash)
8600-                self.failUnlessEqual(k, 3)
8601-                self.failUnlessEqual(N, 10)
8602-                self.failUnlessEqual(segsize, 21)
8603-                self.failUnlessEqual(datalen, len(CONTENTS))
8604-                self.failUnlessEqual(pubkey, p._pubkey.serialize())
8605-                sig_material = struct.pack(">BQ32s16s BBQQ",
8606-                                           0, p._new_seqnum, root_hash, IV,
8607-                                           k, N, segsize, datalen)
8608-                self.failUnless(p._pubkey.verify(sig_material, signature))
8609-                #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
8610-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
8611-                for shnum,share_hash in share_hash_chain.items():
8612-                    self.failUnless(isinstance(shnum, int))
8613-                    self.failUnless(isinstance(share_hash, str))
8614-                    self.failUnlessEqual(len(share_hash), 32)
8615-                self.failUnless(isinstance(block_hash_tree, list))
8616-                self.failUnlessEqual(len(block_hash_tree), 1) # very small tree
8617-                self.failUnlessEqual(IV, "SALT"*4)
8618-                self.failUnlessEqual(len(share_data), len("%07d" % 1))
8619-                self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
8620-        d.addCallback(_generated)
8621-        return d
8622-    test_generate.todo = "Write an equivalent of this for the new uploader"
8623-
8624-    # TODO: when we publish to 20 peers, we should get one share per peer on 10
8625-    # when we publish to 3 peers, we should get either 3 or 4 shares per peer
8626-    # when we publish to zero peers, we should get a NotEnoughSharesError
8627-
8628 class PublishMixin:
8629     def publish_one(self):
8630         # publish a file and create shares, which can then be manipulated
8631[interfaces.py: create IMutableUploadable
8632Kevan Carstensen <kevan@isnotajoke.com>**20100706215217
8633 Ignore-this: bee202ec2bfbd8e41f2d4019cce176c7
8634] hunk ./src/allmydata/interfaces.py 1693
8635         """The upload is finished, and whatever filehandle was in use may be
8636         closed."""
8637 
8638+
8639+class IMutableUploadable(Interface):
8640+    """
8641+    I represent content that is due to be uploaded to a mutable filecap.
8642+    """
8643+    # This is somewhat simpler than the IUploadable interface above
8644+    # because mutable files do not need to be concerned with possibly
8645+    # generating a CHK, nor with per-file keys. It is a subset of the
8646+    # methods in IUploadable, though, so we could just as well implement
8647+    # the mutable uploadables as IUploadables that don't happen to use
8648+    # those methods (with the understanding that the unused methods will
8649+    # never be called on such objects)
8650+    def get_size():
8651+        """
8652+        Returns a Deferred that fires with the size of the content held
8653+        by the uploadable.
8654+        """
8655+
8656+    def read(length):
8657+        """
8658+        Returns a list of strings which, when concatenated, are the next
8659+        length bytes of the file, or fewer if there are fewer bytes
8660+        between the current location and the end of the file.
8661+        """
8662+
8663+    def close():
8664+        """
8665+        The process that used the Uploadable is finished using it, so
8666+        the uploadable may be closed.
8667+        """
8668+
8669 class IUploadResults(Interface):
8670     """I am returned by upload() methods. I contain a number of public
8671     attributes which can be read to determine the results of the upload. Some
8672[mutable/publish.py: add MutableDataHandle and MutableFileHandle
8673Kevan Carstensen <kevan@isnotajoke.com>**20100706215257
8674 Ignore-this: 295ea3bc2a962fd14fb7877fc76c011c
8675] {
8676hunk ./src/allmydata/mutable/publish.py 8
8677 from zope.interface import implements
8678 from twisted.internet import defer
8679 from twisted.python import failure
8680-from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
8681+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \
8682+                                 IMutableUploadable
8683 from allmydata.util import base32, hashutil, mathutil, idlib, log
8684 from allmydata import hashtree, codec
8685 from allmydata.storage.server import si_b2a
8686hunk ./src/allmydata/mutable/publish.py 971
8687             e = UncoordinatedWriteError()
8688         f = failure.Failure(e)
8689         eventually(self.done_deferred.callback, f)
8690+
8691+
8692+class MutableFileHandle:
8693+    """
8694+    I am a mutable uploadable built around a filehandle-like object,
8695+    usually either a StringIO instance or a handle to an actual file.
8696+    """
8697+    implements(IMutableUploadable)
8698+
8699+    def __init__(self, filehandle):
8700+        # The filehandle is defined as a generally file-like object that
8701+        # has these two methods. We don't care beyond that.
8702+        assert hasattr(filehandle, "read")
8703+        assert hasattr(filehandle, "close")
8704+
8705+        self._filehandle = filehandle
8706+
8707+
8708+    def get_size(self):
8709+        """
8710+        I return the amount of data in my filehandle.
8711+        """
8712+        if not hasattr(self, "_size"):
8713+            old_position = self._filehandle.tell()
8714+            # Seek to the end of the file by seeking 0 bytes from the
8715+            # file's end
8716+            self._filehandle.seek(0, os.SEEK_END)
8717+            self._size = self._filehandle.tell()
8718+            # Restore the previous position, in case this was called
8719+            # after a read.
8720+            self._filehandle.seek(old_position)
8721+            assert self._filehandle.tell() == old_position
8722+
8723+        assert hasattr(self, "_size")
8724+        return self._size
8725+
8726+
8727+    def read(self, length):
8728+        """
8729+        I return some data (up to length bytes) from my filehandle.
8730+
8731+        In most cases, I return length bytes. If I don't, it is because
8732+        length is longer than the distance between my current position
8733+        in the file that I represent and its end. In that case, I return
8734+        as many bytes as I can before going over the EOF.
8735+        """
8736+        return [self._filehandle.read(length)]
8737+
8738+
8739+    def close(self):
8740+        """
8741+        I close the underlying filehandle. Any further operations on the
8742+        filehandle fail at this point.
8743+        """
8744+        self._filehandle.close()
8745+
8746+
8747+class MutableDataHandle(MutableFileHandle):
8748+    """
8749+    I am a mutable uploadable built around a string, which I then cast
8750+    into a StringIO and treat as a filehandle.
8751+    """
8752+
8753+    def __init__(self, s):
8754+        # Take a string and return a file-like uploadable.
8755+        assert isinstance(s, str)
8756+
8757+        MutableFileHandle.__init__(self, StringIO(s))
8758}
8759[mutable/publish.py: reorganize in preparation of file-like uploadables
8760Kevan Carstensen <kevan@isnotajoke.com>**20100706215541
8761 Ignore-this: 5346c9f919ee5b73807c8f287c64e8ce
8762] {
8763hunk ./src/allmydata/mutable/publish.py 4
8764 
8765 
8766 import os, struct, time
8767+from StringIO import StringIO
8768 from itertools import count
8769 from zope.interface import implements
8770 from twisted.internet import defer
8771hunk ./src/allmydata/mutable/publish.py 118
8772         self._status.set_helper(False)
8773         self._status.set_progress(0.0)
8774         self._status.set_active(True)
8775-        # We use this to control how the file is written.
8776-        version = self._node.get_version()
8777-        assert version in (SDMF_VERSION, MDMF_VERSION)
8778-        self._version = version
8779+        self._version = self._node.get_version()
8780+        assert self._version in (SDMF_VERSION, MDMF_VERSION)
8781+
8782 
8783     def get_status(self):
8784         return self._status
8785hunk ./src/allmydata/mutable/publish.py 141
8786 
8787         # 0. Setup encoding parameters, encoder, and other such things.
8788         # 1. Encrypt, encode, and publish segments.
8789+        self.data = StringIO(newdata)
8790+        self.datalength = len(newdata)
8791 
8792hunk ./src/allmydata/mutable/publish.py 144
8793-        self.log("starting publish, datalen is %s" % len(newdata))
8794-        self._status.set_size(len(newdata))
8795+        self.log("starting publish, datalen is %s" % self.datalength)
8796+        self._status.set_size(self.datalength)
8797         self._status.set_status("Started")
8798         self._started = time.time()
8799 
8800hunk ./src/allmydata/mutable/publish.py 193
8801         self.full_peerlist = full_peerlist # for use later, immutable
8802         self.bad_peers = set() # peerids who have errbacked/refused requests
8803 
8804-        self.newdata = newdata
8805-
8806         # This will set self.segment_size, self.num_segments, and
8807         # self.fec.
8808         self.setup_encoding_parameters()
8809hunk ./src/allmydata/mutable/publish.py 272
8810                                                 self.required_shares,
8811                                                 self.total_shares,
8812                                                 self.segment_size,
8813-                                                len(self.newdata))
8814+                                                self.datalength)
8815             self.writers[shnum].peerid = peerid
8816             if (peerid, shnum) in self._servermap.servermap:
8817                 old_versionid, old_timestamp = self._servermap.servermap[key]
8818hunk ./src/allmydata/mutable/publish.py 318
8819         if self._version == MDMF_VERSION:
8820             segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
8821         else:
8822-            segment_size = len(self.newdata) # SDMF is only one segment
8823+            segment_size = self.datalength # SDMF is only one segment
8824         # this must be a multiple of self.required_shares
8825         segment_size = mathutil.next_multiple(segment_size,
8826                                               self.required_shares)
8827hunk ./src/allmydata/mutable/publish.py 324
8828         self.segment_size = segment_size
8829         if segment_size:
8830-            self.num_segments = mathutil.div_ceil(len(self.newdata),
8831+            self.num_segments = mathutil.div_ceil(self.datalength,
8832                                                   segment_size)
8833         else:
8834             self.num_segments = 0
8835hunk ./src/allmydata/mutable/publish.py 337
8836             assert self.num_segments in (0, 1) # SDMF
8837         # calculate the tail segment size.
8838 
8839-        if segment_size and self.newdata:
8840-            self.tail_segment_size = len(self.newdata) % segment_size
8841+        if segment_size and self.datalength:
8842+            self.tail_segment_size = self.datalength % segment_size
8843         else:
8844             self.tail_segment_size = 0
8845 
8846hunk ./src/allmydata/mutable/publish.py 438
8847             segsize = self.segment_size
8848 
8849 
8850-        offset = self.segment_size * segnum
8851-        length = segsize + offset
8852         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
8853hunk ./src/allmydata/mutable/publish.py 439
8854-        data = self.newdata[offset:length]
8855+        data = self.data.read(segsize)
8856+
8857         assert len(data) == segsize
8858 
8859         salt = os.urandom(16)
8860hunk ./src/allmydata/mutable/publish.py 502
8861             d.addCallback(self._got_write_answer, writer, started)
8862             d.addErrback(self._connection_problem, writer)
8863             dl.append(d)
8864-            # TODO: Naturally, we need to check on the results of these.
8865         return defer.DeferredList(dl)
8866 
8867 
8868}
8869[test/test_mutable.py: write tests for MutableFileHandle and MutableDataHandle
8870Kevan Carstensen <kevan@isnotajoke.com>**20100706215649
8871 Ignore-this: df719a0c52b4bbe9be4fae206c7ab3e7
8872] {
8873hunk ./src/allmydata/test/test_mutable.py 2
8874 
8875-import struct
8876+import struct, os
8877 from cStringIO import StringIO
8878 from twisted.trial import unittest
8879 from twisted.internet import defer, reactor
8880hunk ./src/allmydata/test/test_mutable.py 26
8881      NeedMoreDataError, UnrecoverableFileError, UncoordinatedWriteError, \
8882      NotEnoughServersError, CorruptShareError
8883 from allmydata.mutable.retrieve import Retrieve
8884-from allmydata.mutable.publish import Publish
8885+from allmydata.mutable.publish import Publish, MutableFileHandle, \
8886+                                      MutableDataHandle
8887 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
8888 from allmydata.mutable.layout import unpack_header, unpack_share, \
8889                                      MDMFSlotReadProxy
8890hunk ./src/allmydata/test/test_mutable.py 2465
8891         d.addCallback(lambda data:
8892             self.failUnlessEqual(data, CONTENTS))
8893         return d
8894+
8895+
8896+class FileHandle(unittest.TestCase):
8897+    def setUp(self):
8898+        self.test_data = "Test Data" * 50000
8899+        self.sio = StringIO(self.test_data)
8900+        self.uploadable = MutableFileHandle(self.sio)
8901+
8902+
8903+    def test_filehandle_read(self):
8904+        self.basedir = "mutable/FileHandle/test_filehandle_read"
8905+        chunk_size = 10
8906+        for i in xrange(0, len(self.test_data), chunk_size):
8907+            data = self.uploadable.read(chunk_size)
8908+            data = "".join(data)
8909+            start = i
8910+            end = i + chunk_size
8911+            self.failUnlessEqual(data, self.test_data[start:end])
8912+
8913+
8914+    def test_filehandle_get_size(self):
8915+        self.basedir = "mutable/FileHandle/test_filehandle_get_size"
8916+        actual_size = len(self.test_data)
8917+        size = self.uploadable.get_size()
8918+        self.failUnlessEqual(size, actual_size)
8919+
8920+
8921+    def test_filehandle_get_size_out_of_order(self):
8922+        # We should be able to call get_size whenever we want without
8923+        # disturbing the location of the seek pointer.
8924+        chunk_size = 100
8925+        data = self.uploadable.read(chunk_size)
8926+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8927+
8928+        # Now get the size.
8929+        size = self.uploadable.get_size()
8930+        self.failUnlessEqual(size, len(self.test_data))
8931+
8932+        # Now get more data. We should be right where we left off.
8933+        more_data = self.uploadable.read(chunk_size)
8934+        start = chunk_size
8935+        end = chunk_size * 2
8936+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
8937+
8938+
8939+    def test_filehandle_file(self):
8940+        # Make sure that the MutableFileHandle works on a file as well
8941+        # as a StringIO object, since in some cases it will be asked to
8942+        # deal with files.
8943+        self.basedir = self.mktemp()
8944+        # necessary? What am I doing wrong here?
8945+        os.mkdir(self.basedir)
8946+        f_path = os.path.join(self.basedir, "test_file")
8947+        f = open(f_path, "w")
8948+        f.write(self.test_data)
8949+        f.close()
8950+        f = open(f_path, "r")
8951+
8952+        uploadable = MutableFileHandle(f)
8953+
8954+        data = uploadable.read(len(self.test_data))
8955+        self.failUnlessEqual("".join(data), self.test_data)
8956+        size = uploadable.get_size()
8957+        self.failUnlessEqual(size, len(self.test_data))
8958+
8959+
8960+    def test_close(self):
8961+        # Make sure that the MutableFileHandle closes its handle when
8962+        # told to do so.
8963+        self.uploadable.close()
8964+        self.failUnless(self.sio.closed)
8965+
8966+
8967+class DataHandle(unittest.TestCase):
8968+    def setUp(self):
8969+        self.test_data = "Test Data" * 50000
8970+        self.uploadable = MutableDataHandle(self.test_data)
8971+
8972+
8973+    def test_datahandle_read(self):
8974+        chunk_size = 10
8975+        for i in xrange(0, len(self.test_data), chunk_size):
8976+            data = self.uploadable.read(chunk_size)
8977+            data = "".join(data)
8978+            start = i
8979+            end = i + chunk_size
8980+            self.failUnlessEqual(data, self.test_data[start:end])
8981+
8982+
8983+    def test_datahandle_get_size(self):
8984+        actual_size = len(self.test_data)
8985+        size = self.uploadable.get_size()
8986+        self.failUnlessEqual(size, actual_size)
8987+
8988+
8989+    def test_datahandle_get_size_out_of_order(self):
8990+        # We should be able to call get_size whenever we want without
8991+        # disturbing the location of the seek pointer.
8992+        chunk_size = 100
8993+        data = self.uploadable.read(chunk_size)
8994+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8995+
8996+        # Now get the size.
8997+        size = self.uploadable.get_size()
8998+        self.failUnlessEqual(size, len(self.test_data))
8999+
9000+        # Now get more data. We should be right where we left off.
9001+        more_data = self.uploadable.read(chunk_size)
9002+        start = chunk_size
9003+        end = chunk_size * 2
9004+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
9005}
9006[Alter tests to work with the new APIs
9007Kevan Carstensen <kevan@isnotajoke.com>**20100708000031
9008 Ignore-this: 1f377904ac61ce40e9a04716fbd2ad95
9009] {
9010hunk ./src/allmydata/test/common.py 12
9011 from allmydata import uri, dirnode, client
9012 from allmydata.introducer.server import IntroducerNode
9013 from allmydata.interfaces import IMutableFileNode, IImmutableFileNode, \
9014-     FileTooLargeError, NotEnoughSharesError, ICheckable
9015+     FileTooLargeError, NotEnoughSharesError, ICheckable, \
9016+     IMutableUploadable
9017 from allmydata.check_results import CheckResults, CheckAndRepairResults, \
9018      DeepCheckResults, DeepCheckAndRepairResults
9019 from allmydata.mutable.common import CorruptShareError
9020hunk ./src/allmydata/test/common.py 18
9021 from allmydata.mutable.layout import unpack_header
9022+from allmydata.mutable.publish import MutableDataHandle
9023 from allmydata.storage.server import storage_index_to_dir
9024 from allmydata.storage.mutable import MutableShareFile
9025 from allmydata.util import hashutil, log, fileutil, pollmixin
9026hunk ./src/allmydata/test/common.py 182
9027         self.init_from_cap(make_mutable_file_cap())
9028     def create(self, contents, key_generator=None, keysize=None):
9029         initial_contents = self._get_initial_contents(contents)
9030-        if len(initial_contents) > self.MUTABLE_SIZELIMIT:
9031+        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
9032             raise FileTooLargeError("SDMF is limited to one segment, and "
9033hunk ./src/allmydata/test/common.py 184
9034-                                    "%d > %d" % (len(initial_contents),
9035+                                    "%d > %d" % (initial_contents.get_size(),
9036                                                  self.MUTABLE_SIZELIMIT))
9037hunk ./src/allmydata/test/common.py 186
9038-        self.all_contents[self.storage_index] = initial_contents
9039+        data = initial_contents.read(initial_contents.get_size())
9040+        data = "".join(data)
9041+        self.all_contents[self.storage_index] = data
9042         return defer.succeed(self)
9043     def _get_initial_contents(self, contents):
9044hunk ./src/allmydata/test/common.py 191
9045-        if isinstance(contents, str):
9046-            return contents
9047         if contents is None:
9048hunk ./src/allmydata/test/common.py 192
9049-            return ""
9050+            return MutableDataHandle("")
9051+
9052+        if IMutableUploadable.providedBy(contents):
9053+            return contents
9054+
9055         assert callable(contents), "%s should be callable, not %s" % \
9056                (contents, type(contents))
9057         return contents(self)
9058hunk ./src/allmydata/test/common.py 309
9059         return defer.succeed(self.all_contents[self.storage_index])
9060 
9061     def overwrite(self, new_contents):
9062-        if len(new_contents) > self.MUTABLE_SIZELIMIT:
9063+        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
9064             raise FileTooLargeError("SDMF is limited to one segment, and "
9065hunk ./src/allmydata/test/common.py 311
9066-                                    "%d > %d" % (len(new_contents),
9067+                                    "%d > %d" % (new_contents.get_size(),
9068                                                  self.MUTABLE_SIZELIMIT))
9069         assert not self.is_readonly()
9070hunk ./src/allmydata/test/common.py 314
9071-        self.all_contents[self.storage_index] = new_contents
9072+        new_data = new_contents.read(new_contents.get_size())
9073+        new_data = "".join(new_data)
9074+        self.all_contents[self.storage_index] = new_data
9075         return defer.succeed(None)
9076     def modify(self, modifier):
9077         # this does not implement FileTooLargeError, but the real one does
9078hunk ./src/allmydata/test/common.py 324
9079     def _modify(self, modifier):
9080         assert not self.is_readonly()
9081         old_contents = self.all_contents[self.storage_index]
9082-        self.all_contents[self.storage_index] = modifier(old_contents, None, True)
9083+        new_data = modifier(old_contents, None, True)
9084+        if new_data is not None:
9085+            new_data = new_data.read(new_data.get_size())
9086+            new_data = "".join(new_data)
9087+        self.all_contents[self.storage_index] = new_data
9088         return None
9089 
9090 def make_mutable_file_cap():
9091hunk ./src/allmydata/test/test_checker.py 11
9092 from allmydata.test.no_network import GridTestMixin
9093 from allmydata.immutable.upload import Data
9094 from allmydata.test.common_web import WebRenderingMixin
9095+from allmydata.mutable.publish import MutableDataHandle
9096 
9097 class FakeClient:
9098     def get_storage_broker(self):
9099hunk ./src/allmydata/test/test_checker.py 291
9100         def _stash_immutable(ur):
9101             self.imm = c0.create_node_from_uri(ur.uri)
9102         d.addCallback(_stash_immutable)
9103-        d.addCallback(lambda ign: c0.create_mutable_file("contents"))
9104+        d.addCallback(lambda ign:
9105+            c0.create_mutable_file(MutableDataHandle("contents")))
9106         def _stash_mutable(node):
9107             self.mut = node
9108         d.addCallback(_stash_mutable)
9109hunk ./src/allmydata/test/test_cli.py 12
9110 from allmydata.util import fileutil, hashutil, base32
9111 from allmydata import uri
9112 from allmydata.immutable import upload
9113+from allmydata.mutable.publish import MutableDataHandle
9114 from allmydata.dirnode import normalize
9115 
9116 # Test that the scripts can be imported -- although the actual tests of their
9117hunk ./src/allmydata/test/test_cli.py 1975
9118         self.set_up_grid()
9119         c0 = self.g.clients[0]
9120         DATA = "data" * 100
9121-        d = c0.create_mutable_file(DATA)
9122+        DATA_uploadable = MutableDataHandle(DATA)
9123+        d = c0.create_mutable_file(DATA_uploadable)
9124         def _stash_uri(n):
9125             self.uri = n.get_uri()
9126         d.addCallback(_stash_uri)
9127hunk ./src/allmydata/test/test_cli.py 2077
9128                                            upload.Data("literal",
9129                                                         convergence="")))
9130         d.addCallback(_stash_uri, "small")
9131-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"1"))
9132+        d.addCallback(lambda ign:
9133+            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
9134         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9135         d.addCallback(_stash_uri, "mutable")
9136 
9137hunk ./src/allmydata/test/test_cli.py 2096
9138         # root/small
9139         # root/mutable
9140 
9141+        # We haven't broken anything yet, so this should all be healthy.
9142         d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
9143                                               self.rooturi))
9144         def _check2((rc, out, err)):
9145hunk ./src/allmydata/test/test_cli.py 2111
9146                             in lines, out)
9147         d.addCallback(_check2)
9148 
9149+        # Similarly, all of these results should be as we expect them to
9150+        # be for a healthy file layout.
9151         d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
9152         def _check_stats((rc, out, err)):
9153             self.failUnlessReallyEqual(err, "")
9154hunk ./src/allmydata/test/test_cli.py 2128
9155             self.failUnlessIn(" 317-1000 : 1    (1000 B, 1000 B)", lines)
9156         d.addCallback(_check_stats)
9157 
9158+        # Now we break things.
9159         def _clobber_shares(ignored):
9160             shares = self.find_uri_shares(self.uris[u"gööd"])
9161             self.failUnlessReallyEqual(len(shares), 10)
9162hunk ./src/allmydata/test/test_cli.py 2147
9163         d.addCallback(_clobber_shares)
9164 
9165         # root
9166-        # root/gööd  [9 shares]
9167+        # root/gööd  [1 missing share]
9168         # root/small
9169         # root/mutable [1 corrupt share]
9170 
9171hunk ./src/allmydata/test/test_cli.py 2153
9172         d.addCallback(lambda ign:
9173                       self.do_cli("deep-check", "--verbose", self.rooturi))
9174+        # This should reveal the missing share, but not the corrupt
9175+        # share, since we didn't tell the deep check operation to also
9176+        # verify.
9177         def _check3((rc, out, err)):
9178             self.failUnlessReallyEqual(err, "")
9179             self.failUnlessReallyEqual(rc, 0)
9180hunk ./src/allmydata/test/test_cli.py 2204
9181                                   "--verbose", "--verify", "--repair",
9182                                   self.rooturi))
9183         def _check6((rc, out, err)):
9184+            # We've just repaired the directory. There is no reason for
9185+            # that repair to be unsuccessful.
9186             self.failUnlessReallyEqual(err, "")
9187             self.failUnlessReallyEqual(rc, 0)
9188             lines = out.splitlines()
9189hunk ./src/allmydata/test/test_deepcheck.py 9
9190 from twisted.internet import threads # CLI tests use deferToThread
9191 from allmydata.immutable import upload
9192 from allmydata.mutable.common import UnrecoverableFileError
9193+from allmydata.mutable.publish import MutableDataHandle
9194 from allmydata.util import idlib
9195 from allmydata.util import base32
9196 from allmydata.scripts import runner
9197hunk ./src/allmydata/test/test_deepcheck.py 38
9198         self.basedir = "deepcheck/MutableChecker/good"
9199         self.set_up_grid()
9200         CONTENTS = "a little bit of data"
9201-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9202+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9203+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9204         def _created(node):
9205             self.node = node
9206             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9207hunk ./src/allmydata/test/test_deepcheck.py 61
9208         self.basedir = "deepcheck/MutableChecker/corrupt"
9209         self.set_up_grid()
9210         CONTENTS = "a little bit of data"
9211-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9212+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9213+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9214         def _stash_and_corrupt(node):
9215             self.node = node
9216             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9217hunk ./src/allmydata/test/test_deepcheck.py 99
9218         self.basedir = "deepcheck/MutableChecker/delete_share"
9219         self.set_up_grid()
9220         CONTENTS = "a little bit of data"
9221-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9222+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9223+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9224         def _stash_and_delete(node):
9225             self.node = node
9226             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9227hunk ./src/allmydata/test/test_deepcheck.py 223
9228             self.root = n
9229             self.root_uri = n.get_uri()
9230         d.addCallback(_created_root)
9231-        d.addCallback(lambda ign: c0.create_mutable_file("mutable file contents"))
9232+        d.addCallback(lambda ign:
9233+            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
9234         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
9235         def _created_mutable(n):
9236             self.mutable = n
9237hunk ./src/allmydata/test/test_deepcheck.py 965
9238     def create_mangled(self, ignored, name):
9239         nodetype, mangletype = name.split("-", 1)
9240         if nodetype == "mutable":
9241-            d = self.g.clients[0].create_mutable_file("mutable file contents")
9242+            mutable_uploadable = MutableDataHandle("mutable file contents")
9243+            d = self.g.clients[0].create_mutable_file(mutable_uploadable)
9244             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
9245         elif nodetype == "large":
9246             large = upload.Data("Lots of data\n" * 1000 + name + "\n", None)
9247hunk ./src/allmydata/test/test_dirnode.py 1305
9248     implements(IMutableFileNode)
9249     counter = 0
9250     def __init__(self, initial_contents=""):
9251-        self.data = self._get_initial_contents(initial_contents)
9252+        data = self._get_initial_contents(initial_contents)
9253+        self.data = data.read(data.get_size())
9254+        self.data = "".join(self.data)
9255+
9256         counter = FakeMutableFile.counter
9257         FakeMutableFile.counter += 1
9258         writekey = hashutil.ssk_writekey_hash(str(counter))
9259hunk ./src/allmydata/test/test_dirnode.py 1355
9260         pass
9261 
9262     def modify(self, modifier):
9263-        self.data = modifier(self.data, None, True)
9264+        data = modifier(self.data, None, True)
9265+        self.data = data.read(data.get_size())
9266+        self.data = "".join(self.data)
9267         return defer.succeed(None)
9268 
9269 class FakeNodeMaker(NodeMaker):
9270hunk ./src/allmydata/test/test_hung_server.py 10
9271 from allmydata.util.consumer import download_to_data
9272 from allmydata.immutable import upload
9273 from allmydata.mutable.common import UnrecoverableFileError
9274+from allmydata.mutable.publish import MutableDataHandle
9275 from allmydata.storage.common import storage_index_to_dir
9276 from allmydata.test.no_network import GridTestMixin
9277 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
9278hunk ./src/allmydata/test/test_hung_server.py 96
9279         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
9280 
9281         if mutable:
9282-            d = nm.create_mutable_file(mutable_plaintext)
9283+            uploadable = MutableDataHandle(mutable_plaintext)
9284+            d = nm.create_mutable_file(uploadable)
9285             def _uploaded_mutable(node):
9286                 self.uri = node.get_uri()
9287                 self.shares = self.find_uri_shares(self.uri)
9288hunk ./src/allmydata/test/test_mutable.py 297
9289             d.addCallback(lambda smap: smap.dump(StringIO()))
9290             d.addCallback(lambda sio:
9291                           self.failUnless("3-of-10" in sio.getvalue()))
9292-            d.addCallback(lambda res: n.overwrite("contents 1"))
9293+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9294             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9295             d.addCallback(lambda res: n.download_best_version())
9296             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9297hunk ./src/allmydata/test/test_mutable.py 304
9298             d.addCallback(lambda res: n.get_size_of_best_version())
9299             d.addCallback(lambda size:
9300                           self.failUnlessEqual(size, len("contents 1")))
9301-            d.addCallback(lambda res: n.overwrite("contents 2"))
9302+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9303             d.addCallback(lambda res: n.download_best_version())
9304             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9305             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9306hunk ./src/allmydata/test/test_mutable.py 308
9307-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9308+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9309             d.addCallback(lambda res: n.download_best_version())
9310             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9311             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9312hunk ./src/allmydata/test/test_mutable.py 320
9313             # mapupdate-to-retrieve data caching (i.e. make the shares larger
9314             # than the default readsize, which is 2000 bytes). A 15kB file
9315             # will have 5kB shares.
9316-            d.addCallback(lambda res: n.overwrite("large size file" * 1000))
9317+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
9318             d.addCallback(lambda res: n.download_best_version())
9319             d.addCallback(lambda res:
9320                           self.failUnlessEqual(res, "large size file" * 1000))
9321hunk ./src/allmydata/test/test_mutable.py 343
9322             # to make them big enough to force the file to be uploaded
9323             # in more than one segment.
9324             big_contents = "contents1" * 100000 # about 900 KiB
9325+            big_contents_uploadable = MutableDataHandle(big_contents)
9326             d.addCallback(lambda ignored:
9327hunk ./src/allmydata/test/test_mutable.py 345
9328-                n.overwrite(big_contents))
9329+                n.overwrite(big_contents_uploadable))
9330             d.addCallback(lambda ignored:
9331                 n.download_best_version())
9332             d.addCallback(lambda data:
9333hunk ./src/allmydata/test/test_mutable.py 355
9334             # segments, so that we make the downloader deal with
9335             # multiple segments.
9336             bigger_contents = "contents2" * 1000000 # about 9MiB
9337+            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
9338             d.addCallback(lambda ignored:
9339hunk ./src/allmydata/test/test_mutable.py 357
9340-                n.overwrite(bigger_contents))
9341+                n.overwrite(bigger_contents_uploadable))
9342             d.addCallback(lambda ignored:
9343                 n.download_best_version())
9344             d.addCallback(lambda data:
9345hunk ./src/allmydata/test/test_mutable.py 368
9346 
9347 
9348     def test_create_with_initial_contents(self):
9349-        d = self.nodemaker.create_mutable_file("contents 1")
9350+        upload1 = MutableDataHandle("contents 1")
9351+        d = self.nodemaker.create_mutable_file(upload1)
9352         def _created(n):
9353             d = n.download_best_version()
9354             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9355hunk ./src/allmydata/test/test_mutable.py 373
9356-            d.addCallback(lambda res: n.overwrite("contents 2"))
9357+            upload2 = MutableDataHandle("contents 2")
9358+            d.addCallback(lambda res: n.overwrite(upload2))
9359             d.addCallback(lambda res: n.download_best_version())
9360             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9361             return d
9362hunk ./src/allmydata/test/test_mutable.py 380
9363         d.addCallback(_created)
9364         return d
9365+    test_create_with_initial_contents.timeout = 15
9366 
9367 
9368     def test_create_mdmf_with_initial_contents(self):
9369hunk ./src/allmydata/test/test_mutable.py 385
9370         initial_contents = "foobarbaz" * 131072 # 900KiB
9371-        d = self.nodemaker.create_mutable_file(initial_contents,
9372+        initial_contents_uploadable = MutableDataHandle(initial_contents)
9373+        d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
9374                                                version=MDMF_VERSION)
9375         def _created(n):
9376             d = n.download_best_version()
9377hunk ./src/allmydata/test/test_mutable.py 392
9378             d.addCallback(lambda data:
9379                 self.failUnlessEqual(data, initial_contents))
9380+            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
9381             d.addCallback(lambda ignored:
9382hunk ./src/allmydata/test/test_mutable.py 394
9383-                n.overwrite(initial_contents + "foobarbaz"))
9384+                n.overwrite(uploadable2))
9385             d.addCallback(lambda ignored:
9386                 n.download_best_version())
9387             d.addCallback(lambda data:
9388hunk ./src/allmydata/test/test_mutable.py 413
9389             key = n.get_writekey()
9390             self.failUnless(isinstance(key, str), key)
9391             self.failUnlessEqual(len(key), 16) # AES key size
9392-            return data
9393+            return MutableDataHandle(data)
9394         d = self.nodemaker.create_mutable_file(_make_contents)
9395         def _created(n):
9396             return n.download_best_version()
9397hunk ./src/allmydata/test/test_mutable.py 429
9398             key = n.get_writekey()
9399             self.failUnless(isinstance(key, str), key)
9400             self.failUnlessEqual(len(key), 16)
9401-            return data
9402+            return MutableDataHandle(data)
9403         d = self.nodemaker.create_mutable_file(_make_contents,
9404                                                version=MDMF_VERSION)
9405         d.addCallback(lambda n:
9406hunk ./src/allmydata/test/test_mutable.py 441
9407 
9408     def test_create_with_too_large_contents(self):
9409         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9410-        d = self.nodemaker.create_mutable_file(BIG)
9411+        BIG_uploadable = MutableDataHandle(BIG)
9412+        d = self.nodemaker.create_mutable_file(BIG_uploadable)
9413         def _created(n):
9414hunk ./src/allmydata/test/test_mutable.py 444
9415-            d = n.overwrite(BIG)
9416+            other_BIG_uploadable = MutableDataHandle(BIG)
9417+            d = n.overwrite(other_BIG_uploadable)
9418             return d
9419         d.addCallback(_created)
9420         return d
9421hunk ./src/allmydata/test/test_mutable.py 459
9422 
9423     def test_modify(self):
9424         def _modifier(old_contents, servermap, first_time):
9425-            return old_contents + "line2"
9426+            new_contents = old_contents + "line2"
9427+            return MutableDataHandle(new_contents)
9428         def _non_modifier(old_contents, servermap, first_time):
9429hunk ./src/allmydata/test/test_mutable.py 462
9430-            return old_contents
9431+            return MutableDataHandle(old_contents)
9432         def _none_modifier(old_contents, servermap, first_time):
9433             return None
9434         def _error_modifier(old_contents, servermap, first_time):
9435hunk ./src/allmydata/test/test_mutable.py 468
9436             raise ValueError("oops")
9437         def _toobig_modifier(old_contents, servermap, first_time):
9438-            return "b" * (self.OLD_MAX_SEGMENT_SIZE+1)
9439+            new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9440+            return MutableDataHandle(new_content)
9441         calls = []
9442         def _ucw_error_modifier(old_contents, servermap, first_time):
9443             # simulate an UncoordinatedWriteError once
9444hunk ./src/allmydata/test/test_mutable.py 476
9445             calls.append(1)
9446             if len(calls) <= 1:
9447                 raise UncoordinatedWriteError("simulated")
9448-            return old_contents + "line3"
9449+            new_contents = old_contents + "line3"
9450+            return MutableDataHandle(new_contents)
9451         def _ucw_error_non_modifier(old_contents, servermap, first_time):
9452             # simulate an UncoordinatedWriteError once, and don't actually
9453             # modify the contents on subsequent invocations
9454hunk ./src/allmydata/test/test_mutable.py 484
9455             calls.append(1)
9456             if len(calls) <= 1:
9457                 raise UncoordinatedWriteError("simulated")
9458-            return old_contents
9459+            return MutableDataHandle(old_contents)
9460 
9461hunk ./src/allmydata/test/test_mutable.py 486
9462-        d = self.nodemaker.create_mutable_file("line1")
9463+        initial_contents = "line1"
9464+        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
9465         def _created(n):
9466             d = n.modify(_modifier)
9467             d.addCallback(lambda res: n.download_best_version())
9468hunk ./src/allmydata/test/test_mutable.py 548
9469 
9470     def test_modify_backoffer(self):
9471         def _modifier(old_contents, servermap, first_time):
9472-            return old_contents + "line2"
9473+            return MutableDataHandle(old_contents + "line2")
9474         calls = []
9475         def _ucw_error_modifier(old_contents, servermap, first_time):
9476             # simulate an UncoordinatedWriteError once
9477hunk ./src/allmydata/test/test_mutable.py 555
9478             calls.append(1)
9479             if len(calls) <= 1:
9480                 raise UncoordinatedWriteError("simulated")
9481-            return old_contents + "line3"
9482+            return MutableDataHandle(old_contents + "line3")
9483         def _always_ucw_error_modifier(old_contents, servermap, first_time):
9484             raise UncoordinatedWriteError("simulated")
9485         def _backoff_stopper(node, f):
9486hunk ./src/allmydata/test/test_mutable.py 570
9487         giveuper._delay = 0.1
9488         giveuper.factor = 1
9489 
9490-        d = self.nodemaker.create_mutable_file("line1")
9491+        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
9492         def _created(n):
9493             d = n.modify(_modifier)
9494             d.addCallback(lambda res: n.download_best_version())
9495hunk ./src/allmydata/test/test_mutable.py 620
9496             d.addCallback(lambda smap: smap.dump(StringIO()))
9497             d.addCallback(lambda sio:
9498                           self.failUnless("3-of-10" in sio.getvalue()))
9499-            d.addCallback(lambda res: n.overwrite("contents 1"))
9500+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9501             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9502             d.addCallback(lambda res: n.download_best_version())
9503             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9504hunk ./src/allmydata/test/test_mutable.py 624
9505-            d.addCallback(lambda res: n.overwrite("contents 2"))
9506+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9507             d.addCallback(lambda res: n.download_best_version())
9508             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9509             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9510hunk ./src/allmydata/test/test_mutable.py 628
9511-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9512+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9513             d.addCallback(lambda res: n.download_best_version())
9514             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9515             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9516hunk ./src/allmydata/test/test_mutable.py 646
9517         # publish a file and create shares, which can then be manipulated
9518         # later.
9519         self.CONTENTS = "New contents go here" * 1000
9520+        self.uploadable = MutableDataHandle(self.CONTENTS)
9521         self._storage = FakeStorage()
9522         self._nodemaker = make_nodemaker(self._storage)
9523         self._storage_broker = self._nodemaker.storage_broker
9524hunk ./src/allmydata/test/test_mutable.py 650
9525-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9526+        d = self._nodemaker.create_mutable_file(self.uploadable)
9527         def _created(node):
9528             self._fn = node
9529             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9530hunk ./src/allmydata/test/test_mutable.py 662
9531         # an MDMF file.
9532         # self.CONTENTS should have more than one segment.
9533         self.CONTENTS = "This is an MDMF file" * 100000
9534+        self.uploadable = MutableDataHandle(self.CONTENTS)
9535         self._storage = FakeStorage()
9536         self._nodemaker = make_nodemaker(self._storage)
9537         self._storage_broker = self._nodemaker.storage_broker
9538hunk ./src/allmydata/test/test_mutable.py 666
9539-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
9540+        d = self._nodemaker.create_mutable_file(self.uploadable, version=MDMF_VERSION)
9541         def _created(node):
9542             self._fn = node
9543             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9544hunk ./src/allmydata/test/test_mutable.py 678
9545         # like publish_one, except that the result is guaranteed to be
9546         # an SDMF file
9547         self.CONTENTS = "This is an SDMF file" * 1000
9548+        self.uploadable = MutableDataHandle(self.CONTENTS)
9549         self._storage = FakeStorage()
9550         self._nodemaker = make_nodemaker(self._storage)
9551         self._storage_broker = self._nodemaker.storage_broker
9552hunk ./src/allmydata/test/test_mutable.py 682
9553-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
9554+        d = self._nodemaker.create_mutable_file(self.uploadable, version=SDMF_VERSION)
9555         def _created(node):
9556             self._fn = node
9557             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9558hunk ./src/allmydata/test/test_mutable.py 696
9559                          "Contents 2",
9560                          "Contents 3a",
9561                          "Contents 3b"]
9562+        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
9563         self._copied_shares = {}
9564         self._storage = FakeStorage()
9565         self._nodemaker = make_nodemaker(self._storage)
9566hunk ./src/allmydata/test/test_mutable.py 700
9567-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
9568+        d = self._nodemaker.create_mutable_file(self.uploadables[0], version=version) # seqnum=1
9569         def _created(node):
9570             self._fn = node
9571             # now create multiple versions of the same file, and accumulate
9572hunk ./src/allmydata/test/test_mutable.py 707
9573             # their shares, so we can mix and match them later.
9574             d = defer.succeed(None)
9575             d.addCallback(self._copy_shares, 0)
9576-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[1])) #s2
9577+            d.addCallback(lambda res: node.overwrite(self.uploadables[1])) #s2
9578             d.addCallback(self._copy_shares, 1)
9579hunk ./src/allmydata/test/test_mutable.py 709
9580-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[2])) #s3
9581+            d.addCallback(lambda res: node.overwrite(self.uploadables[2])) #s3
9582             d.addCallback(self._copy_shares, 2)
9583hunk ./src/allmydata/test/test_mutable.py 711
9584-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[3])) #s4a
9585+            d.addCallback(lambda res: node.overwrite(self.uploadables[3])) #s4a
9586             d.addCallback(self._copy_shares, 3)
9587             # now we replace all the shares with version s3, and upload a new
9588             # version to get s4b.
9589hunk ./src/allmydata/test/test_mutable.py 717
9590             rollback = dict([(i,2) for i in range(10)])
9591             d.addCallback(lambda res: self._set_versions(rollback))
9592-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[4])) #s4b
9593+            d.addCallback(lambda res: node.overwrite(self.uploadables[4])) #s4b
9594             d.addCallback(self._copy_shares, 4)
9595             # we leave the storage in state 4
9596             return d
9597hunk ./src/allmydata/test/test_mutable.py 826
9598         # create a new file, which is large enough to knock the privkey out
9599         # of the early part of the file
9600         LARGE = "These are Larger contents" * 200 # about 5KB
9601-        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE))
9602+        LARGE_uploadable = MutableDataHandle(LARGE)
9603+        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
9604         def _created(large_fn):
9605             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
9606             return self.make_servermap(MODE_WRITE, large_fn2)
9607hunk ./src/allmydata/test/test_mutable.py 1842
9608 class MultipleEncodings(unittest.TestCase):
9609     def setUp(self):
9610         self.CONTENTS = "New contents go here"
9611+        self.uploadable = MutableDataHandle(self.CONTENTS)
9612         self._storage = FakeStorage()
9613         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
9614         self._storage_broker = self._nodemaker.storage_broker
9615hunk ./src/allmydata/test/test_mutable.py 1846
9616-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9617+        d = self._nodemaker.create_mutable_file(self.uploadable)
9618         def _created(node):
9619             self._fn = node
9620         d.addCallback(_created)
9621hunk ./src/allmydata/test/test_mutable.py 1872
9622         s = self._storage
9623         s._peers = {} # clear existing storage
9624         p2 = Publish(fn2, self._storage_broker, None)
9625-        d = p2.publish(data)
9626+        uploadable = MutableDataHandle(data)
9627+        d = p2.publish(uploadable)
9628         def _published(res):
9629             shares = s._peers
9630             s._peers = {}
9631hunk ./src/allmydata/test/test_mutable.py 2049
9632         self._set_versions(target)
9633 
9634         def _modify(oldversion, servermap, first_time):
9635-            return oldversion + " modified"
9636+            return MutableDataHandle(oldversion + " modified")
9637         d = self._fn.modify(_modify)
9638         d.addCallback(lambda res: self._fn.download_best_version())
9639         expected = self.CONTENTS[2] + " modified"
9640hunk ./src/allmydata/test/test_mutable.py 2175
9641         self.basedir = "mutable/Problems/test_publish_surprise"
9642         self.set_up_grid()
9643         nm = self.g.clients[0].nodemaker
9644-        d = nm.create_mutable_file("contents 1")
9645+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9646         def _created(n):
9647             d = defer.succeed(None)
9648             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9649hunk ./src/allmydata/test/test_mutable.py 2185
9650             d.addCallback(_got_smap1)
9651             # then modify the file, leaving the old map untouched
9652             d.addCallback(lambda res: log.msg("starting winning write"))
9653-            d.addCallback(lambda res: n.overwrite("contents 2"))
9654+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9655             # now attempt to modify the file with the old servermap. This
9656             # will look just like an uncoordinated write, in which every
9657             # single share got updated between our mapupdate and our publish
9658hunk ./src/allmydata/test/test_mutable.py 2194
9659                           self.shouldFail(UncoordinatedWriteError,
9660                                           "test_publish_surprise", None,
9661                                           n.upload,
9662-                                          "contents 2a", self.old_map))
9663+                                          MutableDataHandle("contents 2a"), self.old_map))
9664             return d
9665         d.addCallback(_created)
9666         return d
9667hunk ./src/allmydata/test/test_mutable.py 2203
9668         self.basedir = "mutable/Problems/test_retrieve_surprise"
9669         self.set_up_grid()
9670         nm = self.g.clients[0].nodemaker
9671-        d = nm.create_mutable_file("contents 1")
9672+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9673         def _created(n):
9674             d = defer.succeed(None)
9675             d.addCallback(lambda res: n.get_servermap(MODE_READ))
9676hunk ./src/allmydata/test/test_mutable.py 2213
9677             d.addCallback(_got_smap1)
9678             # then modify the file, leaving the old map untouched
9679             d.addCallback(lambda res: log.msg("starting winning write"))
9680-            d.addCallback(lambda res: n.overwrite("contents 2"))
9681+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9682             # now attempt to retrieve the old version with the old servermap.
9683             # This will look like someone has changed the file since we
9684             # updated the servermap.
9685hunk ./src/allmydata/test/test_mutable.py 2241
9686         self.basedir = "mutable/Problems/test_unexpected_shares"
9687         self.set_up_grid()
9688         nm = self.g.clients[0].nodemaker
9689-        d = nm.create_mutable_file("contents 1")
9690+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9691         def _created(n):
9692             d = defer.succeed(None)
9693             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9694hunk ./src/allmydata/test/test_mutable.py 2253
9695                 self.g.remove_server(peer0)
9696                 # then modify the file, leaving the old map untouched
9697                 log.msg("starting winning write")
9698-                return n.overwrite("contents 2")
9699+                return n.overwrite(MutableDataHandle("contents 2"))
9700             d.addCallback(_got_smap1)
9701             # now attempt to modify the file with the old servermap. This
9702             # will look just like an uncoordinated write, in which every
9703hunk ./src/allmydata/test/test_mutable.py 2263
9704                           self.shouldFail(UncoordinatedWriteError,
9705                                           "test_surprise", None,
9706                                           n.upload,
9707-                                          "contents 2a", self.old_map))
9708+                                          MutableDataHandle("contents 2a"), self.old_map))
9709             return d
9710         d.addCallback(_created)
9711         return d
9712hunk ./src/allmydata/test/test_mutable.py 2267
9713+    test_unexpected_shares.timeout = 15
9714 
9715     def test_bad_server(self):
9716         # Break one server, then create the file: the initial publish should
9717hunk ./src/allmydata/test/test_mutable.py 2303
9718         d.addCallback(_break_peer0)
9719         # now "create" the file, using the pre-established key, and let the
9720         # initial publish finally happen
9721-        d.addCallback(lambda res: nm.create_mutable_file("contents 1"))
9722+        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
9723         # that ought to work
9724         def _got_node(n):
9725             d = n.download_best_version()
9726hunk ./src/allmydata/test/test_mutable.py 2312
9727             def _break_peer1(res):
9728                 self.connection1.broken = True
9729             d.addCallback(_break_peer1)
9730-            d.addCallback(lambda res: n.overwrite("contents 2"))
9731+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9732             # that ought to work too
9733             d.addCallback(lambda res: n.download_best_version())
9734             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9735hunk ./src/allmydata/test/test_mutable.py 2344
9736         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
9737         self.g.break_server(peerids[0])
9738 
9739-        d = nm.create_mutable_file("contents 1")
9740+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9741         def _created(n):
9742             d = n.download_best_version()
9743             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9744hunk ./src/allmydata/test/test_mutable.py 2352
9745             def _break_second_server(res):
9746                 self.g.break_server(peerids[1])
9747             d.addCallback(_break_second_server)
9748-            d.addCallback(lambda res: n.overwrite("contents 2"))
9749+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9750             # that ought to work too
9751             d.addCallback(lambda res: n.download_best_version())
9752             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9753hunk ./src/allmydata/test/test_mutable.py 2371
9754         d = self.shouldFail(NotEnoughServersError,
9755                             "test_publish_all_servers_bad",
9756                             "Ran out of non-bad servers",
9757-                            nm.create_mutable_file, "contents")
9758+                            nm.create_mutable_file, MutableDataHandle("contents"))
9759         return d
9760 
9761     def test_publish_no_servers(self):
9762hunk ./src/allmydata/test/test_mutable.py 2383
9763         d = self.shouldFail(NotEnoughServersError,
9764                             "test_publish_no_servers",
9765                             "Ran out of non-bad servers",
9766-                            nm.create_mutable_file, "contents")
9767+                            nm.create_mutable_file, MutableDataHandle("contents"))
9768         return d
9769     test_publish_no_servers.timeout = 30
9770 
9771hunk ./src/allmydata/test/test_mutable.py 2401
9772         # we need some contents that are large enough to push the privkey out
9773         # of the early part of the file
9774         LARGE = "These are Larger contents" * 2000 # about 50KB
9775-        d = nm.create_mutable_file(LARGE)
9776+        LARGE_uploadable = MutableDataHandle(LARGE)
9777+        d = nm.create_mutable_file(LARGE_uploadable)
9778         def _created(n):
9779             self.uri = n.get_uri()
9780             self.n2 = nm.create_from_cap(self.uri)
9781hunk ./src/allmydata/test/test_mutable.py 2438
9782         self.set_up_grid(num_servers=20)
9783         nm = self.g.clients[0].nodemaker
9784         LARGE = "These are Larger contents" * 2000 # about 50KiB
9785+        LARGE_uploadable = MutableDataHandle(LARGE)
9786         nm._node_cache = DevNullDictionary() # disable the nodecache
9787 
9788hunk ./src/allmydata/test/test_mutable.py 2441
9789-        d = nm.create_mutable_file(LARGE)
9790+        d = nm.create_mutable_file(LARGE_uploadable)
9791         def _created(n):
9792             self.uri = n.get_uri()
9793             self.n2 = nm.create_from_cap(self.uri)
9794hunk ./src/allmydata/test/test_mutable.py 2464
9795         self.set_up_grid(num_servers=20)
9796         nm = self.g.clients[0].nodemaker
9797         CONTENTS = "contents" * 2000
9798-        d = nm.create_mutable_file(CONTENTS)
9799+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9800+        d = nm.create_mutable_file(CONTENTS_uploadable)
9801         def _created(node):
9802             self._node = node
9803         d.addCallback(_created)
9804hunk ./src/allmydata/test/test_system.py 22
9805 from allmydata.monitor import Monitor
9806 from allmydata.mutable.common import NotWriteableError
9807 from allmydata.mutable import layout as mutable_layout
9808+from allmydata.mutable.publish import MutableDataHandle
9809 from foolscap.api import DeadReferenceError
9810 from twisted.python.failure import Failure
9811 from twisted.web.client import getPage
9812hunk ./src/allmydata/test/test_system.py 460
9813     def test_mutable(self):
9814         self.basedir = "system/SystemTest/test_mutable"
9815         DATA = "initial contents go here."  # 25 bytes % 3 != 0
9816+        DATA_uploadable = MutableDataHandle(DATA)
9817         NEWDATA = "new contents yay"
9818hunk ./src/allmydata/test/test_system.py 462
9819+        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
9820         NEWERDATA = "this is getting old"
9821hunk ./src/allmydata/test/test_system.py 464
9822+        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
9823 
9824         d = self.set_up_nodes(use_key_generator=True)
9825 
9826hunk ./src/allmydata/test/test_system.py 471
9827         def _create_mutable(res):
9828             c = self.clients[0]
9829             log.msg("starting create_mutable_file")
9830-            d1 = c.create_mutable_file(DATA)
9831+            d1 = c.create_mutable_file(DATA_uploadable)
9832             def _done(res):
9833                 log.msg("DONE: %s" % (res,))
9834                 self._mutable_node_1 = res
9835hunk ./src/allmydata/test/test_system.py 558
9836             self.failUnlessEqual(res, DATA)
9837             # replace the data
9838             log.msg("starting replace1")
9839-            d1 = newnode.overwrite(NEWDATA)
9840+            d1 = newnode.overwrite(NEWDATA_uploadable)
9841             d1.addCallback(lambda res: newnode.download_best_version())
9842             return d1
9843         d.addCallback(_check_download_3)
9844hunk ./src/allmydata/test/test_system.py 572
9845             newnode2 = self.clients[3].create_node_from_uri(uri)
9846             self._newnode3 = self.clients[3].create_node_from_uri(uri)
9847             log.msg("starting replace2")
9848-            d1 = newnode1.overwrite(NEWERDATA)
9849+            d1 = newnode1.overwrite(NEWERDATA_uploadable)
9850             d1.addCallback(lambda res: newnode2.download_best_version())
9851             return d1
9852         d.addCallback(_check_download_4)
9853hunk ./src/allmydata/test/test_system.py 642
9854         def _check_empty_file(res):
9855             # make sure we can create empty files, this usually screws up the
9856             # segsize math
9857-            d1 = self.clients[2].create_mutable_file("")
9858+            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
9859             d1.addCallback(lambda newnode: newnode.download_best_version())
9860             d1.addCallback(lambda res: self.failUnlessEqual("", res))
9861             return d1
9862hunk ./src/allmydata/test/test_system.py 673
9863                                  self.key_generator_svc.key_generator.pool_size + size_delta)
9864 
9865         d.addCallback(check_kg_poolsize, 0)
9866-        d.addCallback(lambda junk: self.clients[3].create_mutable_file('hello, world'))
9867+        d.addCallback(lambda junk:
9868+            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
9869         d.addCallback(check_kg_poolsize, -1)
9870         d.addCallback(lambda junk: self.clients[3].create_dirnode())
9871         d.addCallback(check_kg_poolsize, -2)
9872hunk ./src/allmydata/test/test_web.py 3183
9873         def _stash_mutable_uri(n, which):
9874             self.uris[which] = n.get_uri()
9875             assert isinstance(self.uris[which], str)
9876-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9877+        d.addCallback(lambda ign:
9878+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9879         d.addCallback(_stash_mutable_uri, "corrupt")
9880         d.addCallback(lambda ign:
9881                       c0.upload(upload.Data("literal", convergence="")))
9882hunk ./src/allmydata/test/test_web.py 3330
9883         def _stash_mutable_uri(n, which):
9884             self.uris[which] = n.get_uri()
9885             assert isinstance(self.uris[which], str)
9886-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9887+        d.addCallback(lambda ign:
9888+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9889         d.addCallback(_stash_mutable_uri, "corrupt")
9890 
9891         def _compute_fileurls(ignored):
9892hunk ./src/allmydata/test/test_web.py 3993
9893         def _stash_mutable_uri(n, which):
9894             self.uris[which] = n.get_uri()
9895             assert isinstance(self.uris[which], str)
9896-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
9897+        d.addCallback(lambda ign:
9898+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
9899         d.addCallback(_stash_mutable_uri, "mutable")
9900 
9901         def _compute_fileurls(ignored):
9902hunk ./src/allmydata/test/test_web.py 4093
9903                                                         convergence="")))
9904         d.addCallback(_stash_uri, "small")
9905 
9906-        d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
9907+        d.addCallback(lambda ign:
9908+            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
9909         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9910         d.addCallback(_stash_uri, "mutable")
9911 
9912}
9913[Alter mutable files to use file-like objects for publishing instead of strings.
9914Kevan Carstensen <kevan@isnotajoke.com>**20100708000732
9915 Ignore-this: 8dd07d95386b6d540bc21289f981ebd0
9916] {
9917hunk ./src/allmydata/dirnode.py 11
9918 from allmydata.mutable.common import NotWriteableError
9919 from allmydata.mutable.filenode import MutableFileNode
9920 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
9921+from allmydata.mutable.publish import MutableDataHandle
9922 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
9923      IImmutableFileNode, IMutableFileNode, \
9924      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
9925hunk ./src/allmydata/dirnode.py 104
9926 
9927         del children[self.name]
9928         new_contents = self.node._pack_contents(children)
9929-        return new_contents
9930+        uploadable = MutableDataHandle(new_contents)
9931+        return uploadable
9932 
9933 
9934 class MetadataSetter:
9935hunk ./src/allmydata/dirnode.py 130
9936 
9937         children[name] = (child, metadata)
9938         new_contents = self.node._pack_contents(children)
9939-        return new_contents
9940+        uploadable = MutableDataHandle(new_contents)
9941+        return uploadable
9942 
9943 
9944 class Adder:
9945hunk ./src/allmydata/dirnode.py 175
9946 
9947             children[name] = (child, metadata)
9948         new_contents = self.node._pack_contents(children)
9949-        return new_contents
9950+        uploadable = MutableDataHandle(new_contents)
9951+        return uploadable
9952 
9953 def _encrypt_rw_uri(writekey, rw_uri):
9954     precondition(isinstance(rw_uri, str), rw_uri)
9955hunk ./src/allmydata/mutable/filenode.py 7
9956 from zope.interface import implements
9957 from twisted.internet import defer, reactor
9958 from foolscap.api import eventually
9959-from allmydata.interfaces import IMutableFileNode, \
9960-     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
9961+from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
9962+                                 NotEnoughSharesError, \
9963+                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
9964 from allmydata.util import hashutil, log
9965 from allmydata.util.assertutil import precondition
9966 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
9967hunk ./src/allmydata/mutable/filenode.py 16
9968 from allmydata.monitor import Monitor
9969 from pycryptopp.cipher.aes import AES
9970 
9971-from allmydata.mutable.publish import Publish
9972+from allmydata.mutable.publish import Publish, MutableFileHandle, \
9973+                                      MutableDataHandle
9974 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
9975      ResponseCache, UncoordinatedWriteError
9976 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
9977hunk ./src/allmydata/mutable/filenode.py 133
9978         return self._upload(initial_contents, None)
9979 
9980     def _get_initial_contents(self, contents):
9981-        if isinstance(contents, str):
9982-            return contents
9983         if contents is None:
9984hunk ./src/allmydata/mutable/filenode.py 134
9985-            return ""
9986+            return MutableDataHandle("")
9987+
9988+        if IMutableUploadable.providedBy(contents):
9989+            return contents
9990+
9991         assert callable(contents), "%s should be callable, not %s" % \
9992                (contents, type(contents))
9993         return contents(self)
9994hunk ./src/allmydata/mutable/filenode.py 353
9995     def overwrite(self, new_contents):
9996         return self._do_serialized(self._overwrite, new_contents)
9997     def _overwrite(self, new_contents):
9998+        assert IMutableUploadable.providedBy(new_contents)
9999+
10000         servermap = ServerMap()
10001         d = self._update_servermap(servermap, mode=MODE_WRITE)
10002         d.addCallback(lambda ignored: self._upload(new_contents, servermap))
10003hunk ./src/allmydata/mutable/filenode.py 431
10004                 # recovery when it observes UCWE, we need to do a second
10005                 # publish. See #551 for details. We'll basically loop until
10006                 # we managed an uncontested publish.
10007-                new_contents = old_contents
10008-            precondition(isinstance(new_contents, str),
10009-                         "Modifier function must return a string or None")
10010+                old_uploadable = MutableDataHandle(old_contents)
10011+                new_contents = old_uploadable
10012+            precondition((IMutableUploadable.providedBy(new_contents) or
10013+                          new_contents is None),
10014+                         "Modifier function must return an IMutableUploadable "
10015+                         "or None")
10016             return self._upload(new_contents, servermap)
10017         d.addCallback(_apply)
10018         return d
10019hunk ./src/allmydata/mutable/filenode.py 472
10020         return self._do_serialized(self._upload, new_contents, servermap)
10021     def _upload(self, new_contents, servermap):
10022         assert self._pubkey, "update_servermap must be called before publish"
10023+        assert IMutableUploadable.providedBy(new_contents)
10024+
10025         p = Publish(self, self._storage_broker, servermap)
10026         if self._history:
10027hunk ./src/allmydata/mutable/filenode.py 476
10028-            self._history.notify_publish(p.get_status(), len(new_contents))
10029+            self._history.notify_publish(p.get_status(), new_contents.get_size())
10030         d = p.publish(new_contents)
10031hunk ./src/allmydata/mutable/filenode.py 478
10032-        d.addCallback(self._did_upload, len(new_contents))
10033+        d.addCallback(self._did_upload, new_contents.get_size())
10034         return d
10035     def _did_upload(self, res, size):
10036         self._most_recent_size = size
10037hunk ./src/allmydata/mutable/publish.py 141
10038 
10039         # 0. Setup encoding parameters, encoder, and other such things.
10040         # 1. Encrypt, encode, and publish segments.
10041-        self.data = StringIO(newdata)
10042-        self.datalength = len(newdata)
10043+        assert IMutableUploadable.providedBy(newdata)
10044+
10045+        self.data = newdata
10046+        self.datalength = newdata.get_size()
10047 
10048         self.log("starting publish, datalen is %s" % self.datalength)
10049         self._status.set_size(self.datalength)
10050hunk ./src/allmydata/mutable/publish.py 442
10051 
10052         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
10053         data = self.data.read(segsize)
10054+        # XXX: This is dumb. Why return a list?
10055+        data = "".join(data)
10056 
10057         assert len(data) == segsize
10058 
10059hunk ./src/allmydata/mutable/repairer.py 5
10060 from zope.interface import implements
10061 from twisted.internet import defer
10062 from allmydata.interfaces import IRepairResults, ICheckResults
10063+from allmydata.mutable.publish import MutableDataHandle
10064 
10065 class RepairResults:
10066     implements(IRepairResults)
10067hunk ./src/allmydata/mutable/repairer.py 108
10068             raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.")
10069 
10070         d = self.node.download_version(smap, best_version, fetch_privkey=True)
10071+        d.addCallback(lambda data:
10072+            MutableDataHandle(data))
10073         d.addCallback(self.node.upload, smap)
10074         d.addCallback(self.get_results, smap)
10075         return d
10076hunk ./src/allmydata/nodemaker.py 9
10077 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
10078 from allmydata.immutable.upload import Data
10079 from allmydata.mutable.filenode import MutableFileNode
10080+from allmydata.mutable.publish import MutableDataHandle
10081 from allmydata.dirnode import DirectoryNode, pack_children
10082 from allmydata.unknown import UnknownNode
10083 from allmydata import uri
10084merger 0.0 (
10085merger 0.0 (
10086hunk ./src/allmydata/nodemaker.py 107
10087-                                     pack_children(n, initial_children))
10088+                                     pack_children(n, initial_children),
10089+                                     version)
10090hunk ./src/allmydata/nodemaker.py 107
10091-                                     pack_children(n, initial_children))
10092+                                     pack_children(initial_children, n.get_writekey()))
10093)
10094hunk ./src/allmydata/nodemaker.py 107
10095-                                     pack_children(n, initial_children),
10096+                                     MutableDataHandle(
10097+                                        pack_children(n, initial_children)),
10098)
10099hunk ./src/allmydata/web/filenode.py 12
10100 from allmydata.interfaces import ExistingChildError
10101 from allmydata.monitor import Monitor
10102 from allmydata.immutable.upload import FileHandle
10103+from allmydata.mutable.publish import MutableFileHandle
10104 from allmydata.util import log, base32
10105 
10106 from allmydata.web.common import text_plain, WebError, RenderMixin, \
10107hunk ./src/allmydata/web/filenode.py 27
10108         # a new file is being uploaded in our place.
10109         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10110         if mutable:
10111-            req.content.seek(0)
10112-            data = req.content.read()
10113+            data = MutableFileHandle(req.content)
10114             d = client.create_mutable_file(data)
10115             def _uploaded(newnode):
10116                 d2 = self.parentnode.set_node(self.name, newnode,
10117hunk ./src/allmydata/web/filenode.py 61
10118         d.addCallback(lambda res: childnode.get_uri())
10119         return d
10120 
10121-    def _read_data_from_formpost(self, req):
10122-        # SDMF: files are small, and we can only upload data, so we read
10123-        # the whole file into memory before uploading.
10124-        contents = req.fields["file"]
10125-        contents.file.seek(0)
10126-        data = contents.file.read()
10127-        return data
10128 
10129     def replace_me_with_a_formpost(self, req, client, replace):
10130         # create a new file, maybe mutable, maybe immutable
10131hunk ./src/allmydata/web/filenode.py 66
10132         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10133 
10134+        # create an immutable file
10135+        contents = req.fields["file"]
10136         if mutable:
10137hunk ./src/allmydata/web/filenode.py 69
10138-            data = self._read_data_from_formpost(req)
10139-            d = client.create_mutable_file(data)
10140+            uploadable = MutableFileHandle(contents.file)
10141+            d = client.create_mutable_file(uploadable)
10142             def _uploaded(newnode):
10143                 d2 = self.parentnode.set_node(self.name, newnode,
10144                                               overwrite=replace)
10145hunk ./src/allmydata/web/filenode.py 78
10146                 return d2
10147             d.addCallback(_uploaded)
10148             return d
10149-        # create an immutable file
10150-        contents = req.fields["file"]
10151+
10152         uploadable = FileHandle(contents.file, convergence=client.convergence)
10153         d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
10154         d.addCallback(lambda newnode: newnode.get_uri())
10155hunk ./src/allmydata/web/filenode.py 84
10156         return d
10157 
10158+
10159 class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
10160     def __init__(self, client, parentnode, name):
10161         rend.Page.__init__(self)
10162hunk ./src/allmydata/web/filenode.py 278
10163 
10164     def replace_my_contents(self, req):
10165         req.content.seek(0)
10166-        new_contents = req.content.read()
10167+        new_contents = MutableFileHandle(req.content)
10168         d = self.node.overwrite(new_contents)
10169         d.addCallback(lambda res: self.node.get_uri())
10170         return d
10171hunk ./src/allmydata/web/filenode.py 286
10172     def replace_my_contents_with_a_formpost(self, req):
10173         # we have a mutable file. Get the data from the formpost, and replace
10174         # the mutable file's contents with it.
10175-        new_contents = self._read_data_from_formpost(req)
10176+        new_contents = req.fields['file']
10177+        new_contents = MutableFileHandle(new_contents.file)
10178+
10179         d = self.node.overwrite(new_contents)
10180         d.addCallback(lambda res: self.node.get_uri())
10181         return d
10182hunk ./src/allmydata/web/unlinked.py 7
10183 from twisted.internet import defer
10184 from nevow import rend, url, tags as T
10185 from allmydata.immutable.upload import FileHandle
10186+from allmydata.mutable.publish import MutableFileHandle
10187 from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
10188      convert_children_json, WebError
10189 from allmydata.web import status
10190hunk ./src/allmydata/web/unlinked.py 23
10191 def PUTUnlinkedSSK(req, client):
10192     # SDMF: files are small, and we can only upload data
10193     req.content.seek(0)
10194-    data = req.content.read()
10195+    data = MutableFileHandle(req.content)
10196     d = client.create_mutable_file(data)
10197     d.addCallback(lambda n: n.get_uri())
10198     return d
10199hunk ./src/allmydata/web/unlinked.py 87
10200     # "POST /uri", to create an unlinked file.
10201     # SDMF: files are small, and we can only upload data
10202     contents = req.fields["file"]
10203-    contents.file.seek(0)
10204-    data = contents.file.read()
10205+    data = MutableFileHandle(contents.file)
10206     d = client.create_mutable_file(data)
10207     d.addCallback(lambda n: n.get_uri())
10208     return d
10209}
10210[test/test_sftp.py: alter a setup routine to work with new mutable file APIs.
10211Kevan Carstensen <kevan@isnotajoke.com>**20100708193522
10212 Ignore-this: 434bbe1347072076c0836d26fca8ac8a
10213] {
10214hunk ./src/allmydata/test/test_sftp.py 32
10215 
10216 from allmydata.util.consumer import download_to_data
10217 from allmydata.immutable import upload
10218+from allmydata.mutable import publish
10219 from allmydata.test.no_network import GridTestMixin
10220 from allmydata.test.common import ShouldFailMixin
10221 from allmydata.test.common_util import ReallyEqualMixin
10222hunk ./src/allmydata/test/test_sftp.py 84
10223         return d
10224 
10225     def _set_up_tree(self):
10226-        d = self.client.create_mutable_file("mutable file contents")
10227+        u = publish.MutableDataHandle("mutable file contents")
10228+        d = self.client.create_mutable_file(u)
10229         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
10230         def _created_mutable(n):
10231             self.mutable = n
10232}
10233[mutable/publish.py: make MutableFileHandle seek to the beginning of its file handle before reading.
10234Kevan Carstensen <kevan@isnotajoke.com>**20100708193600
10235 Ignore-this: 453a737dc62a79c77b3d360fed9000ab
10236] hunk ./src/allmydata/mutable/publish.py 989
10237         assert hasattr(filehandle, "close")
10238 
10239         self._filehandle = filehandle
10240+        # We must start reading at the beginning of the file, or we risk
10241+        # encountering errors when the data read does not match the size
10242+        # reported to the uploader.
10243+        self._filehandle.seek(0)
10244 
10245 
10246     def get_size(self):
10247[Refactor download interfaces to be more uniform, per #993
10248Kevan Carstensen <kevan@isnotajoke.com>**20100709232912
10249 Ignore-this: 277c5699c4a2dd7c03ecfa0a28458f5b
10250] {
10251hunk ./src/allmydata/immutable/filenode.py 10
10252 from foolscap.api import eventually
10253 from allmydata.interfaces import IImmutableFileNode, ICheckable, \
10254      IDownloadTarget, IUploadResults
10255-from allmydata.util import dictutil, log, base32
10256+from allmydata.util import dictutil, log, base32, consumer
10257 from allmydata.uri import CHKFileURI, LiteralFileURI
10258 from allmydata.immutable.checker import Checker
10259 from allmydata.check_results import CheckResults, CheckAndRepairResults
10260hunk ./src/allmydata/immutable/filenode.py 318
10261                       self.download_cache.read(consumer, offset, size))
10262         return d
10263 
10264+    # IReadable, IFileNode
10265+
10266+    def get_best_readable_version(self):
10267+        """
10268+        Return an IReadable of the best version of this file. Since
10269+        immutable files can have only one version, we just return the
10270+        current filenode.
10271+        """
10272+        return self
10273+
10274+
10275+    def download_best_version(self):
10276+        """
10277+        Download the best version of this file, returning its contents
10278+        as a bytestring. Since there is only one version of an immutable
10279+        file, we download and return the contents of this file.
10280+        """
10281+        d = consumer.download_to_data(self)
10282+        return d
10283+
10284+    # for an immutable file, download_to_data (specified in IReadable)
10285+    # is the same as download_best_version (specified in IFileNode). For
10286+    # mutable files, the difference is more meaningful, since they can
10287+    # have multiple versions.
10288+    download_to_data = download_best_version
10289+
10290+
10291+    # get_size() (IReadable), get_current_size() (IFilesystemNode), and
10292+    # get_size_of_best_version(IFileNode) are all the same for immutable
10293+    # files.
10294+    get_size_of_best_version = get_current_size
10295+
10296+
10297 class LiteralProducer:
10298     implements(IPushProducer)
10299     def resumeProducing(self):
10300hunk ./src/allmydata/immutable/filenode.py 409
10301         d = basic.FileSender().beginFileTransfer(StringIO(data), consumer)
10302         d.addCallback(lambda lastSent: consumer)
10303         return d
10304+
10305+    # IReadable, IFileNode, IFilesystemNode
10306+    def get_best_readable_version(self):
10307+        return self
10308+
10309+
10310+    def download_best_version(self):
10311+        return defer.succeed(self.u.data)
10312+
10313+
10314+    download_to_data = download_best_version
10315+    get_size_of_best_version = get_current_size
10316hunk ./src/allmydata/interfaces.py 563
10317 class MustNotBeUnknownRWError(CapConstraintError):
10318     """Cannot add an unknown child cap specified in a rw_uri field."""
10319 
10320+
10321+class IReadable(Interface):
10322+    """I represent a readable object -- either an immutable file, or a
10323+    specific version of a mutable file.
10324+    """
10325+
10326+    def is_readonly():
10327+        """Return True if this reference provides mutable access to the given
10328+        file or directory (i.e. if you can modify it), or False if not. Note
10329+        that even if this reference is read-only, someone else may hold a
10330+        read-write reference to it.
10331+
10332+        For an IReadable returned by get_best_readable_version(), this will
10333+        always return True, but for instances of subinterfaces such as
10334+        IMutableFileVersion, it may return False."""
10335+
10336+    def is_mutable():
10337+        """Return True if this file or directory is mutable (by *somebody*,
10338+        not necessarily you), False if it is is immutable. Note that a file
10339+        might be mutable overall, but your reference to it might be
10340+        read-only. On the other hand, all references to an immutable file
10341+        will be read-only; there are no read-write references to an immutable
10342+        file."""
10343+
10344+    def get_storage_index():
10345+        """Return the storage index of the file."""
10346+
10347+    def get_size():
10348+        """Return the length (in bytes) of this readable object."""
10349+
10350+    def download_to_data():
10351+        """Download all of the file contents. I return a Deferred that fires
10352+        with the contents as a byte string."""
10353+
10354+    def read(consumer, offset=0, size=None):
10355+        """Download a portion (possibly all) of the file's contents, making
10356+        them available to the given IConsumer. Return a Deferred that fires
10357+        (with the consumer) when the consumer is unregistered (either because
10358+        the last byte has been given to it, or because the consumer threw an
10359+        exception during write(), possibly because it no longer wants to
10360+        receive data). The portion downloaded will start at 'offset' and
10361+        contain 'size' bytes (or the remainder of the file if size==None).
10362+
10363+        The consumer will be used in non-streaming mode: an IPullProducer
10364+        will be attached to it.
10365+
10366+        The consumer will not receive data right away: several network trips
10367+        must occur first. The order of events will be::
10368+
10369+         consumer.registerProducer(p, streaming)
10370+          (if streaming == False)::
10371+           consumer does p.resumeProducing()
10372+            consumer.write(data)
10373+           consumer does p.resumeProducing()
10374+            consumer.write(data).. (repeat until all data is written)
10375+         consumer.unregisterProducer()
10376+         deferred.callback(consumer)
10377+
10378+        If a download error occurs, or an exception is raised by
10379+        consumer.registerProducer() or consumer.write(), I will call
10380+        consumer.unregisterProducer() and then deliver the exception via
10381+        deferred.errback(). To cancel the download, the consumer should call
10382+        p.stopProducing(), which will result in an exception being delivered
10383+        via deferred.errback().
10384+
10385+        See src/allmydata/util/consumer.py for an example of a simple
10386+        download-to-memory consumer.
10387+        """
10388+
10389+
10390+class IMutableFileVersion(IReadable):
10391+    """I provide access to a particular version of a mutable file. The
10392+    access is read/write if I was obtained from a filenode derived from
10393+    a write cap, or read-only if the filenode was derived from a read cap.
10394+    """
10395+
10396+    def get_sequence_number():
10397+        """Return the sequence number of this version."""
10398+
10399+    def get_servermap():
10400+        """Return the IMutableFileServerMap instance that was used to create
10401+        this object.
10402+        """
10403+
10404+    def get_writekey():
10405+        """Return this filenode's writekey, or None if the node does not have
10406+        write-capability. This may be used to assist with data structures
10407+        that need to make certain data available only to writers, such as the
10408+        read-write child caps in dirnodes. The recommended process is to have
10409+        reader-visible data be submitted to the filenode in the clear (where
10410+        it will be encrypted by the filenode using the readkey), but encrypt
10411+        writer-visible data using this writekey.
10412+        """
10413+
10414+    # TODO: Can this be overwrite instead of replace?
10415+    def replace(new_contents):
10416+        """Replace the contents of the mutable file, provided that no other
10417+        node has published (or is attempting to publish, concurrently) a
10418+        newer version of the file than this one.
10419+
10420+        I will avoid modifying any share that is different than the version
10421+        given by get_sequence_number(). However, if another node is writing
10422+        to the file at the same time as me, I may manage to update some shares
10423+        while they update others. If I see any evidence of this, I will signal
10424+        UncoordinatedWriteError, and the file will be left in an inconsistent
10425+        state (possibly the version you provided, possibly the old version,
10426+        possibly somebody else's version, and possibly a mix of shares from
10427+        all of these).
10428+
10429+        The recommended response to UncoordinatedWriteError is to either
10430+        return it to the caller (since they failed to coordinate their
10431+        writes), or to attempt some sort of recovery. It may be sufficient to
10432+        wait a random interval (with exponential backoff) and repeat your
10433+        operation. If I do not signal UncoordinatedWriteError, then I was
10434+        able to write the new version without incident.
10435+
10436+        I return a Deferred that fires (with a PublishStatus object) when the
10437+        update has completed.
10438+        """
10439+
10440+    def modify(modifier_cb):
10441+        """Modify the contents of the file, by downloading this version,
10442+        applying the modifier function (or bound method), then uploading
10443+        the new version. This will succeed as long as no other node
10444+        publishes a version between the download and the upload.
10445+        I return a Deferred that fires (with a PublishStatus object) when
10446+        the update is complete.
10447+
10448+        The modifier callable will be given three arguments: a string (with
10449+        the old contents), a 'first_time' boolean, and a servermap. As with
10450+        download_to_data(), the old contents will be from this version,
10451+        but the modifier can use the servermap to make other decisions
10452+        (such as refusing to apply the delta if there are multiple parallel
10453+        versions, or if there is evidence of a newer unrecoverable version).
10454+        'first_time' will be True the first time the modifier is called,
10455+        and False on any subsequent calls.
10456+
10457+        The callable should return a string with the new contents. The
10458+        callable must be prepared to be called multiple times, and must
10459+        examine the input string to see if the change that it wants to make
10460+        is already present in the old version. If it does not need to make
10461+        any changes, it can either return None, or return its input string.
10462+
10463+        If the modifier raises an exception, it will be returned in the
10464+        errback.
10465+        """
10466+
10467+
10468 # The hierarchy looks like this:
10469 #  IFilesystemNode
10470 #   IFileNode
10471hunk ./src/allmydata/interfaces.py 801
10472     def raise_error():
10473         """Raise any error associated with this node."""
10474 
10475+    # XXX: These may not be appropriate outside the context of an IReadable.
10476     def get_size():
10477         """Return the length (in bytes) of the data this node represents. For
10478         directory nodes, I return the size of the backing store. I return
10479hunk ./src/allmydata/interfaces.py 818
10480 class IFileNode(IFilesystemNode):
10481     """I am a node which represents a file: a sequence of bytes. I am not a
10482     container, like IDirectoryNode."""
10483+    def get_best_readable_version():
10484+        """Return a Deferred that fires with an IReadable for the 'best'
10485+        available version of the file. The IReadable provides only read
10486+        access, even if this filenode was derived from a write cap.
10487 
10488hunk ./src/allmydata/interfaces.py 823
10489-class IImmutableFileNode(IFileNode):
10490-    def read(consumer, offset=0, size=None):
10491-        """Download a portion (possibly all) of the file's contents, making
10492-        them available to the given IConsumer. Return a Deferred that fires
10493-        (with the consumer) when the consumer is unregistered (either because
10494-        the last byte has been given to it, or because the consumer threw an
10495-        exception during write(), possibly because it no longer wants to
10496-        receive data). The portion downloaded will start at 'offset' and
10497-        contain 'size' bytes (or the remainder of the file if size==None).
10498-
10499-        The consumer will be used in non-streaming mode: an IPullProducer
10500-        will be attached to it.
10501+        For an immutable file, there is only one version. For a mutable
10502+        file, the 'best' version is the recoverable version with the
10503+        highest sequence number. If no uncoordinated writes have occurred,
10504+        and if enough shares are available, then this will be the most
10505+        recent version that has been uploaded. If no version is recoverable,
10506+        the Deferred will errback with an UnrecoverableFileError.
10507+        """
10508 
10509hunk ./src/allmydata/interfaces.py 831
10510-        The consumer will not receive data right away: several network trips
10511-        must occur first. The order of events will be::
10512+    def download_best_version():
10513+        """Download the contents of the version that would be returned
10514+        by get_best_readable_version(). This is equivalent to calling
10515+        download_to_data() on the IReadable given by that method.
10516 
10517hunk ./src/allmydata/interfaces.py 836
10518-         consumer.registerProducer(p, streaming)
10519-          (if streaming == False)::
10520-           consumer does p.resumeProducing()
10521-            consumer.write(data)
10522-           consumer does p.resumeProducing()
10523-            consumer.write(data).. (repeat until all data is written)
10524-         consumer.unregisterProducer()
10525-         deferred.callback(consumer)
10526+        I return a Deferred that fires with a byte string when the file
10527+        has been fully downloaded. To support streaming download, use
10528+        the 'read' method of IReadable. If no version is recoverable,
10529+        the Deferred will errback with an UnrecoverableFileError.
10530+        """
10531 
10532hunk ./src/allmydata/interfaces.py 842
10533-        If a download error occurs, or an exception is raised by
10534-        consumer.registerProducer() or consumer.write(), I will call
10535-        consumer.unregisterProducer() and then deliver the exception via
10536-        deferred.errback(). To cancel the download, the consumer should call
10537-        p.stopProducing(), which will result in an exception being delivered
10538-        via deferred.errback().
10539+    def get_size_of_best_version():
10540+        """Find the size of the version that would be returned by
10541+        get_best_readable_version().
10542 
10543hunk ./src/allmydata/interfaces.py 846
10544-        See src/allmydata/util/consumer.py for an example of a simple
10545-        download-to-memory consumer.
10546+        I return a Deferred that fires with an integer. If no version
10547+        is recoverable, the Deferred will errback with an
10548+        UnrecoverableFileError.
10549         """
10550 
10551hunk ./src/allmydata/interfaces.py 851
10552+
10553+class IImmutableFileNode(IFileNode, IReadable):
10554+    """I am a node representing an immutable file. Immutable files have
10555+    only one version"""
10556+
10557+
10558 class IMutableFileNode(IFileNode):
10559     """I provide access to a 'mutable file', which retains its identity
10560     regardless of what contents are put in it.
10561hunk ./src/allmydata/interfaces.py 916
10562     only be retrieved and updated all-at-once, as a single big string. Future
10563     versions of our mutable files will remove this restriction.
10564     """
10565-
10566-    def download_best_version():
10567-        """Download the 'best' available version of the file, meaning one of
10568-        the recoverable versions with the highest sequence number. If no
10569+    def get_best_mutable_version():
10570+        """Return a Deferred that fires with an IMutableFileVersion for
10571+        the 'best' available version of the file. The best version is
10572+        the recoverable version with the highest sequence number. If no
10573         uncoordinated writes have occurred, and if enough shares are
10574hunk ./src/allmydata/interfaces.py 921
10575-        available, then this will be the most recent version that has been
10576-        uploaded.
10577-
10578-        I update an internal servermap with MODE_READ, determine which
10579-        version of the file is indicated by
10580-        servermap.best_recoverable_version(), and return a Deferred that
10581-        fires with its contents. If no version is recoverable, the Deferred
10582-        will errback with UnrecoverableFileError.
10583-        """
10584-
10585-    def get_size_of_best_version():
10586-        """Find the size of the version that would be downloaded with
10587-        download_best_version(), without actually downloading the whole file.
10588+        available, then this will be the most recent version that has
10589+        been uploaded.
10590 
10591hunk ./src/allmydata/interfaces.py 924
10592-        I return a Deferred that fires with an integer.
10593+        If no version is recoverable, the Deferred will errback with an
10594+        UnrecoverableFileError.
10595         """
10596 
10597     def overwrite(new_contents):
10598hunk ./src/allmydata/interfaces.py 964
10599         errback.
10600         """
10601 
10602-
10603     def get_servermap(mode):
10604         """Return a Deferred that fires with an IMutableFileServerMap
10605         instance, updated using the given mode.
10606hunk ./src/allmydata/test/test_filenode.py 98
10607         def _check_segment(res):
10608             self.failUnlessEqual(res, DATA[1:1+5])
10609         d.addCallback(_check_segment)
10610+        d.addCallback(lambda ignored:
10611+            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
10612+        d.addCallback(lambda ignored:
10613+            fn1.get_size_of_best_version())
10614+        d.addCallback(lambda size:
10615+            self.failUnlessEqual(size, len(DATA)))
10616+        d.addCallback(lambda ignored:
10617+            fn1.download_to_data())
10618+        d.addCallback(lambda data:
10619+            self.failUnlessEqual(data, DATA))
10620+        d.addCallback(lambda ignored:
10621+            fn1.download_best_version())
10622+        d.addCallback(lambda data:
10623+            self.failUnlessEqual(data, DATA))
10624 
10625         return d
10626 
10627hunk ./src/allmydata/test/test_immutable.py 153
10628         return d
10629 
10630 
10631+    def test_download_to_data(self):
10632+        d = self.n.download_to_data()
10633+        d.addCallback(lambda data:
10634+            self.failUnlessEqual(data, common.TEST_DATA))
10635+        return d
10636+
10637+
10638+    def test_download_best_version(self):
10639+        d = self.n.download_best_version()
10640+        d.addCallback(lambda data:
10641+            self.failUnlessEqual(data, common.TEST_DATA))
10642+        return d
10643+
10644+
10645+    def test_get_best_readable_version(self):
10646+        n = self.n.get_best_readable_version()
10647+        self.failUnlessEqual(n, self.n)
10648+
10649+    def test_get_size_of_best_version(self):
10650+        d = self.n.get_size_of_best_version()
10651+        d.addCallback(lambda size:
10652+            self.failUnlessEqual(size, len(common.TEST_DATA)))
10653+        return d
10654+
10655+
10656 # XXX extend these tests to show bad behavior of various kinds from servers: raising exception from each remove_foo() method, for example
10657 
10658 # XXX test disconnect DeadReferenceError from get_buckets and get_block_whatsit
10659}
10660[frontends/sftpd.py: alter a mutable file overwrite to work with the new API
10661Kevan Carstensen <kevan@isnotajoke.com>**20100717014446
10662 Ignore-this: 2c57bbc8d9ab97a0e9af0634c00efc86
10663] {
10664hunk ./src/allmydata/frontends/sftpd.py 33
10665 from allmydata.interfaces import IFileNode, IDirectoryNode, ExistingChildError, \
10666      NoSuchChildError, ChildOfWrongTypeError
10667 from allmydata.mutable.common import NotWriteableError
10668+from allmydata.mutable.publish import MutableFileHandle
10669 from allmydata.immutable.upload import FileHandle
10670 from allmydata.dirnode import update_metadata
10671 from allmydata.util.fileutil import EncryptedTemporaryFile
10672merger 0.0 (
10673hunk ./src/allmydata/frontends/sftpd.py 664
10674-            # TODO: use download interface described in #993 when implemented.
10675hunk ./src/allmydata/frontends/sftpd.py 664
10676-            # TODO: use download interface described in #993 when implemented.
10677-            if filenode.is_mutable():
10678-                self.async.addCallback(lambda ign: filenode.download_best_version())
10679-                def _downloaded(data):
10680-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
10681-                    self.consumer.write(data)
10682-                    self.consumer.finish()
10683-                    return None
10684-                self.async.addCallback(_downloaded)
10685-            else:
10686-                download_size = filenode.get_size()
10687-                assert download_size is not None, "download_size is None"
10688+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
10689+
10690+            def _read(version):
10691+                download_size = version.get_size()
10692+                assert download_size is not None
10693+
10694)
10695hunk ./src/allmydata/frontends/sftpd.py 677
10696                 download_size = filenode.get_size()
10697                 assert download_size is not None, "download_size is None"
10698                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
10699-                def _read(ign):
10700-                    if noisy: self.log("_read immutable", level=NOISY)
10701-                    filenode.read(self.consumer, 0, None)
10702-                self.async.addCallback(_read)
10703+
10704+                if noisy: self.log("_read", level=NOISY)
10705+                version.read(self.consumer, 0, None)
10706+            self.async.addCallback(_read)
10707 
10708         eventually(self.async.callback, None)
10709 
10710hunk ./src/allmydata/frontends/sftpd.py 824
10711                     assert parent and childname, (parent, childname, self.metadata)
10712                     d2.addCallback(lambda ign: parent.set_metadata_for(childname, self.metadata))
10713 
10714-                d2.addCallback(lambda ign: self.consumer.get_current_size())
10715-                d2.addCallback(lambda size: self.consumer.read(0, size))
10716-                d2.addCallback(lambda new_contents: self.filenode.overwrite(new_contents))
10717+                d2.addCallback(lambda ign: self.filenode.overwrite(MutableFileHandle(self.consumer.get_file())))
10718             else:
10719                 def _add_file(ign):
10720                     self.log("_add_file childname=%r" % (childname,), level=OPERATIONAL)
10721}
10722[mutable/filenode.py: implement most of IVersion, per #993
10723Kevan Carstensen <kevan@isnotajoke.com>**20100717014516
10724 Ignore-this: d4551142b32ea97040ce0e98a394fde5
10725] {
10726hunk ./src/allmydata/mutable/filenode.py 8
10727 from twisted.internet import defer, reactor
10728 from foolscap.api import eventually
10729 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
10730-                                 NotEnoughSharesError, \
10731-                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
10732-from allmydata.util import hashutil, log
10733+     NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
10734+     IMutableFileVersion
10735+from allmydata.util import hashutil, log, consumer
10736 from allmydata.util.assertutil import precondition
10737 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
10738 from allmydata.monitor import Monitor
10739hunk ./src/allmydata/mutable/filenode.py 17
10740 from pycryptopp.cipher.aes import AES
10741 
10742 from allmydata.mutable.publish import Publish, MutableFileHandle, \
10743-                                      MutableDataHandle
10744+                                      MutableData
10745 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
10746      ResponseCache, UncoordinatedWriteError
10747 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
10748hunk ./src/allmydata/mutable/filenode.py 134
10749 
10750     def _get_initial_contents(self, contents):
10751         if contents is None:
10752-            return MutableDataHandle("")
10753+            return MutableData("")
10754 
10755         if IMutableUploadable.providedBy(contents):
10756             return contents
10757hunk ./src/allmydata/mutable/filenode.py 208
10758 
10759     def get_size(self):
10760         return self._most_recent_size
10761+
10762     def get_current_size(self):
10763         d = self.get_size_of_best_version()
10764         d.addCallback(self._stash_size)
10765hunk ./src/allmydata/mutable/filenode.py 213
10766         return d
10767+
10768     def _stash_size(self, size):
10769         self._most_recent_size = size
10770         return size
10771hunk ./src/allmydata/mutable/filenode.py 272
10772             return cmp(self.__class__, them.__class__)
10773         return cmp(self._uri, them._uri)
10774 
10775-    def _do_serialized(self, cb, *args, **kwargs):
10776-        # note: to avoid deadlock, this callable is *not* allowed to invoke
10777-        # other serialized methods within this (or any other)
10778-        # MutableFileNode. The callable should be a bound method of this same
10779-        # MFN instance.
10780-        d = defer.Deferred()
10781-        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
10782-        # we need to put off d.callback until this Deferred is finished being
10783-        # processed. Otherwise the caller's subsequent activities (like,
10784-        # doing other things with this node) can cause reentrancy problems in
10785-        # the Deferred code itself
10786-        self._serializer.addBoth(lambda res: eventually(d.callback, res))
10787-        # add a log.err just in case something really weird happens, because
10788-        # self._serializer stays around forever, therefore we won't see the
10789-        # usual Unhandled Error in Deferred that would give us a hint.
10790-        self._serializer.addErrback(log.err)
10791-        return d
10792 
10793     #################################
10794     # ICheckable
10795hunk ./src/allmydata/mutable/filenode.py 297
10796 
10797 
10798     #################################
10799-    # IMutableFileNode
10800+    # IFileNode
10801+
10802+    def get_best_readable_version(self):
10803+        """
10804+        I return a Deferred that fires with a MutableFileVersion
10805+        representing the best readable version of the file that I
10806+        represent
10807+        """
10808+        return self.get_readable_version()
10809+
10810+
10811+    def get_readable_version(self, servermap=None, version=None):
10812+        """
10813+        I return a Deferred that fires with an MutableFileVersion for my
10814+        version argument, if there is a recoverable file of that version
10815+        on the grid. If there is no recoverable version, I fire with an
10816+        UnrecoverableFileError.
10817+
10818+        If a servermap is provided, I look in there for the requested
10819+        version. If no servermap is provided, I create and update a new
10820+        one.
10821+
10822+        If no version is provided, then I return a MutableFileVersion
10823+        representing the best recoverable version of the file.
10824+        """
10825+        d = self._get_version_from_servermap(MODE_READ, servermap, version)
10826+        def _build_version((servermap, their_version)):
10827+            assert their_version in servermap.recoverable_versions()
10828+            assert their_version in servermap.make_versionmap()
10829+
10830+            mfv = MutableFileVersion(self,
10831+                                     servermap,
10832+                                     their_version,
10833+                                     self._storage_index,
10834+                                     self._storage_broker,
10835+                                     self._readkey,
10836+                                     history=self._history)
10837+            assert mfv.is_readonly()
10838+            # our caller can use this to download the contents of the
10839+            # mutable file.
10840+            return mfv
10841+        return d.addCallback(_build_version)
10842+
10843+
10844+    def _get_version_from_servermap(self,
10845+                                    mode,
10846+                                    servermap=None,
10847+                                    version=None):
10848+        """
10849+        I return a Deferred that fires with (servermap, version).
10850+
10851+        This function performs validation and a servermap update. If it
10852+        returns (servermap, version), the caller can assume that:
10853+            - servermap was last updated in mode.
10854+            - version is recoverable, and corresponds to the servermap.
10855+
10856+        If version and servermap are provided to me, I will validate
10857+        that version exists in the servermap, and that the servermap was
10858+        updated correctly.
10859+
10860+        If version is not provided, but servermap is, I will validate
10861+        the servermap and return the best recoverable version that I can
10862+        find in the servermap.
10863+
10864+        If the version is provided but the servermap isn't, I will
10865+        obtain a servermap that has been updated in the correct mode and
10866+        validate that version is found and recoverable.
10867+
10868+        If neither servermap nor version are provided, I will obtain a
10869+        servermap updated in the correct mode, and return the best
10870+        recoverable version that I can find in there.
10871+        """
10872+        # XXX: wording ^^^^
10873+        if servermap and servermap.last_update_mode == mode:
10874+            d = defer.succeed(servermap)
10875+        else:
10876+            d = self._get_servermap(mode)
10877+
10878+        def _get_version(servermap, version):
10879+            if version and version not in servermap.recoverable_versions():
10880+                version = None
10881+            else:
10882+                version = servermap.best_recoverable_version()
10883+            if not version:
10884+                raise UnrecoverableFileError("no recoverable versions")
10885+            return (servermap, version)
10886+        return d.addCallback(_get_version, version)
10887+
10888 
10889     def download_best_version(self):
10890hunk ./src/allmydata/mutable/filenode.py 387
10891+        """
10892+        I return a Deferred that fires with the contents of the best
10893+        version of this mutable file.
10894+        """
10895         return self._do_serialized(self._download_best_version)
10896hunk ./src/allmydata/mutable/filenode.py 392
10897+
10898+
10899     def _download_best_version(self):
10900hunk ./src/allmydata/mutable/filenode.py 395
10901-        servermap = ServerMap()
10902-        d = self._try_once_to_download_best_version(servermap, MODE_READ)
10903-        def _maybe_retry(f):
10904-            f.trap(NotEnoughSharesError)
10905-            # the download is worth retrying once. Make sure to use the
10906-            # old servermap, since it is what remembers the bad shares,
10907-            # but use MODE_WRITE to make it look for even more shares.
10908-            # TODO: consider allowing this to retry multiple times.. this
10909-            # approach will let us tolerate about 8 bad shares, I think.
10910-            return self._try_once_to_download_best_version(servermap,
10911-                                                           MODE_WRITE)
10912+        """
10913+        I am the serialized sibling of download_best_version.
10914+        """
10915+        d = self.get_best_readable_version()
10916+        d.addCallback(self._record_size)
10917+        d.addCallback(lambda version: version.download_to_data())
10918+
10919+        # It is possible that the download will fail because there
10920+        # aren't enough shares to be had. If so, we will try again after
10921+        # updating the servermap in MODE_WRITE, which may find more
10922+        # shares than updating in MODE_READ, as we just did. We can do
10923+        # this by getting the best mutable version and downloading from
10924+        # that -- the best mutable version will be a MutableFileVersion
10925+        # with a servermap that was last updated in MODE_WRITE, as we
10926+        # want. If this fails, then we give up.
10927+        def _maybe_retry(failure):
10928+            failure.trap(NotEnoughSharesError)
10929+
10930+            d = self.get_best_mutable_version()
10931+            d.addCallback(self._record_size)
10932+            d.addCallback(lambda version: version.download_to_data())
10933+            return d
10934+
10935         d.addErrback(_maybe_retry)
10936         return d
10937hunk ./src/allmydata/mutable/filenode.py 420
10938-    def _try_once_to_download_best_version(self, servermap, mode):
10939-        d = self._update_servermap(servermap, mode)
10940-        d.addCallback(self._once_updated_download_best_version, servermap)
10941-        return d
10942-    def _once_updated_download_best_version(self, ignored, servermap):
10943-        goal = servermap.best_recoverable_version()
10944-        if not goal:
10945-            raise UnrecoverableFileError("no recoverable versions")
10946-        return self._try_once_to_download_version(servermap, goal)
10947+
10948+
10949+    def _record_size(self, mfv):
10950+        """
10951+        I record the size of a mutable file version.
10952+        """
10953+        self._most_recent_size = mfv.get_size()
10954+        return mfv
10955+
10956 
10957     def get_size_of_best_version(self):
10958hunk ./src/allmydata/mutable/filenode.py 431
10959-        d = self.get_servermap(MODE_READ)
10960-        def _got_servermap(smap):
10961-            ver = smap.best_recoverable_version()
10962-            if not ver:
10963-                raise UnrecoverableFileError("no recoverable version")
10964-            return smap.size_of_version(ver)
10965-        d.addCallback(_got_servermap)
10966-        return d
10967+        """
10968+        I return the size of the best version of this mutable file.
10969+
10970+        This is equivalent to calling get_size() on the result of
10971+        get_best_readable_version().
10972+        """
10973+        d = self.get_best_readable_version()
10974+        return d.addCallback(lambda mfv: mfv.get_size())
10975+
10976+
10977+    #################################
10978+    # IMutableFileNode
10979+
10980+    def get_best_mutable_version(self, servermap=None):
10981+        """
10982+        I return a Deferred that fires with a MutableFileVersion
10983+        representing the best readable version of the file that I
10984+        represent. I am like get_best_readable_version, except that I
10985+        will try to make a writable version if I can.
10986+        """
10987+        return self.get_mutable_version(servermap=servermap)
10988+
10989+
10990+    def get_mutable_version(self, servermap=None, version=None):
10991+        """
10992+        I return a version of this mutable file. I return a Deferred
10993+        that fires with a MutableFileVersion
10994+
10995+        If version is provided, the Deferred will fire with a
10996+        MutableFileVersion initailized with that version. Otherwise, it
10997+        will fire with the best version that I can recover.
10998+
10999+        If servermap is provided, I will use that to find versions
11000+        instead of performing my own servermap update.
11001+        """
11002+        if self.is_readonly():
11003+            return self.get_readable_version(servermap=servermap,
11004+                                             version=version)
11005+
11006+        # get_mutable_version => write intent, so we require that the
11007+        # servermap is updated in MODE_WRITE
11008+        d = self._get_version_from_servermap(MODE_WRITE, servermap, version)
11009+        def _build_version((servermap, smap_version)):
11010+            # these should have been set by the servermap update.
11011+            assert self._secret_holder
11012+            assert self._writekey
11013+
11014+            mfv = MutableFileVersion(self,
11015+                                     servermap,
11016+                                     smap_version,
11017+                                     self._storage_index,
11018+                                     self._storage_broker,
11019+                                     self._readkey,
11020+                                     self._writekey,
11021+                                     self._secret_holder,
11022+                                     history=self._history)
11023+            assert not mfv.is_readonly()
11024+            return mfv
11025+
11026+        return d.addCallback(_build_version)
11027+
11028+
11029+    # XXX: I'm uncomfortable with the difference between upload and
11030+    #      overwrite, which, FWICT, is basically that you don't have to
11031+    #      do a servermap update before you overwrite. We split them up
11032+    #      that way anyway, so I guess there's no real difficulty in
11033+    #      offering both ways to callers, but it also makes the
11034+    #      public-facing API cluttery, and makes it hard to discern the
11035+    #      right way of doing things.
11036 
11037hunk ./src/allmydata/mutable/filenode.py 501
11038+    # In general, we leave it to callers to ensure that they aren't
11039+    # going to cause UncoordinatedWriteErrors when working with
11040+    # MutableFileVersions. We know that the next three operations
11041+    # (upload, overwrite, and modify) will all operate on the same
11042+    # version, so we say that only one of them can be going on at once,
11043+    # and serialize them to ensure that that actually happens, since as
11044+    # the caller in this situation it is our job to do that.
11045     def overwrite(self, new_contents):
11046hunk ./src/allmydata/mutable/filenode.py 509
11047+        """
11048+        I overwrite the contents of the best recoverable version of this
11049+        mutable file with new_contents. This is equivalent to calling
11050+        overwrite on the result of get_best_mutable_version with
11051+        new_contents as an argument. I return a Deferred that eventually
11052+        fires with the results of my replacement process.
11053+        """
11054         return self._do_serialized(self._overwrite, new_contents)
11055hunk ./src/allmydata/mutable/filenode.py 517
11056+
11057+
11058     def _overwrite(self, new_contents):
11059hunk ./src/allmydata/mutable/filenode.py 520
11060-        assert IMutableUploadable.providedBy(new_contents)
11061+        """
11062+        I am the serialized sibling of overwrite.
11063+        """
11064+        d = self.get_best_mutable_version()
11065+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
11066+
11067+
11068+
11069+    def upload(self, new_contents, servermap):
11070+        """
11071+        I overwrite the contents of the best recoverable version of this
11072+        mutable file with new_contents, using servermap instead of
11073+        creating/updating our own servermap. I return a Deferred that
11074+        fires with the results of my upload.
11075+        """
11076+        return self._do_serialized(self._upload, new_contents, servermap)
11077+
11078+
11079+    def _upload(self, new_contents, servermap):
11080+        """
11081+        I am the serialized sibling of upload.
11082+        """
11083+        d = self.get_best_mutable_version(servermap)
11084+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
11085+
11086+
11087+    def modify(self, modifier, backoffer=None):
11088+        """
11089+        I modify the contents of the best recoverable version of this
11090+        mutable file with the modifier. This is equivalent to calling
11091+        modify on the result of get_best_mutable_version. I return a
11092+        Deferred that eventually fires with an UploadResults instance
11093+        describing this process.
11094+        """
11095+        return self._do_serialized(self._modify, modifier, backoffer)
11096+
11097+
11098+    def _modify(self, modifier, backoffer):
11099+        """
11100+        I am the serialized sibling of modify.
11101+        """
11102+        d = self.get_best_mutable_version()
11103+        return d.addCallback(lambda mfv: mfv.modify(modifier, backoffer))
11104+
11105+
11106+    def download_version(self, servermap, version, fetch_privkey=False):
11107+        """
11108+        Download the specified version of this mutable file. I return a
11109+        Deferred that fires with the contents of the specified version
11110+        as a bytestring, or errbacks if the file is not recoverable.
11111+        """
11112+        d = self.get_readable_version(servermap, version)
11113+        return d.addCallback(lambda mfv: mfv.download_to_data(fetch_privkey))
11114+
11115+
11116+    def get_servermap(self, mode):
11117+        """
11118+        I return a servermap that has been updated in mode.
11119+
11120+        mode should be one of MODE_READ, MODE_WRITE, MODE_CHECK or
11121+        MODE_ANYTHING. See servermap.py for more on what these mean.
11122+        """
11123+        return self._do_serialized(self._get_servermap, mode)
11124+
11125 
11126hunk ./src/allmydata/mutable/filenode.py 585
11127+    def _get_servermap(self, mode):
11128+        """
11129+        I am a serialized twin to get_servermap.
11130+        """
11131         servermap = ServerMap()
11132hunk ./src/allmydata/mutable/filenode.py 590
11133-        d = self._update_servermap(servermap, mode=MODE_WRITE)
11134-        d.addCallback(lambda ignored: self._upload(new_contents, servermap))
11135+        return self._update_servermap(servermap, mode)
11136+
11137+
11138+    def _update_servermap(self, servermap, mode):
11139+        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11140+                             mode)
11141+        if self._history:
11142+            self._history.notify_mapupdate(u.get_status())
11143+        return u.update()
11144+
11145+
11146+    def set_version(self, version):
11147+        # I can be set in two ways:
11148+        #  1. When the node is created.
11149+        #  2. (for an existing share) when the Servermap is updated
11150+        #     before I am read.
11151+        assert version in (MDMF_VERSION, SDMF_VERSION)
11152+        self._protocol_version = version
11153+
11154+
11155+    def get_version(self):
11156+        return self._protocol_version
11157+
11158+
11159+    def _do_serialized(self, cb, *args, **kwargs):
11160+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11161+        # other serialized methods within this (or any other)
11162+        # MutableFileNode. The callable should be a bound method of this same
11163+        # MFN instance.
11164+        d = defer.Deferred()
11165+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11166+        # we need to put off d.callback until this Deferred is finished being
11167+        # processed. Otherwise the caller's subsequent activities (like,
11168+        # doing other things with this node) can cause reentrancy problems in
11169+        # the Deferred code itself
11170+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11171+        # add a log.err just in case something really weird happens, because
11172+        # self._serializer stays around forever, therefore we won't see the
11173+        # usual Unhandled Error in Deferred that would give us a hint.
11174+        self._serializer.addErrback(log.err)
11175         return d
11176 
11177 
11178hunk ./src/allmydata/mutable/filenode.py 633
11179+    def _upload(self, new_contents, servermap):
11180+        """
11181+        A MutableFileNode still has to have some way of getting
11182+        published initially, which is what I am here for. After that,
11183+        all publishing, updating, modifying and so on happens through
11184+        MutableFileVersions.
11185+        """
11186+        assert self._pubkey, "update_servermap must be called before publish"
11187+
11188+        p = Publish(self, self._storage_broker, servermap)
11189+        if self._history:
11190+            self._history.notify_publish(p.get_status(),
11191+                                         new_contents.get_size())
11192+        d = p.publish(new_contents)
11193+        d.addCallback(self._did_upload, new_contents.get_size())
11194+        return d
11195+
11196+
11197+    def _did_upload(self, res, size):
11198+        self._most_recent_size = size
11199+        return res
11200+
11201+
11202+class MutableFileVersion:
11203+    """
11204+    I represent a specific version (most likely the best version) of a
11205+    mutable file.
11206+
11207+    Since I implement IReadable, instances which hold a
11208+    reference to an instance of me are guaranteed the ability (absent
11209+    connection difficulties or unrecoverable versions) to read the file
11210+    that I represent. Depending on whether I was initialized with a
11211+    write capability or not, I may also provide callers the ability to
11212+    overwrite or modify the contents of the mutable file that I
11213+    reference.
11214+    """
11215+    implements(IMutableFileVersion)
11216+
11217+    def __init__(self,
11218+                 node,
11219+                 servermap,
11220+                 version,
11221+                 storage_index,
11222+                 storage_broker,
11223+                 readcap,
11224+                 writekey=None,
11225+                 write_secrets=None,
11226+                 history=None):
11227+
11228+        self._node = node
11229+        self._servermap = servermap
11230+        self._version = version
11231+        self._storage_index = storage_index
11232+        self._write_secrets = write_secrets
11233+        self._history = history
11234+        self._storage_broker = storage_broker
11235+
11236+        #assert isinstance(readcap, IURI)
11237+        self._readcap = readcap
11238+
11239+        self._writekey = writekey
11240+        self._serializer = defer.succeed(None)
11241+        self._size = None
11242+
11243+
11244+    def get_sequence_number(self):
11245+        """
11246+        Get the sequence number of the mutable version that I represent.
11247+        """
11248+        return 0
11249+
11250+
11251+    # TODO: Terminology?
11252+    def get_writekey(self):
11253+        """
11254+        I return a writekey or None if I don't have a writekey.
11255+        """
11256+        return self._writekey
11257+
11258+
11259+    def overwrite(self, new_contents):
11260+        """
11261+        I overwrite the contents of this mutable file version with the
11262+        data in new_contents.
11263+        """
11264+        assert not self.is_readonly()
11265+
11266+        return self._do_serialized(self._overwrite, new_contents)
11267+
11268+
11269+    def _overwrite(self, new_contents):
11270+        assert IMutableUploadable.providedBy(new_contents)
11271+        assert self._servermap.last_update_mode == MODE_WRITE
11272+
11273+        return self._upload(new_contents)
11274+
11275+
11276     def modify(self, modifier, backoffer=None):
11277         """I use a modifier callback to apply a change to the mutable file.
11278         I implement the following pseudocode::
11279hunk ./src/allmydata/mutable/filenode.py 770
11280         backoffer should not invoke any methods on this MutableFileNode
11281         instance, and it needs to be highly conscious of deadlock issues.
11282         """
11283+        assert not self.is_readonly()
11284+
11285         return self._do_serialized(self._modify, modifier, backoffer)
11286hunk ./src/allmydata/mutable/filenode.py 773
11287+
11288+
11289     def _modify(self, modifier, backoffer):
11290hunk ./src/allmydata/mutable/filenode.py 776
11291-        servermap = ServerMap()
11292         if backoffer is None:
11293             backoffer = BackoffAgent().delay
11294hunk ./src/allmydata/mutable/filenode.py 778
11295-        return self._modify_and_retry(servermap, modifier, backoffer, True)
11296-    def _modify_and_retry(self, servermap, modifier, backoffer, first_time):
11297-        d = self._modify_once(servermap, modifier, first_time)
11298+        return self._modify_and_retry(modifier, backoffer, True)
11299+
11300+
11301+    def _modify_and_retry(self, modifier, backoffer, first_time):
11302+        """
11303+        I try to apply modifier to the contents of this version of the
11304+        mutable file. If I succeed, I return an UploadResults instance
11305+        describing my success. If I fail, I try again after waiting for
11306+        a little bit.
11307+        """
11308+        log.msg("doing modify")
11309+        d = self._modify_once(modifier, first_time)
11310         def _retry(f):
11311             f.trap(UncoordinatedWriteError)
11312             d2 = defer.maybeDeferred(backoffer, self, f)
11313hunk ./src/allmydata/mutable/filenode.py 794
11314             d2.addCallback(lambda ignored:
11315-                           self._modify_and_retry(servermap, modifier,
11316+                           self._modify_and_retry(modifier,
11317                                                   backoffer, False))
11318             return d2
11319         d.addErrback(_retry)
11320hunk ./src/allmydata/mutable/filenode.py 799
11321         return d
11322-    def _modify_once(self, servermap, modifier, first_time):
11323-        d = self._update_servermap(servermap, MODE_WRITE)
11324-        d.addCallback(self._once_updated_download_best_version, servermap)
11325+
11326+
11327+    def _modify_once(self, modifier, first_time):
11328+        """
11329+        I attempt to apply a modifier to the contents of the mutable
11330+        file.
11331+        """
11332+        assert self._servermap.last_update_mode == MODE_WRITE
11333+
11334+        # download_to_data is serialized, so we have to call this to
11335+        # avoid deadlock.
11336+        d = self._try_to_download_data()
11337         def _apply(old_contents):
11338hunk ./src/allmydata/mutable/filenode.py 812
11339-            new_contents = modifier(old_contents, servermap, first_time)
11340+            new_contents = modifier(old_contents, self._servermap, first_time)
11341             if new_contents is None or new_contents == old_contents:
11342hunk ./src/allmydata/mutable/filenode.py 814
11343+                log.msg("no changes")
11344                 # no changes need to be made
11345                 if first_time:
11346                     return
11347hunk ./src/allmydata/mutable/filenode.py 822
11348                 # recovery when it observes UCWE, we need to do a second
11349                 # publish. See #551 for details. We'll basically loop until
11350                 # we managed an uncontested publish.
11351-                old_uploadable = MutableDataHandle(old_contents)
11352+                old_uploadable = MutableData(old_contents)
11353                 new_contents = old_uploadable
11354             precondition((IMutableUploadable.providedBy(new_contents) or
11355                           new_contents is None),
11356hunk ./src/allmydata/mutable/filenode.py 828
11357                          "Modifier function must return an IMutableUploadable "
11358                          "or None")
11359-            return self._upload(new_contents, servermap)
11360+            return self._upload(new_contents)
11361         d.addCallback(_apply)
11362         return d
11363 
11364hunk ./src/allmydata/mutable/filenode.py 832
11365-    def get_servermap(self, mode):
11366-        return self._do_serialized(self._get_servermap, mode)
11367-    def _get_servermap(self, mode):
11368-        servermap = ServerMap()
11369-        return self._update_servermap(servermap, mode)
11370-    def _update_servermap(self, servermap, mode):
11371-        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11372-                             mode)
11373-        if self._history:
11374-            self._history.notify_mapupdate(u.get_status())
11375-        return u.update()
11376 
11377hunk ./src/allmydata/mutable/filenode.py 833
11378-    def download_version(self, servermap, version, fetch_privkey=False):
11379-        return self._do_serialized(self._try_once_to_download_version,
11380-                                   servermap, version, fetch_privkey)
11381-    def _try_once_to_download_version(self, servermap, version,
11382-                                      fetch_privkey=False):
11383-        r = Retrieve(self, servermap, version, fetch_privkey)
11384+    def is_readonly(self):
11385+        """
11386+        I return True if this MutableFileVersion provides no write
11387+        access to the file that it encapsulates, and False if it
11388+        provides the ability to modify the file.
11389+        """
11390+        return self._writekey is None
11391+
11392+
11393+    def is_mutable(self):
11394+        """
11395+        I return True, since mutable files are always mutable by
11396+        somebody.
11397+        """
11398+        return True
11399+
11400+
11401+    def get_storage_index(self):
11402+        """
11403+        I return the storage index of the reference that I encapsulate.
11404+        """
11405+        return self._storage_index
11406+
11407+
11408+    def get_size(self):
11409+        """
11410+        I return the length, in bytes, of this readable object.
11411+        """
11412+        return self._servermap.size_of_version(self._version)
11413+
11414+
11415+    def download_to_data(self, fetch_privkey=False):
11416+        """
11417+        I return a Deferred that fires with the contents of this
11418+        readable object as a byte string.
11419+
11420+        """
11421+        c = consumer.MemoryConsumer()
11422+        d = self.read(c, fetch_privkey=fetch_privkey)
11423+        d.addCallback(lambda mc: "".join(mc.chunks))
11424+        return d
11425+
11426+
11427+    def _try_to_download_data(self):
11428+        """
11429+        I am an unserialized cousin of download_to_data; I am called
11430+        from the children of modify() to download the data associated
11431+        with this mutable version.
11432+        """
11433+        c = consumer.MemoryConsumer()
11434+        # modify will almost certainly write, so we need the privkey.
11435+        d = self._read(c, fetch_privkey=True)
11436+        d.addCallback(lambda mc: "".join(mc.chunks))
11437+        return d
11438+
11439+
11440+    def _update_servermap(self, mode=MODE_READ):
11441+        """
11442+        I update our Servermap according to my mode argument. I return a
11443+        Deferred that fires with None when this has finished. The
11444+        updated Servermap will be at self._servermap in that case.
11445+        """
11446+        d = self._node.get_servermap(mode)
11447+
11448+        def _got_servermap(servermap):
11449+            assert servermap.last_update_mode == mode
11450+
11451+            self._servermap = servermap
11452+        d.addCallback(_got_servermap)
11453+        return d
11454+
11455+
11456+    def read(self, consumer, offset=0, size=None, fetch_privkey=False):
11457+        """
11458+        I read a portion (possibly all) of the mutable file that I
11459+        reference into consumer.
11460+        """
11461+        return self._do_serialized(self._read, consumer, offset, size,
11462+                                   fetch_privkey)
11463+
11464+
11465+    def _read(self, consumer, offset=0, size=None, fetch_privkey=False):
11466+        """
11467+        I am the serialized companion of read.
11468+        """
11469+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
11470         if self._history:
11471             self._history.notify_retrieve(r.get_status())
11472hunk ./src/allmydata/mutable/filenode.py 921
11473-        d = r.download()
11474-        d.addCallback(self._downloaded_version)
11475+        d = r.download(consumer, offset, size)
11476+        return d
11477+
11478+
11479+    def _do_serialized(self, cb, *args, **kwargs):
11480+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11481+        # other serialized methods within this (or any other)
11482+        # MutableFileNode. The callable should be a bound method of this same
11483+        # MFN instance.
11484+        d = defer.Deferred()
11485+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11486+        # we need to put off d.callback until this Deferred is finished being
11487+        # processed. Otherwise the caller's subsequent activities (like,
11488+        # doing other things with this node) can cause reentrancy problems in
11489+        # the Deferred code itself
11490+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11491+        # add a log.err just in case something really weird happens, because
11492+        # self._serializer stays around forever, therefore we won't see the
11493+        # usual Unhandled Error in Deferred that would give us a hint.
11494+        self._serializer.addErrback(log.err)
11495         return d
11496hunk ./src/allmydata/mutable/filenode.py 942
11497-    def _downloaded_version(self, data):
11498-        self._most_recent_size = len(data)
11499-        return data
11500 
11501hunk ./src/allmydata/mutable/filenode.py 943
11502-    def upload(self, new_contents, servermap):
11503-        return self._do_serialized(self._upload, new_contents, servermap)
11504-    def _upload(self, new_contents, servermap):
11505-        assert self._pubkey, "update_servermap must be called before publish"
11506-        assert IMutableUploadable.providedBy(new_contents)
11507 
11508hunk ./src/allmydata/mutable/filenode.py 944
11509-        p = Publish(self, self._storage_broker, servermap)
11510+    def _upload(self, new_contents):
11511+        #assert self._pubkey, "update_servermap must be called before publish"
11512+        p = Publish(self._node, self._storage_broker, self._servermap)
11513         if self._history:
11514hunk ./src/allmydata/mutable/filenode.py 948
11515-            self._history.notify_publish(p.get_status(), new_contents.get_size())
11516+            self._history.notify_publish(p.get_status(),
11517+                                         new_contents.get_size())
11518         d = p.publish(new_contents)
11519         d.addCallback(self._did_upload, new_contents.get_size())
11520         return d
11521hunk ./src/allmydata/mutable/filenode.py 953
11522-    def _did_upload(self, res, size):
11523-        self._most_recent_size = size
11524-        return res
11525-
11526-
11527-    def set_version(self, version):
11528-        # I can be set in two ways:
11529-        #  1. When the node is created.
11530-        #  2. (for an existing share) when the Servermap is updated
11531-        #     before I am read.
11532-        assert version in (MDMF_VERSION, SDMF_VERSION)
11533-        self._protocol_version = version
11534 
11535 
11536hunk ./src/allmydata/mutable/filenode.py 955
11537-    def get_version(self):
11538-        return self._protocol_version
11539+    def _did_upload(self, res, size):
11540+        self._size = size
11541+        return res
11542}
11543[mutable/publish.py: enable segmented uploading for big files, change constants and wording, change MutableDataHandle to MutableData
11544Kevan Carstensen <kevan@isnotajoke.com>**20100717014549
11545 Ignore-this: f736c60c90ff09c98544af17146cf654
11546] {
11547hunk ./src/allmydata/mutable/publish.py 145
11548 
11549         self.data = newdata
11550         self.datalength = newdata.get_size()
11551+        if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
11552+            self._version = MDMF_VERSION
11553+        else:
11554+            self._version = SDMF_VERSION
11555 
11556         self.log("starting publish, datalen is %s" % self.datalength)
11557         self._status.set_size(self.datalength)
11558hunk ./src/allmydata/mutable/publish.py 1007
11559             old_position = self._filehandle.tell()
11560             # Seek to the end of the file by seeking 0 bytes from the
11561             # file's end
11562-            self._filehandle.seek(0, os.SEEK_END)
11563+            self._filehandle.seek(0, 2) # 2 == os.SEEK_END in 2.5+
11564             self._size = self._filehandle.tell()
11565             # Restore the previous position, in case this was called
11566             # after a read.
11567hunk ./src/allmydata/mutable/publish.py 1022
11568         """
11569         I return some data (up to length bytes) from my filehandle.
11570 
11571-        In most cases, I return length bytes. If I don't, it is because
11572-        length is longer than the distance between my current position
11573-        in the file that I represent and its end. In that case, I return
11574-        as many bytes as I can before going over the EOF.
11575+        In most cases, I return length bytes, but sometimes I won't --
11576+        for example, if I am asked to read beyond the end of a file, or
11577+        an error occurs.
11578         """
11579         return [self._filehandle.read(length)]
11580 
11581hunk ./src/allmydata/mutable/publish.py 1037
11582         self._filehandle.close()
11583 
11584 
11585-class MutableDataHandle(MutableFileHandle):
11586+class MutableData(MutableFileHandle):
11587     """
11588     I am a mutable uploadable built around a string, which I then cast
11589     into a StringIO and treat as a filehandle.
11590}
11591[immutable/filenode.py: fix broken implementation of #993 interfaces
11592Kevan Carstensen <kevan@isnotajoke.com>**20100717015049
11593 Ignore-this: 19ac8cf5d31ac88d4a1998ac342db004
11594] {
11595hunk ./src/allmydata/immutable/filenode.py 326
11596         immutable files can have only one version, we just return the
11597         current filenode.
11598         """
11599-        return self
11600+        return defer.succeed(self)
11601 
11602 
11603     def download_best_version(self):
11604hunk ./src/allmydata/immutable/filenode.py 412
11605 
11606     # IReadable, IFileNode, IFilesystemNode
11607     def get_best_readable_version(self):
11608-        return self
11609+        return defer.succeed(self)
11610 
11611 
11612     def download_best_version(self):
11613}
11614[mutable/retrieve.py: alter Retrieve so that it can download parts of mutable files
11615Kevan Carstensen <kevan@isnotajoke.com>**20100717015123
11616 Ignore-this: f49a6d3c05afc784aff0a4c63964a3e5
11617] {
11618hunk ./src/allmydata/mutable/retrieve.py 7
11619 from zope.interface import implements
11620 from twisted.internet import defer
11621 from twisted.python import failure
11622+from twisted.internet.interfaces import IPushProducer, IConsumer
11623 from foolscap.api import DeadReferenceError, eventually, fireEventually
11624 from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
11625                                  MDMF_VERSION, SDMF_VERSION
11626hunk ./src/allmydata/mutable/retrieve.py 86
11627     # times, and each will have a separate response chain. However the
11628     # Retrieve object will remain tied to a specific version of the file, and
11629     # will use a single ServerMap instance.
11630+    implements(IPushProducer)
11631 
11632     def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
11633                  verify=False):
11634hunk ./src/allmydata/mutable/retrieve.py 129
11635         # 3. When we are validating readers, we need to validate the
11636         #    signature on the prefix. Do we? We already do this in the
11637         #    servermap update?
11638-        #
11639-        # (just work on 1 and 2 for now, I guess)
11640         self._verify = False
11641         if verify:
11642             self._verify = True
11643hunk ./src/allmydata/mutable/retrieve.py 143
11644         self._status.set_size(datalength)
11645         self._status.set_encoding(k, N)
11646         self.readers = {}
11647+        self._paused = False
11648+        self._paused_deferred = None
11649+
11650 
11651     def get_status(self):
11652         return self._status
11653hunk ./src/allmydata/mutable/retrieve.py 157
11654             kwargs["facility"] = "tahoe.mutable.retrieve"
11655         return log.msg(*args, **kwargs)
11656 
11657-    def download(self):
11658+
11659+    ###################
11660+    # IPushProducer
11661+
11662+    def pauseProducing(self):
11663+        """
11664+        I am called by my download target if we have produced too much
11665+        data for it to handle. I make the downloader stop producing new
11666+        data until my resumeProducing method is called.
11667+        """
11668+        if self._paused:
11669+            return
11670+
11671+        # fired when the download is unpaused.
11672+        self._pause_deferred = defer.Deferred()
11673+        self._paused = True
11674+
11675+
11676+    def resumeProducing(self):
11677+        """
11678+        I am called by my download target once it is ready to begin
11679+        receiving data again.
11680+        """
11681+        if not self._paused:
11682+            return
11683+
11684+        self._paused = False
11685+        p = self._pause_deferred
11686+        self._pause_deferred = None
11687+        eventually(p.callback, None)
11688+
11689+
11690+    def _check_for_paused(self, res):
11691+        """
11692+        I am called just before a write to the consumer. I return a
11693+        Deferred that eventually fires with the data that is to be
11694+        written to the consumer. If the download has not been paused,
11695+        the Deferred fires immediately. Otherwise, the Deferred fires
11696+        when the downloader is unpaused.
11697+        """
11698+        if self._paused:
11699+            d = defer.Deferred()
11700+            self._pause_defered.addCallback(lambda ignored: d.callback(res))
11701+            return d
11702+        return defer.succeed(res)
11703+
11704+
11705+    def download(self, consumer=None, offset=0, size=None):
11706+        assert IConsumer.providedBy(consumer) or self._verify
11707+
11708+        if consumer:
11709+            self._consumer = consumer
11710+            # we provide IPushProducer, so streaming=True, per
11711+            # IConsumer.
11712+            self._consumer.registerProducer(self, streaming=True)
11713+
11714         self._done_deferred = defer.Deferred()
11715         self._started = time.time()
11716         self._status.set_status("Retrieving Shares")
11717hunk ./src/allmydata/mutable/retrieve.py 217
11718 
11719+        self._offset = offset
11720+        self._read_length = size
11721+
11722         # first, which servers can we use?
11723         versionmap = self.servermap.make_versionmap()
11724         shares = versionmap[self.verinfo]
11725hunk ./src/allmydata/mutable/retrieve.py 278
11726         assert len(self.remaining_sharemap) >= k
11727 
11728         self.log("starting download")
11729+        self._paused = False
11730         self._add_active_peers()
11731         # The download process beyond this is a state machine.
11732         # _add_active_peers will select the peers that we want to use
11733hunk ./src/allmydata/mutable/retrieve.py 324
11734 
11735         self._segment_decoder = codec.CRSDecoder()
11736         self._segment_decoder.set_params(segsize, k, n)
11737-        self._current_segment = 0
11738 
11739         if  not self._tail_data_size:
11740             self._tail_data_size = segsize
11741hunk ./src/allmydata/mutable/retrieve.py 349
11742             # So we don't have to do this later.
11743             self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
11744 
11745-        # If we have more than one segment, we are an SDMF file, which
11746-        # means that we need to validate the salts as we receive them.
11747-        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
11748-        self._salt_hash_tree[0] = IV # from the prefix.
11749+        # Our last task is to tell the downloader where to start and
11750+        # where to stop. We use three parameters for that:
11751+        #   - self._start_segment: the segment that we need to start
11752+        #     downloading from.
11753+        #   - self._current_segment: the next segment that we need to
11754+        #     download.
11755+        #   - self._last_segment: The last segment that we were asked to
11756+        #     download.
11757+        #
11758+        #  We say that the download is complete when
11759+        #  self._current_segment > self._last_segment. We use
11760+        #  self._start_segment and self._last_segment to know when to
11761+        #  strip things off of segments, and how much to strip.
11762+        if self._offset:
11763+            self.log("got offset: %d" % self._offset)
11764+            # our start segment is the first segment containing the
11765+            # offset we were given.
11766+            start = mathutil.div_ceil(self._offset,
11767+                                      self._segment_size)
11768+            # this gets us the first segment after self._offset. Then
11769+            # our start segment is the one before it.
11770+            start -= 1
11771+
11772+            assert start < self._num_segments
11773+            self._start_segment = start
11774+            self.log("got start segment: %d" % self._start_segment)
11775+        else:
11776+            self._start_segment = 0
11777+
11778+
11779+        if self._read_length:
11780+            # our end segment is the last segment containing part of the
11781+            # segment that we were asked to read.
11782+            self.log("got read length %d" % self._read_length)
11783+            end_data = self._offset + self._read_length
11784+            end = mathutil.div_ceil(end_data,
11785+                                    self._segment_size)
11786+            end -= 1
11787+            assert end < self._num_segments
11788+            self._last_segment = end
11789+            self.log("got end segment: %d" % self._last_segment)
11790+        else:
11791+            self._last_segment = self._num_segments - 1
11792 
11793hunk ./src/allmydata/mutable/retrieve.py 393
11794+        self._current_segment = self._start_segment
11795 
11796     def _add_active_peers(self):
11797         """
11798hunk ./src/allmydata/mutable/retrieve.py 637
11799         that this Retrieve is currently responsible for downloading.
11800         """
11801         assert len(self._active_readers) >= self._required_shares
11802-        if self._current_segment < self._num_segments:
11803+        if self._current_segment <= self._last_segment:
11804             d = self._process_segment(self._current_segment)
11805         else:
11806             d = defer.succeed(None)
11807hunk ./src/allmydata/mutable/retrieve.py 701
11808             d.addCallback(self._decrypt_segment)
11809             d.addErrback(self._validation_or_decoding_failed,
11810                          self._active_readers)
11811+            # check to see whether we've been paused before writing
11812+            # anything.
11813+            d.addCallback(self._check_for_paused)
11814             d.addCallback(self._set_segment)
11815             return d
11816         else:
11817hunk ./src/allmydata/mutable/retrieve.py 716
11818         target that is handling the file download.
11819         """
11820         self.log("got plaintext for segment %d" % self._current_segment)
11821-        self._plaintext += segment
11822+        if self._current_segment == self._start_segment:
11823+            # We're on the first segment. It's possible that we want
11824+            # only some part of the end of this segment, and that we
11825+            # just downloaded the whole thing to get that part. If so,
11826+            # we need to account for that and give the reader just the
11827+            # data that they want.
11828+            n = self._offset % self._segment_size
11829+            self.log("stripping %d bytes off of the first segment" % n)
11830+            self.log("original segment length: %d" % len(segment))
11831+            segment = segment[n:]
11832+            self.log("new segment length: %d" % len(segment))
11833+
11834+        if self._current_segment == self._last_segment and self._read_length is not None:
11835+            # We're on the last segment. It's possible that we only want
11836+            # part of the beginning of this segment, and that we
11837+            # downloaded the whole thing anyway. Make sure to give the
11838+            # caller only the portion of the segment that they want to
11839+            # receive.
11840+            extra = self._read_length
11841+            if self._start_segment != self._last_segment:
11842+                extra -= self._segment_size - \
11843+                            (self._offset % self._segment_size)
11844+            extra %= self._segment_size
11845+            self.log("original segment length: %d" % len(segment))
11846+            segment = segment[:extra]
11847+            self.log("new segment length: %d" % len(segment))
11848+            self.log("only taking %d bytes of the last segment" % extra)
11849+
11850+        if not self._verify:
11851+            self._consumer.write(segment)
11852+        else:
11853+            # we don't care about the plaintext if we are doing a verify.
11854+            segment = None
11855         self._current_segment += 1
11856 
11857 
11858hunk ./src/allmydata/mutable/retrieve.py 848
11859                                         reader.shnum,
11860                                         "corrupt hashes: %s" % e)
11861 
11862-        # TODO: Validate the salt, too.
11863         self.log('share %d is valid for segment %d' % (reader.shnum,
11864                                                        segnum))
11865         return {reader.shnum: (block, salt)}
11866hunk ./src/allmydata/mutable/retrieve.py 1014
11867               _done_deferred to errback.
11868         """
11869         self.log("checking for doneness")
11870-        if self._current_segment == self._num_segments:
11871+        if self._current_segment > self._last_segment:
11872             # No more segments to download, we're done.
11873             self.log("got plaintext, done")
11874             return self._done()
11875hunk ./src/allmydata/mutable/retrieve.py 1043
11876             ret = list(self._bad_shares)
11877             self.log("done verifying, found %d bad shares" % len(ret))
11878         else:
11879-            ret = self._plaintext
11880+            # TODO: upload status here?
11881+            ret = self._consumer
11882+            self._consumer.unregisterProducer()
11883         eventually(self._done_deferred.callback, ret)
11884 
11885 
11886hunk ./src/allmydata/mutable/retrieve.py 1066
11887                       "encoding %(k)d-of-%(n)d")
11888             args = {"have": self._current_segment,
11889                     "total": self._num_segments,
11890+                    "need": self._last_segment,
11891                     "k": self._required_shares,
11892                     "n": self._total_shares,
11893                     "bad": len(self._bad_shares)}
11894}
11895[change MutableDataHandle to MutableData in code.
11896Kevan Carstensen <kevan@isnotajoke.com>**20100717015210
11897 Ignore-this: f85ae425eabc21b47ad60bd6bf1f7dec
11898] {
11899hunk ./src/allmydata/dirnode.py 11
11900 from allmydata.mutable.common import NotWriteableError
11901 from allmydata.mutable.filenode import MutableFileNode
11902 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
11903-from allmydata.mutable.publish import MutableDataHandle
11904+from allmydata.mutable.publish import MutableData
11905 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
11906      IImmutableFileNode, IMutableFileNode, \
11907      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
11908hunk ./src/allmydata/dirnode.py 104
11909 
11910         del children[self.name]
11911         new_contents = self.node._pack_contents(children)
11912-        uploadable = MutableDataHandle(new_contents)
11913+        uploadable = MutableData(new_contents)
11914         return uploadable
11915 
11916 
11917hunk ./src/allmydata/dirnode.py 130
11918 
11919         children[name] = (child, metadata)
11920         new_contents = self.node._pack_contents(children)
11921-        uploadable = MutableDataHandle(new_contents)
11922+        uploadable = MutableData(new_contents)
11923         return uploadable
11924 
11925 
11926hunk ./src/allmydata/dirnode.py 175
11927 
11928             children[name] = (child, metadata)
11929         new_contents = self.node._pack_contents(children)
11930-        uploadable = MutableDataHandle(new_contents)
11931+        uploadable = MutableData(new_contents)
11932         return uploadable
11933 
11934 def _encrypt_rw_uri(writekey, rw_uri):
11935hunk ./src/allmydata/mutable/repairer.py 5
11936 from zope.interface import implements
11937 from twisted.internet import defer
11938 from allmydata.interfaces import IRepairResults, ICheckResults
11939-from allmydata.mutable.publish import MutableDataHandle
11940+from allmydata.mutable.publish import MutableData
11941 
11942 class RepairResults:
11943     implements(IRepairResults)
11944hunk ./src/allmydata/mutable/repairer.py 109
11945 
11946         d = self.node.download_version(smap, best_version, fetch_privkey=True)
11947         d.addCallback(lambda data:
11948-            MutableDataHandle(data))
11949+            MutableData(data))
11950         d.addCallback(self.node.upload, smap)
11951         d.addCallback(self.get_results, smap)
11952         return d
11953hunk ./src/allmydata/nodemaker.py 9
11954 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
11955 from allmydata.immutable.upload import Data
11956 from allmydata.mutable.filenode import MutableFileNode
11957-from allmydata.mutable.publish import MutableDataHandle
11958+from allmydata.mutable.publish import MutableData
11959 from allmydata.dirnode import DirectoryNode, pack_children
11960 from allmydata.unknown import UnknownNode
11961 from allmydata import uri
11962merger 0.0 (
11963merger 0.0 (
11964hunk ./src/allmydata/nodemaker.py 107
11965-                                     pack_children(n, initial_children),
11966+                                     MutableDataHandle(
11967+                                        pack_children(n, initial_children)),
11968merger 0.0 (
11969hunk ./src/allmydata/nodemaker.py 107
11970-                                     pack_children(n, initial_children))
11971+                                     pack_children(n, initial_children),
11972+                                     version)
11973hunk ./src/allmydata/nodemaker.py 107
11974-                                     pack_children(n, initial_children))
11975+                                     pack_children(initial_children, n.get_writekey()))
11976)
11977)
11978hunk ./src/allmydata/nodemaker.py 107
11979-                                     MutableDataHandle(
11980+                                     MutableData(
11981)
11982hunk ./src/allmydata/test/common.py 18
11983      DeepCheckResults, DeepCheckAndRepairResults
11984 from allmydata.mutable.common import CorruptShareError
11985 from allmydata.mutable.layout import unpack_header
11986-from allmydata.mutable.publish import MutableDataHandle
11987+from allmydata.mutable.publish import MutableData
11988 from allmydata.storage.server import storage_index_to_dir
11989 from allmydata.storage.mutable import MutableShareFile
11990 from allmydata.util import hashutil, log, fileutil, pollmixin
11991hunk ./src/allmydata/test/common.py 192
11992         return defer.succeed(self)
11993     def _get_initial_contents(self, contents):
11994         if contents is None:
11995-            return MutableDataHandle("")
11996+            return MutableData("")
11997 
11998         if IMutableUploadable.providedBy(contents):
11999             return contents
12000hunk ./src/allmydata/test/test_checker.py 11
12001 from allmydata.test.no_network import GridTestMixin
12002 from allmydata.immutable.upload import Data
12003 from allmydata.test.common_web import WebRenderingMixin
12004-from allmydata.mutable.publish import MutableDataHandle
12005+from allmydata.mutable.publish import MutableData
12006 
12007 class FakeClient:
12008     def get_storage_broker(self):
12009hunk ./src/allmydata/test/test_checker.py 292
12010             self.imm = c0.create_node_from_uri(ur.uri)
12011         d.addCallback(_stash_immutable)
12012         d.addCallback(lambda ign:
12013-            c0.create_mutable_file(MutableDataHandle("contents")))
12014+            c0.create_mutable_file(MutableData("contents")))
12015         def _stash_mutable(node):
12016             self.mut = node
12017         d.addCallback(_stash_mutable)
12018hunk ./src/allmydata/test/test_cli.py 12
12019 from allmydata.util import fileutil, hashutil, base32
12020 from allmydata import uri
12021 from allmydata.immutable import upload
12022-from allmydata.mutable.publish import MutableDataHandle
12023+from allmydata.mutable.publish import MutableData
12024 from allmydata.dirnode import normalize
12025 
12026 # Test that the scripts can be imported -- although the actual tests of their
12027hunk ./src/allmydata/test/test_cli.py 1975
12028         self.set_up_grid()
12029         c0 = self.g.clients[0]
12030         DATA = "data" * 100
12031-        DATA_uploadable = MutableDataHandle(DATA)
12032+        DATA_uploadable = MutableData(DATA)
12033         d = c0.create_mutable_file(DATA_uploadable)
12034         def _stash_uri(n):
12035             self.uri = n.get_uri()
12036hunk ./src/allmydata/test/test_cli.py 2078
12037                                                         convergence="")))
12038         d.addCallback(_stash_uri, "small")
12039         d.addCallback(lambda ign:
12040-            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
12041+            c0.create_mutable_file(MutableData(DATA+"1")))
12042         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
12043         d.addCallback(_stash_uri, "mutable")
12044 
12045hunk ./src/allmydata/test/test_deepcheck.py 9
12046 from twisted.internet import threads # CLI tests use deferToThread
12047 from allmydata.immutable import upload
12048 from allmydata.mutable.common import UnrecoverableFileError
12049-from allmydata.mutable.publish import MutableDataHandle
12050+from allmydata.mutable.publish import MutableData
12051 from allmydata.util import idlib
12052 from allmydata.util import base32
12053 from allmydata.scripts import runner
12054hunk ./src/allmydata/test/test_deepcheck.py 38
12055         self.basedir = "deepcheck/MutableChecker/good"
12056         self.set_up_grid()
12057         CONTENTS = "a little bit of data"
12058-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12059+        CONTENTS_uploadable = MutableData(CONTENTS)
12060         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12061         def _created(node):
12062             self.node = node
12063hunk ./src/allmydata/test/test_deepcheck.py 61
12064         self.basedir = "deepcheck/MutableChecker/corrupt"
12065         self.set_up_grid()
12066         CONTENTS = "a little bit of data"
12067-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12068+        CONTENTS_uploadable = MutableData(CONTENTS)
12069         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12070         def _stash_and_corrupt(node):
12071             self.node = node
12072hunk ./src/allmydata/test/test_deepcheck.py 99
12073         self.basedir = "deepcheck/MutableChecker/delete_share"
12074         self.set_up_grid()
12075         CONTENTS = "a little bit of data"
12076-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12077+        CONTENTS_uploadable = MutableData(CONTENTS)
12078         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12079         def _stash_and_delete(node):
12080             self.node = node
12081hunk ./src/allmydata/test/test_deepcheck.py 224
12082             self.root_uri = n.get_uri()
12083         d.addCallback(_created_root)
12084         d.addCallback(lambda ign:
12085-            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
12086+            c0.create_mutable_file(MutableData("mutable file contents")))
12087         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
12088         def _created_mutable(n):
12089             self.mutable = n
12090hunk ./src/allmydata/test/test_deepcheck.py 965
12091     def create_mangled(self, ignored, name):
12092         nodetype, mangletype = name.split("-", 1)
12093         if nodetype == "mutable":
12094-            mutable_uploadable = MutableDataHandle("mutable file contents")
12095+            mutable_uploadable = MutableData("mutable file contents")
12096             d = self.g.clients[0].create_mutable_file(mutable_uploadable)
12097             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
12098         elif nodetype == "large":
12099hunk ./src/allmydata/test/test_hung_server.py 10
12100 from allmydata.util.consumer import download_to_data
12101 from allmydata.immutable import upload
12102 from allmydata.mutable.common import UnrecoverableFileError
12103-from allmydata.mutable.publish import MutableDataHandle
12104+from allmydata.mutable.publish import MutableData
12105 from allmydata.storage.common import storage_index_to_dir
12106 from allmydata.test.no_network import GridTestMixin
12107 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
12108hunk ./src/allmydata/test/test_hung_server.py 96
12109         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
12110 
12111         if mutable:
12112-            uploadable = MutableDataHandle(mutable_plaintext)
12113+            uploadable = MutableData(mutable_plaintext)
12114             d = nm.create_mutable_file(uploadable)
12115             def _uploaded_mutable(node):
12116                 self.uri = node.get_uri()
12117hunk ./src/allmydata/test/test_mutable.py 27
12118      NotEnoughServersError, CorruptShareError
12119 from allmydata.mutable.retrieve import Retrieve
12120 from allmydata.mutable.publish import Publish, MutableFileHandle, \
12121-                                      MutableDataHandle
12122+                                      MutableData
12123 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
12124 from allmydata.mutable.layout import unpack_header, unpack_share, \
12125                                      MDMFSlotReadProxy
12126hunk ./src/allmydata/test/test_mutable.py 297
12127             d.addCallback(lambda smap: smap.dump(StringIO()))
12128             d.addCallback(lambda sio:
12129                           self.failUnless("3-of-10" in sio.getvalue()))
12130-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
12131+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
12132             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
12133             d.addCallback(lambda res: n.download_best_version())
12134             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12135hunk ./src/allmydata/test/test_mutable.py 304
12136             d.addCallback(lambda res: n.get_size_of_best_version())
12137             d.addCallback(lambda size:
12138                           self.failUnlessEqual(size, len("contents 1")))
12139-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12140+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12141             d.addCallback(lambda res: n.download_best_version())
12142             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12143             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12144hunk ./src/allmydata/test/test_mutable.py 308
12145-            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
12146+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
12147             d.addCallback(lambda res: n.download_best_version())
12148             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
12149             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
12150hunk ./src/allmydata/test/test_mutable.py 320
12151             # mapupdate-to-retrieve data caching (i.e. make the shares larger
12152             # than the default readsize, which is 2000 bytes). A 15kB file
12153             # will have 5kB shares.
12154-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
12155+            d.addCallback(lambda res: n.overwrite(MutableData("large size file" * 1000)))
12156             d.addCallback(lambda res: n.download_best_version())
12157             d.addCallback(lambda res:
12158                           self.failUnlessEqual(res, "large size file" * 1000))
12159hunk ./src/allmydata/test/test_mutable.py 343
12160             # to make them big enough to force the file to be uploaded
12161             # in more than one segment.
12162             big_contents = "contents1" * 100000 # about 900 KiB
12163-            big_contents_uploadable = MutableDataHandle(big_contents)
12164+            big_contents_uploadable = MutableData(big_contents)
12165             d.addCallback(lambda ignored:
12166                 n.overwrite(big_contents_uploadable))
12167             d.addCallback(lambda ignored:
12168hunk ./src/allmydata/test/test_mutable.py 355
12169             # segments, so that we make the downloader deal with
12170             # multiple segments.
12171             bigger_contents = "contents2" * 1000000 # about 9MiB
12172-            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
12173+            bigger_contents_uploadable = MutableData(bigger_contents)
12174             d.addCallback(lambda ignored:
12175                 n.overwrite(bigger_contents_uploadable))
12176             d.addCallback(lambda ignored:
12177hunk ./src/allmydata/test/test_mutable.py 368
12178 
12179 
12180     def test_create_with_initial_contents(self):
12181-        upload1 = MutableDataHandle("contents 1")
12182+        upload1 = MutableData("contents 1")
12183         d = self.nodemaker.create_mutable_file(upload1)
12184         def _created(n):
12185             d = n.download_best_version()
12186hunk ./src/allmydata/test/test_mutable.py 373
12187             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12188-            upload2 = MutableDataHandle("contents 2")
12189+            upload2 = MutableData("contents 2")
12190             d.addCallback(lambda res: n.overwrite(upload2))
12191             d.addCallback(lambda res: n.download_best_version())
12192             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12193hunk ./src/allmydata/test/test_mutable.py 385
12194 
12195     def test_create_mdmf_with_initial_contents(self):
12196         initial_contents = "foobarbaz" * 131072 # 900KiB
12197-        initial_contents_uploadable = MutableDataHandle(initial_contents)
12198+        initial_contents_uploadable = MutableData(initial_contents)
12199         d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
12200                                                version=MDMF_VERSION)
12201         def _created(n):
12202hunk ./src/allmydata/test/test_mutable.py 392
12203             d = n.download_best_version()
12204             d.addCallback(lambda data:
12205                 self.failUnlessEqual(data, initial_contents))
12206-            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
12207+            uploadable2 = MutableData(initial_contents + "foobarbaz")
12208             d.addCallback(lambda ignored:
12209                 n.overwrite(uploadable2))
12210             d.addCallback(lambda ignored:
12211hunk ./src/allmydata/test/test_mutable.py 413
12212             key = n.get_writekey()
12213             self.failUnless(isinstance(key, str), key)
12214             self.failUnlessEqual(len(key), 16) # AES key size
12215-            return MutableDataHandle(data)
12216+            return MutableData(data)
12217         d = self.nodemaker.create_mutable_file(_make_contents)
12218         def _created(n):
12219             return n.download_best_version()
12220hunk ./src/allmydata/test/test_mutable.py 429
12221             key = n.get_writekey()
12222             self.failUnless(isinstance(key, str), key)
12223             self.failUnlessEqual(len(key), 16)
12224-            return MutableDataHandle(data)
12225+            return MutableData(data)
12226         d = self.nodemaker.create_mutable_file(_make_contents,
12227                                                version=MDMF_VERSION)
12228         d.addCallback(lambda n:
12229hunk ./src/allmydata/test/test_mutable.py 441
12230 
12231     def test_create_with_too_large_contents(self):
12232         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
12233-        BIG_uploadable = MutableDataHandle(BIG)
12234+        BIG_uploadable = MutableData(BIG)
12235         d = self.nodemaker.create_mutable_file(BIG_uploadable)
12236         def _created(n):
12237hunk ./src/allmydata/test/test_mutable.py 444
12238-            other_BIG_uploadable = MutableDataHandle(BIG)
12239+            other_BIG_uploadable = MutableData(BIG)
12240             d = n.overwrite(other_BIG_uploadable)
12241             return d
12242         d.addCallback(_created)
12243hunk ./src/allmydata/test/test_mutable.py 460
12244     def test_modify(self):
12245         def _modifier(old_contents, servermap, first_time):
12246             new_contents = old_contents + "line2"
12247-            return MutableDataHandle(new_contents)
12248+            return MutableData(new_contents)
12249         def _non_modifier(old_contents, servermap, first_time):
12250hunk ./src/allmydata/test/test_mutable.py 462
12251-            return MutableDataHandle(old_contents)
12252+            return MutableData(old_contents)
12253         def _none_modifier(old_contents, servermap, first_time):
12254             return None
12255         def _error_modifier(old_contents, servermap, first_time):
12256hunk ./src/allmydata/test/test_mutable.py 469
12257             raise ValueError("oops")
12258         def _toobig_modifier(old_contents, servermap, first_time):
12259             new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
12260-            return MutableDataHandle(new_content)
12261+            return MutableData(new_content)
12262         calls = []
12263         def _ucw_error_modifier(old_contents, servermap, first_time):
12264             # simulate an UncoordinatedWriteError once
12265hunk ./src/allmydata/test/test_mutable.py 477
12266             if len(calls) <= 1:
12267                 raise UncoordinatedWriteError("simulated")
12268             new_contents = old_contents + "line3"
12269-            return MutableDataHandle(new_contents)
12270+            return MutableData(new_contents)
12271         def _ucw_error_non_modifier(old_contents, servermap, first_time):
12272             # simulate an UncoordinatedWriteError once, and don't actually
12273             # modify the contents on subsequent invocations
12274hunk ./src/allmydata/test/test_mutable.py 484
12275             calls.append(1)
12276             if len(calls) <= 1:
12277                 raise UncoordinatedWriteError("simulated")
12278-            return MutableDataHandle(old_contents)
12279+            return MutableData(old_contents)
12280 
12281         initial_contents = "line1"
12282hunk ./src/allmydata/test/test_mutable.py 487
12283-        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
12284+        d = self.nodemaker.create_mutable_file(MutableData(initial_contents))
12285         def _created(n):
12286             d = n.modify(_modifier)
12287             d.addCallback(lambda res: n.download_best_version())
12288hunk ./src/allmydata/test/test_mutable.py 548
12289 
12290     def test_modify_backoffer(self):
12291         def _modifier(old_contents, servermap, first_time):
12292-            return MutableDataHandle(old_contents + "line2")
12293+            return MutableData(old_contents + "line2")
12294         calls = []
12295         def _ucw_error_modifier(old_contents, servermap, first_time):
12296             # simulate an UncoordinatedWriteError once
12297hunk ./src/allmydata/test/test_mutable.py 555
12298             calls.append(1)
12299             if len(calls) <= 1:
12300                 raise UncoordinatedWriteError("simulated")
12301-            return MutableDataHandle(old_contents + "line3")
12302+            return MutableData(old_contents + "line3")
12303         def _always_ucw_error_modifier(old_contents, servermap, first_time):
12304             raise UncoordinatedWriteError("simulated")
12305         def _backoff_stopper(node, f):
12306hunk ./src/allmydata/test/test_mutable.py 570
12307         giveuper._delay = 0.1
12308         giveuper.factor = 1
12309 
12310-        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
12311+        d = self.nodemaker.create_mutable_file(MutableData("line1"))
12312         def _created(n):
12313             d = n.modify(_modifier)
12314             d.addCallback(lambda res: n.download_best_version())
12315hunk ./src/allmydata/test/test_mutable.py 620
12316             d.addCallback(lambda smap: smap.dump(StringIO()))
12317             d.addCallback(lambda sio:
12318                           self.failUnless("3-of-10" in sio.getvalue()))
12319-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
12320+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
12321             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
12322             d.addCallback(lambda res: n.download_best_version())
12323             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12324hunk ./src/allmydata/test/test_mutable.py 624
12325-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12326+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12327             d.addCallback(lambda res: n.download_best_version())
12328             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12329             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12330hunk ./src/allmydata/test/test_mutable.py 628
12331-            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
12332+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
12333             d.addCallback(lambda res: n.download_best_version())
12334             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
12335             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
12336hunk ./src/allmydata/test/test_mutable.py 646
12337         # publish a file and create shares, which can then be manipulated
12338         # later.
12339         self.CONTENTS = "New contents go here" * 1000
12340-        self.uploadable = MutableDataHandle(self.CONTENTS)
12341+        self.uploadable = MutableData(self.CONTENTS)
12342         self._storage = FakeStorage()
12343         self._nodemaker = make_nodemaker(self._storage)
12344         self._storage_broker = self._nodemaker.storage_broker
12345hunk ./src/allmydata/test/test_mutable.py 662
12346         # an MDMF file.
12347         # self.CONTENTS should have more than one segment.
12348         self.CONTENTS = "This is an MDMF file" * 100000
12349-        self.uploadable = MutableDataHandle(self.CONTENTS)
12350+        self.uploadable = MutableData(self.CONTENTS)
12351         self._storage = FakeStorage()
12352         self._nodemaker = make_nodemaker(self._storage)
12353         self._storage_broker = self._nodemaker.storage_broker
12354hunk ./src/allmydata/test/test_mutable.py 678
12355         # like publish_one, except that the result is guaranteed to be
12356         # an SDMF file
12357         self.CONTENTS = "This is an SDMF file" * 1000
12358-        self.uploadable = MutableDataHandle(self.CONTENTS)
12359+        self.uploadable = MutableData(self.CONTENTS)
12360         self._storage = FakeStorage()
12361         self._nodemaker = make_nodemaker(self._storage)
12362         self._storage_broker = self._nodemaker.storage_broker
12363hunk ./src/allmydata/test/test_mutable.py 696
12364                          "Contents 2",
12365                          "Contents 3a",
12366                          "Contents 3b"]
12367-        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
12368+        self.uploadables = [MutableData(d) for d in self.CONTENTS]
12369         self._copied_shares = {}
12370         self._storage = FakeStorage()
12371         self._nodemaker = make_nodemaker(self._storage)
12372hunk ./src/allmydata/test/test_mutable.py 826
12373         # create a new file, which is large enough to knock the privkey out
12374         # of the early part of the file
12375         LARGE = "These are Larger contents" * 200 # about 5KB
12376-        LARGE_uploadable = MutableDataHandle(LARGE)
12377+        LARGE_uploadable = MutableData(LARGE)
12378         d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
12379         def _created(large_fn):
12380             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
12381hunk ./src/allmydata/test/test_mutable.py 1842
12382 class MultipleEncodings(unittest.TestCase):
12383     def setUp(self):
12384         self.CONTENTS = "New contents go here"
12385-        self.uploadable = MutableDataHandle(self.CONTENTS)
12386+        self.uploadable = MutableData(self.CONTENTS)
12387         self._storage = FakeStorage()
12388         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
12389         self._storage_broker = self._nodemaker.storage_broker
12390hunk ./src/allmydata/test/test_mutable.py 1872
12391         s = self._storage
12392         s._peers = {} # clear existing storage
12393         p2 = Publish(fn2, self._storage_broker, None)
12394-        uploadable = MutableDataHandle(data)
12395+        uploadable = MutableData(data)
12396         d = p2.publish(uploadable)
12397         def _published(res):
12398             shares = s._peers
12399hunk ./src/allmydata/test/test_mutable.py 2049
12400         self._set_versions(target)
12401 
12402         def _modify(oldversion, servermap, first_time):
12403-            return MutableDataHandle(oldversion + " modified")
12404+            return MutableData(oldversion + " modified")
12405         d = self._fn.modify(_modify)
12406         d.addCallback(lambda res: self._fn.download_best_version())
12407         expected = self.CONTENTS[2] + " modified"
12408hunk ./src/allmydata/test/test_mutable.py 2175
12409         self.basedir = "mutable/Problems/test_publish_surprise"
12410         self.set_up_grid()
12411         nm = self.g.clients[0].nodemaker
12412-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12413+        d = nm.create_mutable_file(MutableData("contents 1"))
12414         def _created(n):
12415             d = defer.succeed(None)
12416             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12417hunk ./src/allmydata/test/test_mutable.py 2185
12418             d.addCallback(_got_smap1)
12419             # then modify the file, leaving the old map untouched
12420             d.addCallback(lambda res: log.msg("starting winning write"))
12421-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12422+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12423             # now attempt to modify the file with the old servermap. This
12424             # will look just like an uncoordinated write, in which every
12425             # single share got updated between our mapupdate and our publish
12426hunk ./src/allmydata/test/test_mutable.py 2194
12427                           self.shouldFail(UncoordinatedWriteError,
12428                                           "test_publish_surprise", None,
12429                                           n.upload,
12430-                                          MutableDataHandle("contents 2a"), self.old_map))
12431+                                          MutableData("contents 2a"), self.old_map))
12432             return d
12433         d.addCallback(_created)
12434         return d
12435hunk ./src/allmydata/test/test_mutable.py 2203
12436         self.basedir = "mutable/Problems/test_retrieve_surprise"
12437         self.set_up_grid()
12438         nm = self.g.clients[0].nodemaker
12439-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12440+        d = nm.create_mutable_file(MutableData("contents 1"))
12441         def _created(n):
12442             d = defer.succeed(None)
12443             d.addCallback(lambda res: n.get_servermap(MODE_READ))
12444hunk ./src/allmydata/test/test_mutable.py 2213
12445             d.addCallback(_got_smap1)
12446             # then modify the file, leaving the old map untouched
12447             d.addCallback(lambda res: log.msg("starting winning write"))
12448-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12449+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12450             # now attempt to retrieve the old version with the old servermap.
12451             # This will look like someone has changed the file since we
12452             # updated the servermap.
12453hunk ./src/allmydata/test/test_mutable.py 2241
12454         self.basedir = "mutable/Problems/test_unexpected_shares"
12455         self.set_up_grid()
12456         nm = self.g.clients[0].nodemaker
12457-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12458+        d = nm.create_mutable_file(MutableData("contents 1"))
12459         def _created(n):
12460             d = defer.succeed(None)
12461             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12462hunk ./src/allmydata/test/test_mutable.py 2253
12463                 self.g.remove_server(peer0)
12464                 # then modify the file, leaving the old map untouched
12465                 log.msg("starting winning write")
12466-                return n.overwrite(MutableDataHandle("contents 2"))
12467+                return n.overwrite(MutableData("contents 2"))
12468             d.addCallback(_got_smap1)
12469             # now attempt to modify the file with the old servermap. This
12470             # will look just like an uncoordinated write, in which every
12471hunk ./src/allmydata/test/test_mutable.py 2263
12472                           self.shouldFail(UncoordinatedWriteError,
12473                                           "test_surprise", None,
12474                                           n.upload,
12475-                                          MutableDataHandle("contents 2a"), self.old_map))
12476+                                          MutableData("contents 2a"), self.old_map))
12477             return d
12478         d.addCallback(_created)
12479         return d
12480hunk ./src/allmydata/test/test_mutable.py 2303
12481         d.addCallback(_break_peer0)
12482         # now "create" the file, using the pre-established key, and let the
12483         # initial publish finally happen
12484-        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
12485+        d.addCallback(lambda res: nm.create_mutable_file(MutableData("contents 1")))
12486         # that ought to work
12487         def _got_node(n):
12488             d = n.download_best_version()
12489hunk ./src/allmydata/test/test_mutable.py 2312
12490             def _break_peer1(res):
12491                 self.connection1.broken = True
12492             d.addCallback(_break_peer1)
12493-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12494+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12495             # that ought to work too
12496             d.addCallback(lambda res: n.download_best_version())
12497             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12498hunk ./src/allmydata/test/test_mutable.py 2344
12499         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
12500         self.g.break_server(peerids[0])
12501 
12502-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12503+        d = nm.create_mutable_file(MutableData("contents 1"))
12504         def _created(n):
12505             d = n.download_best_version()
12506             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12507hunk ./src/allmydata/test/test_mutable.py 2352
12508             def _break_second_server(res):
12509                 self.g.break_server(peerids[1])
12510             d.addCallback(_break_second_server)
12511-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12512+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12513             # that ought to work too
12514             d.addCallback(lambda res: n.download_best_version())
12515             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12516hunk ./src/allmydata/test/test_mutable.py 2371
12517         d = self.shouldFail(NotEnoughServersError,
12518                             "test_publish_all_servers_bad",
12519                             "Ran out of non-bad servers",
12520-                            nm.create_mutable_file, MutableDataHandle("contents"))
12521+                            nm.create_mutable_file, MutableData("contents"))
12522         return d
12523 
12524     def test_publish_no_servers(self):
12525hunk ./src/allmydata/test/test_mutable.py 2383
12526         d = self.shouldFail(NotEnoughServersError,
12527                             "test_publish_no_servers",
12528                             "Ran out of non-bad servers",
12529-                            nm.create_mutable_file, MutableDataHandle("contents"))
12530+                            nm.create_mutable_file, MutableData("contents"))
12531         return d
12532     test_publish_no_servers.timeout = 30
12533 
12534hunk ./src/allmydata/test/test_mutable.py 2401
12535         # we need some contents that are large enough to push the privkey out
12536         # of the early part of the file
12537         LARGE = "These are Larger contents" * 2000 # about 50KB
12538-        LARGE_uploadable = MutableDataHandle(LARGE)
12539+        LARGE_uploadable = MutableData(LARGE)
12540         d = nm.create_mutable_file(LARGE_uploadable)
12541         def _created(n):
12542             self.uri = n.get_uri()
12543hunk ./src/allmydata/test/test_mutable.py 2438
12544         self.set_up_grid(num_servers=20)
12545         nm = self.g.clients[0].nodemaker
12546         LARGE = "These are Larger contents" * 2000 # about 50KiB
12547-        LARGE_uploadable = MutableDataHandle(LARGE)
12548+        LARGE_uploadable = MutableData(LARGE)
12549         nm._node_cache = DevNullDictionary() # disable the nodecache
12550 
12551         d = nm.create_mutable_file(LARGE_uploadable)
12552hunk ./src/allmydata/test/test_mutable.py 2464
12553         self.set_up_grid(num_servers=20)
12554         nm = self.g.clients[0].nodemaker
12555         CONTENTS = "contents" * 2000
12556-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12557+        CONTENTS_uploadable = MutableData(CONTENTS)
12558         d = nm.create_mutable_file(CONTENTS_uploadable)
12559         def _created(node):
12560             self._node = node
12561hunk ./src/allmydata/test/test_mutable.py 2565
12562 class DataHandle(unittest.TestCase):
12563     def setUp(self):
12564         self.test_data = "Test Data" * 50000
12565-        self.uploadable = MutableDataHandle(self.test_data)
12566+        self.uploadable = MutableData(self.test_data)
12567 
12568 
12569     def test_datahandle_read(self):
12570hunk ./src/allmydata/test/test_sftp.py 84
12571         return d
12572 
12573     def _set_up_tree(self):
12574-        u = publish.MutableDataHandle("mutable file contents")
12575+        u = publish.MutableData("mutable file contents")
12576         d = self.client.create_mutable_file(u)
12577         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
12578         def _created_mutable(n):
12579hunk ./src/allmydata/test/test_system.py 22
12580 from allmydata.monitor import Monitor
12581 from allmydata.mutable.common import NotWriteableError
12582 from allmydata.mutable import layout as mutable_layout
12583-from allmydata.mutable.publish import MutableDataHandle
12584+from allmydata.mutable.publish import MutableData
12585 from foolscap.api import DeadReferenceError
12586 from twisted.python.failure import Failure
12587 from twisted.web.client import getPage
12588hunk ./src/allmydata/test/test_system.py 460
12589     def test_mutable(self):
12590         self.basedir = "system/SystemTest/test_mutable"
12591         DATA = "initial contents go here."  # 25 bytes % 3 != 0
12592-        DATA_uploadable = MutableDataHandle(DATA)
12593+        DATA_uploadable = MutableData(DATA)
12594         NEWDATA = "new contents yay"
12595hunk ./src/allmydata/test/test_system.py 462
12596-        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
12597+        NEWDATA_uploadable = MutableData(NEWDATA)
12598         NEWERDATA = "this is getting old"
12599hunk ./src/allmydata/test/test_system.py 464
12600-        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
12601+        NEWERDATA_uploadable = MutableData(NEWERDATA)
12602 
12603         d = self.set_up_nodes(use_key_generator=True)
12604 
12605hunk ./src/allmydata/test/test_system.py 642
12606         def _check_empty_file(res):
12607             # make sure we can create empty files, this usually screws up the
12608             # segsize math
12609-            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
12610+            d1 = self.clients[2].create_mutable_file(MutableData(""))
12611             d1.addCallback(lambda newnode: newnode.download_best_version())
12612             d1.addCallback(lambda res: self.failUnlessEqual("", res))
12613             return d1
12614hunk ./src/allmydata/test/test_system.py 674
12615 
12616         d.addCallback(check_kg_poolsize, 0)
12617         d.addCallback(lambda junk:
12618-            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
12619+            self.clients[3].create_mutable_file(MutableData('hello, world')))
12620         d.addCallback(check_kg_poolsize, -1)
12621         d.addCallback(lambda junk: self.clients[3].create_dirnode())
12622         d.addCallback(check_kg_poolsize, -2)
12623hunk ./src/allmydata/test/test_web.py 3184
12624             self.uris[which] = n.get_uri()
12625             assert isinstance(self.uris[which], str)
12626         d.addCallback(lambda ign:
12627-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
12628+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12629         d.addCallback(_stash_mutable_uri, "corrupt")
12630         d.addCallback(lambda ign:
12631                       c0.upload(upload.Data("literal", convergence="")))
12632hunk ./src/allmydata/test/test_web.py 3331
12633             self.uris[which] = n.get_uri()
12634             assert isinstance(self.uris[which], str)
12635         d.addCallback(lambda ign:
12636-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
12637+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12638         d.addCallback(_stash_mutable_uri, "corrupt")
12639 
12640         def _compute_fileurls(ignored):
12641hunk ./src/allmydata/test/test_web.py 3994
12642             self.uris[which] = n.get_uri()
12643             assert isinstance(self.uris[which], str)
12644         d.addCallback(lambda ign:
12645-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
12646+            c0.create_mutable_file(publish.MutableData(DATA+"2")))
12647         d.addCallback(_stash_mutable_uri, "mutable")
12648 
12649         def _compute_fileurls(ignored):
12650hunk ./src/allmydata/test/test_web.py 4094
12651         d.addCallback(_stash_uri, "small")
12652 
12653         d.addCallback(lambda ign:
12654-            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
12655+            c0.create_mutable_file(publish.MutableData("mutable")))
12656         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
12657         d.addCallback(_stash_uri, "mutable")
12658 
12659}
12660[tests: fix tests that were broken by #993
12661Kevan Carstensen <kevan@isnotajoke.com>**20100717015230
12662 Ignore-this: f0ace6538c6d824b7fe271c40a7ebf8d
12663] {
12664hunk ./src/allmydata/test/common.py 152
12665         consumer.write(data[start:end])
12666         return consumer
12667 
12668+
12669+    def get_best_readable_version(self):
12670+        return defer.succeed(self)
12671+
12672+
12673+    download_best_version = download_to_data
12674+
12675+
12676+    def download_to_data(self):
12677+        return download_to_data(self)
12678+
12679+
12680+    def get_size_of_best_version(self):
12681+        return defer.succeed(self.get_size)
12682+
12683+
12684 def make_chk_file_cap(size):
12685     return uri.CHKFileURI(key=os.urandom(16),
12686                           uri_extension_hash=os.urandom(32),
12687hunk ./src/allmydata/test/common.py 318
12688         return d
12689 
12690     def download_best_version(self):
12691+        return defer.succeed(self._download_best_version())
12692+
12693+
12694+    def _download_best_version(self, ignored=None):
12695         if isinstance(self.my_uri, uri.LiteralFileURI):
12696hunk ./src/allmydata/test/common.py 323
12697-            return defer.succeed(self.my_uri.data)
12698+            return self.my_uri.data
12699         if self.storage_index not in self.all_contents:
12700hunk ./src/allmydata/test/common.py 325
12701-            return defer.fail(NotEnoughSharesError(None, 0, 3))
12702-        return defer.succeed(self.all_contents[self.storage_index])
12703+            raise NotEnoughSharesError(None, 0, 3)
12704+        return self.all_contents[self.storage_index]
12705+
12706 
12707     def overwrite(self, new_contents):
12708         if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
12709hunk ./src/allmydata/test/common.py 352
12710         self.all_contents[self.storage_index] = new_data
12711         return None
12712 
12713+    # As actually implemented, MutableFilenode and MutableFileVersion
12714+    # are distinct. However, nothing in the webapi uses (yet) that
12715+    # distinction -- it just uses the unified download interface
12716+    # provided by get_best_readable_version and read. When we start
12717+    # doing cooler things like LDMF, we will want to revise this code to
12718+    # be less simplistic.
12719+    def get_best_readable_version(self):
12720+        return defer.succeed(self)
12721+
12722+
12723+    def read(self, consumer, offset=0, size=None):
12724+        data = self._download_best_version()
12725+        if size:
12726+            data = data[offset:offset+size]
12727+        consumer.write(data)
12728+        return defer.succeed(consumer)
12729+
12730+
12731 def make_mutable_file_cap():
12732     return uri.WriteableSSKFileURI(writekey=os.urandom(16),
12733                                    fingerprint=os.urandom(32))
12734hunk ./src/allmydata/test/test_filenode.py 98
12735         def _check_segment(res):
12736             self.failUnlessEqual(res, DATA[1:1+5])
12737         d.addCallback(_check_segment)
12738-        d.addCallback(lambda ignored:
12739-            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
12740+        d.addCallback(lambda ignored: fn1.get_best_readable_version())
12741+        d.addCallback(lambda fn2: self.failUnlessEqual(fn1, fn2))
12742         d.addCallback(lambda ignored:
12743             fn1.get_size_of_best_version())
12744         d.addCallback(lambda size:
12745hunk ./src/allmydata/test/test_immutable.py 168
12746 
12747 
12748     def test_get_best_readable_version(self):
12749-        n = self.n.get_best_readable_version()
12750-        self.failUnlessEqual(n, self.n)
12751+        d = self.n.get_best_readable_version()
12752+        d.addCallback(lambda n2:
12753+            self.failUnlessEqual(n2, self.n))
12754+        return d
12755 
12756     def test_get_size_of_best_version(self):
12757         d = self.n.get_size_of_best_version()
12758hunk ./src/allmydata/test/test_mutable.py 8
12759 from twisted.internet import defer, reactor
12760 from allmydata import uri, client
12761 from allmydata.nodemaker import NodeMaker
12762-from allmydata.util import base32
12763+from allmydata.util import base32, consumer
12764 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
12765      ssk_pubkey_fingerprint_hash
12766hunk ./src/allmydata/test/test_mutable.py 11
12767+from allmydata.util.deferredutil import gatherResults
12768 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
12769      NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
12770 from allmydata.monitor import Monitor
12771hunk ./src/allmydata/test/test_mutable.py 1000
12772         if version is None:
12773             version = servermap.best_recoverable_version()
12774         r = Retrieve(self._fn, servermap, version)
12775-        return r.download()
12776+        c = consumer.MemoryConsumer()
12777+        d = r.download(consumer=c)
12778+        d.addCallback(lambda mc: "".join(mc.chunks))
12779+        return d
12780+
12781 
12782     def test_basic(self):
12783         d = self.make_servermap()
12784hunk ./src/allmydata/test/test_mutable.py 1263
12785                             in str(servermap.problems[0]))
12786             ver = servermap.best_recoverable_version()
12787             r = Retrieve(self._fn, servermap, ver)
12788-            return r.download()
12789+            c = consumer.MemoryConsumer()
12790+            return r.download(c)
12791         d.addCallback(_do_retrieve)
12792hunk ./src/allmydata/test/test_mutable.py 1266
12793+        d.addCallback(lambda mc: "".join(mc.chunks))
12794         d.addCallback(lambda new_contents:
12795                       self.failUnlessEqual(new_contents, self.CONTENTS))
12796         return d
12797}
12798[test/test_immutable.py: add tests for #993-related modifications
12799Kevan Carstensen <kevan@isnotajoke.com>**20100717015402
12800 Ignore-this: d94ad98bd0d322ead85af2e0aa95be38
12801] hunk ./src/allmydata/test/test_mutable.py 2607
12802         start = chunk_size
12803         end = chunk_size * 2
12804         self.failUnlessEqual("".join(more_data), self.test_data[start:end])
12805+
12806+
12807+class Version(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
12808+    def setUp(self):
12809+        GridTestMixin.setUp(self)
12810+        self.basedir = self.mktemp()
12811+        self.set_up_grid()
12812+        self.c = self.g.clients[0]
12813+        self.nm = self.c.nodemaker
12814+        self.data = "test data" * 100000 # about 900 KiB; MDMF
12815+        self.small_data = "test data" * 10 # about 90 B; SDMF
12816+        return self.do_upload()
12817+
12818+
12819+    def do_upload(self):
12820+        d1 = self.nm.create_mutable_file(MutableData(self.data),
12821+                                         version=MDMF_VERSION)
12822+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
12823+        dl = gatherResults([d1, d2])
12824+        def _then((n1, n2)):
12825+            assert isinstance(n1, MutableFileNode)
12826+            assert isinstance(n2, MutableFileNode)
12827+
12828+            self.mdmf_node = n1
12829+            self.sdmf_node = n2
12830+        dl.addCallback(_then)
12831+        return dl
12832+
12833+
12834+    def test_get_readonly_mutable_version(self):
12835+        # Attempting to get a mutable version of a mutable file from a
12836+        # filenode initialized with a readcap should return a readonly
12837+        # version of that same node.
12838+        ro = self.mdmf_node.get_readonly()
12839+        d = ro.get_best_mutable_version()
12840+        d.addCallback(lambda version:
12841+            self.failUnless(version.is_readonly()))
12842+        d.addCallback(lambda ignored:
12843+            self.sdmf_node.get_readonly())
12844+        d.addCallback(lambda version:
12845+            self.failUnless(version.is_readonly()))
12846+        return d
12847+
12848+
12849+    def test_get_sequence_number(self):
12850+        d = self.mdmf_node.get_best_readable_version()
12851+        d.addCallback(lambda bv:
12852+            self.failUnlessEqual(bv.get_sequence_number(), 0))
12853+        d.addCallback(lambda ignored:
12854+            self.sdmf_node.get_best_readable_version())
12855+        d.addCallback(lambda bv:
12856+            self.failUnlessEqual(bv.get_sequence_number(), 0))
12857+        # Now update. The sequence number in both cases should be 1 in
12858+        # both cases.
12859+        def _do_update(ignored):
12860+            new_data = MutableData("foo bar baz" * 100000)
12861+            new_small_data = MutableData("foo bar baz" * 10)
12862+            d1 = self.mdmf_node.overwrite(new_data)
12863+            d2 = self.sdmf_node.overwrite(new_small_data)
12864+            dl = gatherResults([d1, d2])
12865+            return dl
12866+        d.addCallback(_do_update)
12867+        d.addCallback(lambda ignored:
12868+            self.mdmf_node.get_best_readable_version())
12869+        d.addCallback(lambda bv:
12870+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12871+        d.addCallback(lambda ignored:
12872+            self.sdmf_node.get_best_readable_version())
12873+        d.addCallback(lambda bv:
12874+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12875+        return d
12876+
12877+
12878+    def test_get_writekey(self):
12879+        d = self.mdmf_node.get_best_mutable_version()
12880+        d.addCallback(lambda bv:
12881+            self.failUnlessEqual(bv.get_writekey(),
12882+                                 self.mdmf_node.get_writekey()))
12883+        d.addCallback(lambda ignored:
12884+            self.sdmf_node.get_best_mutable_version())
12885+        d.addCallback(lambda bv:
12886+            self.failUnlessEqual(bv.get_writekey(),
12887+                                 self.sdmf_node.get_writekey()))
12888+        return d
12889+
12890+
12891+    def test_get_storage_index(self):
12892+        d = self.mdmf_node.get_best_mutable_version()
12893+        d.addCallback(lambda bv:
12894+            self.failUnlessEqual(bv.get_storage_index(),
12895+                                 self.mdmf_node.get_storage_index()))
12896+        d.addCallback(lambda ignored:
12897+            self.sdmf_node.get_best_mutable_version())
12898+        d.addCallback(lambda bv:
12899+            self.failUnlessEqual(bv.get_storage_index(),
12900+                                 self.sdmf_node.get_storage_index()))
12901+        return d
12902+
12903+
12904+    def test_get_readonly_version(self):
12905+        d = self.mdmf_node.get_best_readable_version()
12906+        d.addCallback(lambda bv:
12907+            self.failUnless(bv.is_readonly()))
12908+        d.addCallback(lambda ignored:
12909+            self.sdmf_node.get_best_readable_version())
12910+        d.addCallback(lambda bv:
12911+            self.failUnless(bv.is_readonly()))
12912+        return d
12913+
12914+
12915+    def test_get_mutable_version(self):
12916+        d = self.mdmf_node.get_best_mutable_version()
12917+        d.addCallback(lambda bv:
12918+            self.failIf(bv.is_readonly()))
12919+        d.addCallback(lambda ignored:
12920+            self.sdmf_node.get_best_mutable_version())
12921+        d.addCallback(lambda bv:
12922+            self.failIf(bv.is_readonly()))
12923+        return d
12924+
12925+
12926+    def test_toplevel_overwrite(self):
12927+        new_data = MutableData("foo bar baz" * 100000)
12928+        new_small_data = MutableData("foo bar baz" * 10)
12929+        d = self.mdmf_node.overwrite(new_data)
12930+        d.addCallback(lambda ignored:
12931+            self.mdmf_node.download_best_version())
12932+        d.addCallback(lambda data:
12933+            self.failUnlessEqual(data, "foo bar baz" * 100000))
12934+        d.addCallback(lambda ignored:
12935+            self.sdmf_node.overwrite(new_small_data))
12936+        d.addCallback(lambda ignored:
12937+            self.sdmf_node.download_best_version())
12938+        d.addCallback(lambda data:
12939+            self.failUnlessEqual(data, "foo bar baz" * 10))
12940+        return d
12941+
12942+
12943+    def test_toplevel_modify(self):
12944+        def modifier(old_contents, servermap, first_time):
12945+            return MutableData(old_contents + "modified")
12946+        d = self.mdmf_node.modify(modifier)
12947+        d.addCallback(lambda ignored:
12948+            self.mdmf_node.download_best_version())
12949+        d.addCallback(lambda data:
12950+            self.failUnlessIn("modified", data))
12951+        d.addCallback(lambda ignored:
12952+            self.sdmf_node.modify(modifier))
12953+        d.addCallback(lambda ignored:
12954+            self.sdmf_node.download_best_version())
12955+        d.addCallback(lambda data:
12956+            self.failUnlessIn("modified", data))
12957+        return d
12958+
12959+
12960+    def test_version_modify(self):
12961+        # TODO: When we can publish multiple versions, alter this test
12962+        # to modify a version other than the best usable version, then
12963+        # test to see that the best recoverable version is that.
12964+        def modifier(old_contents, servermap, first_time):
12965+            return MutableData(old_contents + "modified")
12966+        d = self.mdmf_node.modify(modifier)
12967+        d.addCallback(lambda ignored:
12968+            self.mdmf_node.download_best_version())
12969+        d.addCallback(lambda data:
12970+            self.failUnlessIn("modified", data))
12971+        d.addCallback(lambda ignored:
12972+            self.sdmf_node.modify(modifier))
12973+        d.addCallback(lambda ignored:
12974+            self.sdmf_node.download_best_version())
12975+        d.addCallback(lambda data:
12976+            self.failUnlessIn("modified", data))
12977+        return d
12978+
12979+
12980+    def test_download_version(self):
12981+        # This will only pass once we get the ability to publish
12982+        # multiple recoverable versions.
12983+        self.failUnless(False)
12984+
12985+
12986+    def test_partial_read(self):
12987+        # read only a few bytes at a time, and see that the results are
12988+        # what we expect.
12989+        d = self.mdmf_node.get_best_readable_version()
12990+        def _read_data(version):
12991+            c = consumer.MemoryConsumer()
12992+            d2 = defer.succeed(None)
12993+            for i in xrange(0, len(self.data), 10000):
12994+                d2.addCallback(lambda ignored, i=i: version.read(c, i, 10000))
12995+            d2.addCallback(lambda ignored:
12996+                self.failUnlessEqual(self.data, "".join(c.chunks)))
12997+            return d2
12998+        d.addCallback(_read_data)
12999+        return d
13000+
13001+
13002+    def test_read(self):
13003+        d = self.mdmf_node.get_best_readable_version()
13004+        def _read_data(version):
13005+            c = consumer.MemoryConsumer()
13006+            d2 = defer.succeed(None)
13007+            d2.addCallback(lambda ignored: version.read(c))
13008+            d2.addCallback(lambda ignored:
13009+                self.failUnlessEqual("".join(c.chunks), self.data))
13010+            return d2
13011+        d.addCallback(_read_data)
13012+        return d
13013+
13014+
13015+    def test_download_best_version(self):
13016+        d = self.mdmf_node.download_best_version()
13017+        d.addCallback(lambda data:
13018+            self.failUnlessEqual(data, self.data))
13019+        d.addCallback(lambda ignored:
13020+            self.sdmf_node.download_best_version())
13021+        d.addCallback(lambda data:
13022+            self.failUnlessEqual(data, self.small_data))
13023+        return d
13024[web/filenode.py: alter download code to use the new #993 interface.
13025Kevan Carstensen <kevan@isnotajoke.com>**20100717015426
13026 Ignore-this: 4e8b89f9b616755f1f644ecea4cda0bb
13027] {
13028hunk ./src/allmydata/web/filenode.py 167
13029             # properly. So we assume that at least the browser will agree
13030             # with itself, and echo back the same bytes that we were given.
13031             filename = get_arg(req, "filename", self.name) or "unknown"
13032-            if self.node.is_mutable():
13033-                # some day: d = self.node.get_best_version()
13034-                d = makeMutableDownloadable(self.node)
13035-            else:
13036-                d = defer.succeed(self.node)
13037+            d = self.node.get_best_readable_version()
13038             d.addCallback(lambda dn: FileDownloader(dn, filename))
13039             return d
13040         if t == "json":
13041hunk ./src/allmydata/web/filenode.py 191
13042         if t:
13043             raise WebError("GET file: bad t=%s" % t)
13044         filename = get_arg(req, "filename", self.name) or "unknown"
13045-        if self.node.is_mutable():
13046-            # some day: d = self.node.get_best_version()
13047-            d = makeMutableDownloadable(self.node)
13048-        else:
13049-            d = defer.succeed(self.node)
13050+        d = self.node.get_best_readable_version()
13051         d.addCallback(lambda dn: FileDownloader(dn, filename))
13052         return d
13053 
13054hunk ./src/allmydata/web/filenode.py 285
13055         d.addCallback(lambda res: self.node.get_uri())
13056         return d
13057 
13058-class MutableDownloadable:
13059-    #implements(IDownloadable)
13060-    def __init__(self, size, node):
13061-        self.size = size
13062-        self.node = node
13063-    def get_size(self):
13064-        return self.size
13065-    def is_mutable(self):
13066-        return True
13067-    def read(self, consumer, offset=0, size=None):
13068-        d = self.node.download_best_version()
13069-        d.addCallback(self._got_data, consumer, offset, size)
13070-        return d
13071-    def _got_data(self, contents, consumer, offset, size):
13072-        start = offset
13073-        if size is not None:
13074-            end = offset+size
13075-        else:
13076-            end = self.size
13077-        # SDMF: we can write the whole file in one big chunk
13078-        consumer.write(contents[start:end])
13079-        return consumer
13080-
13081-def makeMutableDownloadable(n):
13082-    d = defer.maybeDeferred(n.get_size_of_best_version)
13083-    d.addCallback(MutableDownloadable, n)
13084-    return d
13085 
13086 class FileDownloader(rend.Page):
13087     # since we override the rendering process (to let the tahoe Downloader
13088}
13089[test/common.py: remove FileTooLargeErrors that tested for an SDMF limitation that no longer exists
13090Kevan Carstensen <kevan@isnotajoke.com>**20100717015501
13091 Ignore-this: 8b17689d9391a4870a327c1d7c0b3225
13092] {
13093hunk ./src/allmydata/test/common.py 198
13094         self.init_from_cap(make_mutable_file_cap())
13095     def create(self, contents, key_generator=None, keysize=None):
13096         initial_contents = self._get_initial_contents(contents)
13097-        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
13098-            raise FileTooLargeError("SDMF is limited to one segment, and "
13099-                                    "%d > %d" % (initial_contents.get_size(),
13100-                                                 self.MUTABLE_SIZELIMIT))
13101         data = initial_contents.read(initial_contents.get_size())
13102         data = "".join(data)
13103         self.all_contents[self.storage_index] = data
13104hunk ./src/allmydata/test/common.py 326
13105 
13106 
13107     def overwrite(self, new_contents):
13108-        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
13109-            raise FileTooLargeError("SDMF is limited to one segment, and "
13110-                                    "%d > %d" % (new_contents.get_size(),
13111-                                                 self.MUTABLE_SIZELIMIT))
13112         assert not self.is_readonly()
13113         new_data = new_contents.read(new_contents.get_size())
13114         new_data = "".join(new_data)
13115}
13116[nodemaker.py: resolve a conflict introduced in one of the 1.7.1 patches
13117Kevan Carstensen <kevan@isnotajoke.com>**20100720213109
13118 Ignore-this: 4e7d4e611f4cdf04824e9040167aa11
13119] hunk ./src/allmydata/nodemaker.py 107
13120                          "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
13121             node.raise_error()
13122         d = self.create_mutable_file(lambda n:
13123-                                     pack_children(n, initial_children))
13124+                                     MutableData(pack_children(initial_children,
13125+                                                    n.get_writekey())),
13126+                                     version)
13127         d.addCallback(self._create_dirnode)
13128         return d
13129 
13130[frontends/sftpd.py: fix conflicts with trunk
13131Kevan Carstensen <kevan@isnotajoke.com>**20100727224651
13132 Ignore-this: 5636e7a27162bf3ca14d6c9dc07a015
13133] {
13134hunk ./src/allmydata/frontends/sftpd.py 664
13135         else:
13136             assert IFileNode.providedBy(filenode), filenode
13137 
13138-            # TODO: use download interface described in #993 when implemented.
13139-            if filenode.is_mutable():
13140-                self.async.addCallback(lambda ign: filenode.download_best_version())
13141-                def _downloaded(data):
13142-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
13143-                    self.consumer.write(data)
13144-                    self.consumer.finish()
13145-                    return None
13146-                self.async.addCallback(_downloaded)
13147-            else:
13148-                download_size = filenode.get_size()
13149-                assert download_size is not None, "download_size is None"
13150+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
13151+
13152+            def _read(version):
13153+                if noisy: self.log("_read", level=NOISY)
13154+                download_size = version.get_size()
13155+                assert download_size is not None
13156+
13157                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
13158 
13159hunk ./src/allmydata/frontends/sftpd.py 673
13160-                if noisy: self.log("_read", level=NOISY)
13161                 version.read(self.consumer, 0, None)
13162             self.async.addCallback(_read)
13163 
13164}
13165[interfaces.py: Create an IWritable interface
13166Kevan Carstensen <kevan@isnotajoke.com>**20100727224703
13167 Ignore-this: 3fd15da701c31c024963d7ee5c896124
13168] hunk ./src/allmydata/interfaces.py 633
13169         """
13170 
13171 
13172+class IWritable(Interface):
13173+    """
13174+    I define methods that callers can use to update SDMF and MDMF
13175+    mutable files on a Tahoe-LAFS grid.
13176+    """
13177+    # XXX: For the moment, we have only this. It is possible that we
13178+    #      want to move overwrite() and modify() in here too.
13179+    def update(data, offset):
13180+        """
13181+        I write the data from my data argument to the MDMF file,
13182+        starting at offset. I continue writing data until my data
13183+        argument is exhausted, appending data to the file as necessary.
13184+        """
13185+        # assert IMutableUploadable.providedBy(data)
13186+        # to append data: offset=node.get_size_of_best_version()
13187+        # do we want to support compacting MDMF?
13188+        # for an MDMF file, this can be done with O(data.get_size())
13189+        # memory. For an SDMF file, any modification takes
13190+        # O(node.get_size_of_best_version()).
13191+
13192+
13193 class IMutableFileVersion(IReadable):
13194     """I provide access to a particular version of a mutable file. The
13195     access is read/write if I was obtained from a filenode derived from
13196[mutable/layout.py: Alter MDMFSlotWriteProxy to perform all write operations in one actual write operation
13197Kevan Carstensen <kevan@isnotajoke.com>**20100727224725
13198 Ignore-this: 41d577e9d65eba9a38a4051c2a05d4be
13199] {
13200hunk ./src/allmydata/mutable/layout.py 814
13201         # last thing we write to the remote server.
13202         self._offsets = {}
13203         self._testvs = []
13204+        # This is a list of write vectors that will be sent to our
13205+        # remote server once we are directed to write things there.
13206+        self._writevs = []
13207         self._secrets = secrets
13208         # The segment size needs to be a multiple of the k parameter --
13209         # any padding should have been carried out by the publisher
13210hunk ./src/allmydata/mutable/layout.py 947
13211 
13212     def put_block(self, data, segnum, salt):
13213         """
13214-        Put the encrypted-and-encoded data segment in the slot, along
13215-        with the salt.
13216+        I queue a write vector for the data, salt, and segment number
13217+        provided to me. I return None, as I do not actually cause
13218+        anything to be written yet.
13219         """
13220         if segnum >= self._num_segments:
13221             raise LayoutInvalid("I won't overwrite the private key")
13222hunk ./src/allmydata/mutable/layout.py 967
13223         offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
13224         data = salt + data
13225 
13226-        datavs = [tuple([offset, data])]
13227-        return self._write(datavs)
13228+        self._writevs.append(tuple([offset, data]))
13229 
13230 
13231     def put_encprivkey(self, encprivkey):
13232hunk ./src/allmydata/mutable/layout.py 972
13233         """
13234-        Put the encrypted private key in the remote slot.
13235+        I queue a write vector for the encrypted private key provided to
13236+        me.
13237         """
13238         assert self._offsets
13239         assert self._offsets['enc_privkey']
13240hunk ./src/allmydata/mutable/layout.py 986
13241         if "share_hash_chain" in self._offsets:
13242             raise LayoutInvalid("You must write this before the block hash tree")
13243 
13244-        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
13245-        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
13246-        def _on_failure():
13247-            del(self._offsets['block_hash_tree'])
13248-        return self._write(datavs, on_failure=_on_failure)
13249+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + \
13250+            len(encprivkey)
13251+        self._writevs.append(tuple([self._offsets['enc_privkey'], encprivkey]))
13252 
13253 
13254     def put_blockhashes(self, blockhashes):
13255hunk ./src/allmydata/mutable/layout.py 993
13256         """
13257-        Put the block hash tree in the remote slot.
13258+        I queue a write vector to put the block hash tree in blockhashes
13259+        onto the remote server.
13260 
13261hunk ./src/allmydata/mutable/layout.py 996
13262-        The encrypted private key must be put before the block hash
13263+        The encrypted private key must be queued before the block hash
13264         tree, since we need to know how large it is to know where the
13265         block hash tree should go. The block hash tree must be put
13266         before the salt hash tree, since its size determines the
13267hunk ./src/allmydata/mutable/layout.py 1014
13268                                 "you put the share hash chain")
13269         blockhashes_s = "".join(blockhashes)
13270         self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
13271-        datavs = []
13272-        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
13273-        def _on_failure():
13274-            del(self._offsets['share_hash_chain'])
13275-        return self._write(datavs, on_failure=_on_failure)
13276+
13277+        self._writevs.append(tuple([self._offsets['block_hash_tree'],
13278+                                  blockhashes_s]))
13279 
13280 
13281     def put_sharehashes(self, sharehashes):
13282hunk ./src/allmydata/mutable/layout.py 1021
13283         """
13284-        Put the share hash chain in the remote slot.
13285+        I queue a write vector to put the share hash chain in my
13286+        argument onto the remote server.
13287 
13288hunk ./src/allmydata/mutable/layout.py 1024
13289-        The salt hash tree must be put before the share hash chain,
13290+        The salt hash tree must be queued before the share hash chain,
13291         since we need to know where the salt hash tree ends before we
13292         can know where the share hash chain starts. The share hash chain
13293         must be put before the signature, since the length of the packed
13294hunk ./src/allmydata/mutable/layout.py 1044
13295         if "verification_key" in self._offsets:
13296             raise LayoutInvalid("You must write the share hash chain "
13297                                 "before you write the signature")
13298-        datavs = []
13299         sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
13300                                   for i in sorted(sharehashes.keys())])
13301         self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
13302hunk ./src/allmydata/mutable/layout.py 1047
13303-        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
13304-        def _on_failure():
13305-            del(self._offsets['signature'])
13306-        return self._write(datavs, on_failure=_on_failure)
13307+        self._writevs.append(tuple([self._offsets['share_hash_chain'],
13308+                            sharehashes_s]))
13309 
13310 
13311     def put_root_hash(self, roothash):
13312hunk ./src/allmydata/mutable/layout.py 1069
13313         if len(roothash) != HASH_SIZE:
13314             raise LayoutInvalid("hashes and salts must be exactly %d bytes"
13315                                  % HASH_SIZE)
13316-        datavs = []
13317         self._root_hash = roothash
13318         # To write both of these values, we update the checkstring on
13319         # the remote server, which includes them
13320hunk ./src/allmydata/mutable/layout.py 1073
13321         checkstring = self.get_checkstring()
13322-        datavs.append(tuple([0, checkstring]))
13323+        self._writevs.append(tuple([0, checkstring]))
13324         # This write, if successful, changes the checkstring, so we need
13325         # to update our internal checkstring to be consistent with the
13326         # one on the server.
13327hunk ./src/allmydata/mutable/layout.py 1077
13328-        def _on_success():
13329-            self._testvs = [(0, len(checkstring), "eq", checkstring)]
13330-        def _on_failure():
13331-            self._root_hash = None
13332-        return self._write(datavs,
13333-                           on_success=_on_success,
13334-                           on_failure=_on_failure)
13335 
13336 
13337     def get_signable(self):
13338hunk ./src/allmydata/mutable/layout.py 1100
13339 
13340     def put_signature(self, signature):
13341         """
13342-        Put the signature field to the remote slot.
13343+        I queue a write vector for the signature of the MDMF share.
13344 
13345         I require that the root hash and share hash chain have been put
13346         to the grid before I will write the signature to the grid.
13347hunk ./src/allmydata/mutable/layout.py 1123
13348             raise LayoutInvalid("You must write the signature before the verification key")
13349 
13350         self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
13351-        datavs = []
13352-        datavs.append(tuple([self._offsets['signature'], signature]))
13353-        def _on_failure():
13354-            del(self._offsets['verification_key'])
13355-        return self._write(datavs, on_failure=_on_failure)
13356+        self._writevs.append(tuple([self._offsets['signature'], signature]))
13357 
13358 
13359     def put_verification_key(self, verification_key):
13360hunk ./src/allmydata/mutable/layout.py 1128
13361         """
13362-        Put the verification key into the remote slot.
13363+        I queue a write vector for the verification key.
13364 
13365         I require that the signature have been written to the storage
13366         server before I allow the verification key to be written to the
13367hunk ./src/allmydata/mutable/layout.py 1138
13368             raise LayoutInvalid("You must put the signature before you "
13369                                 "can put the verification key")
13370         self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
13371-        datavs = []
13372-        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
13373-        def _on_failure():
13374-            del(self._offsets['EOF'])
13375-        return self._write(datavs, on_failure=_on_failure)
13376+        self._writevs.append(tuple([self._offsets['verification_key'],
13377+                            verification_key]))
13378+
13379 
13380     def _get_offsets_tuple(self):
13381         return tuple([(key, value) for key, value in self._offsets.items()])
13382hunk ./src/allmydata/mutable/layout.py 1145
13383 
13384+
13385     def get_verinfo(self):
13386         return (self._seqnum,
13387                 self._root_hash,
13388hunk ./src/allmydata/mutable/layout.py 1159
13389 
13390     def finish_publishing(self):
13391         """
13392-        Write the offset table and encoding parameters to the remote
13393-        slot, since that's the only thing we have yet to publish at this
13394-        point.
13395+        I add a write vector for the offsets table, and then cause all
13396+        of the write vectors that I've dealt with so far to be published
13397+        to the remote server, ending the write process.
13398         """
13399         if "EOF" not in self._offsets:
13400             raise LayoutInvalid("You must put the verification key before "
13401hunk ./src/allmydata/mutable/layout.py 1174
13402                               self._offsets['signature'],
13403                               self._offsets['verification_key'],
13404                               self._offsets['EOF'])
13405-        datavs = []
13406-        datavs.append(tuple([offsets_offset, offsets]))
13407+        self._writevs.append(tuple([offsets_offset, offsets]))
13408         encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
13409         params = struct.pack(">BBQQ",
13410                              self._required_shares,
13411hunk ./src/allmydata/mutable/layout.py 1181
13412                              self._total_shares,
13413                              self._segment_size,
13414                              self._data_length)
13415-        datavs.append(tuple([encoding_parameters_offset, params]))
13416-        return self._write(datavs)
13417+        self._writevs.append(tuple([encoding_parameters_offset, params]))
13418+        return self._write(self._writevs)
13419 
13420 
13421     def _write(self, datavs, on_failure=None, on_success=None):
13422}
13423[test/test_mutable.py: test that write operations occur all at once
13424Kevan Carstensen <kevan@isnotajoke.com>**20100727224817
13425 Ignore-this: 44cb37c6887ee9baa3e67645ece9555d
13426] {
13427hunk ./src/allmydata/test/test_mutable.py 100
13428         self.storage = storage
13429         self.queries = 0
13430     def callRemote(self, methname, *args, **kwargs):
13431+        self.queries += 1
13432         def _call():
13433             meth = getattr(self, methname)
13434             return meth(*args, **kwargs)
13435hunk ./src/allmydata/test/test_mutable.py 109
13436         return d
13437 
13438     def callRemoteOnly(self, methname, *args, **kwargs):
13439+        self.queries += 1
13440         d = self.callRemote(methname, *args, **kwargs)
13441         d.addBoth(lambda ignore: None)
13442         pass
13443hunk ./src/allmydata/test/test_mutable.py 370
13444         return d
13445 
13446 
13447+    def test_mdmf_write_count(self):
13448+        # Publishing an MDMF file should only cause one write for each
13449+        # share that is to be published. Otherwise, we introduce
13450+        # undesirable semantics that are a regression from SDMF
13451+        upload = MutableData("MDMF" * 100000) # about 400 KiB
13452+        d = self.nodemaker.create_mutable_file(upload,
13453+                                               version=MDMF_VERSION)
13454+        def _check_server_write_counts(ignored):
13455+            sb = self.nodemaker.storage_broker
13456+            peers = sb.test_servers.values()
13457+            for peer in peers:
13458+                self.failUnlessEqual(peer.queries, 1)
13459+        d.addCallback(_check_server_write_counts)
13460+        return d
13461+
13462+
13463     def test_create_with_initial_contents(self):
13464         upload1 = MutableData("contents 1")
13465         d = self.nodemaker.create_mutable_file(upload1)
13466}
13467[test/test_storage.py: modify proxy tests to work with the new writing semantics
13468Kevan Carstensen <kevan@isnotajoke.com>**20100727224853
13469 Ignore-this: 2b6bdde6dc9d8e4e7f096cdb725b40cf
13470] {
13471hunk ./src/allmydata/test/test_storage.py 1681
13472         # diagnose the problem. This test ensures that the read vector
13473         # is working appropriately.
13474         mw = self._make_new_mw("si1", 0)
13475-        d = defer.succeed(None)
13476 
13477hunk ./src/allmydata/test/test_storage.py 1682
13478-        # Write one share. This should return a checkstring of nothing,
13479-        # since there is no data there.
13480-        d.addCallback(lambda ignored:
13481-            mw.put_block(self.block, 0, self.salt))
13482-        def _check_first_write(results):
13483-            result, readvs = results
13484-            self.failUnless(result)
13485-            self.failIf(readvs)
13486-        d.addCallback(_check_first_write)
13487-        # Now, there should be a different checkstring returned when
13488-        # we write other shares
13489-        d.addCallback(lambda ignored:
13490-            mw.put_block(self.block, 1, self.salt))
13491-        def _check_next_write(results):
13492-            result, readvs = results
13493+        for i in xrange(6):
13494+            mw.put_block(self.block, i, self.salt)
13495+        mw.put_encprivkey(self.encprivkey)
13496+        mw.put_blockhashes(self.block_hash_tree)
13497+        mw.put_sharehashes(self.share_hash_chain)
13498+        mw.put_root_hash(self.root_hash)
13499+        mw.put_signature(self.signature)
13500+        mw.put_verification_key(self.verification_key)
13501+        d = mw.finish_publishing()
13502+        def _then(results):
13503+            self.failUnless(len(results), 2)
13504+            result, readv = results
13505             self.failUnless(result)
13506hunk ./src/allmydata/test/test_storage.py 1695
13507-            self.expected_checkstring = mw.get_checkstring()
13508-            self.failUnlessIn(0, readvs)
13509-            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
13510-        d.addCallback(_check_next_write)
13511-        # Add the other four shares
13512-        for i in xrange(2, 6):
13513-            d.addCallback(lambda ignored, i=i:
13514-                mw.put_block(self.block, i, self.salt))
13515-            d.addCallback(_check_next_write)
13516-        # Add the encrypted private key
13517-        d.addCallback(lambda ignored:
13518-            mw.put_encprivkey(self.encprivkey))
13519-        d.addCallback(_check_next_write)
13520-        # Add the block hash tree and share hash tree
13521-        d.addCallback(lambda ignored:
13522-            mw.put_blockhashes(self.block_hash_tree))
13523-        d.addCallback(_check_next_write)
13524-        d.addCallback(lambda ignored:
13525-            mw.put_sharehashes(self.share_hash_chain))
13526-        d.addCallback(_check_next_write)
13527-        # Add the root hash and the salt hash. This should change the
13528-        # checkstring, but not in a way that we'll be able to see right
13529-        # now, since the read vectors are applied before the write
13530-        # vectors.
13531+            self.failIf(readv)
13532+            self.old_checkstring = mw.get_checkstring()
13533+            mw.set_checkstring("")
13534+        d.addCallback(_then)
13535         d.addCallback(lambda ignored:
13536hunk ./src/allmydata/test/test_storage.py 1700
13537-            mw.put_root_hash(self.root_hash))
13538-        def _check_old_testv_after_new_one_is_written(results):
13539+            mw.finish_publishing())
13540+        def _then_again(results):
13541+            self.failUnlessEqual(len(results), 2)
13542             result, readvs = results
13543hunk ./src/allmydata/test/test_storage.py 1704
13544-            self.failUnless(result)
13545+            self.failIf(result)
13546             self.failUnlessIn(0, readvs)
13547hunk ./src/allmydata/test/test_storage.py 1706
13548-            self.failUnlessEqual(self.expected_checkstring,
13549-                                 readvs[0][0])
13550-            new_checkstring = mw.get_checkstring()
13551-            self.failIfEqual(new_checkstring,
13552-                             readvs[0][0])
13553-        d.addCallback(_check_old_testv_after_new_one_is_written)
13554-        # Now add the signature. This should succeed, meaning that the
13555-        # data gets written and the read vector matches what the writer
13556-        # thinks should be there.
13557-        d.addCallback(lambda ignored:
13558-            mw.put_signature(self.signature))
13559-        d.addCallback(_check_next_write)
13560+            readv = readvs[0][0]
13561+            self.failUnlessEqual(readv, self.old_checkstring)
13562+        d.addCallback(_then_again)
13563         # The checkstring remains the same for the rest of the process.
13564         return d
13565 
13566hunk ./src/allmydata/test/test_storage.py 1811
13567         # same share.
13568         mw1 = self._make_new_mw("si1", 0)
13569         mw2 = self._make_new_mw("si1", 0)
13570-        d = defer.succeed(None)
13571+
13572         def _check_success(results):
13573             result, readvs = results
13574             self.failUnless(result)
13575hunk ./src/allmydata/test/test_storage.py 1820
13576             result, readvs = results
13577             self.failIf(result)
13578 
13579-        d.addCallback(lambda ignored:
13580-            mw1.put_block(self.block, 0, self.salt))
13581+        def _write_share(mw):
13582+            for i in xrange(6):
13583+                mw.put_block(self.block, i, self.salt)
13584+            mw.put_encprivkey(self.encprivkey)
13585+            mw.put_blockhashes(self.block_hash_tree)
13586+            mw.put_sharehashes(self.share_hash_chain)
13587+            mw.put_root_hash(self.root_hash)
13588+            mw.put_signature(self.signature)
13589+            mw.put_verification_key(self.verification_key)
13590+            return mw.finish_publishing()
13591+        d = _write_share(mw1)
13592         d.addCallback(_check_success)
13593         d.addCallback(lambda ignored:
13594hunk ./src/allmydata/test/test_storage.py 1833
13595-            mw2.put_block(self.block, 0, self.salt))
13596+            _write_share(mw2))
13597         d.addCallback(_check_failure)
13598         return d
13599 
13600hunk ./src/allmydata/test/test_storage.py 1859
13601 
13602     def test_write_test_vectors(self):
13603         # If we give the write proxy a bogus test vector at
13604-        # any point during the process, it should fail to write.
13605+        # any point during the process, it should fail to write when we
13606+        # tell it to write.
13607+        def _check_failure(results):
13608+            self.failUnlessEqual(len(results), 2)
13609+            res, d = results
13610+            self.failIf(res)
13611+
13612+        def _check_success(results):
13613+            self.failUnlessEqual(len(results), 2)
13614+            res, d = results
13615+            self.failUnless(results)
13616+
13617         mw = self._make_new_mw("si1", 0)
13618         mw.set_checkstring("this is a lie")
13619hunk ./src/allmydata/test/test_storage.py 1873
13620-        # The initial write should be expecting to find the improbable
13621-        # checkstring above in place; finding nothing, it should fail.
13622-        d = defer.succeed(None)
13623-        d.addCallback(lambda ignored:
13624-            mw.put_block(self.block, 0, self.salt))
13625-        def _check_failure(results):
13626-            result, readv = results
13627-            self.failIf(result)
13628+        for i in xrange(6):
13629+            mw.put_block(self.block, i, self.salt)
13630+        mw.put_encprivkey(self.encprivkey)
13631+        mw.put_blockhashes(self.block_hash_tree)
13632+        mw.put_sharehashes(self.share_hash_chain)
13633+        mw.put_root_hash(self.root_hash)
13634+        mw.put_signature(self.signature)
13635+        mw.put_verification_key(self.verification_key)
13636+        d = mw.finish_publishing()
13637         d.addCallback(_check_failure)
13638hunk ./src/allmydata/test/test_storage.py 1883
13639-        # Now set the checkstring to the empty string, which
13640-        # indicates that no share is there.
13641         d.addCallback(lambda ignored:
13642             mw.set_checkstring(""))
13643         d.addCallback(lambda ignored:
13644hunk ./src/allmydata/test/test_storage.py 1886
13645-            mw.put_block(self.block, 0, self.salt))
13646-        def _check_success(results):
13647-            result, readv = results
13648-            self.failUnless(result)
13649-        d.addCallback(_check_success)
13650-        # Now set the checkstring to something wrong
13651-        d.addCallback(lambda ignored:
13652-            mw.set_checkstring("something wrong"))
13653-        # This should fail to do anything
13654-        d.addCallback(lambda ignored:
13655-            mw.put_block(self.block, 1, self.salt))
13656-        d.addCallback(_check_failure)
13657-        # Now set it back to what it should be.
13658-        d.addCallback(lambda ignored:
13659-            mw.set_checkstring(mw.get_checkstring()))
13660-        for i in xrange(1, 6):
13661-            d.addCallback(lambda ignored, i=i:
13662-                mw.put_block(self.block, i, self.salt))
13663-            d.addCallback(_check_success)
13664-        d.addCallback(lambda ignored:
13665-            mw.put_encprivkey(self.encprivkey))
13666-        d.addCallback(_check_success)
13667-        d.addCallback(lambda ignored:
13668-            mw.put_blockhashes(self.block_hash_tree))
13669-        d.addCallback(_check_success)
13670-        d.addCallback(lambda ignored:
13671-            mw.put_sharehashes(self.share_hash_chain))
13672-        d.addCallback(_check_success)
13673-        def _keep_old_checkstring(ignored):
13674-            self.old_checkstring = mw.get_checkstring()
13675-            mw.set_checkstring("foobarbaz")
13676-        d.addCallback(_keep_old_checkstring)
13677-        d.addCallback(lambda ignored:
13678-            mw.put_root_hash(self.root_hash))
13679-        d.addCallback(_check_failure)
13680-        d.addCallback(lambda ignored:
13681-            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
13682-        def _restore_old_checkstring(ignored):
13683-            mw.set_checkstring(self.old_checkstring)
13684-        d.addCallback(_restore_old_checkstring)
13685-        d.addCallback(lambda ignored:
13686-            mw.put_root_hash(self.root_hash))
13687-        d.addCallback(_check_success)
13688-        # The checkstring should have been set appropriately for us on
13689-        # the last write; if we try to change it to something else,
13690-        # that change should cause the verification key step to fail.
13691-        d.addCallback(lambda ignored:
13692-            mw.set_checkstring("something else"))
13693-        d.addCallback(lambda ignored:
13694-            mw.put_signature(self.signature))
13695-        d.addCallback(_check_failure)
13696-        d.addCallback(lambda ignored:
13697-            mw.set_checkstring(mw.get_checkstring()))
13698-        d.addCallback(lambda ignored:
13699-            mw.put_signature(self.signature))
13700-        d.addCallback(_check_success)
13701-        d.addCallback(lambda ignored:
13702-            mw.put_verification_key(self.verification_key))
13703+            mw.finish_publishing())
13704         d.addCallback(_check_success)
13705         return d
13706 
13707hunk ./src/allmydata/test/test_storage.py 1891
13708 
13709-    def test_offset_only_set_on_success(self):
13710-        # The write proxy should be smart enough to detect when a write
13711-        # has failed, and to temper its definition of progress based on
13712-        # that.
13713-        mw = self._make_new_mw("si1", 0)
13714-        d = defer.succeed(None)
13715-        for i in xrange(1, 6):
13716-            d.addCallback(lambda ignored, i=i:
13717-                mw.put_block(self.block, i, self.salt))
13718-        def _break_checkstring(ignored):
13719-            self._old_checkstring = mw.get_checkstring()
13720-            mw.set_checkstring("foobarbaz")
13721-
13722-        def _fix_checkstring(ignored):
13723-            mw.set_checkstring(self._old_checkstring)
13724-
13725-        d.addCallback(_break_checkstring)
13726-
13727-        # Setting the encrypted private key shouldn't work now, which is
13728-        # to be expected and is tested elsewhere. We also want to make
13729-        # sure that we can't add the block hash tree after a failed
13730-        # write of this sort.
13731-        d.addCallback(lambda ignored:
13732-            mw.put_encprivkey(self.encprivkey))
13733-        d.addCallback(lambda ignored:
13734-            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
13735-                            None,
13736-                            mw.put_blockhashes, self.block_hash_tree))
13737-        d.addCallback(_fix_checkstring)
13738-        d.addCallback(lambda ignored:
13739-            mw.put_encprivkey(self.encprivkey))
13740-        d.addCallback(_break_checkstring)
13741-        d.addCallback(lambda ignored:
13742-            mw.put_blockhashes(self.block_hash_tree))
13743-        d.addCallback(lambda ignored:
13744-            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
13745-                            None,
13746-                            mw.put_sharehashes, self.share_hash_chain))
13747-        d.addCallback(_fix_checkstring)
13748-        d.addCallback(lambda ignored:
13749-            mw.put_blockhashes(self.block_hash_tree))
13750-        d.addCallback(_break_checkstring)
13751-        d.addCallback(lambda ignored:
13752-            mw.put_sharehashes(self.share_hash_chain))
13753-        d.addCallback(lambda ignored:
13754-            self.shouldFail(LayoutInvalid, "out-of-order root hash",
13755-                            None,
13756-                            mw.put_root_hash, self.root_hash))
13757-        d.addCallback(_fix_checkstring)
13758-        d.addCallback(lambda ignored:
13759-            mw.put_sharehashes(self.share_hash_chain))
13760-        d.addCallback(_break_checkstring)
13761-        d.addCallback(lambda ignored:
13762-            mw.put_root_hash(self.root_hash))
13763-        d.addCallback(lambda ignored:
13764-            self.shouldFail(LayoutInvalid, "out-of-order signature",
13765-                            None,
13766-                            mw.put_signature, self.signature))
13767-        d.addCallback(_fix_checkstring)
13768-        d.addCallback(lambda ignored:
13769-            mw.put_root_hash(self.root_hash))
13770-        d.addCallback(_break_checkstring)
13771-        d.addCallback(lambda ignored:
13772-            mw.put_signature(self.signature))
13773-        d.addCallback(lambda ignored:
13774-            self.shouldFail(LayoutInvalid, "out-of-order verification key",
13775-                            None,
13776-                            mw.put_verification_key,
13777-                            self.verification_key))
13778-        d.addCallback(_fix_checkstring)
13779-        d.addCallback(lambda ignored:
13780-            mw.put_signature(self.signature))
13781-        d.addCallback(_break_checkstring)
13782-        d.addCallback(lambda ignored:
13783-            mw.put_verification_key(self.verification_key))
13784-        d.addCallback(lambda ignored:
13785-            self.shouldFail(LayoutInvalid, "out-of-order finish",
13786-                            None,
13787-                            mw.finish_publishing))
13788-        return d
13789-
13790-
13791     def serialize_blockhashes(self, blockhashes):
13792         return "".join(blockhashes)
13793 
13794hunk ./src/allmydata/test/test_storage.py 1905
13795         # This translates to a file with 6 6-byte segments, and with 2-byte
13796         # blocks.
13797         mw = self._make_new_mw("si1", 0)
13798-        mw2 = self._make_new_mw("si1", 1)
13799         # Test writing some blocks.
13800         read = self.ss.remote_slot_readv
13801         expected_sharedata_offset = struct.calcsize(MDMFHEADER)
13802hunk ./src/allmydata/test/test_storage.py 1910
13803         written_block_size = 2 + len(self.salt)
13804         written_block = self.block + self.salt
13805-        def _check_block_write(i, share):
13806-            self.failUnlessEqual(read("si1", [share], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
13807-                                {share: [written_block]})
13808-        d = defer.succeed(None)
13809         for i in xrange(6):
13810hunk ./src/allmydata/test/test_storage.py 1911
13811-            d.addCallback(lambda ignored, i=i:
13812-                mw.put_block(self.block, i, self.salt))
13813-            d.addCallback(lambda ignored, i=i:
13814-                _check_block_write(i, 0))
13815-        # Now try the same thing, but with share 1 instead of share 0.
13816-        for i in xrange(6):
13817-            d.addCallback(lambda ignored, i=i:
13818-                mw2.put_block(self.block, i, self.salt))
13819-            d.addCallback(lambda ignored, i=i:
13820-                _check_block_write(i, 1))
13821+            mw.put_block(self.block, i, self.salt)
13822 
13823hunk ./src/allmydata/test/test_storage.py 1913
13824-        # Next, we make a fake encrypted private key, and put it onto the
13825-        # storage server.
13826-        d.addCallback(lambda ignored:
13827-            mw.put_encprivkey(self.encprivkey))
13828-        expected_private_key_offset = expected_sharedata_offset + \
13829+        mw.put_encprivkey(self.encprivkey)
13830+        mw.put_blockhashes(self.block_hash_tree)
13831+        mw.put_sharehashes(self.share_hash_chain)
13832+        mw.put_root_hash(self.root_hash)
13833+        mw.put_signature(self.signature)
13834+        mw.put_verification_key(self.verification_key)
13835+        d = mw.finish_publishing()
13836+        def _check_publish(results):
13837+            self.failUnlessEqual(len(results), 2)
13838+            result, ign = results
13839+            self.failUnless(result, "publish failed")
13840+            for i in xrange(6):
13841+                self.failUnlessEqual(read("si1", [0], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
13842+                                {0: [written_block]})
13843+
13844+            expected_private_key_offset = expected_sharedata_offset + \
13845                                       len(written_block) * 6
13846hunk ./src/allmydata/test/test_storage.py 1930
13847-        self.failUnlessEqual(len(self.encprivkey), 7)
13848-        d.addCallback(lambda ignored:
13849+            self.failUnlessEqual(len(self.encprivkey), 7)
13850             self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
13851hunk ./src/allmydata/test/test_storage.py 1932
13852-                                 {0: [self.encprivkey]}))
13853+                                 {0: [self.encprivkey]})
13854 
13855hunk ./src/allmydata/test/test_storage.py 1934
13856-        # Next, we put a fake block hash tree.
13857-        d.addCallback(lambda ignored:
13858-            mw.put_blockhashes(self.block_hash_tree))
13859-        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
13860-        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
13861-        d.addCallback(lambda ignored:
13862+            expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
13863+            self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
13864             self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
13865hunk ./src/allmydata/test/test_storage.py 1937
13866-                                 {0: [self.block_hash_tree_s]}))
13867+                                 {0: [self.block_hash_tree_s]})
13868 
13869hunk ./src/allmydata/test/test_storage.py 1939
13870-        # Next, put a fake share hash chain
13871-        d.addCallback(lambda ignored:
13872-            mw.put_sharehashes(self.share_hash_chain))
13873-        expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
13874-        d.addCallback(lambda ignored:
13875+            expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
13876             self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
13877hunk ./src/allmydata/test/test_storage.py 1941
13878-                                 {0: [self.share_hash_chain_s]}))
13879+                                 {0: [self.share_hash_chain_s]})
13880 
13881hunk ./src/allmydata/test/test_storage.py 1943
13882-        # Next, we put what is supposed to be the root hash of
13883-        # our share hash tree but isn't       
13884-        d.addCallback(lambda ignored:
13885-            mw.put_root_hash(self.root_hash))
13886-        # The root hash gets inserted at byte 9 (its position is in the header,
13887-        # and is fixed).
13888-        def _check(ignored):
13889             self.failUnlessEqual(read("si1", [0], [(9, 32)]),
13890                                  {0: [self.root_hash]})
13891hunk ./src/allmydata/test/test_storage.py 1945
13892-        d.addCallback(_check)
13893-
13894-        # Next, we put a signature of the header block.
13895-        d.addCallback(lambda ignored:
13896-            mw.put_signature(self.signature))
13897-        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
13898-        self.failUnlessEqual(len(self.signature), 9)
13899-        d.addCallback(lambda ignored:
13900+            expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
13901+            self.failUnlessEqual(len(self.signature), 9)
13902             self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
13903hunk ./src/allmydata/test/test_storage.py 1948
13904-                                 {0: [self.signature]}))
13905+                                 {0: [self.signature]})
13906 
13907hunk ./src/allmydata/test/test_storage.py 1950
13908-        # Next, we put the verification key
13909-        d.addCallback(lambda ignored:
13910-            mw.put_verification_key(self.verification_key))
13911-        expected_verification_key_offset = expected_signature_offset + len(self.signature)
13912-        self.failUnlessEqual(len(self.verification_key), 6)
13913-        d.addCallback(lambda ignored:
13914+            expected_verification_key_offset = expected_signature_offset + len(self.signature)
13915+            self.failUnlessEqual(len(self.verification_key), 6)
13916             self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
13917hunk ./src/allmydata/test/test_storage.py 1953
13918-                                 {0: [self.verification_key]}))
13919+                                 {0: [self.verification_key]})
13920 
13921hunk ./src/allmydata/test/test_storage.py 1955
13922-        def _check_signable(ignored):
13923-            # Make sure that the signable is what we think it should be.
13924             signable = mw.get_signable()
13925             verno, seq, roothash, k, n, segsize, datalen = \
13926                                             struct.unpack(">BQ32sBBQQ",
13927hunk ./src/allmydata/test/test_storage.py 1966
13928             self.failUnlessEqual(n, 10)
13929             self.failUnlessEqual(segsize, 6)
13930             self.failUnlessEqual(datalen, 36)
13931-        d.addCallback(_check_signable)
13932-        # Next, we cause the offset table to be published.
13933-        d.addCallback(lambda ignored:
13934-            mw.finish_publishing())
13935-        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
13936+            expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
13937 
13938hunk ./src/allmydata/test/test_storage.py 1968
13939-        def _check_offsets(ignored):
13940             # Check the version number to make sure that it is correct.
13941             expected_version_number = struct.pack(">B", 1)
13942             self.failUnlessEqual(read("si1", [0], [(0, 1)]),
13943hunk ./src/allmydata/test/test_storage.py 2008
13944             expected_offset = struct.pack(">Q", expected_eof_offset)
13945             self.failUnlessEqual(read("si1", [0], [(99, 8)]),
13946                                  {0: [expected_offset]})
13947-        d.addCallback(_check_offsets)
13948+        d.addCallback(_check_publish)
13949         return d
13950 
13951     def _make_new_mw(self, si, share, datalength=36):
13952}
13953[mutable/publish.py: alter mutable publisher to work with new writing semantics
13954Kevan Carstensen <kevan@isnotajoke.com>**20100727225001
13955 Ignore-this: a6b4628e749e09bfcddf3309271b5831
13956] {
13957hunk ./src/allmydata/mutable/publish.py 389
13958         if self._state == PUSHING_BLOCKS_STATE:
13959             return self.push_segment(self._current_segment)
13960 
13961-        # XXX: Do we want more granularity in states? Is that useful at
13962-        #      all?
13963-        #      Yes -- quicker reaction to UCW.
13964         elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
13965             return self.push_everything_else()
13966 
13967hunk ./src/allmydata/mutable/publish.py 490
13968         results, salt = encoded_and_salt
13969         shares, shareids = results
13970         started = time.time()
13971-        dl = []
13972         for i in xrange(len(shares)):
13973             sharedata = shares[i]
13974             shareid = shareids[i]
13975hunk ./src/allmydata/mutable/publish.py 502
13976 
13977             # find the writer for this share
13978             writer = self.writers[shareid]
13979-            d = writer.put_block(sharedata, segnum, salt)
13980-            d.addCallback(self._got_write_answer, writer, started)
13981-            d.addErrback(self._connection_problem, writer)
13982-            dl.append(d)
13983-        return defer.DeferredList(dl)
13984+            writer.put_block(sharedata, segnum, salt)
13985 
13986 
13987     def push_everything_else(self):
13988hunk ./src/allmydata/mutable/publish.py 510
13989         I put everything else associated with a share.
13990         """
13991         encprivkey = self._encprivkey
13992-        d = self.push_encprivkey()
13993-        d.addCallback(self.push_blockhashes)
13994-        d.addCallback(self.push_sharehashes)
13995-        d.addCallback(self.push_toplevel_hashes_and_signature)
13996-        d.addCallback(self.finish_publishing)
13997+        self.push_encprivkey()
13998+        self.push_blockhashes()
13999+        self.push_sharehashes()
14000+        self.push_toplevel_hashes_and_signature()
14001+        d = self.finish_publishing()
14002         def _change_state(ignored):
14003             self._state = DONE_STATE
14004         d.addCallback(_change_state)
14005hunk ./src/allmydata/mutable/publish.py 525
14006     def push_encprivkey(self):
14007         started = time.time()
14008         encprivkey = self._encprivkey
14009-        dl = []
14010         for writer in self.writers.itervalues():
14011hunk ./src/allmydata/mutable/publish.py 526
14012-            d = writer.put_encprivkey(encprivkey)
14013-            d.addCallback(self._got_write_answer, writer, started)
14014-            d.addErrback(self._connection_problem, writer)
14015-            dl.append(d)
14016-        d = defer.DeferredList(dl)
14017-        return d
14018+            writer.put_encprivkey(encprivkey)
14019 
14020 
14021hunk ./src/allmydata/mutable/publish.py 529
14022-    def push_blockhashes(self, ignored):
14023+    def push_blockhashes(self):
14024         started = time.time()
14025hunk ./src/allmydata/mutable/publish.py 531
14026-        dl = []
14027         self.sharehash_leaves = [None] * len(self.blockhashes)
14028         for shnum, blockhashes in self.blockhashes.iteritems():
14029             t = hashtree.HashTree(blockhashes)
14030hunk ./src/allmydata/mutable/publish.py 538
14031             # set the leaf for future use.
14032             self.sharehash_leaves[shnum] = t[0]
14033             writer = self.writers[shnum]
14034-            d = writer.put_blockhashes(self.blockhashes[shnum])
14035-            d.addCallback(self._got_write_answer, writer, started)
14036-            d.addErrback(self._connection_problem, self.writers[shnum])
14037-            dl.append(d)
14038-        d = defer.DeferredList(dl)
14039-        return d
14040+            writer.put_blockhashes(self.blockhashes[shnum])
14041 
14042 
14043hunk ./src/allmydata/mutable/publish.py 541
14044-    def push_sharehashes(self, ignored):
14045+    def push_sharehashes(self):
14046         started = time.time()
14047         share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
14048         share_hash_chain = {}
14049hunk ./src/allmydata/mutable/publish.py 545
14050-        ds = []
14051         for shnum in xrange(len(self.sharehash_leaves)):
14052             needed_indices = share_hash_tree.needed_hashes(shnum)
14053             self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
14054hunk ./src/allmydata/mutable/publish.py 550
14055                                              for i in needed_indices] )
14056             writer = self.writers[shnum]
14057-            d = writer.put_sharehashes(self.sharehashes[shnum])
14058-            d.addCallback(self._got_write_answer, writer, started)
14059-            d.addErrback(self._connection_problem, writer)
14060-            ds.append(d)
14061+            writer.put_sharehashes(self.sharehashes[shnum])
14062         self.root_hash = share_hash_tree[0]
14063hunk ./src/allmydata/mutable/publish.py 552
14064-        d = defer.DeferredList(ds)
14065-        return d
14066 
14067 
14068hunk ./src/allmydata/mutable/publish.py 554
14069-    def push_toplevel_hashes_and_signature(self, ignored):
14070+    def push_toplevel_hashes_and_signature(self):
14071         # We need to to three things here:
14072         #   - Push the root hash and salt hash
14073         #   - Get the checkstring of the resulting layout; sign that.
14074hunk ./src/allmydata/mutable/publish.py 560
14075         #   - Push the signature
14076         started = time.time()
14077-        ds = []
14078         for shnum in xrange(self.total_shares):
14079             writer = self.writers[shnum]
14080hunk ./src/allmydata/mutable/publish.py 562
14081-            d = writer.put_root_hash(self.root_hash)
14082-            d.addCallback(self._got_write_answer, writer, started)
14083-            ds.append(d)
14084-        d = defer.DeferredList(ds)
14085-        d.addCallback(self._update_checkstring)
14086-        d.addCallback(self._make_and_place_signature)
14087-        return d
14088+            writer.put_root_hash(self.root_hash)
14089+        self._update_checkstring()
14090+        self._make_and_place_signature()
14091 
14092 
14093hunk ./src/allmydata/mutable/publish.py 567
14094-    def _update_checkstring(self, ignored):
14095+    def _update_checkstring(self):
14096         """
14097         After putting the root hash, MDMF files will have the
14098         checkstring written to the storage server. This means that we
14099hunk ./src/allmydata/mutable/publish.py 578
14100         self._checkstring = self.writers.values()[0].get_checkstring()
14101 
14102 
14103-    def _make_and_place_signature(self, ignored):
14104+    def _make_and_place_signature(self):
14105         """
14106         I create and place the signature.
14107         """
14108hunk ./src/allmydata/mutable/publish.py 586
14109         signable = self.writers[0].get_signable()
14110         self.signature = self._privkey.sign(signable)
14111 
14112-        ds = []
14113         for (shnum, writer) in self.writers.iteritems():
14114hunk ./src/allmydata/mutable/publish.py 587
14115-            d = writer.put_signature(self.signature)
14116-            d.addCallback(self._got_write_answer, writer, started)
14117-            d.addErrback(self._connection_problem, writer)
14118-            ds.append(d)
14119-        return defer.DeferredList(ds)
14120+            writer.put_signature(self.signature)
14121 
14122 
14123hunk ./src/allmydata/mutable/publish.py 590
14124-    def finish_publishing(self, ignored):
14125+    def finish_publishing(self):
14126         # We're almost done -- we just need to put the verification key
14127         # and the offsets
14128         started = time.time()
14129hunk ./src/allmydata/mutable/publish.py 601
14130         # TODO: Bad, since we remove from this same dict. We need to
14131         # make a copy, or just use a non-iterated value.
14132         for (shnum, writer) in self.writers.iteritems():
14133-            d = writer.put_verification_key(verification_key)
14134-            d.addCallback(self._got_write_answer, writer, started)
14135-            d.addCallback(self._record_verinfo)
14136-            d.addCallback(lambda ignored, writer=writer:
14137-                writer.finish_publishing())
14138+            writer.put_verification_key(verification_key)
14139+            d = writer.finish_publishing()
14140             d.addCallback(self._got_write_answer, writer, started)
14141             d.addErrback(self._connection_problem, writer)
14142             ds.append(d)
14143hunk ./src/allmydata/mutable/publish.py 606
14144+        self._record_verinfo()
14145         return defer.DeferredList(ds)
14146 
14147 
14148hunk ./src/allmydata/mutable/publish.py 610
14149-    def _record_verinfo(self, ignored):
14150+    def _record_verinfo(self):
14151         self.versioninfo = self.writers.values()[0].get_verinfo()
14152 
14153 
14154}
14155[test/test_mutable.py: Add tests for new servermap behavior
14156Kevan Carstensen <kevan@isnotajoke.com>**20100728232434
14157 Ignore-this: aa6da7dbc9f86eb8840c8f0e779e644d
14158] {
14159hunk ./src/allmydata/test/test_mutable.py 773
14160     def setUp(self):
14161         return self.publish_one()
14162 
14163-    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None):
14164+    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None,
14165+                       update_range=None):
14166         if fn is None:
14167             fn = self._fn
14168         if sb is None:
14169hunk ./src/allmydata/test/test_mutable.py 780
14170             sb = self._storage_broker
14171         smu = ServermapUpdater(fn, sb, Monitor(),
14172-                               ServerMap(), mode)
14173+                               ServerMap(), mode, update_range=update_range)
14174         d = smu.update()
14175         return d
14176 
14177hunk ./src/allmydata/test/test_mutable.py 855
14178         d.addCallback(lambda sm: self.failUnlessOneRecoverable(sm, 10))
14179         return d
14180 
14181+
14182     def test_mark_bad(self):
14183         d = defer.succeed(None)
14184         ms = self.make_servermap
14185hunk ./src/allmydata/test/test_mutable.py 970
14186         return d
14187 
14188 
14189+    def test_fetch_update(self):
14190+        d = defer.succeed(None)
14191+        d.addCallback(lambda ignored:
14192+            self.publish_mdmf())
14193+        d.addCallback(lambda ignored:
14194+            self.make_servermap(mode=MODE_WRITE, update_range=(1, 2)))
14195+        def _check_servermap(sm):
14196+            # 10 shares
14197+            self.failUnlessEqual(len(sm.update_data), 10)
14198+            # one version
14199+            for data in sm.update_data.itervalues():
14200+                self.failUnlessEqual(len(data), 1)
14201+        d.addCallback(_check_servermap)
14202+        return d
14203+
14204+
14205     def test_servermapupdater_finds_sdmf_files(self):
14206         d = defer.succeed(None)
14207         d.addCallback(lambda ignored:
14208hunk ./src/allmydata/test/test_mutable.py 1756
14209 
14210     def test_mdmf_repairable_5shares(self):
14211         d = self.publish_mdmf()
14212-        def _delete_all_shares(ign):
14213+        def _delete_some_shares(ign):
14214             shares = self._storage._peers
14215             for peerid in shares:
14216                 for shnum in list(shares[peerid]):
14217hunk ./src/allmydata/test/test_mutable.py 1762
14218                     if shnum > 5:
14219                         del shares[peerid][shnum]
14220-        d.addCallback(_delete_all_shares)
14221+        d.addCallback(_delete_some_shares)
14222         d.addCallback(lambda ign: self._fn.check(Monitor()))
14223hunk ./src/allmydata/test/test_mutable.py 1764
14224+        def _check(cr):
14225+            self.failIf(cr.is_healthy())
14226+            self.failUnless(cr.is_recoverable())
14227+            return cr
14228+        d.addCallback(_check)
14229         d.addCallback(lambda check_results: self._fn.repair(check_results))
14230hunk ./src/allmydata/test/test_mutable.py 1770
14231-        def _check(crr):
14232+        def _check1(crr):
14233             self.failUnlessEqual(crr.get_successful(), True)
14234hunk ./src/allmydata/test/test_mutable.py 1772
14235-        d.addCallback(_check)
14236+        d.addCallback(_check1)
14237         return d
14238 
14239 
14240}
14241[mutable/filenode.py: add an update method.
14242Kevan Carstensen <kevan@isnotajoke.com>**20100730234029
14243 Ignore-this: 3ed4dcfb8a247812ed357216913334e7
14244] {
14245hunk ./src/allmydata/mutable/filenode.py 9
14246 from foolscap.api import eventually
14247 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
14248      NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
14249-     IMutableFileVersion
14250-from allmydata.util import hashutil, log, consumer
14251+     IMutableFileVersion, IWritable
14252+from allmydata.util import hashutil, log, consumer, deferredutil
14253 from allmydata.util.assertutil import precondition
14254 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
14255 from allmydata.monitor import Monitor
14256hunk ./src/allmydata/mutable/filenode.py 17
14257 from pycryptopp.cipher.aes import AES
14258 
14259 from allmydata.mutable.publish import Publish, MutableFileHandle, \
14260-                                      MutableData
14261+                                      MutableData,\
14262+                                      DEFAULT_MAX_SEGMENT_SIZE, \
14263+                                      TransformingUploadable
14264 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
14265      ResponseCache, UncoordinatedWriteError
14266 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
14267hunk ./src/allmydata/mutable/filenode.py 671
14268     overwrite or modify the contents of the mutable file that I
14269     reference.
14270     """
14271-    implements(IMutableFileVersion)
14272+    implements(IMutableFileVersion, IWritable)
14273 
14274     def __init__(self,
14275                  node,
14276hunk ./src/allmydata/mutable/filenode.py 960
14277     def _did_upload(self, res, size):
14278         self._size = size
14279         return res
14280+
14281+    def _update(self, data, offset):
14282+        """
14283+        Do an update of this mutable file version by inserting data at
14284+        offset within the file. If offset is the EOF, this is an append
14285+        operation. I return a Deferred that fires with the results of
14286+        the update operation when it has completed.
14287+
14288+        In cases where update does not append any data, or where it does
14289+        not append so many blocks that the block count crosses a
14290+        power-of-two boundary, this operation will use roughly
14291+        O(data.get_size()) memory/bandwidth/CPU to perform the update.
14292+        Otherwise, it must download, re-encode, and upload the entire
14293+        file again, which will use O(filesize) resources.
14294+        """
14295+        return self._do_serialized(self._update, data, offset)
14296+
14297+
14298+    def _update(self, data, offset):
14299+        """
14300+        I update the mutable file version represented by this particular
14301+        IMutableVersion by inserting the data in data at the offset
14302+        offset. I return a Deferred that fires when this has been
14303+        completed.
14304+        """
14305+        d = self._do_update_update(data, offset)
14306+        d.addCallback(self._decode_and_decrypt_segments)
14307+        d.addCallback(self._build_uploadable_and_finish)
14308+        return d
14309+
14310+
14311+    def _do_update_update(self, data, offset):
14312+        """
14313+        I start the Servermap update that gets us the data we need to
14314+        continue the update process. I return a Deferred that fires when
14315+        the servermap update is done.
14316+        """
14317+        assert IMutableUploadable.providedBy(data)
14318+        assert self.is_mutable()
14319+        # offset == self.get_size() is valid and means that we are
14320+        # appending data to the file.
14321+        assert offset <= self.get_size()
14322+
14323+        datasize = data.get_size()
14324+        # We'll need the segment that the data starts in, regardless of
14325+        # what we'll do later.
14326+        start_segment = mathutil.div_ceil(offset, DEFAULT_MAX_SEGMENT_SIZE)
14327+        start_segment -= 1
14328+
14329+        # We only need the end segment if the data we append does not go
14330+        # beyond the current end-of-file.
14331+        end_segment = start_segment
14332+        if offset + data.get_size() < self.get_size():
14333+            end_data = offset + data.get_size()
14334+            end_segment = mathutil.div_ceil(end_data, DEFAULT_MAX_SEGMENT_SIZE)
14335+            end_segment -= 1
14336+
14337+        # Now ask for the servermap to be updated in MODE_WRITE with
14338+        # this update range.
14339+        u = ServermapUpdater(self, self._storage_broker, Monitor(),
14340+                             self._servermap,
14341+                             mode=MODE_WRITE,
14342+                             update_range=(start_segment, end_segment))
14343+        return u.update()
14344+
14345+
14346+    def _decode_and_decrypt_segments(self, ignored, data, offset):
14347+        """
14348+        After the servermap update, I take the encrypted and encoded
14349+        data that the servermap fetched while doing its update and
14350+        transform it into decoded-and-decrypted plaintext that can be
14351+        used by the new uploadable. I return a Deferred that fires with
14352+        the segments.
14353+        """
14354+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
14355+        # decode: takes in our blocks and salts from the servermap,
14356+        # returns a Deferred that fires with the corresponding plaintext
14357+        # segments. Does not download -- simply takes advantage of
14358+        # existing infrastructure within the Retrieve class to avoid
14359+        # duplicating code.
14360+        sm = self._servermap
14361+        # XXX: If the methods in the servermap don't work as
14362+        # abstractions, you should rewrite them instead of going around
14363+        # them.
14364+        data = sm.update_data
14365+        data = [data[1] for i in update_data if i[0] == self._verinfo]
14366+        start_seg_data = [d[0] for d in data]
14367+        end_seg_data = [d[1] for d in data]
14368+        d1 = r.decode(start_seg_data)
14369+        d2 = r.decode(end_seg_data)
14370+        return deferredutil.gatherResults([d1, d2])
14371+
14372+
14373+    def _build_uploadable_and_finish(self, segments, data, offset):
14374+        """
14375+        After the process has the plaintext segments, I build the
14376+        TransformingUploadable that the publisher will eventually
14377+        re-upload to the grid. I then invoke the publisher with that
14378+        uploadable, and return a Deferred when the publish operation has
14379+        completed without issue.
14380+        """
14381+        u = TransformingUploadable(data, offset,
14382+                                   DEFAULT_MAX_SEGMENT_SIZE,
14383+                                   segments[0],
14384+                                   segments[1])
14385+        p = Publish(self._node, self._storage_broker, self._servermap)
14386+        return p.update(data, offset, blockhashes)
14387}
14388[mutable/publish.py: learn how to update as well as publish files.
14389Kevan Carstensen <kevan@isnotajoke.com>**20100730234056
14390 Ignore-this: 4c04857280da970f5c1c6c75466f1cd9
14391] {
14392hunk ./src/allmydata/mutable/publish.py 132
14393             kwargs["facility"] = "tahoe.mutable.publish"
14394         return log.msg(*args, **kwargs)
14395 
14396+
14397+    def update(self, data, offset, blockhashes):
14398+        """
14399+        I replace the contents of this file with the contents of data,
14400+        starting at offset. I return a Deferred that fires with None
14401+        when the replacement has been completed, or with an error if
14402+        something went wrong during the process.
14403+
14404+        Note that this process will not upload new shares. If the file
14405+        being updated is in need of repair, callers will have to repair
14406+        it on their own.
14407+        """
14408+        # How this works:
14409+        # 1: Make peer assignments. We'll assign each share that we know
14410+        # about on the grid to that peer that currently holds that
14411+        # share, and will not place any new shares.
14412+        # 2: Setup encoding parameters. Most of these will stay the same
14413+        # -- datalength will change, as will some of the offsets.
14414+        # 3. Upload the new segments.
14415+        # 4. Be done.
14416+        assert IMutableUploadable.providedBy(data)
14417+
14418+        self.data = data
14419+
14420+        # XXX: Use the MutableFileVersion instead.
14421+        self.datalength = self._node.get_size()
14422+        if offset + data.get_size() > self.datalength:
14423+            self.datalength = offset + data.get_size()
14424+
14425+        if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
14426+            self._version = MDMF_VERSION
14427+        else:
14428+            self._version = SDMF_VERSION
14429+
14430+        self.log("starting update")
14431+        self.log("adding new data of length %d at offset %d" % \
14432+                    data.get_size(), offset)
14433+        self.log("new data length is %d" % self.datalength)
14434+        self._status.set_size(self.datalength)
14435+        self._status.set_status("Started")
14436+        self._started = time.time()
14437+
14438+        self.done_deferred = defer.Deferred()
14439+
14440+        self._writekey = self._node.get_writekey()
14441+        assert self._writekey, "need write capability to publish"
14442+
14443+        # first, which servers will we publish to? We require that the
14444+        # servermap was updated in MODE_WRITE, so we can depend upon the
14445+        # peerlist computed by that process instead of computing our own.
14446+        assert self._servermap
14447+        assert self._servermap.last_update_mode in (MODE_WRITE, MODE_CHECK)
14448+        # we will push a version that is one larger than anything present
14449+        # in the grid, according to the servermap.
14450+        self._new_seqnum = self._servermap.highest_seqnum() + 1
14451+        self._status.set_servermap(self._servermap)
14452+
14453+        self.log(format="new seqnum will be %(seqnum)d",
14454+                 seqnum=self._new_seqnum, level=log.NOISY)
14455+
14456+        # We're updating an existing file, so all of the following
14457+        # should be available.
14458+        self.readkey = self._node.get_readkey()
14459+        self.required_shares = self._node.get_required_shares()
14460+        assert self.required_shares is not None
14461+        self.total_shares = self._node.get_total_shares()
14462+        assert self.total_shares is not None
14463+        self._status.set_encoding(self.required_shares, self.total_shares)
14464+
14465+        self._pubkey = self._node.get_pubkey()
14466+        assert self._pubkey
14467+        self._privkey = self._node.get_privkey()
14468+        assert self._privkey
14469+        self._encprivkey = self._node.get_encprivkey()
14470+
14471+        sb = self._storage_broker
14472+        full_peerlist = sb.get_servers_for_index(self._storage_index)
14473+        self.full_peerlist = full_peerlist # for use later, immutable
14474+        self.bad_peers = set() # peerids who have errbacked/refused requests
14475+
14476+        # This will set self.segment_size, self.num_segments, and
14477+        # self.fec. TODO: Does it know how to do the offset? Probably
14478+        # not. So do that part next.
14479+        self.setup_encoding_parameters(offset=offset)
14480+
14481+        # if we experience any surprises (writes which were rejected because
14482+        # our test vector did not match, or shares which we didn't expect to
14483+        # see), we set this flag and report an UncoordinatedWriteError at the
14484+        # end of the publish process.
14485+        self.surprised = False
14486+
14487+        # as a failsafe, refuse to iterate through self.loop more than a
14488+        # thousand times.
14489+        self.looplimit = 1000
14490+
14491+        # we keep track of three tables. The first is our goal: which share
14492+        # we want to see on which servers. This is initially populated by the
14493+        # existing servermap.
14494+        self.goal = set() # pairs of (peerid, shnum) tuples
14495+
14496+        # the second table is our list of outstanding queries: those which
14497+        # are in flight and may or may not be delivered, accepted, or
14498+        # acknowledged. Items are added to this table when the request is
14499+        # sent, and removed when the response returns (or errbacks).
14500+        self.outstanding = set() # (peerid, shnum) tuples
14501+
14502+        # the third is a table of successes: share which have actually been
14503+        # placed. These are populated when responses come back with success.
14504+        # When self.placed == self.goal, we're done.
14505+        self.placed = set() # (peerid, shnum) tuples
14506+
14507+        # we also keep a mapping from peerid to RemoteReference. Each time we
14508+        # pull a connection out of the full peerlist, we add it to this for
14509+        # use later.
14510+        self.connections = {}
14511+
14512+        self.bad_share_checkstrings = {}
14513+
14514+        # This is set at the last step of the publishing process.
14515+        self.versioninfo = ""
14516+
14517+        # we use the servermap to populate the initial goal: this way we will
14518+        # try to update each existing share in place. Since we're
14519+        # updating, we ignore damaged and missing shares -- callers must
14520+        # do a repair to repair and recreate these.
14521+        for (peerid, shnum) in self._servermap.servermap:
14522+            self.goal.add( (peerid, shnum) )
14523+            self.connections[peerid] = self._servermap.connections[peerid]
14524+        self.writers = {}
14525+        if self._version == MDMF_VERSION:
14526+            writer_class = MDMFSlotWriteProxy
14527+        else:
14528+            writer_class = SDMFSlotWriteProxy
14529+
14530+        # For each (peerid, shnum) in self.goal, we make a
14531+        # write proxy for that peer. We'll use this to write
14532+        # shares to the peer.
14533+        for key in self.goal:
14534+            peerid, shnum = key
14535+            write_enabler = self._node.get_write_enabler(peerid)
14536+            renew_secret = self._node.get_renewal_secret(peerid)
14537+            cancel_secret = self._node.get_cancel_secret(peerid)
14538+            secrets = (write_enabler, renew_secret, cancel_secret)
14539+
14540+            self.writers[shnum] =  writer_class(shnum,
14541+                                                self.connections[peerid],
14542+                                                self._storage_index,
14543+                                                secrets,
14544+                                                self._new_seqnum,
14545+                                                self.required_shares,
14546+                                                self.total_shares,
14547+                                                self.segment_size,
14548+                                                self.datalength)
14549+            self.writers[shnum].peerid = peerid
14550+            assert (peerid, shnum) in self._servermap.servermap
14551+            old_versionid, old_timestamp = self._servermap.servermap[key]
14552+            (old_seqnum, old_root_hash, old_salt, old_segsize,
14553+             old_datalength, old_k, old_N, old_prefix,
14554+             old_offsets_tuple) = old_versionid
14555+            self.writers[shnum].set_checkstring(old_seqnum,
14556+                                                old_root_hash,
14557+                                                old_salt)
14558+
14559+        # Our remote shares will not have a complete checkstring until
14560+        # after we are done writing share data and have started to write
14561+        # blocks. In the meantime, we need to know what to look for when
14562+        # writing, so that we can detect UncoordinatedWriteErrors.
14563+        self._checkstring = self.writers.values()[0].get_checkstring()
14564+
14565+        # Now, we start pushing shares.
14566+        self._status.timings["setup"] = time.time() - self._started
14567+        # First, we encrypt, encode, and publish the shares that we need
14568+        # to encrypt, encode, and publish.
14569+
14570+        # Our update process fetched these for us. We need to update
14571+        # them in place as publishing happens.
14572+        self.blockhashes = {} # (shnum, [blochashes])
14573+        for (i, bht) in blockhashes.iteritems():
14574+            self.blockhashes[i] = bht
14575+
14576+        # These are filled in later, after we've modified the block hash
14577+        # tree suitably.
14578+        self.sharehash_leaves = None # eventually [sharehashes]
14579+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
14580+                              # validate the share]
14581+
14582+        d = defer.succeed(None)
14583+        self.log("Starting push")
14584+
14585+        self._state = PUSHING_BLOCKS_STATE
14586+        self._push()
14587+
14588+        return self.done_deferred
14589+
14590+
14591     def publish(self, newdata):
14592         """Publish the filenode's current contents.  Returns a Deferred that
14593         fires (with None) when the publish has done as much work as it's ever
14594hunk ./src/allmydata/mutable/publish.py 502
14595         # that we publish. We define it this way so that empty publishes
14596         # will still have something to write to the remote slot.
14597         self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
14598+        for i in xrange(self.total_shares):
14599+            blocks = self.blockhashes[i]
14600+            for j in xrange(self.num_segments):
14601+                blocks.append(None)
14602         self.sharehash_leaves = None # eventually [sharehashes]
14603         self.sharehashes = {} # shnum -> [sharehash leaves necessary to
14604                               # validate the share]
14605hunk ./src/allmydata/mutable/publish.py 519
14606         return self.done_deferred
14607 
14608 
14609-    def setup_encoding_parameters(self):
14610+    def setup_encoding_parameters(self, offset=0):
14611         if self._version == MDMF_VERSION:
14612             segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
14613         else:
14614hunk ./src/allmydata/mutable/publish.py 528
14615         segment_size = mathutil.next_multiple(segment_size,
14616                                               self.required_shares)
14617         self.segment_size = segment_size
14618+
14619+        # Calculate the starting segment for the upload.
14620         if segment_size:
14621             self.num_segments = mathutil.div_ceil(self.datalength,
14622                                                   segment_size)
14623hunk ./src/allmydata/mutable/publish.py 533
14624+            self.starting_segment = mathutil.div_ceil(offset,
14625+                                                      segment_size)
14626+            if offset == 0:
14627+                self.starting_segment = 0
14628+
14629         else:
14630             self.num_segments = 0
14631hunk ./src/allmydata/mutable/publish.py 540
14632+            self.starting_segment = 0
14633+
14634 
14635         self.log("building encoding parameters for file")
14636         self.log("got segsize %d" % self.segment_size)
14637hunk ./src/allmydata/mutable/publish.py 576
14638                                 self.total_shares)
14639             self.tail_fec = tail_fec
14640 
14641-        self._current_segment = 0
14642+        self._current_segment = self.starting_segment
14643+        self.end_segment = self.num_segments - 1
14644+        # Now figure out where the last segment should be.
14645+        if self.data.get_size() != self.datalength:
14646+            end = offset + self.data.get_size()
14647+            self.end_segment = mathutil.div_ceil(end,
14648+                                                 segment_size)
14649+            self.end_segment -= 1
14650+        self.log("got start segment %d" % self.starting_segment)
14651+        self.log("got end segment %d" % self.end_segment)
14652 
14653 
14654     def _push(self, ignored=None):
14655hunk ./src/allmydata/mutable/publish.py 618
14656         if self.num_segments == 0 and self._version == SDMF_VERSION:
14657             self._add_dummy_salts()
14658 
14659-        if segnum == self.num_segments:
14660+        if segnum > self.end_segment:
14661             # We don't have any more segments to push.
14662             self._state = PUSHING_EVERYTHING_ELSE_STATE
14663             return self._push()
14664hunk ./src/allmydata/mutable/publish.py 715
14665             else:
14666                 hashed = sharedata
14667             block_hash = hashutil.block_hash(hashed)
14668-            self.blockhashes[shareid].append(block_hash)
14669+            self.blockhashes[shareid][segnum] = block_hash
14670 
14671             # find the writer for this share
14672             writer = self.writers[shareid]
14673hunk ./src/allmydata/mutable/publish.py 1227
14674         assert isinstance(s, str)
14675 
14676         MutableFileHandle.__init__(self, StringIO(s))
14677+
14678+
14679+class TransformingUploadable:
14680+    """
14681+    I am an IMutableUploadable that wraps another IMutableUploadable,
14682+    and some segments that are already on the grid. When I am called to
14683+    read, I handle merging of boundary segments.
14684+    """
14685+    implements(IMutableUploadable)
14686+
14687+
14688+    def __init__(self, data, offset, segment_size, start, end):
14689+        assert IMutableUploadable.providedBy(data)
14690+        # offset == data.get_size() means that we're appending.
14691+        assert offset <= data.get_size()
14692+
14693+        self._newdata = data
14694+        self._offset = offset
14695+        self._segment_size = segment_size
14696+        self._start = start
14697+        self._end = end
14698+
14699+        self._read_marker = 0
14700+        self._first_segment_offset = offset % segment_size
14701+
14702+
14703+    def get_size(self):
14704+        # TODO
14705+        pass
14706+
14707+
14708+    def read(self, length):
14709+        # We can be in three states here:
14710+        #   0. In the first segment of data. In this segment, we need to
14711+        #      return the original data until we get to the new data.
14712+        #      This is so that replacing data in the middle of an
14713+        #      existing segment works as expected.
14714+        #   1. After the first segment, before the last segment. In this
14715+        #      state, we delegate all reads to the underlying
14716+        #      IMutableUploadable.
14717+        #   2. Reading the last segment of data. If our replacement ends
14718+        #      in the middle of an existing segment, we need to pad the
14719+        #      replacement data with enough data from the end of segment
14720+        #      so that the replacement can happen.
14721+
14722+        # are we in state 0?
14723+        if self._read_marker < self._first_segment_offset:
14724+            # We need to read at least some data from the first segment
14725+            # to satisfy this read.
14726+            old_data_length = self._first_segment_offset - self._read_marker
14727+            if old_data_length > length:
14728+                old_data_length = length
14729+
14730+            new_data_length = length - old_data_length
14731+            old_data_end = old_data_length + self._read_marker
14732+            old_data = self._start[self._read_marker:old_data_end]
14733+            new_data = self._newdata.read(new_data_length)
14734+            new_data = "".join(new_data)
14735+            data = old_data + new_data
14736+
14737+        # are we in state 3?
14738+        elif self._read_marker + self._length > \
14739+                self._offset + self._newdata.get_size():
14740+            # We need to pad this read (for the last segment) with an
14741+            # appropriate amount of data from the old segment.
14742+            new_data_length = self._newdata.get_size() - self._read_marker
14743+            new_data = self._newdata.read(new_data_length)
14744+            new_data = "".join(data)
14745+            old_data_length = length - new_data_length
14746+            old_data_offset = new_data_length
14747+            old_data = self._end[old_data_offset:old_data_offset +
14748+                                 old_data_length]
14749+            data = new_data + old_data
14750+        else:
14751+            data = self._newdata.read(length)
14752+            data = "".join(data)
14753+
14754+        assert len(data) == length
14755+        self._read_marker += length
14756+        return data
14757+
14758+
14759+    def close(self):
14760+        pass
14761}
14762[mutable/retrieve.py: expose decoding and decrypting methods to callers
14763Kevan Carstensen <kevan@isnotajoke.com>**20100730234141
14764 Ignore-this: db66ee9019755f49388fe74049f26982
14765] hunk ./src/allmydata/mutable/retrieve.py 291
14766         return self._done_deferred
14767 
14768 
14769+    def decode(blocks_and_salts, segnum):
14770+        """
14771+        I am a helper method that the mutable file update process uses
14772+        as a shortcut to decode and decrypt the segments that it needs
14773+        to fetch in order to perform a file update. I take in a
14774+        collection of blocks and salts, and pick some of those to make a
14775+        segment with. I return the plaintext associated with that
14776+        segment.
14777+        """
14778+        self._setup_encoding_parameters()
14779+        d = self._decode_blocks(blocks_and_salts, segnum)
14780+        d.addCallback(self._decrypt_segment)
14781+        return d
14782+
14783+
14784     def _setup_encoding_parameters(self):
14785         """
14786         I set up the encoding parameters, including k, n, the number
14787[mutable/servermap.py: lay some groundwork for IWritable
14788Kevan Carstensen <kevan@isnotajoke.com>**20100730234229
14789 Ignore-this: c9ef2cace79db1e15937d030bcad20d9
14790] {
14791hunk ./src/allmydata/mutable/servermap.py 9
14792 from twisted.python import failure
14793 from foolscap.api import DeadReferenceError, RemoteException, eventually, \
14794                          fireEventually
14795-from allmydata.util import base32, hashutil, idlib, log
14796+from allmydata.util import base32, hashutil, idlib, log, deferredutil
14797 from allmydata.storage.server import si_b2a
14798 from allmydata.interfaces import IServermapUpdaterStatus
14799 from pycryptopp.publickey import rsa
14800hunk ./src/allmydata/mutable/servermap.py 124
14801         self.bad_shares = {} # maps (peerid,shnum) to old checkstring
14802         self.last_update_mode = None
14803         self.last_update_time = 0
14804+        self.update_data = {} # (verinfo,shnum) => data
14805 
14806     def copy(self):
14807         s = ServerMap()
14808hunk ./src/allmydata/mutable/servermap.py 340
14809         return False
14810 
14811 
14812+    def get_update_data_for_share_and_verinfo(self, shnum, verinfo):
14813+        """
14814+        I return the update data for the given shnum
14815+        """
14816+        update_data = self.update_data[shnum]
14817+        update_datum = [i[1] for i in update_data if i[0] == verinfo][0]
14818+        return update_datum
14819+
14820+
14821+    def set_update_data_for_share_and_verinfo(self, shnum, verinfo, data):
14822+        """
14823+        I record the block hash tree for the given shnum.
14824+        """
14825+        self.update_data.setdefault(shnum , []).append((verinfo, data))
14826+
14827+
14828 class ServermapUpdater:
14829     def __init__(self, filenode, storage_broker, monitor, servermap,
14830hunk ./src/allmydata/mutable/servermap.py 358
14831-                 mode=MODE_READ, add_lease=False):
14832+                 mode=MODE_READ, add_lease=False, update_range=None):
14833         """I update a servermap, locating a sufficient number of useful
14834         shares and remembering where they are located.
14835 
14836hunk ./src/allmydata/mutable/servermap.py 405
14837             # we use unpack_prefix_and_signature, so we need 1k
14838             self._read_size = 1000
14839         self._need_privkey = False
14840+
14841         if mode == MODE_WRITE and not self._node.get_privkey():
14842             self._need_privkey = True
14843         # check+repair: repair requires the privkey, so if we didn't happen
14844hunk ./src/allmydata/mutable/servermap.py 412
14845         # to ask for it during the check, we'll have problems doing the
14846         # publish.
14847 
14848+        self.fetch_update_data = False
14849+        if mode == MODE_WRITE and update_range:
14850+            # We're updating the servermap in preparation for an
14851+            # in-place file update, so we need to fetch some additional
14852+            # data from each share that we find.
14853+            assert len(update_range) == 2
14854+
14855+            self.start_segment = update_range[0]
14856+            self.end_segment = update_range[1]
14857+            self.fetch_update_data = True
14858+
14859         prefix = si_b2a(self._storage_index)[:5]
14860         self._log_number = log.msg(format="SharemapUpdater(%(si)s): starting (%(mode)s)",
14861                                    si=prefix, mode=mode)
14862hunk ./src/allmydata/mutable/servermap.py 643
14863                       level=log.NOISY)
14864         now = time.time()
14865         elapsed = now - started
14866-        self._queries_outstanding.discard(peerid)
14867-        self._servermap.reachable_peers.add(peerid)
14868-        self._must_query.discard(peerid)
14869-        self._queries_completed += 1
14870+        def _done_processing(ignored=None):
14871+            self._queries_outstanding.discard(peerid)
14872+            self._servermap.reachable_peers.add(peerid)
14873+            self._must_query.discard(peerid)
14874+            self._queries_completed += 1
14875         if not self._running:
14876             self.log("but we're not running, so we'll ignore it", parent=lp,
14877                      level=log.NOISY)
14878hunk ./src/allmydata/mutable/servermap.py 651
14879+            _done_processing()
14880             self._status.add_per_server_time(peerid, "late", started, elapsed)
14881             return
14882         self._status.add_per_server_time(peerid, "query", started, elapsed)
14883hunk ./src/allmydata/mutable/servermap.py 679
14884             #     public key. We use this to validate the signature.
14885             if not self._node.get_pubkey():
14886                 # fetch and set the public key.
14887-                d = reader.get_verification_key()
14888+                d = reader.get_verification_key(queue=True)
14889                 d.addCallback(lambda results, shnum=shnum, peerid=peerid:
14890                     self._try_to_set_pubkey(results, peerid, shnum, lp))
14891                 # XXX: Make self._pubkey_query_failed?
14892hunk ./src/allmydata/mutable/servermap.py 688
14893             else:
14894                 # we already have the public key.
14895                 d = defer.succeed(None)
14896+
14897             # Neither of these two branches return anything of
14898             # consequence, so the first entry in our deferredlist will
14899             # be None.
14900hunk ./src/allmydata/mutable/servermap.py 705
14901             #   to get the version information. In MDMF, this lives at
14902             #   the end of the share, so unless the file is quite small,
14903             #   we'll need to do a remote fetch to get it.
14904-            d3 = reader.get_signature()
14905+            d3 = reader.get_signature(queue=True)
14906             d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
14907                 self._got_corrupt_share(error, shnum, peerid, data, lp))
14908             #  Once we have all three of these responses, we can move on
14909hunk ./src/allmydata/mutable/servermap.py 714
14910             # Does the node already have a privkey? If not, we'll try to
14911             # fetch it here.
14912             if self._need_privkey:
14913-                d4 = reader.get_encprivkey()
14914+                d4 = reader.get_encprivkey(queue=True)
14915                 d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
14916                     self._try_to_validate_privkey(results, peerid, shnum, lp))
14917                 d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
14918hunk ./src/allmydata/mutable/servermap.py 722
14919             else:
14920                 d4 = defer.succeed(None)
14921 
14922-            dl = defer.DeferredList([d, d2, d3, d4])
14923+
14924+            if self.fetch_update_data:
14925+                # fetch the block hash tree and first + last segment, as
14926+                # configured earlier.
14927+                # Then set them in wherever we happen to want to set
14928+                # them.
14929+                ds = []
14930+                # XXX: We do this above, too. Is there a good way to
14931+                # make the two routines share the value without
14932+                # introducing more roundtrips?
14933+                ds.append(reader.get_verinfo())
14934+                ds.append(reader.get_blockhashes(queue=True))
14935+                ds.append(reader.get_block_and_salt(self.start_segment,
14936+                                                    queue=True))
14937+                ds.append(reader.get_block_and_salt(self.end_segment,
14938+                                                    queue=True))
14939+                d5 = deferredutil.gatherResults(ds)
14940+                d5.addCallback(self._got_update_results_one_share, shnum)
14941+            else:
14942+                d5 = defer.succeed(None)
14943+
14944+            dl = defer.DeferredList([d, d2, d3, d4, d5])
14945+            reader.flush()
14946             dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
14947                 self._got_signature_one_share(results, shnum, peerid, lp))
14948             dl.addErrback(lambda error, shnum=shnum, data=data:
14949hunk ./src/allmydata/mutable/servermap.py 764
14950         # that we returned to our caller to fire, which tells them that
14951         # they have a complete servermap, and that we won't be touching
14952         # the servermap anymore.
14953+        dl.addCallback(_done_processing)
14954         dl.addCallback(self._check_for_done)
14955         dl.addErrback(self._fatal_error)
14956         # all done!
14957hunk ./src/allmydata/mutable/servermap.py 799
14958                  peerid=idlib.shortnodeid_b2a(peerid),
14959                  level=log.NOISY,
14960                  parent=lp)
14961-        _, verinfo, signature, __ = results
14962+        _, verinfo, signature, __, ___ = results
14963         (seqnum,
14964          root_hash,
14965          saltish,
14966hunk ./src/allmydata/mutable/servermap.py 864
14967         return verinfo
14968 
14969 
14970+    def _got_update_results_one_share(self, results, share):
14971+        """
14972+        I record the update results in results.
14973+        """
14974+        assert len(results) == 4
14975+        verinfo, blockhashes, start, end = results
14976+        update_data = (blockhashes, start, end)
14977+        self._servermap.set_update_data_for_share_and_verinfo(share,
14978+                                                              verinfo,
14979+                                                              update_data)
14980+
14981+
14982     def _deserialize_pubkey(self, pubkey_s):
14983         verifier = rsa.create_verifying_key_from_string(pubkey_s)
14984         return verifier
14985}
14986[mutable: fix bugs that prevented the update tests from working
14987Kevan Carstensen <kevan@isnotajoke.com>**20100802224821
14988 Ignore-this: e87a1c81f7ecf9643248554a820dc62d
14989] {
14990hunk ./src/allmydata/mutable/filenode.py 10
14991 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
14992      NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
14993      IMutableFileVersion, IWritable
14994-from allmydata.util import hashutil, log, consumer, deferredutil
14995+from allmydata.util import hashutil, log, consumer, deferredutil, mathutil
14996 from allmydata.util.assertutil import precondition
14997 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
14998 from allmydata.monitor import Monitor
14999hunk ./src/allmydata/mutable/filenode.py 961
15000         self._size = size
15001         return res
15002 
15003-    def _update(self, data, offset):
15004+    def update(self, data, offset):
15005         """
15006         Do an update of this mutable file version by inserting data at
15007         offset within the file. If offset is the EOF, this is an append
15008hunk ./src/allmydata/mutable/filenode.py 986
15009         completed.
15010         """
15011         d = self._do_update_update(data, offset)
15012-        d.addCallback(self._decode_and_decrypt_segments)
15013-        d.addCallback(self._build_uploadable_and_finish)
15014+        d.addCallback(self._decode_and_decrypt_segments, data, offset)
15015+        d.addCallback(self._build_uploadable_and_finish, data, offset)
15016         return d
15017 
15018 
15019hunk ./src/allmydata/mutable/filenode.py 1016
15020             end_data = offset + data.get_size()
15021             end_segment = mathutil.div_ceil(end_data, DEFAULT_MAX_SEGMENT_SIZE)
15022             end_segment -= 1
15023+        self._start_segment = start_segment
15024+        self._end_segment = end_segment
15025 
15026         # Now ask for the servermap to be updated in MODE_WRITE with
15027         # this update range.
15028hunk ./src/allmydata/mutable/filenode.py 1021
15029-        u = ServermapUpdater(self, self._storage_broker, Monitor(),
15030+        u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
15031                              self._servermap,
15032                              mode=MODE_WRITE,
15033                              update_range=(start_segment, end_segment))
15034hunk ./src/allmydata/mutable/filenode.py 1036
15035         used by the new uploadable. I return a Deferred that fires with
15036         the segments.
15037         """
15038-        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
15039+        r = Retrieve(self._node, self._servermap, self._version)
15040         # decode: takes in our blocks and salts from the servermap,
15041         # returns a Deferred that fires with the corresponding plaintext
15042         # segments. Does not download -- simply takes advantage of
15043hunk ./src/allmydata/mutable/filenode.py 1046
15044         # XXX: If the methods in the servermap don't work as
15045         # abstractions, you should rewrite them instead of going around
15046         # them.
15047-        data = sm.update_data
15048-        data = [data[1] for i in update_data if i[0] == self._verinfo]
15049-        start_seg_data = [d[0] for d in data]
15050-        end_seg_data = [d[1] for d in data]
15051-        d1 = r.decode(start_seg_data)
15052-        d2 = r.decode(end_seg_data)
15053-        return deferredutil.gatherResults([d1, d2])
15054+        update_data = sm.update_data
15055+        start_segments = {} # shnum -> start segment
15056+        end_segments = {} # shnum -> end segment
15057+        blockhashes = {} # shnum -> blockhash tree
15058+        for (shnum, data) in update_data.iteritems():
15059+            data = [d[1] for d in data if d[0] == self._version]
15060+
15061+            # Every data entry in our list should now be share shnum for
15062+            # a particular version of the mutable file, so all of the
15063+            # entries should be identical.
15064+            datum = data[0]
15065+            assert filter(lambda x: x != datum, data) == []
15066+
15067+            blockhashes[shnum] = datum[0]
15068+            start_segments[shnum] = datum[1]
15069+            end_segments[shnum] = datum[2]
15070+
15071+        d1 = r.decode(start_segments, self._start_segment)
15072+        d2 = r.decode(end_segments, self._end_segment)
15073+        d3 = defer.succeed(blockhashes)
15074+        return deferredutil.gatherResults([d1, d2, d3])
15075 
15076 
15077hunk ./src/allmydata/mutable/filenode.py 1069
15078-    def _build_uploadable_and_finish(self, segments, data, offset):
15079+    def _build_uploadable_and_finish(self, segments_and_bht, data, offset):
15080         """
15081         After the process has the plaintext segments, I build the
15082         TransformingUploadable that the publisher will eventually
15083hunk ./src/allmydata/mutable/filenode.py 1079
15084         """
15085         u = TransformingUploadable(data, offset,
15086                                    DEFAULT_MAX_SEGMENT_SIZE,
15087-                                   segments[0],
15088-                                   segments[1])
15089+                                   segments_and_bht[0],
15090+                                   segments_and_bht[1])
15091         p = Publish(self._node, self._storage_broker, self._servermap)
15092hunk ./src/allmydata/mutable/filenode.py 1082
15093-        return p.update(data, offset, blockhashes)
15094+        return p.update(data, offset, segments_and_bht[2])
15095hunk ./src/allmydata/mutable/publish.py 168
15096 
15097         self.log("starting update")
15098         self.log("adding new data of length %d at offset %d" % \
15099-                    data.get_size(), offset)
15100+                    (data.get_size(), offset))
15101         self.log("new data length is %d" % self.datalength)
15102         self._status.set_size(self.datalength)
15103         self._status.set_status("Started")
15104hunk ./src/allmydata/mutable/publish.py 1254
15105 
15106 
15107     def get_size(self):
15108-        # TODO
15109-        pass
15110+        return self._offset + self._newdata.get_size()
15111 
15112 
15113     def read(self, length):
15114hunk ./src/allmydata/mutable/retrieve.py 145
15115         self.readers = {}
15116         self._paused = False
15117         self._paused_deferred = None
15118+        self._offset = None
15119+        self._read_length = None
15120 
15121 
15122     def get_status(self):
15123hunk ./src/allmydata/mutable/retrieve.py 293
15124         return self._done_deferred
15125 
15126 
15127-    def decode(blocks_and_salts, segnum):
15128+    def decode(self, blocks_and_salts, segnum):
15129         """
15130         I am a helper method that the mutable file update process uses
15131         as a shortcut to decode and decrypt the segments that it needs
15132hunk ./src/allmydata/mutable/retrieve.py 302
15133         segment with. I return the plaintext associated with that
15134         segment.
15135         """
15136+        # shnum => block hash tree. Unusued, but setup_encoding_parameters will
15137+        # want to set this.
15138+        # XXX: Make it so that it won't set this if we're just decoding.
15139+        self._block_hash_trees = {}
15140         self._setup_encoding_parameters()
15141hunk ./src/allmydata/mutable/retrieve.py 307
15142+        # This is the form expected by decode.
15143+        blocks_and_salts = blocks_and_salts.items()
15144+        blocks_and_salts = [(True, [d]) for d in blocks_and_salts]
15145+
15146         d = self._decode_blocks(blocks_and_salts, segnum)
15147         d.addCallback(self._decrypt_segment)
15148         return d
15149hunk ./src/allmydata/mutable/servermap.py 870
15150         """
15151         assert len(results) == 4
15152         verinfo, blockhashes, start, end = results
15153+        (seqnum,
15154+         root_hash,
15155+         saltish,
15156+         segsize,
15157+         datalen,
15158+         k,
15159+         n,
15160+         prefix,
15161+         offsets) = verinfo
15162+        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
15163+
15164+        # XXX: This should be done for us in the method, so
15165+        # presumably you can go in there and fix it.
15166+        verinfo = (seqnum,
15167+                   root_hash,
15168+                   saltish,
15169+                   segsize,
15170+                   datalen,
15171+                   k,
15172+                   n,
15173+                   prefix,
15174+                   offsets_tuple)
15175+
15176         update_data = (blockhashes, start, end)
15177         self._servermap.set_update_data_for_share_and_verinfo(share,
15178                                                               verinfo,
15179}
15180[mutable: fix some bugs in update logic.
15181Kevan Carstensen <kevan@isnotajoke.com>**20100806000338
15182 Ignore-this: 6fd3040efd6de8b2b80e7bb9f90f8003
15183] {
15184hunk ./src/allmydata/mutable/filenode.py 10
15185 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
15186      NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
15187      IMutableFileVersion, IWritable
15188+from allmydata import hashtree
15189 from allmydata.util import hashutil, log, consumer, deferredutil, mathutil
15190 from allmydata.util.assertutil import precondition
15191 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
15192hunk ./src/allmydata/mutable/filenode.py 986
15193         offset. I return a Deferred that fires when this has been
15194         completed.
15195         """
15196+        # We have two cases here:
15197+        # 1. The new data will add few enough segments so that it does
15198+        #    not cross into the next power-of-two boundary.
15199+        # 2. It doesn't.
15200+        #
15201+        # In the former case, we can modify the file in place. In the
15202+        # latter case, we need to re-encode the file.
15203+        new_size = data.get_size() + offset
15204+        old_size = self.get_size()
15205+        segment_size = self._version[3]
15206+        num_old_segments = mathutil.div_ceil(old_size,
15207+                                             segment_size)
15208+        num_new_segments = mathutil.div_ceil(new_size,
15209+                                             segment_size)
15210+        log.msg("got %d old segments, %d new segments" % \
15211+                        (num_old_segments, num_new_segments))
15212+
15213+        # We also do a whole file re-encode if the file is an SDMF file.
15214+        if self._version[2]: # version[2] == SDMF salt, which MDMF lacks
15215+            log.msg("doing re-encode instead of in-place update")
15216+            return self._do_modify_update(data, offset)
15217+
15218+        log.msg("updating in place")
15219         d = self._do_update_update(data, offset)
15220         d.addCallback(self._decode_and_decrypt_segments, data, offset)
15221         d.addCallback(self._build_uploadable_and_finish, data, offset)
15222hunk ./src/allmydata/mutable/filenode.py 1015
15223         return d
15224 
15225 
15226+    def _do_modify_update(self, data, offset):
15227+        """
15228+        I perform a file update by modifying the contents of the file
15229+        after downloading it, then reuploading it. I am less efficient
15230+        than _do_update_update, but am necessary for certain updates.
15231+        """
15232+        def m(old, servermap, first_time):
15233+            start = offset
15234+            rest = offset + data.get_size()
15235+            new = old[:start]
15236+            new += "".join(data.read(data.get_size()))
15237+            new += old[rest:]
15238+            return MutableData(new)
15239+        return self._modify(m, None)
15240+
15241+
15242     def _do_update_update(self, data, offset):
15243         """
15244         I start the Servermap update that gets us the data we need to
15245hunk ./src/allmydata/mutable/filenode.py 1118
15246         completed without issue.
15247         """
15248         u = TransformingUploadable(data, offset,
15249-                                   DEFAULT_MAX_SEGMENT_SIZE,
15250+                                   self._version[3],
15251                                    segments_and_bht[0],
15252                                    segments_and_bht[1])
15253         p = Publish(self._node, self._storage_broker, self._servermap)
15254hunk ./src/allmydata/mutable/filenode.py 1122
15255-        return p.update(data, offset, segments_and_bht[2])
15256+        return p.update(u, offset, segments_and_bht[2], self._version)
15257hunk ./src/allmydata/mutable/publish.py 133
15258         return log.msg(*args, **kwargs)
15259 
15260 
15261-    def update(self, data, offset, blockhashes):
15262+    def update(self, data, offset, blockhashes, version):
15263         """
15264         I replace the contents of this file with the contents of data,
15265         starting at offset. I return a Deferred that fires with None
15266hunk ./src/allmydata/mutable/publish.py 158
15267 
15268         # XXX: Use the MutableFileVersion instead.
15269         self.datalength = self._node.get_size()
15270-        if offset + data.get_size() > self.datalength:
15271-            self.datalength = offset + data.get_size()
15272+        if data.get_size() > self.datalength:
15273+            self.datalength = data.get_size()
15274 
15275         if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
15276             self._version = MDMF_VERSION
15277hunk ./src/allmydata/mutable/publish.py 310
15278         # them in place as publishing happens.
15279         self.blockhashes = {} # (shnum, [blochashes])
15280         for (i, bht) in blockhashes.iteritems():
15281-            self.blockhashes[i] = bht
15282+            # We need to extract the leaves from our old hash tree.
15283+            old_segcount = mathutil.div_ceil(version[4],
15284+                                             version[3])
15285+            h = hashtree.IncompleteHashTree(old_segcount)
15286+            bht = dict(enumerate(bht))
15287+            h.set_hashes(bht)
15288+            leaves = h[h.get_leaf_index(0):]
15289+            for j in xrange(self.num_segments - len(leaves)):
15290+                leaves.append(None)
15291+
15292+            assert len(leaves) >= self.num_segments
15293+            self.blockhashes[i] = leaves
15294+            # This list will now be the leaves that were set during the
15295+            # initial upload + enough empty hashes to make it a
15296+            # power-of-two. If we exceed a power of two boundary, we
15297+            # should be encoding the file over again, and should not be
15298+            # here. So, we have
15299+            #assert len(self.blockhashes[i]) == \
15300+            #    hashtree.roundup_pow2(self.num_segments), \
15301+            #        len(self.blockhashes[i])
15302+            # XXX: Except this doesn't work. Figure out why.
15303 
15304         # These are filled in later, after we've modified the block hash
15305         # tree suitably.
15306hunk ./src/allmydata/mutable/publish.py 555
15307                                                   segment_size)
15308             self.starting_segment = mathutil.div_ceil(offset,
15309                                                       segment_size)
15310+            self.starting_segment -= 1
15311             if offset == 0:
15312                 self.starting_segment = 0
15313 
15314hunk ./src/allmydata/mutable/publish.py 574
15315 
15316         if segment_size and self.datalength:
15317             self.tail_segment_size = self.datalength % segment_size
15318+            self.log("got tail segment size %d" % self.tail_segment_size)
15319         else:
15320             self.tail_segment_size = 0
15321 
15322hunk ./src/allmydata/mutable/publish.py 602
15323         self.end_segment = self.num_segments - 1
15324         # Now figure out where the last segment should be.
15325         if self.data.get_size() != self.datalength:
15326-            end = offset + self.data.get_size()
15327+            end = self.data.get_size()
15328             self.end_segment = mathutil.div_ceil(end,
15329                                                  segment_size)
15330             self.end_segment -= 1
15331hunk ./src/allmydata/mutable/publish.py 685
15332         # XXX: This is dumb. Why return a list?
15333         data = "".join(data)
15334 
15335-        assert len(data) == segsize
15336+        assert len(data) == segsize, len(data)
15337 
15338         salt = os.urandom(16)
15339 
15340hunk ./src/allmydata/mutable/publish.py 737
15341             else:
15342                 hashed = sharedata
15343             block_hash = hashutil.block_hash(hashed)
15344+            old_hash = self.blockhashes[shareid][segnum]
15345             self.blockhashes[shareid][segnum] = block_hash
15346hunk ./src/allmydata/mutable/publish.py 739
15347-
15348             # find the writer for this share
15349             writer = self.writers[shareid]
15350             writer.put_block(sharedata, segnum, salt)
15351hunk ./src/allmydata/mutable/publish.py 776
15352             self.blockhashes[shnum] = list(t)
15353             # set the leaf for future use.
15354             self.sharehash_leaves[shnum] = t[0]
15355+
15356             writer = self.writers[shnum]
15357             writer.put_blockhashes(self.blockhashes[shnum])
15358 
15359hunk ./src/allmydata/mutable/publish.py 1200
15360         # reported to the uploader.
15361         self._filehandle.seek(0)
15362 
15363+        # We have not yet read anything, so our position is 0.
15364+        self._marker = 0
15365+
15366 
15367     def get_size(self):
15368         """
15369hunk ./src/allmydata/mutable/publish.py 1223
15370         return self._size
15371 
15372 
15373+    def pos(self):
15374+        """
15375+        I return the position of my read marker -- i.e., how much data I
15376+        have already read and returned to callers.
15377+        """
15378+        return self._marker
15379+
15380+
15381     def read(self, length):
15382         """
15383         I return some data (up to length bytes) from my filehandle.
15384hunk ./src/allmydata/mutable/publish.py 1239
15385         for example, if I am asked to read beyond the end of a file, or
15386         an error occurs.
15387         """
15388-        return [self._filehandle.read(length)]
15389+        results = self._filehandle.read(length)
15390+        self._marker += len(results)
15391+        return [results]
15392 
15393 
15394     def close(self):
15395hunk ./src/allmydata/mutable/publish.py 1276
15396 
15397     def __init__(self, data, offset, segment_size, start, end):
15398         assert IMutableUploadable.providedBy(data)
15399-        # offset == data.get_size() means that we're appending.
15400-        assert offset <= data.get_size()
15401 
15402         self._newdata = data
15403         self._offset = offset
15404hunk ./src/allmydata/mutable/publish.py 1284
15405         self._end = end
15406 
15407         self._read_marker = 0
15408+
15409         self._first_segment_offset = offset % segment_size
15410 
15411hunk ./src/allmydata/mutable/publish.py 1287
15412+        num = self.log("TransformingUploadable: starting", parent=None)
15413+        self._log_number = num
15414+        self.log("got fso: %d" % self._first_segment_offset)
15415+        self.log("got offset: %d" % self._offset)
15416+
15417+
15418+    def log(self, *args, **kwargs):
15419+        if 'parent' not in kwargs:
15420+            kwargs['parent'] = self._log_number
15421+        if "facility" not in kwargs:
15422+            kwargs["facility"] = "tahoe.mutable.transforminguploadable"
15423+        return log.msg(*args, **kwargs)
15424+
15425 
15426     def get_size(self):
15427         return self._offset + self._newdata.get_size()
15428hunk ./src/allmydata/mutable/publish.py 1306
15429 
15430 
15431     def read(self, length):
15432-        # We can be in three states here:
15433-        #   0. In the first segment of data. In this segment, we need to
15434-        #      return the original data until we get to the new data.
15435-        #      This is so that replacing data in the middle of an
15436-        #      existing segment works as expected.
15437-        #   1. After the first segment, before the last segment. In this
15438-        #      state, we delegate all reads to the underlying
15439-        #      IMutableUploadable.
15440-        #   2. Reading the last segment of data. If our replacement ends
15441-        #      in the middle of an existing segment, we need to pad the
15442-        #      replacement data with enough data from the end of segment
15443-        #      so that the replacement can happen.
15444+        # We can get data from 3 sources here.
15445+        #   1. The first of the segments provided to us.
15446+        #   2. The data that we're replacing things with.
15447+        #   3. The last of the segments provided to us.
15448 
15449         # are we in state 0?
15450hunk ./src/allmydata/mutable/publish.py 1312
15451-        if self._read_marker < self._first_segment_offset:
15452-            # We need to read at least some data from the first segment
15453-            # to satisfy this read.
15454-            old_data_length = self._first_segment_offset - self._read_marker
15455+        self.log("reading %d bytes" % length)
15456+
15457+        old_start_data = ""
15458+        old_data_length = self._first_segment_offset - self._read_marker
15459+        if old_data_length > 0:
15460             if old_data_length > length:
15461                 old_data_length = length
15462hunk ./src/allmydata/mutable/publish.py 1319
15463+            self.log("returning %d bytes of old start data" % old_data_length)
15464 
15465hunk ./src/allmydata/mutable/publish.py 1321
15466-            new_data_length = length - old_data_length
15467             old_data_end = old_data_length + self._read_marker
15468hunk ./src/allmydata/mutable/publish.py 1322
15469-            old_data = self._start[self._read_marker:old_data_end]
15470-            new_data = self._newdata.read(new_data_length)
15471-            new_data = "".join(new_data)
15472-            data = old_data + new_data
15473+            old_start_data = self._start[self._read_marker:old_data_end]
15474+            length -= old_data_length
15475+        else:
15476+            # otherwise calculations later get screwed up.
15477+            old_data_length = 0
15478 
15479hunk ./src/allmydata/mutable/publish.py 1328
15480-        # are we in state 3?
15481-        elif self._read_marker + self._length > \
15482-                self._offset + self._newdata.get_size():
15483-            # We need to pad this read (for the last segment) with an
15484-            # appropriate amount of data from the old segment.
15485-            new_data_length = self._newdata.get_size() - self._read_marker
15486-            new_data = self._newdata.read(new_data_length)
15487-            new_data = "".join(data)
15488-            old_data_length = length - new_data_length
15489-            old_data_offset = new_data_length
15490-            old_data = self._end[old_data_offset:old_data_offset +
15491-                                 old_data_length]
15492-            data = new_data + old_data
15493-        else:
15494-            data = self._newdata.read(length)
15495-            data = "".join(data)
15496+        # Is there enough new data to satisfy this read? If not, we need
15497+        # to pad the end of the data with data from our last segment.
15498+        old_end_length = length - \
15499+            (self._newdata.get_size() - self._newdata.pos())
15500+        old_end_data = ""
15501+        if old_end_length > 0:
15502+            self.log("reading %d bytes of old end data" % old_end_length)
15503+
15504+            # TODO: We're not explicitly checking for tail segment size
15505+            # here. Is that a problem?
15506+            old_data_offset = (length - old_end_length + \
15507+                               old_data_length) % self._segment_size
15508+            self.log("reading at offset %d" % old_data_offset)
15509+            old_end = old_data_offset + old_end_length
15510+            old_end_data = self._end[old_data_offset:old_end]
15511+            length -= old_end_length
15512+            assert length == self._newdata.get_size() - self._newdata.pos()
15513+
15514+        self.log("reading %d bytes of new data" % length)
15515+        new_data = self._newdata.read(length)
15516+        new_data = "".join(new_data)
15517 
15518hunk ./src/allmydata/mutable/publish.py 1350
15519-        assert len(data) == length
15520-        self._read_marker += length
15521-        return data
15522+        self._read_marker += len(old_start_data + new_data + old_end_data)
15523 
15524hunk ./src/allmydata/mutable/publish.py 1352
15525+        return old_start_data + new_data + old_end_data
15526 
15527     def close(self):
15528         pass
15529}
15530[test/test_mutable.py: add tests for updating behavior
15531Kevan Carstensen <kevan@isnotajoke.com>**20100806000408
15532 Ignore-this: d7860e9b44744f95ea20a706b921ec54
15533] {
15534hunk ./src/allmydata/test/test_mutable.py 8
15535 from twisted.internet import defer, reactor
15536 from allmydata import uri, client
15537 from allmydata.nodemaker import NodeMaker
15538-from allmydata.util import base32, consumer
15539+from allmydata.util import base32, consumer, mathutil
15540 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
15541      ssk_pubkey_fingerprint_hash
15542 from allmydata.util.deferredutil import gatherResults
15543hunk ./src/allmydata/test/test_mutable.py 28
15544      NotEnoughServersError, CorruptShareError
15545 from allmydata.mutable.retrieve import Retrieve
15546 from allmydata.mutable.publish import Publish, MutableFileHandle, \
15547-                                      MutableData
15548+                                      MutableData, \
15549+                                      DEFAULT_MAX_SEGMENT_SIZE
15550 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
15551 from allmydata.mutable.layout import unpack_header, unpack_share, \
15552                                      MDMFSlotReadProxy
15553hunk ./src/allmydata/test/test_mutable.py 2868
15554         d.addCallback(lambda data:
15555             self.failUnlessEqual(data, self.small_data))
15556         return d
15557+
15558+
15559+class Update(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
15560+    def setUp(self):
15561+        GridTestMixin.setUp(self)
15562+        self.basedir = self.mktemp()
15563+        self.set_up_grid()
15564+        self.c = self.g.clients[0]
15565+        self.nm = self.c.nodemaker
15566+        self.data = "test data" * 100000 # about 900 KiB; MDMF
15567+        self.small_data = "test data" * 10 # about 90 B; SDMF
15568+        return self.do_upload()
15569+
15570+
15571+    def do_upload(self):
15572+        d1 = self.nm.create_mutable_file(MutableData(self.data),
15573+                                         version=MDMF_VERSION)
15574+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
15575+        dl = gatherResults([d1, d2])
15576+        def _then((n1, n2)):
15577+            assert isinstance(n1, MutableFileNode)
15578+            assert isinstance(n2, MutableFileNode)
15579+
15580+            self.mdmf_node = n1
15581+            self.sdmf_node = n2
15582+        dl.addCallback(_then)
15583+        return dl
15584+
15585+
15586+    def test_append(self):
15587+        # We should be able to append data to the middle of a mutable
15588+        # file and get what we expect.
15589+        new_data = self.data + "appended"
15590+        d = self.mdmf_node.get_best_mutable_version()
15591+        d.addCallback(lambda mv:
15592+            mv.update(MutableData("appended"), len(self.data)))
15593+        d.addCallback(lambda ignored:
15594+            self.mdmf_node.download_best_version())
15595+        d.addCallback(lambda results:
15596+            self.failUnlessEqual(results, new_data))
15597+        return d
15598+
15599+
15600+    def test_replace(self):
15601+        # We should be able to replace data in the middle of a mutable
15602+        # file and get what we expect back.
15603+        new_data = self.data[:100]
15604+        new_data += "appended"
15605+        new_data += self.data[108:]
15606+        d = self.mdmf_node.get_best_mutable_version()
15607+        d.addCallback(lambda mv:
15608+            mv.update(MutableData("appended"), 100))
15609+        d.addCallback(lambda ignored:
15610+            self.mdmf_node.download_best_version())
15611+        d.addCallback(lambda results:
15612+            self.failUnlessEqual(results, new_data))
15613+        return d
15614+
15615+
15616+    def test_replace_and_extend(self):
15617+        # We should be able to replace data in the middle of a mutable
15618+        # file and extend that mutable file and get what we expect.
15619+        new_data = self.data[:100]
15620+        new_data += "modified " * 100000
15621+        d = self.mdmf_node.get_best_mutable_version()
15622+        d.addCallback(lambda mv:
15623+            mv.update(MutableData("modified " * 100000), 100))
15624+        d.addCallback(lambda ignored:
15625+            self.mdmf_node.download_best_version())
15626+        d.addCallback(lambda results:
15627+            self.failUnlessEqual(results, new_data))
15628+        return d
15629+
15630+
15631+    def test_append_power_of_two(self):
15632+        # If we attempt to extend a mutable file so that its segment
15633+        # count crosses a power-of-two boundary, the update operation
15634+        # should know how to reencode the file.
15635+
15636+        # Note that the data populating self.mdmf_node is about 900 KiB
15637+        # long -- this is 7 segments in the default segment size. So we
15638+        # need to add 2 segments worth of data to push it over a
15639+        # power-of-two boundary.
15640+        segment = "a" * DEFAULT_MAX_SEGMENT_SIZE
15641+        new_data = self.data + (segment * 2)
15642+        d = self.mdmf_node.get_best_mutable_version()
15643+        d.addCallback(lambda mv:
15644+            mv.update(MutableData(segment * 2), len(self.data)))
15645+        d.addCallback(lambda ignored:
15646+            self.mdmf_node.download_best_version())
15647+        d.addCallback(lambda results:
15648+            self.failUnlessEqual(results, new_data))
15649+        return d
15650+
15651+
15652+    def test_update_sdmf(self):
15653+        # Running update on a single-segment file should still work.
15654+        new_data = self.small_data + "appended"
15655+        d = self.sdmf_node.get_best_mutable_version()
15656+        d.addCallback(lambda mv:
15657+            mv.update(MutableData("appended"), len(self.small_data)))
15658+        d.addCallback(lambda ignored:
15659+            self.sdmf_node.download_best_version())
15660+        d.addCallback(lambda results:
15661+            self.failUnlessEqual(results, new_data))
15662+        return d
15663+
15664+    def test_replace_in_last_segment(self):
15665+        # The wrapper should know how to handle the tail segment
15666+        # appropriately.
15667+        replace_offset = len(self.data) - 100
15668+        new_data = self.data[:replace_offset] + "replaced"
15669+        rest_offset = replace_offset + len("replaced")
15670+        new_data += self.data[rest_offset:]
15671+        d = self.mdmf_node.get_best_mutable_version()
15672+        d.addCallback(lambda mv:
15673+            mv.update(MutableData("replaced"), replace_offset))
15674+        d.addCallback(lambda ignored:
15675+            self.mdmf_node.download_best_version())
15676+        d.addCallback(lambda results:
15677+            self.failUnlessEqual(results, new_data))
15678+        return d
15679+
15680+
15681+    def test_multiple_segment_replace(self):
15682+        replace_offset = 2 * DEFAULT_MAX_SEGMENT_SIZE
15683+        new_data = self.data[:replace_offset]
15684+        new_segment = "a" * DEFAULT_MAX_SEGMENT_SIZE
15685+        new_data += 2 * new_segment
15686+        new_data += "replaced"
15687+        rest_offset = len(new_data)
15688+        new_data += self.data[rest_offset:]
15689+        d = self.mdmf_node.get_best_mutable_version()
15690+        d.addCallback(lambda mv:
15691+            mv.update(MutableData((2 * new_segment) + "replaced"),
15692+                      replace_offset))
15693+        d.addCallback(lambda ignored:
15694+            self.mdmf_node.download_best_version())
15695+        d.addCallback(lambda results:
15696+            self.failUnlessEqual(results, new_data))
15697+        return d
15698}
15699
15700Context:
15701
15702[misc/build_helpers/run-with-pythonpath.py: fix stale comment, and remove 'trial' example that is not the right way to run trial.
15703david-sarah@jacaranda.org**20100726225729
15704 Ignore-this: a61f55557ad69a1633bfb2b8172cce97
15705] 
15706[docs/specifications/dirnodes.txt: 'mesh'->'grid'.
15707david-sarah@jacaranda.org**20100723061616
15708 Ignore-this: 887bcf921ef00afba8e05e9239035bca
15709] 
15710[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'.
15711david-sarah@jacaranda.org**20100723054703
15712 Ignore-this: f3b98183e7d0a0f391225b8b93ac6c37
15713] 
15714[docs: use current cap to Zooko's wiki page in example text
15715zooko@zooko.com**20100721010543
15716 Ignore-this: 4f36f36758f9fdbaf9eb73eac23b6652
15717 fixes #1134
15718] 
15719[__init__.py: silence DeprecationWarning about BaseException.message globally. fixes #1129
15720david-sarah@jacaranda.org**20100720011939
15721 Ignore-this: 38808986ba79cb2786b010504a22f89
15722] 
15723[test_runner: test that 'tahoe --version' outputs no noise (e.g. DeprecationWarnings).
15724david-sarah@jacaranda.org**20100720011345
15725 Ignore-this: dd358b7b2e5d57282cbe133e8069702e
15726] 
15727[TAG allmydata-tahoe-1.7.1
15728zooko@zooko.com**20100719131352
15729 Ignore-this: 6942056548433dc653a746703819ad8c
15730] 
15731[relnotes.txt: updated for v1.7.1 release!
15732zooko@zooko.com**20100719083059
15733 Ignore-this: 9f10eb19b65a39d652b546c57481da45
15734] 
15735[immutable: add test case of #1128, fix test case of #1118
15736zooko@zooko.com**20100719081612
15737 Ignore-this: 8f9f742e7dac2bd9b49c19bd10f1c204
15738] 
15739[NEWS: add #1118 and reflow
15740zooko@zooko.com**20100719081248
15741 Ignore-this: 37a2e39d58c7b584b3c7f193bc1b30df
15742] 
15743[immutable: fix bug in which preexisting_shares and merged were shallowly referencing the same sets
15744zooko@zooko.com**20100719075426
15745 Ignore-this: 90827f8ce7ff0fc0c3c7f819399b8cf0
15746 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.
15747] 
15748[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.
15749david-sarah@jacaranda.org**20100719044655
15750 Ignore-this: 142d182c0739986812140bb8387077d5
15751] 
15752[docs/known_issues.txt: update release version and date.
15753david-sarah@jacaranda.org**20100718235940
15754 Ignore-this: dbbb42dbfa6c0d205a0b8e6e58eee9c7
15755] 
15756[relnotes.txt, docs/quickstart.html: prepare for 1.7.1 release. Don't claim to work on Cygwin (this might work but is untested).
15757david-sarah@jacaranda.org**20100718235437
15758 Ignore-this: dfc7334ee4bb76c04ee19304a7f1024b
15759] 
15760[immutable: extend the tests to check that the shares that got uploaded really do make a sufficiently Happy distribution
15761zooko@zooko.com**20100719045047
15762 Ignore-this: 89c33a7b795e23018667351045a8d5d0
15763 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.
15764] 
15765[immutable: test for #1118
15766zooko@zooko.com**20100718221537
15767 Ignore-this: 8882aabe2aaec6a0148c87e735d817ad
15768] 
15769[immutable: test for #1124
15770zooko@zooko.com**20100718222907
15771 Ignore-this: 1766e3cbab92ea2a9e246f40eb6e770b
15772] 
15773[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>.
15774david-sarah@jacaranda.org**20100718230420
15775 Ignore-this: aef40f2e74ddeabee5e122e8d80893a1
15776] 
15777[trivial: fix unused import (sorry about that, pyflakes)
15778zooko@zooko.com**20100718215133
15779 Ignore-this: c2414e443405072b51d552295f2c0e8c
15780] 
15781[tests, NEWS, CREDITS re: #1117
15782zooko@zooko.com**20100718203225
15783 Ignore-this: 1f08be2c692fb72cc0dd023259f11354
15784 Give Brian and Kevan promotions, move release date in NEWS to the 18th, commit Brian's test for #1117.
15785 fixes #1117
15786] 
15787[test/test_upload.py: test to see that aborted buckets are ignored by the storage server
15788Kevan Carstensen <kevan@isnotajoke.com>**20100716001046
15789 Ignore-this: cc075c24b1c86d737f3199af894cc780
15790] 
15791[test/test_storage.py: test for the new remote_abort semantics.
15792Kevan Carstensen <kevan@isnotajoke.com>**20100715232148
15793 Ignore-this: d3d6491f17bf670e770ca4b385007515
15794] 
15795[storage/immutable.py: make remote_abort btell the storage server about aborted buckets.
15796Kevan Carstensen <kevan@isnotajoke.com>**20100715232105
15797 Ignore-this: 16ab0090676355abdd5600ed44ff19c9
15798] 
15799[test/test_upload.py: changes to test plumbing for #1117 tests
15800Kevan Carstensen <kevan@isnotajoke.com>**20100715231820
15801 Ignore-this: 78a6d359d7bf8529d283e2815bf1e2de
15802 
15803     - Add a callRemoteOnly method to FakeBucketWriter.
15804     - Change the abort method in FakeBucketWriter to not return a
15805       RuntimeError.
15806] 
15807[immutable/upload.py: abort buckets if peer selection fails
15808Kevan Carstensen <kevan@isnotajoke.com>**20100715231714
15809 Ignore-this: 2a0b643a22284df292d8ed9d91b1fd37
15810] 
15811[test_encodingutil: correct an error in the previous patch to StdlibUnicode.test_open_representable.
15812david-sarah@jacaranda.org**20100718151420
15813 Ignore-this: af050955f623fbc0e4d78e15a0a8a144
15814] 
15815[NEWS: Forward-compatibility improvements for non-ASCII caps (#1051).
15816david-sarah@jacaranda.org**20100718143622
15817 Ignore-this: 1edfebc4bd38a3b5c35e75c99588153f
15818] 
15819[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.
15820david-sarah@jacaranda.org**20100718142915
15821 Ignore-this: c4e78ef4b1478dd400da71cf077ffa4a
15822] 
15823[test_encodingutil: StdlibUnicode.test_open_representable no longer uses a mock.
15824david-sarah@jacaranda.org**20100718125412
15825 Ignore-this: 4bf373a5e2dfe4209e5e364124af29a3
15826] 
15827[docs: add comment clarifying #1051
15828zooko@zooko.com**20100718053250
15829 Ignore-this: 6cfc0930434cbdbbc262dabb58f1505d
15830] 
15831[docs: update NEWS
15832zooko@zooko.com**20100718053225
15833 Ignore-this: 63d5c782ef84812e6d010f0590866831
15834] 
15835[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.
15836david-sarah@jacaranda.org**20100711200252
15837 Ignore-this: c2f193352369d32e06865f8f3e951894
15838] 
15839[Debian documentation update
15840jacob@appelbaum.net**20100305003004] 
15841[debian-docs-patch-final
15842jacob@appelbaum.net**20100304085955] 
15843[M-x whitespace-cleanup
15844zooko@zooko.com**20100718032739
15845 Ignore-this: babfd4af6ad2fc885c957fd5c8b10c3f
15846] 
15847[docs: tidy up NEWS a little
15848zooko@zooko.com**20100718032434
15849 Ignore-this: 54f2820fd1a37c8967609f6bfc4e5e18
15850] 
15851[benchmarking: update bench_dirnode.py to reflect the new directory interfaces
15852zooko@zooko.com**20100718031710
15853 Ignore-this: 368ba523dd3de80d9da29cd58afbe827
15854] 
15855[test_encodingutil: fix test_open_representable, which is only valid when run on a platform for which we know an unrepresentable filename.
15856david-sarah@jacaranda.org**20100718030333
15857 Ignore-this: c114d92c17714a5d4ae005c15267d60c
15858] 
15859[iputil.py: Add support for FreeBSD 7,8 and 9
15860francois@ctrlaltdel.ch**20100718022832
15861 Ignore-this: 1829b4cf4b91107f4cf87841e6167e99
15862 committed by: zooko@zooko.com
15863 date: 2010-07-17
15864 and I also patched: NEWS and CREDITS
15865] 
15866[NEWS: add snippet about #1083
15867zooko@zooko.com**20100718020653
15868 Ignore-this: d353a9d93cbc5a5e6ba4671f78d1e22b
15869] 
15870[fileutil: docstrings for non-obvious usage restrictions on methods of EncryptedTemporaryFile.
15871david-sarah@jacaranda.org**20100717054647
15872 Ignore-this: 46d8fc10782fa8ec2b6c5b168c841943
15873] 
15874[Move EncryptedTemporaryFile from SFTP frontend to allmydata.util.fileutil, and make the FTP frontend also use it (fixing #1083).
15875david-sarah@jacaranda.org**20100711213721
15876 Ignore-this: e452e8ca66391aa2a1a49afe0114f317
15877] 
15878[NEWS: reorder NEWS snippets to be in descending order of interestingness
15879zooko@zooko.com**20100718015929
15880 Ignore-this: 146c42e88a9555a868a04a69dd0e5326
15881] 
15882[Correct stringutils->encodingutil patch to be the newer version, rather than the old version that was committed in error.
15883david-sarah@jacaranda.org**20100718013435
15884 Ignore-this: c8940c4e1aa2e9acc80cd4fe54753cd8
15885] 
15886[test_cli.py: fix error that crept in when rebasing the patch for #1072.
15887david-sarah@jacaranda.org**20100718000123
15888 Ignore-this: 3e8f6cc3a27b747c708221dd581934f4
15889] 
15890[stringutils: add test for when sys.stdout has no encoding attribute (fixes #1099).
15891david-sarah@jacaranda.org**20100717045816
15892 Ignore-this: f28dce6940e909f12f354086d17db54f
15893] 
15894[CLI: add 'tahoe unlink' as an alias to 'tahoe rm', for forward-compatibility.
15895david-sarah@jacaranda.org**20100717220411
15896 Ignore-this: 3ecdde7f2d0498514cef32e118e0b855
15897] 
15898[minor code clean-up in dirnode.py
15899zooko@zooko.com**20100714060255
15900 Ignore-this: bb0ab2783203e605024b3e2f798256a1
15901 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.
15902 fixes #967
15903] 
15904[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.
15905david-sarah@jacaranda.org**20100712003015
15906 Ignore-this: 103b809d180df17a7283077c3104c7be
15907] 
15908[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.
15909david-sarah@jacaranda.org**20100711195525
15910 Ignore-this: deac32d8b91ba26ede18905d3f7d2b93
15911] 
15912[docs: CREDITS and NEWS
15913zooko@zooko.com**20100714060150
15914 Ignore-this: dc83e612f77d69e50ee975f07f6b16fe
15915] 
15916[CREDITS: more creds for Kevan, plus utf-8 BOM
15917zooko@zooko.com**20100619045503
15918 Ignore-this: 72d02bdd7a0f324f1cee8cd399c7c6de
15919] 
15920[cli.py: make command descriptions consistently end with a full stop.
15921david-sarah@jacaranda.org**20100714014538
15922 Ignore-this: 9ee7fa29ca2d1631db4049c2a389a97a
15923] 
15924[SFTP: address some of the comments in zooko's review (#1106).
15925david-sarah@jacaranda.org**20100712025537
15926 Ignore-this: c3921638a2d4f1de2a776ae78e4dc37e
15927] 
15928[docs/logging.txt: note that setting flogging vars might affect tests with race conditions.
15929david-sarah@jacaranda.org**20100712050721
15930 Ignore-this: fc1609d215fcd5561a57fd1226206f27
15931] 
15932[test_storage.py: potential fix for failures when logging is enabled.
15933david-sarah@jacaranda.org**19700713040546
15934 Ignore-this: 5815693a0df3e64c52c3c6b7be2846c7
15935] 
15936[upcase_since_on_welcome
15937terrellrussell@gmail.com**20100708193903] 
15938[server_version_on_welcome_page.dpatch.txt
15939freestorm77@gmail.com**20100605191721
15940 Ignore-this: b450c76dc875f5ac8cca229a666cbd0a
15941 
15942 
15943 - The storage server version is 0 for all storage nodes in the Welcome Page
15944 
15945 
15946] 
15947[NEWS: add NEWS snippets about two recent patches
15948zooko@zooko.com**20100708162058
15949 Ignore-this: 6c9da6a0ad7351a960bdd60f81532899
15950] 
15951[directory_html_top_banner.dpatch
15952freestorm77@gmail.com**20100622205301
15953 Ignore-this: 1d770d975e0c414c996564774f049bca
15954 
15955 The div tag with the link "Return to Welcome page" on the directory.xhtml page is not correct
15956 
15957] 
15958[tahoe_css_toolbar.dpatch
15959freestorm77@gmail.com**20100622210046
15960 Ignore-this: 5b3ebb2e0f52bbba718a932f80c246c0
15961 
15962 CSS modification to be correctly diplayed with Internet Explorer 8
15963 
15964 The links on the top of page directory.xhtml are not diplayed in the same line as display with Firefox.
15965 
15966] 
15967[runnin_test_tahoe_css.dpatch
15968freestorm77@gmail.com**20100622214714
15969 Ignore-this: e0db73d68740aad09a7b9ae60a08c05c
15970 
15971 Runnin test for changes in tahoe.css file
15972 
15973] 
15974[runnin_test_directory_xhtml.dpatch
15975freestorm77@gmail.com**20100622201403
15976 Ignore-this: f8962463fce50b9466405cb59fe11d43
15977 
15978 Runnin test for diretory.xhtml top banner
15979 
15980] 
15981[stringutils.py: tolerate sys.stdout having no 'encoding' attribute.
15982david-sarah@jacaranda.org**20100626040817
15983 Ignore-this: f42cad81cef645ee38ac1df4660cc850
15984] 
15985[quickstart.html: python 2.5 -> 2.6 as recommended version
15986david-sarah@jacaranda.org**20100705175858
15987 Ignore-this: bc3a14645ea1d5435002966ae903199f
15988] 
15989[SFTP: don't call .stopProducing on the producer registered with OverwriteableFileConsumer (which breaks with warner's new downloader).
15990david-sarah@jacaranda.org**20100628231926
15991 Ignore-this: 131b7a5787bc85a9a356b5740d9d996f
15992] 
15993[docs/how_to_make_a_tahoe-lafs_release.txt: trivial correction, install.html should now be quickstart.html.
15994david-sarah@jacaranda.org**20100625223929
15995 Ignore-this: 99a5459cac51bd867cc11ad06927ff30
15996] 
15997[setup: in the Makefile, refuse to upload tarballs unless someone has passed the environment variable "BB_BRANCH" with value "trunk"
15998zooko@zooko.com**20100619034928
15999 Ignore-this: 276ddf9b6ad7ec79e27474862e0f7d6
16000] 
16001[trivial: tiny update to in-line comment
16002zooko@zooko.com**20100614045715
16003 Ignore-this: 10851b0ed2abfed542c97749e5d280bc
16004 (I'm actually committing this patch as a test of the new eager-annotation-computation of trac-darcs.)
16005] 
16006[docs: about.html link to home page early on, and be decentralized storage instead of cloud storage this time around
16007zooko@zooko.com**20100619065318
16008 Ignore-this: dc6db03f696e5b6d2848699e754d8053
16009] 
16010[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"
16011zooko@zooko.com**20100619065124
16012 Ignore-this: e292c7f51c337a84ebfeb366fbd24d6c
16013] 
16014[TAG allmydata-tahoe-1.7.0
16015zooko@zooko.com**20100619052631
16016 Ignore-this: d21e27afe6d85e2e3ba6a3292ba2be1
16017] 
16018Patch bundle hash:
16019763f477370befb6ddfe2070c058c65145e710be5