Ticket #393: 393status21.dpatch

File 393status21.dpatch, 618.8 KB (added by kevan, at 2010-07-27T23:22:22Z)
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
168Tue Jul 27 15:50:46 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
169  * mutable/servermap.py: lay some groundwork for IWritable
170
171New patches:
172
173[Misc. changes to support the work I'm doing
174Kevan Carstensen <kevan@isnotajoke.com>**20100624234637
175 Ignore-this: fdd18fa8cc05f4b4b15ff53ee24a1819
176 
177     - Add a notion of file version number to interfaces.py
178     - Alter mutable file node interfaces to have a notion of version,
179       though this may be changed later.
180     - Alter mutable/filenode.py to conform to these changes.
181     - Add a salt hasher to util/hashutil.py
182] {
183hunk ./src/allmydata/interfaces.py 7
184      ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
185 
186 HASH_SIZE=32
187+SALT_SIZE=16
188+
189+SDMF_VERSION=0
190+MDMF_VERSION=1
191 
192 Hash = StringConstraint(maxLength=HASH_SIZE,
193                         minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
194hunk ./src/allmydata/interfaces.py 811
195         writer-visible data using this writekey.
196         """
197 
198+    def set_version(version):
199+        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
200+        we upload in SDMF for reasons of compatibility. If you want to
201+        change this, set_version will let you do that.
202+
203+        To say that this file should be uploaded in SDMF, pass in a 0. To
204+        say that the file should be uploaded as MDMF, pass in a 1.
205+        """
206+
207+    def get_version():
208+        """Returns the mutable file protocol version."""
209+
210 class NotEnoughSharesError(Exception):
211     """Download was unable to get enough shares"""
212 
213hunk ./src/allmydata/mutable/filenode.py 8
214 from twisted.internet import defer, reactor
215 from foolscap.api import eventually
216 from allmydata.interfaces import IMutableFileNode, \
217-     ICheckable, ICheckResults, NotEnoughSharesError
218+     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
219 from allmydata.util import hashutil, log
220 from allmydata.util.assertutil import precondition
221 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
222hunk ./src/allmydata/mutable/filenode.py 67
223         self._sharemap = {} # known shares, shnum-to-[nodeids]
224         self._cache = ResponseCache()
225         self._most_recent_size = None
226+        # filled in after __init__ if we're being created for the first time;
227+        # filled in by the servermap updater before publishing, otherwise.
228+        # set to this default value in case neither of those things happen,
229+        # or in case the servermap can't find any shares to tell us what
230+        # to publish as.
231+        # TODO: Set this back to None, and find out why the tests fail
232+        #       with it set to None.
233+        self._protocol_version = SDMF_VERSION
234 
235         # all users of this MutableFileNode go through the serializer. This
236         # takes advantage of the fact that Deferreds discard the callbacks
237hunk ./src/allmydata/mutable/filenode.py 472
238     def _did_upload(self, res, size):
239         self._most_recent_size = size
240         return res
241+
242+
243+    def set_version(self, version):
244+        # I can be set in two ways:
245+        #  1. When the node is created.
246+        #  2. (for an existing share) when the Servermap is updated
247+        #     before I am read.
248+        assert version in (MDMF_VERSION, SDMF_VERSION)
249+        self._protocol_version = version
250+
251+
252+    def get_version(self):
253+        return self._protocol_version
254hunk ./src/allmydata/util/hashutil.py 90
255 MUTABLE_READKEY_TAG = "allmydata_mutable_writekey_to_readkey_v1"
256 MUTABLE_DATAKEY_TAG = "allmydata_mutable_readkey_to_datakey_v1"
257 MUTABLE_STORAGEINDEX_TAG = "allmydata_mutable_readkey_to_storage_index_v1"
258+MUTABLE_SALT_TAG = "allmydata_mutable_segment_salt_v1"
259 
260 # dirnodes
261 DIRNODE_CHILD_WRITECAP_TAG = "allmydata_mutable_writekey_and_salt_to_dirnode_child_capkey_v1"
262hunk ./src/allmydata/util/hashutil.py 134
263 def plaintext_segment_hasher():
264     return tagged_hasher(PLAINTEXT_SEGMENT_TAG)
265 
266+def mutable_salt_hash(data):
267+    return tagged_hash(MUTABLE_SALT_TAG, data)
268+def mutable_salt_hasher():
269+    return tagged_hasher(MUTABLE_SALT_TAG)
270+
271 KEYLEN = 16
272 IVLEN = 16
273 
274}
275[nodemaker.py: create MDMF files when asked to
276Kevan Carstensen <kevan@isnotajoke.com>**20100624234833
277 Ignore-this: 26c16aaca9ddab7a7ce37a4530bc970
278] {
279hunk ./src/allmydata/nodemaker.py 3
280 import weakref
281 from zope.interface import implements
282-from allmydata.interfaces import INodeMaker
283+from allmydata.util.assertutil import precondition
284+from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError, \
285+                                 SDMF_VERSION, MDMF_VERSION
286 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
287 from allmydata.immutable.upload import Data
288 from allmydata.mutable.filenode import MutableFileNode
289hunk ./src/allmydata/nodemaker.py 88
290             return self._create_dirnode(filenode)
291         return None
292 
293-    def create_mutable_file(self, contents=None, keysize=None):
294+    def create_mutable_file(self, contents=None, keysize=None,
295+                            version=SDMF_VERSION):
296         n = MutableFileNode(self.storage_broker, self.secret_holder,
297                             self.default_encoding_parameters, self.history)
298hunk ./src/allmydata/nodemaker.py 92
299+        n.set_version(version)
300         d = self.key_generator.generate(keysize)
301         d.addCallback(n.create_with_keys, contents)
302         d.addCallback(lambda res: n)
303hunk ./src/allmydata/nodemaker.py 98
304         return d
305 
306-    def create_new_mutable_directory(self, initial_children={}):
307+    def create_new_mutable_directory(self, initial_children={},
308+                                     version=SDMF_VERSION):
309+        # initial_children must have metadata (i.e. {} instead of None)
310+        for (name, (node, metadata)) in initial_children.iteritems():
311+            precondition(isinstance(metadata, dict),
312+                         "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
313+            node.raise_error()
314         d = self.create_mutable_file(lambda n:
315                                      pack_children(initial_children, n.get_writekey()))
316         d.addCallback(self._create_dirnode)
317merger 0.0 (
318hunk ./src/allmydata/nodemaker.py 106
319-                                     pack_children(n, initial_children))
320+                                     pack_children(initial_children, n.get_writekey()))
321hunk ./src/allmydata/nodemaker.py 106
322-                                     pack_children(n, initial_children))
323+                                     pack_children(n, initial_children),
324+                                     version)
325)
326}
327[storage/server.py: minor code cleanup
328Kevan Carstensen <kevan@isnotajoke.com>**20100624234905
329 Ignore-this: 2358c531c39e48d3c8e56b62b5768228
330] {
331hunk ./src/allmydata/storage/server.py 569
332                                          self)
333         return share
334 
335-    def remote_slot_readv(self, storage_index, shares, readv):
336+    def remote_slot_readv(self, storage_index, shares, readvs):
337         start = time.time()
338         self.count("readv")
339         si_s = si_b2a(storage_index)
340hunk ./src/allmydata/storage/server.py 590
341             if sharenum in shares or not shares:
342                 filename = os.path.join(bucketdir, sharenum_s)
343                 msf = MutableShareFile(filename, self)
344-                datavs[sharenum] = msf.readv(readv)
345+                datavs[sharenum] = msf.readv(readvs)
346         log.msg("returning shares %s" % (datavs.keys(),),
347                 facility="tahoe.storage", level=log.NOISY, parent=lp)
348         self.add_latency("readv", time.time() - start)
349}
350[test/test_mutable.py: alter some tests that were failing due to MDMF; minor code cleanup.
351Kevan Carstensen <kevan@isnotajoke.com>**20100624234924
352 Ignore-this: afb86ec1fbdbfe1a5ef6f46f350273c0
353] {
354hunk ./src/allmydata/test/test_mutable.py 151
355             chr(ord(original[byte_offset]) ^ 0x01) +
356             original[byte_offset+1:])
357 
358+def add_two(original, byte_offset):
359+    # It isn't enough to simply flip the bit for the version number,
360+    # because 1 is a valid version number. So we add two instead.
361+    return (original[:byte_offset] +
362+            chr(ord(original[byte_offset]) ^ 0x02) +
363+            original[byte_offset+1:])
364+
365 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
366     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
367     # list of shnums to corrupt.
368hunk ./src/allmydata/test/test_mutable.py 187
369                 real_offset = offset1
370             real_offset = int(real_offset) + offset2 + offset_offset
371             assert isinstance(real_offset, int), offset
372-            shares[shnum] = flip_bit(data, real_offset)
373+            if offset1 == 0: # verbyte
374+                f = add_two
375+            else:
376+                f = flip_bit
377+            shares[shnum] = f(data, real_offset)
378     return res
379 
380 def make_storagebroker(s=None, num_peers=10):
381hunk ./src/allmydata/test/test_mutable.py 423
382         d.addCallback(_created)
383         return d
384 
385+
386     def test_modify_backoffer(self):
387         def _modifier(old_contents, servermap, first_time):
388             return old_contents + "line2"
389hunk ./src/allmydata/test/test_mutable.py 658
390         d.addCallback(_created)
391         return d
392 
393+
394     def _copy_shares(self, ignored, index):
395         shares = self._storage._peers
396         # we need a deep copy
397}
398[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
399Kevan Carstensen <kevan@isnotajoke.com>**20100626003520
400 Ignore-this: 836e59e2fde0535f6b4bea3468dc8244
401] {
402hunk ./src/allmydata/test/test_mutable.py 168
403                 and shnum not in shnums_to_corrupt):
404                 continue
405             data = shares[shnum]
406-            (version,
407-             seqnum,
408-             root_hash,
409-             IV,
410-             k, N, segsize, datalen,
411-             o) = unpack_header(data)
412-            if isinstance(offset, tuple):
413-                offset1, offset2 = offset
414-            else:
415-                offset1 = offset
416-                offset2 = 0
417-            if offset1 == "pubkey":
418-                real_offset = 107
419-            elif offset1 in o:
420-                real_offset = o[offset1]
421-            else:
422-                real_offset = offset1
423-            real_offset = int(real_offset) + offset2 + offset_offset
424-            assert isinstance(real_offset, int), offset
425-            if offset1 == 0: # verbyte
426-                f = add_two
427-            else:
428-                f = flip_bit
429-            shares[shnum] = f(data, real_offset)
430-    return res
431+            # We're feeding the reader all of the share data, so it
432+            # won't need to use the rref that we didn't provide, nor the
433+            # storage index that we didn't provide. We do this because
434+            # the reader will work for both MDMF and SDMF.
435+            reader = MDMFSlotReadProxy(None, None, shnum, data)
436+            # We need to get the offsets for the next part.
437+            d = reader.get_verinfo()
438+            def _do_corruption(verinfo, data, shnum):
439+                (seqnum,
440+                 root_hash,
441+                 IV,
442+                 segsize,
443+                 datalen,
444+                 k, n, prefix, o) = verinfo
445+                if isinstance(offset, tuple):
446+                    offset1, offset2 = offset
447+                else:
448+                    offset1 = offset
449+                    offset2 = 0
450+                if offset1 == "pubkey":
451+                    real_offset = 107
452+                elif offset1 in o:
453+                    real_offset = o[offset1]
454+                else:
455+                    real_offset = offset1
456+                real_offset = int(real_offset) + offset2 + offset_offset
457+                assert isinstance(real_offset, int), offset
458+                if offset1 == 0: # verbyte
459+                    f = add_two
460+                else:
461+                    f = flip_bit
462+                shares[shnum] = f(data, real_offset)
463+            d.addCallback(_do_corruption, data, shnum)
464+            ds.append(d)
465+    dl = defer.DeferredList(ds)
466+    dl.addCallback(lambda ignored: res)
467+    return dl
468 
469 def make_storagebroker(s=None, num_peers=10):
470     if not s:
471hunk ./src/allmydata/test/test_mutable.py 1177
472         return d
473 
474     def test_download_fails(self):
475-        corrupt(None, self._storage, "signature")
476-        d = self.shouldFail(UnrecoverableFileError, "test_download_anyway",
477+        d = corrupt(None, self._storage, "signature")
478+        d.addCallback(lambda ignored:
479+            self.shouldFail(UnrecoverableFileError, "test_download_anyway",
480                             "no recoverable versions",
481                             self._fn.download_best_version)
482         return d
483hunk ./src/allmydata/test/test_mutable.py 1232
484         return d
485 
486     def test_check_all_bad_sig(self):
487-        corrupt(None, self._storage, 1) # bad sig
488-        d = self._fn.check(Monitor())
489+        d = corrupt(None, self._storage, 1) # bad sig
490+        d.addCallback(lambda ignored:
491+            self._fn.check(Monitor()))
492         d.addCallback(self.check_bad, "test_check_all_bad_sig")
493         return d
494 
495hunk ./src/allmydata/test/test_mutable.py 1239
496     def test_check_all_bad_blocks(self):
497-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
498+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
499         # the Checker won't notice this.. it doesn't look at actual data
500hunk ./src/allmydata/test/test_mutable.py 1241
501-        d = self._fn.check(Monitor())
502+        d.addCallback(lambda ignored:
503+            self._fn.check(Monitor()))
504         d.addCallback(self.check_good, "test_check_all_bad_blocks")
505         return d
506 
507hunk ./src/allmydata/test/test_mutable.py 1252
508         return d
509 
510     def test_verify_all_bad_sig(self):
511-        corrupt(None, self._storage, 1) # bad sig
512-        d = self._fn.check(Monitor(), verify=True)
513+        d = corrupt(None, self._storage, 1) # bad sig
514+        d.addCallback(lambda ignored:
515+            self._fn.check(Monitor(), verify=True))
516         d.addCallback(self.check_bad, "test_verify_all_bad_sig")
517         return d
518 
519hunk ./src/allmydata/test/test_mutable.py 1259
520     def test_verify_one_bad_sig(self):
521-        corrupt(None, self._storage, 1, [9]) # bad sig
522-        d = self._fn.check(Monitor(), verify=True)
523+        d = corrupt(None, self._storage, 1, [9]) # bad sig
524+        d.addCallback(lambda ignored:
525+            self._fn.check(Monitor(), verify=True))
526         d.addCallback(self.check_bad, "test_verify_one_bad_sig")
527         return d
528 
529hunk ./src/allmydata/test/test_mutable.py 1266
530     def test_verify_one_bad_block(self):
531-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
532+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
533         # the Verifier *will* notice this, since it examines every byte
534hunk ./src/allmydata/test/test_mutable.py 1268
535-        d = self._fn.check(Monitor(), verify=True)
536+        d.addCallback(lambda ignored:
537+            self._fn.check(Monitor(), verify=True))
538         d.addCallback(self.check_bad, "test_verify_one_bad_block")
539         d.addCallback(self.check_expected_failure,
540                       CorruptShareError, "block hash tree failure",
541hunk ./src/allmydata/test/test_mutable.py 1277
542         return d
543 
544     def test_verify_one_bad_sharehash(self):
545-        corrupt(None, self._storage, "share_hash_chain", [9], 5)
546-        d = self._fn.check(Monitor(), verify=True)
547+        d = corrupt(None, self._storage, "share_hash_chain", [9], 5)
548+        d.addCallback(lambda ignored:
549+            self._fn.check(Monitor(), verify=True))
550         d.addCallback(self.check_bad, "test_verify_one_bad_sharehash")
551         d.addCallback(self.check_expected_failure,
552                       CorruptShareError, "corrupt hashes",
553hunk ./src/allmydata/test/test_mutable.py 1287
554         return d
555 
556     def test_verify_one_bad_encprivkey(self):
557-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
558-        d = self._fn.check(Monitor(), verify=True)
559+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
560+        d.addCallback(lambda ignored:
561+            self._fn.check(Monitor(), verify=True))
562         d.addCallback(self.check_bad, "test_verify_one_bad_encprivkey")
563         d.addCallback(self.check_expected_failure,
564                       CorruptShareError, "invalid privkey",
565hunk ./src/allmydata/test/test_mutable.py 1297
566         return d
567 
568     def test_verify_one_bad_encprivkey_uncheckable(self):
569-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
570+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
571         readonly_fn = self._fn.get_readonly()
572         # a read-only node has no way to validate the privkey
573hunk ./src/allmydata/test/test_mutable.py 1300
574-        d = readonly_fn.check(Monitor(), verify=True)
575+        d.addCallback(lambda ignored:
576+            readonly_fn.check(Monitor(), verify=True))
577         d.addCallback(self.check_good,
578                       "test_verify_one_bad_encprivkey_uncheckable")
579         return d
580}
581[Alter the ServermapUpdater to find MDMF files
582Kevan Carstensen <kevan@isnotajoke.com>**20100626234118
583 Ignore-this: 25f6278209c2983ba8f307cfe0fde0
584 
585 The servermapupdater should find MDMF files on a grid in the same way
586 that it finds SDMF files. This patch makes it do that.
587] {
588hunk ./src/allmydata/mutable/servermap.py 7
589 from itertools import count
590 from twisted.internet import defer
591 from twisted.python import failure
592-from foolscap.api import DeadReferenceError, RemoteException, eventually
593+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
594+                         fireEventually
595 from allmydata.util import base32, hashutil, idlib, log
596 from allmydata.storage.server import si_b2a
597 from allmydata.interfaces import IServermapUpdaterStatus
598hunk ./src/allmydata/mutable/servermap.py 17
599 from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \
600      DictOfSets, CorruptShareError, NeedMoreDataError
601 from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
602-     SIGNED_PREFIX_LENGTH
603+     SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
604 
605 class UpdateStatus:
606     implements(IServermapUpdaterStatus)
607hunk ./src/allmydata/mutable/servermap.py 254
608         """Return a set of versionids, one for each version that is currently
609         recoverable."""
610         versionmap = self.make_versionmap()
611-
612         recoverable_versions = set()
613         for (verinfo, shares) in versionmap.items():
614             (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
615hunk ./src/allmydata/mutable/servermap.py 366
616         self._servers_responded = set()
617 
618         # how much data should we read?
619+        # SDMF:
620         #  * if we only need the checkstring, then [0:75]
621         #  * if we need to validate the checkstring sig, then [543ish:799ish]
622         #  * if we need the verification key, then [107:436ish]
623hunk ./src/allmydata/mutable/servermap.py 374
624         #  * if we need the encrypted private key, we want [-1216ish:]
625         #   * but we can't read from negative offsets
626         #   * the offset table tells us the 'ish', also the positive offset
627-        # A future version of the SMDF slot format should consider using
628-        # fixed-size slots so we can retrieve less data. For now, we'll just
629-        # read 2000 bytes, which also happens to read enough actual data to
630-        # pre-fetch a 9-entry dirnode.
631+        # MDMF:
632+        #  * Checkstring? [0:72]
633+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
634+        #    the offset table will tell us for sure.
635+        #  * If we need the verification key, we have to consult the offset
636+        #    table as well.
637+        # At this point, we don't know which we are. Our filenode can
638+        # tell us, but it might be lying -- in some cases, we're
639+        # responsible for telling it which kind of file it is.
640         self._read_size = 4000
641         if mode == MODE_CHECK:
642             # we use unpack_prefix_and_signature, so we need 1k
643hunk ./src/allmydata/mutable/servermap.py 432
644         self._queries_completed = 0
645 
646         sb = self._storage_broker
647+        # All of the peers, permuted by the storage index, as usual.
648         full_peerlist = sb.get_servers_for_index(self._storage_index)
649         self.full_peerlist = full_peerlist # for use later, immutable
650         self.extra_peers = full_peerlist[:] # peers are removed as we use them
651hunk ./src/allmydata/mutable/servermap.py 439
652         self._good_peers = set() # peers who had some shares
653         self._empty_peers = set() # peers who don't have any shares
654         self._bad_peers = set() # peers to whom our queries failed
655+        self._readers = {} # peerid -> dict(sharewriters), filled in
656+                           # after responses come in.
657 
658         k = self._node.get_required_shares()
659hunk ./src/allmydata/mutable/servermap.py 443
660+        # For what cases can these conditions work?
661         if k is None:
662             # make a guess
663             k = 3
664hunk ./src/allmydata/mutable/servermap.py 456
665         self.num_peers_to_query = k + self.EPSILON
666 
667         if self.mode == MODE_CHECK:
668+            # We want to query all of the peers.
669             initial_peers_to_query = dict(full_peerlist)
670             must_query = set(initial_peers_to_query.keys())
671             self.extra_peers = []
672hunk ./src/allmydata/mutable/servermap.py 464
673             # we're planning to replace all the shares, so we want a good
674             # chance of finding them all. We will keep searching until we've
675             # seen epsilon that don't have a share.
676+            # We don't query all of the peers because that could take a while.
677             self.num_peers_to_query = N + self.EPSILON
678             initial_peers_to_query, must_query = self._build_initial_querylist()
679             self.required_num_empty_peers = self.EPSILON
680hunk ./src/allmydata/mutable/servermap.py 474
681             # might also avoid the round trip required to read the encrypted
682             # private key.
683 
684-        else:
685+        else: # MODE_READ, MODE_ANYTHING
686+            # 2k peers is good enough.
687             initial_peers_to_query, must_query = self._build_initial_querylist()
688 
689         # this is a set of peers that we are required to get responses from:
690hunk ./src/allmydata/mutable/servermap.py 490
691         # before we can consider ourselves finished, and self.extra_peers
692         # contains the overflow (peers that we should tap if we don't get
693         # enough responses)
694+        # I guess that self._must_query is a subset of
695+        # initial_peers_to_query?
696+        assert set(must_query).issubset(set(initial_peers_to_query))
697 
698         self._send_initial_requests(initial_peers_to_query)
699         self._status.timings["initial_queries"] = time.time() - self._started
700hunk ./src/allmydata/mutable/servermap.py 549
701         # errors that aren't handled by _query_failed (and errors caused by
702         # _query_failed) get logged, but we still want to check for doneness.
703         d.addErrback(log.err)
704-        d.addBoth(self._check_for_done)
705         d.addErrback(self._fatal_error)
706hunk ./src/allmydata/mutable/servermap.py 550
707+        d.addCallback(self._check_for_done)
708         return d
709 
710     def _do_read(self, ss, peerid, storage_index, shnums, readv):
711hunk ./src/allmydata/mutable/servermap.py 569
712         d = ss.callRemote("slot_readv", storage_index, shnums, readv)
713         return d
714 
715+
716+    def _got_corrupt_share(self, e, shnum, peerid, data, lp):
717+        """
718+        I am called when a remote server returns a corrupt share in
719+        response to one of our queries. By corrupt, I mean a share
720+        without a valid signature. I then record the failure, notify the
721+        server of the corruption, and record the share as bad.
722+        """
723+        f = failure.Failure(e)
724+        self.log(format="bad share: %(f_value)s", f_value=str(f),
725+                 failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
726+        # Notify the server that its share is corrupt.
727+        self.notify_server_corruption(peerid, shnum, str(e))
728+        # By flagging this as a bad peer, we won't count any of
729+        # the other shares on that peer as valid, though if we
730+        # happen to find a valid version string amongst those
731+        # shares, we'll keep track of it so that we don't need
732+        # to validate the signature on those again.
733+        self._bad_peers.add(peerid)
734+        self._last_failure = f
735+        # XXX: Use the reader for this?
736+        checkstring = data[:SIGNED_PREFIX_LENGTH]
737+        self._servermap.mark_bad_share(peerid, shnum, checkstring)
738+        self._servermap.problems.append(f)
739+
740+
741+    def _cache_good_sharedata(self, verinfo, shnum, now, data):
742+        """
743+        If one of my queries returns successfully (which means that we
744+        were able to and successfully did validate the signature), I
745+        cache the data that we initially fetched from the storage
746+        server. This will help reduce the number of roundtrips that need
747+        to occur when the file is downloaded, or when the file is
748+        updated.
749+        """
750+        self._node._add_to_cache(verinfo, shnum, 0, data, now)
751+
752+
753     def _got_results(self, datavs, peerid, readsize, stuff, started):
754         lp = self.log(format="got result from [%(peerid)s], %(numshares)d shares",
755                       peerid=idlib.shortnodeid_b2a(peerid),
756hunk ./src/allmydata/mutable/servermap.py 630
757         else:
758             self._empty_peers.add(peerid)
759 
760-        last_verinfo = None
761-        last_shnum = None
762+        ss, storage_index = stuff
763+        ds = []
764+
765         for shnum,datav in datavs.items():
766             data = datav[0]
767hunk ./src/allmydata/mutable/servermap.py 635
768-            try:
769-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
770-                last_verinfo = verinfo
771-                last_shnum = shnum
772-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
773-            except CorruptShareError, e:
774-                # log it and give the other shares a chance to be processed
775-                f = failure.Failure()
776-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
777-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
778-                self.notify_server_corruption(peerid, shnum, str(e))
779-                self._bad_peers.add(peerid)
780-                self._last_failure = f
781-                checkstring = data[:SIGNED_PREFIX_LENGTH]
782-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
783-                self._servermap.problems.append(f)
784-                pass
785-
786-        self._status.timings["cumulative_verify"] += (time.time() - now)
787+            reader = MDMFSlotReadProxy(ss,
788+                                       storage_index,
789+                                       shnum,
790+                                       data)
791+            self._readers.setdefault(peerid, dict())[shnum] = reader
792+            # our goal, with each response, is to validate the version
793+            # information and share data as best we can at this point --
794+            # we do this by validating the signature. To do this, we
795+            # need to do the following:
796+            #   - If we don't already have the public key, fetch the
797+            #     public key. We use this to validate the signature.
798+            if not self._node.get_pubkey():
799+                # fetch and set the public key.
800+                d = reader.get_verification_key()
801+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
802+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
803+                # XXX: Make self._pubkey_query_failed?
804+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
805+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
806+            else:
807+                # we already have the public key.
808+                d = defer.succeed(None)
809+            # Neither of these two branches return anything of
810+            # consequence, so the first entry in our deferredlist will
811+            # be None.
812 
813hunk ./src/allmydata/mutable/servermap.py 661
814-        if self._need_privkey and last_verinfo:
815-            # send them a request for the privkey. We send one request per
816-            # server.
817-            lp2 = self.log("sending privkey request",
818-                           parent=lp, level=log.NOISY)
819-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
820-             offsets_tuple) = last_verinfo
821-            o = dict(offsets_tuple)
822+            # - Next, we need the version information. We almost
823+            #   certainly got this by reading the first thousand or so
824+            #   bytes of the share on the storage server, so we
825+            #   shouldn't need to fetch anything at this step.
826+            d2 = reader.get_verinfo()
827+            d2.addErrback(lambda error, shnum=shnum, peerid=peerid:
828+                self._got_corrupt_share(error, shnum, peerid, data, lp))
829+            # - Next, we need the signature. For an SDMF share, it is
830+            #   likely that we fetched this when doing our initial fetch
831+            #   to get the version information. In MDMF, this lives at
832+            #   the end of the share, so unless the file is quite small,
833+            #   we'll need to do a remote fetch to get it.
834+            d3 = reader.get_signature()
835+            d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
836+                self._got_corrupt_share(error, shnum, peerid, data, lp))
837+            #  Once we have all three of these responses, we can move on
838+            #  to validating the signature
839 
840hunk ./src/allmydata/mutable/servermap.py 679
841-            self._queries_outstanding.add(peerid)
842-            readv = [ (o['enc_privkey'], (o['EOF'] - o['enc_privkey'])) ]
843-            ss = self._servermap.connections[peerid]
844-            privkey_started = time.time()
845-            d = self._do_read(ss, peerid, self._storage_index,
846-                              [last_shnum], readv)
847-            d.addCallback(self._got_privkey_results, peerid, last_shnum,
848-                          privkey_started, lp2)
849-            d.addErrback(self._privkey_query_failed, peerid, last_shnum, lp2)
850-            d.addErrback(log.err)
851-            d.addCallback(self._check_for_done)
852-            d.addErrback(self._fatal_error)
853+            # Does the node already have a privkey? If not, we'll try to
854+            # fetch it here.
855+            if self._need_privkey:
856+                d4 = reader.get_encprivkey()
857+                d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
858+                    self._try_to_validate_privkey(results, peerid, shnum, lp))
859+                d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
860+                    self._privkey_query_failed(error, shnum, data, lp))
861+            else:
862+                d4 = defer.succeed(None)
863 
864hunk ./src/allmydata/mutable/servermap.py 690
865+            dl = defer.DeferredList([d, d2, d3, d4])
866+            dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
867+                self._got_signature_one_share(results, shnum, peerid, lp))
868+            dl.addErrback(lambda error, shnum=shnum, data=data:
869+               self._got_corrupt_share(error, shnum, peerid, data, lp))
870+            dl.addCallback(lambda verinfo, shnum=shnum, peerid=peerid, data=data:
871+                self._cache_good_sharedata(verinfo, shnum, now, data))
872+            ds.append(dl)
873+        # dl is a deferred list that will fire when all of the shares
874+        # that we found on this peer are done processing. When dl fires,
875+        # we know that processing is done, so we can decrement the
876+        # semaphore-like thing that we incremented earlier.
877+        dl = defer.DeferredList(ds, fireOnOneErrback=True)
878+        # Are we done? Done means that there are no more queries to
879+        # send, that there are no outstanding queries, and that we
880+        # haven't received any queries that are still processing. If we
881+        # are done, self._check_for_done will cause the done deferred
882+        # that we returned to our caller to fire, which tells them that
883+        # they have a complete servermap, and that we won't be touching
884+        # the servermap anymore.
885+        dl.addCallback(self._check_for_done)
886+        dl.addErrback(self._fatal_error)
887         # all done!
888         self.log("_got_results done", parent=lp, level=log.NOISY)
889hunk ./src/allmydata/mutable/servermap.py 714
890+        return dl
891+
892+
893+    def _try_to_set_pubkey(self, pubkey_s, peerid, shnum, lp):
894+        if self._node.get_pubkey():
895+            return # don't go through this again if we don't have to
896+        fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
897+        assert len(fingerprint) == 32
898+        if fingerprint != self._node.get_fingerprint():
899+            raise CorruptShareError(peerid, shnum,
900+                                "pubkey doesn't match fingerprint")
901+        self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
902+        assert self._node.get_pubkey()
903+
904 
905     def notify_server_corruption(self, peerid, shnum, reason):
906         ss = self._servermap.connections[peerid]
907hunk ./src/allmydata/mutable/servermap.py 734
908         ss.callRemoteOnly("advise_corrupt_share",
909                           "mutable", self._storage_index, shnum, reason)
910 
911-    def _got_results_one_share(self, shnum, data, peerid, lp):
912+
913+    def _got_signature_one_share(self, results, shnum, peerid, lp):
914+        # It is our job to give versioninfo to our caller. We need to
915+        # raise CorruptShareError if the share is corrupt for any
916+        # reason, something that our caller will handle.
917         self.log(format="_got_results: got shnum #%(shnum)d from peerid %(peerid)s",
918                  shnum=shnum,
919                  peerid=idlib.shortnodeid_b2a(peerid),
920hunk ./src/allmydata/mutable/servermap.py 744
921                  level=log.NOISY,
922                  parent=lp)
923-
924-        # this might raise NeedMoreDataError, if the pubkey and signature
925-        # live at some weird offset. That shouldn't happen, so I'm going to
926-        # treat it as a bad share.
927-        (seqnum, root_hash, IV, k, N, segsize, datalength,
928-         pubkey_s, signature, prefix) = unpack_prefix_and_signature(data)
929-
930-        if not self._node.get_pubkey():
931-            fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
932-            assert len(fingerprint) == 32
933-            if fingerprint != self._node.get_fingerprint():
934-                raise CorruptShareError(peerid, shnum,
935-                                        "pubkey doesn't match fingerprint")
936-            self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
937-
938-        if self._need_privkey:
939-            self._try_to_extract_privkey(data, peerid, shnum, lp)
940-
941-        (ig_version, ig_seqnum, ig_root_hash, ig_IV, ig_k, ig_N,
942-         ig_segsize, ig_datalen, offsets) = unpack_header(data)
943+        _, verinfo, signature, __ = results
944+        (seqnum,
945+         root_hash,
946+         saltish,
947+         segsize,
948+         datalen,
949+         k,
950+         n,
951+         prefix,
952+         offsets) = verinfo[1]
953         offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
954 
955hunk ./src/allmydata/mutable/servermap.py 756
956-        verinfo = (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
957+        # XXX: This should be done for us in the method, so
958+        # presumably you can go in there and fix it.
959+        verinfo = (seqnum,
960+                   root_hash,
961+                   saltish,
962+                   segsize,
963+                   datalen,
964+                   k,
965+                   n,
966+                   prefix,
967                    offsets_tuple)
968hunk ./src/allmydata/mutable/servermap.py 767
969+        # This tuple uniquely identifies a share on the grid; we use it
970+        # to keep track of the ones that we've already seen.
971 
972         if verinfo not in self._valid_versions:
973hunk ./src/allmydata/mutable/servermap.py 771
974-            # it's a new pair. Verify the signature.
975-            valid = self._node.get_pubkey().verify(prefix, signature)
976+            # This is a new version tuple, and we need to validate it
977+            # against the public key before keeping track of it.
978+            assert self._node.get_pubkey()
979+            valid = self._node.get_pubkey().verify(prefix, signature[1])
980             if not valid:
981hunk ./src/allmydata/mutable/servermap.py 776
982-                raise CorruptShareError(peerid, shnum, "signature is invalid")
983+                raise CorruptShareError(peerid, shnum,
984+                                        "signature is invalid")
985 
986hunk ./src/allmydata/mutable/servermap.py 779
987-            # ok, it's a valid verinfo. Add it to the list of validated
988-            # versions.
989-            self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
990-                     % (seqnum, base32.b2a(root_hash)[:4],
991-                        idlib.shortnodeid_b2a(peerid), shnum,
992-                        k, N, segsize, datalength),
993-                     parent=lp)
994-            self._valid_versions.add(verinfo)
995-        # We now know that this is a valid candidate verinfo.
996+        # ok, it's a valid verinfo. Add it to the list of validated
997+        # versions.
998+        self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
999+                 % (seqnum, base32.b2a(root_hash)[:4],
1000+                    idlib.shortnodeid_b2a(peerid), shnum,
1001+                    k, n, segsize, datalen),
1002+                    parent=lp)
1003+        self._valid_versions.add(verinfo)
1004+        # We now know that this is a valid candidate verinfo. Whether or
1005+        # not this instance of it is valid is a matter for the next
1006+        # statement; at this point, we just know that if we see this
1007+        # version info again, that its signature checks out and that
1008+        # we're okay to skip the signature-checking step.
1009 
1010hunk ./src/allmydata/mutable/servermap.py 793
1011+        # (peerid, shnum) are bound in the method invocation.
1012         if (peerid, shnum) in self._servermap.bad_shares:
1013             # we've been told that the rest of the data in this share is
1014             # unusable, so don't add it to the servermap.
1015hunk ./src/allmydata/mutable/servermap.py 808
1016         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
1017         return verinfo
1018 
1019+
1020     def _deserialize_pubkey(self, pubkey_s):
1021         verifier = rsa.create_verifying_key_from_string(pubkey_s)
1022         return verifier
1023hunk ./src/allmydata/mutable/servermap.py 813
1024 
1025-    def _try_to_extract_privkey(self, data, peerid, shnum, lp):
1026-        try:
1027-            r = unpack_share(data)
1028-        except NeedMoreDataError, e:
1029-            # this share won't help us. oh well.
1030-            offset = e.encprivkey_offset
1031-            length = e.encprivkey_length
1032-            self.log("shnum %d on peerid %s: share was too short (%dB) "
1033-                     "to get the encprivkey; [%d:%d] ought to hold it" %
1034-                     (shnum, idlib.shortnodeid_b2a(peerid), len(data),
1035-                      offset, offset+length),
1036-                     parent=lp)
1037-            # NOTE: if uncoordinated writes are taking place, someone might
1038-            # change the share (and most probably move the encprivkey) before
1039-            # we get a chance to do one of these reads and fetch it. This
1040-            # will cause us to see a NotEnoughSharesError(unable to fetch
1041-            # privkey) instead of an UncoordinatedWriteError . This is a
1042-            # nuisance, but it will go away when we move to DSA-based mutable
1043-            # files (since the privkey will be small enough to fit in the
1044-            # write cap).
1045-
1046-            return
1047-
1048-        (seqnum, root_hash, IV, k, N, segsize, datalen,
1049-         pubkey, signature, share_hash_chain, block_hash_tree,
1050-         share_data, enc_privkey) = r
1051-
1052-        return self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1053 
1054     def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
1055hunk ./src/allmydata/mutable/servermap.py 815
1056-
1057+        """
1058+        Given a writekey from a remote server, I validate it against the
1059+        writekey stored in my node. If it is valid, then I set the
1060+        privkey and encprivkey properties of the node.
1061+        """
1062         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
1063         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
1064         if alleged_writekey != self._node.get_writekey():
1065hunk ./src/allmydata/mutable/servermap.py 892
1066         self._queries_completed += 1
1067         self._last_failure = f
1068 
1069-    def _got_privkey_results(self, datavs, peerid, shnum, started, lp):
1070-        now = time.time()
1071-        elapsed = now - started
1072-        self._status.add_per_server_time(peerid, "privkey", started, elapsed)
1073-        self._queries_outstanding.discard(peerid)
1074-        if not self._need_privkey:
1075-            return
1076-        if shnum not in datavs:
1077-            self.log("privkey wasn't there when we asked it",
1078-                     level=log.WEIRD, umid="VA9uDQ")
1079-            return
1080-        datav = datavs[shnum]
1081-        enc_privkey = datav[0]
1082-        self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1083 
1084     def _privkey_query_failed(self, f, peerid, shnum, lp):
1085         self._queries_outstanding.discard(peerid)
1086hunk ./src/allmydata/mutable/servermap.py 906
1087         self._servermap.problems.append(f)
1088         self._last_failure = f
1089 
1090+
1091     def _check_for_done(self, res):
1092         # exit paths:
1093         #  return self._send_more_queries(outstanding) : send some more queries
1094hunk ./src/allmydata/mutable/servermap.py 912
1095         #  return self._done() : all done
1096         #  return : keep waiting, no new queries
1097-
1098         lp = self.log(format=("_check_for_done, mode is '%(mode)s', "
1099                               "%(outstanding)d queries outstanding, "
1100                               "%(extra)d extra peers available, "
1101hunk ./src/allmydata/mutable/servermap.py 1117
1102         self._servermap.last_update_time = self._started
1103         # the servermap will not be touched after this
1104         self.log("servermap: %s" % self._servermap.summarize_versions())
1105+
1106         eventually(self._done_deferred.callback, self._servermap)
1107 
1108     def _fatal_error(self, f):
1109hunk ./src/allmydata/test/test_mutable.py 637
1110         d.addCallback(_created)
1111         return d
1112 
1113-    def publish_multiple(self):
1114+    def publish_mdmf(self):
1115+        # like publish_one, except that the result is guaranteed to be
1116+        # an MDMF file.
1117+        # self.CONTENTS should have more than one segment.
1118+        self.CONTENTS = "This is an MDMF file" * 100000
1119+        self._storage = FakeStorage()
1120+        self._nodemaker = make_nodemaker(self._storage)
1121+        self._storage_broker = self._nodemaker.storage_broker
1122+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
1123+        def _created(node):
1124+            self._fn = node
1125+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1126+        d.addCallback(_created)
1127+        return d
1128+
1129+
1130+    def publish_sdmf(self):
1131+        # like publish_one, except that the result is guaranteed to be
1132+        # an SDMF file
1133+        self.CONTENTS = "This is an SDMF file" * 1000
1134+        self._storage = FakeStorage()
1135+        self._nodemaker = make_nodemaker(self._storage)
1136+        self._storage_broker = self._nodemaker.storage_broker
1137+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
1138+        def _created(node):
1139+            self._fn = node
1140+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1141+        d.addCallback(_created)
1142+        return d
1143+
1144+
1145+    def publish_multiple(self, version=0):
1146         self.CONTENTS = ["Contents 0",
1147                          "Contents 1",
1148                          "Contents 2",
1149hunk ./src/allmydata/test/test_mutable.py 677
1150         self._copied_shares = {}
1151         self._storage = FakeStorage()
1152         self._nodemaker = make_nodemaker(self._storage)
1153-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0]) # seqnum=1
1154+        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
1155         def _created(node):
1156             self._fn = node
1157             # now create multiple versions of the same file, and accumulate
1158hunk ./src/allmydata/test/test_mutable.py 906
1159         return d
1160 
1161 
1162+    def test_servermapupdater_finds_mdmf_files(self):
1163+        # setUp already published an MDMF file for us. We just need to
1164+        # make sure that when we run the ServermapUpdater, the file is
1165+        # reported to have one recoverable version.
1166+        d = defer.succeed(None)
1167+        d.addCallback(lambda ignored:
1168+            self.publish_mdmf())
1169+        d.addCallback(lambda ignored:
1170+            self.make_servermap(mode=MODE_CHECK))
1171+        # Calling make_servermap also updates the servermap in the mode
1172+        # that we specify, so we just need to see what it says.
1173+        def _check_servermap(sm):
1174+            self.failUnlessEqual(len(sm.recoverable_versions()), 1)
1175+        d.addCallback(_check_servermap)
1176+        return d
1177+
1178+
1179+    def test_servermapupdater_finds_sdmf_files(self):
1180+        d = defer.succeed(None)
1181+        d.addCallback(lambda ignored:
1182+            self.publish_sdmf())
1183+        d.addCallback(lambda ignored:
1184+            self.make_servermap(mode=MODE_CHECK))
1185+        d.addCallback(lambda servermap:
1186+            self.failUnlessEqual(len(servermap.recoverable_versions()), 1))
1187+        return d
1188+
1189 
1190 class Roundtrip(unittest.TestCase, testutil.ShouldFailMixin, PublishMixin):
1191     def setUp(self):
1192hunk ./src/allmydata/test/test_mutable.py 1050
1193         return d
1194     test_no_servers_download.timeout = 15
1195 
1196+
1197     def _test_corrupt_all(self, offset, substring,
1198                           should_succeed=False, corrupt_early=True,
1199                           failure_checker=None):
1200}
1201[Make a segmented mutable uploader
1202Kevan Carstensen <kevan@isnotajoke.com>**20100626234204
1203 Ignore-this: d199af8ab0bc64d8ed2bc19c5437bfba
1204 
1205 The mutable file uploader should be able to publish files with one
1206 segment and files with multiple segments. This patch makes it do that.
1207 This is still incomplete, and rather ugly -- I need to flesh out error
1208 handling, I need to write tests, and I need to remove some of the uglier
1209 kludges in the process before I can call this done.
1210] {
1211hunk ./src/allmydata/mutable/publish.py 8
1212 from zope.interface import implements
1213 from twisted.internet import defer
1214 from twisted.python import failure
1215-from allmydata.interfaces import IPublishStatus
1216+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
1217 from allmydata.util import base32, hashutil, mathutil, idlib, log
1218 from allmydata import hashtree, codec
1219 from allmydata.storage.server import si_b2a
1220hunk ./src/allmydata/mutable/publish.py 19
1221      UncoordinatedWriteError, NotEnoughServersError
1222 from allmydata.mutable.servermap import ServerMap
1223 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
1224-     unpack_checkstring, SIGNED_PREFIX
1225+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
1226+
1227+KiB = 1024
1228+DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
1229 
1230 class PublishStatus:
1231     implements(IPublishStatus)
1232hunk ./src/allmydata/mutable/publish.py 112
1233         self._status.set_helper(False)
1234         self._status.set_progress(0.0)
1235         self._status.set_active(True)
1236+        # We use this to control how the file is written.
1237+        version = self._node.get_version()
1238+        assert version in (SDMF_VERSION, MDMF_VERSION)
1239+        self._version = version
1240 
1241     def get_status(self):
1242         return self._status
1243hunk ./src/allmydata/mutable/publish.py 134
1244         simultaneous write.
1245         """
1246 
1247-        # 1: generate shares (SDMF: files are small, so we can do it in RAM)
1248-        # 2: perform peer selection, get candidate servers
1249-        #  2a: send queries to n+epsilon servers, to determine current shares
1250-        #  2b: based upon responses, create target map
1251-        # 3: send slot_testv_and_readv_and_writev messages
1252-        # 4: as responses return, update share-dispatch table
1253-        # 4a: may need to run recovery algorithm
1254-        # 5: when enough responses are back, we're done
1255+        # 0. Setup encoding parameters, encoder, and other such things.
1256+        # 1. Encrypt, encode, and publish segments.
1257 
1258         self.log("starting publish, datalen is %s" % len(newdata))
1259         self._status.set_size(len(newdata))
1260hunk ./src/allmydata/mutable/publish.py 187
1261         self.bad_peers = set() # peerids who have errbacked/refused requests
1262 
1263         self.newdata = newdata
1264-        self.salt = os.urandom(16)
1265 
1266hunk ./src/allmydata/mutable/publish.py 188
1267+        # This will set self.segment_size, self.num_segments, and
1268+        # self.fec.
1269         self.setup_encoding_parameters()
1270 
1271         # if we experience any surprises (writes which were rejected because
1272hunk ./src/allmydata/mutable/publish.py 238
1273             self.bad_share_checkstrings[key] = old_checkstring
1274             self.connections[peerid] = self._servermap.connections[peerid]
1275 
1276-        # create the shares. We'll discard these as they are delivered. SDMF:
1277-        # we're allowed to hold everything in memory.
1278+        # Now, the process dovetails -- if this is an SDMF file, we need
1279+        # to write an SDMF file. Otherwise, we need to write an MDMF
1280+        # file.
1281+        if self._version == MDMF_VERSION:
1282+            return self._publish_mdmf()
1283+        else:
1284+            return self._publish_sdmf()
1285+        #return self.done_deferred
1286+
1287+    def _publish_mdmf(self):
1288+        # Next, we find homes for all of the shares that we don't have
1289+        # homes for yet.
1290+        # TODO: Make this part do peer selection.
1291+        self.update_goal()
1292+        self.writers = {}
1293+        # For each (peerid, shnum) in self.goal, we make an
1294+        # MDMFSlotWriteProxy for that peer. We'll use this to write
1295+        # shares to the peer.
1296+        for key in self.goal:
1297+            peerid, shnum = key
1298+            write_enabler = self._node.get_write_enabler(peerid)
1299+            renew_secret = self._node.get_renewal_secret(peerid)
1300+            cancel_secret = self._node.get_cancel_secret(peerid)
1301+            secrets = (write_enabler, renew_secret, cancel_secret)
1302+
1303+            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
1304+                                                      self.connections[peerid],
1305+                                                      self._storage_index,
1306+                                                      secrets,
1307+                                                      self._new_seqnum,
1308+                                                      self.required_shares,
1309+                                                      self.total_shares,
1310+                                                      self.segment_size,
1311+                                                      len(self.newdata))
1312+            if (peerid, shnum) in self._servermap.servermap:
1313+                old_versionid, old_timestamp = self._servermap.servermap[key]
1314+                (old_seqnum, old_root_hash, old_salt, old_segsize,
1315+                 old_datalength, old_k, old_N, old_prefix,
1316+                 old_offsets_tuple) = old_versionid
1317+                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
1318+
1319+        # Now, we start pushing shares.
1320+        self._status.timings["setup"] = time.time() - self._started
1321+        def _start_pushing(res):
1322+            self._started_pushing = time.time()
1323+            return res
1324+
1325+        # First, we encrypt, encode, and publish the shares that we need
1326+        # to encrypt, encode, and publish.
1327+
1328+        # This will eventually hold the block hash chain for each share
1329+        # that we publish. We define it this way so that empty publishes
1330+        # will still have something to write to the remote slot.
1331+        self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
1332+        self.sharehash_leaves = None # eventually [sharehashes]
1333+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
1334+                              # validate the share]
1335 
1336hunk ./src/allmydata/mutable/publish.py 296
1337+        d = defer.succeed(None)
1338+        self.log("Starting push")
1339+        for i in xrange(self.num_segments - 1):
1340+            d.addCallback(lambda ignored, i=i:
1341+                self.push_segment(i))
1342+            d.addCallback(self._turn_barrier)
1343+        # We have at least one segment, so we will have a tail segment
1344+        if self.num_segments > 0:
1345+            d.addCallback(lambda ignored:
1346+                self.push_tail_segment())
1347+
1348+        d.addCallback(lambda ignored:
1349+            self.push_encprivkey())
1350+        d.addCallback(lambda ignored:
1351+            self.push_blockhashes())
1352+        d.addCallback(lambda ignored:
1353+            self.push_sharehashes())
1354+        d.addCallback(lambda ignored:
1355+            self.push_toplevel_hashes_and_signature())
1356+        d.addCallback(lambda ignored:
1357+            self.finish_publishing())
1358+        return d
1359+
1360+
1361+    def _publish_sdmf(self):
1362         self._status.timings["setup"] = time.time() - self._started
1363hunk ./src/allmydata/mutable/publish.py 322
1364+        self.salt = os.urandom(16)
1365+
1366         d = self._encrypt_and_encode()
1367         d.addCallback(self._generate_shares)
1368         def _start_pushing(res):
1369hunk ./src/allmydata/mutable/publish.py 335
1370 
1371         return self.done_deferred
1372 
1373+
1374     def setup_encoding_parameters(self):
1375hunk ./src/allmydata/mutable/publish.py 337
1376-        segment_size = len(self.newdata)
1377+        if self._version == MDMF_VERSION:
1378+            segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
1379+        else:
1380+            segment_size = len(self.newdata) # SDMF is only one segment
1381         # this must be a multiple of self.required_shares
1382         segment_size = mathutil.next_multiple(segment_size,
1383                                               self.required_shares)
1384hunk ./src/allmydata/mutable/publish.py 350
1385                                                   segment_size)
1386         else:
1387             self.num_segments = 0
1388-        assert self.num_segments in [0, 1,] # SDMF restrictions
1389+        if self._version == SDMF_VERSION:
1390+            assert self.num_segments in (0, 1) # SDMF
1391+            return
1392+        # calculate the tail segment size.
1393+        self.tail_segment_size = len(self.newdata) % segment_size
1394+
1395+        if self.tail_segment_size == 0:
1396+            # The tail segment is the same size as the other segments.
1397+            self.tail_segment_size = segment_size
1398+
1399+        # We'll make an encoder ahead-of-time for the normal-sized
1400+        # segments (defined as any segment of segment_size size.
1401+        # (the part of the code that puts the tail segment will make its
1402+        #  own encoder for that part)
1403+        fec = codec.CRSEncoder()
1404+        fec.set_params(self.segment_size,
1405+                       self.required_shares, self.total_shares)
1406+        self.piece_size = fec.get_block_size()
1407+        self.fec = fec
1408+
1409+
1410+    def push_segment(self, segnum):
1411+        started = time.time()
1412+        segsize = self.segment_size
1413+        self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
1414+        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
1415+        assert len(data) == segsize
1416+
1417+        salt = os.urandom(16)
1418+
1419+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1420+        enc = AES(key)
1421+        crypttext = enc.process(data)
1422+        assert len(crypttext) == len(data)
1423+
1424+        now = time.time()
1425+        self._status.timings["encrypt"] = now - started
1426+        started = now
1427+
1428+        # now apply FEC
1429+
1430+        self._status.set_status("Encoding")
1431+        crypttext_pieces = [None] * self.required_shares
1432+        piece_size = self.piece_size
1433+        for i in range(len(crypttext_pieces)):
1434+            offset = i * piece_size
1435+            piece = crypttext[offset:offset+piece_size]
1436+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1437+            crypttext_pieces[i] = piece
1438+            assert len(piece) == piece_size
1439+        d = self.fec.encode(crypttext_pieces)
1440+        def _done_encoding(res):
1441+            elapsed = time.time() - started
1442+            self._status.timings["encode"] = elapsed
1443+            return res
1444+        d.addCallback(_done_encoding)
1445+
1446+        def _push_shares_and_salt(results):
1447+            shares, shareids = results
1448+            dl = []
1449+            for i in xrange(len(shares)):
1450+                sharedata = shares[i]
1451+                shareid = shareids[i]
1452+                block_hash = hashutil.block_hash(salt + sharedata)
1453+                self.blockhashes[shareid].append(block_hash)
1454+
1455+                # find the writer for this share
1456+                d = self.writers[shareid].put_block(sharedata, segnum, salt)
1457+                dl.append(d)
1458+            # TODO: Naturally, we need to check on the results of these.
1459+            return defer.DeferredList(dl)
1460+        d.addCallback(_push_shares_and_salt)
1461+        return d
1462+
1463+
1464+    def push_tail_segment(self):
1465+        # This is essentially the same as push_segment, except that we
1466+        # don't use the cached encoder that we use elsewhere.
1467+        self.log("Pushing tail segment")
1468+        started = time.time()
1469+        segsize = self.segment_size
1470+        data = self.newdata[segsize * (self.num_segments-1):]
1471+        assert len(data) == self.tail_segment_size
1472+        salt = os.urandom(16)
1473+
1474+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1475+        enc = AES(key)
1476+        crypttext = enc.process(data)
1477+        assert len(crypttext) == len(data)
1478+
1479+        now = time.time()
1480+        self._status.timings['encrypt'] = now - started
1481+        started = now
1482+
1483+        self._status.set_status("Encoding")
1484+        tail_fec = codec.CRSEncoder()
1485+        tail_fec.set_params(self.tail_segment_size,
1486+                            self.required_shares,
1487+                            self.total_shares)
1488+
1489+        crypttext_pieces = [None] * self.required_shares
1490+        piece_size = tail_fec.get_block_size()
1491+        for i in range(len(crypttext_pieces)):
1492+            offset = i * piece_size
1493+            piece = crypttext[offset:offset+piece_size]
1494+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1495+            crypttext_pieces[i] = piece
1496+            assert len(piece) == piece_size
1497+        d = tail_fec.encode(crypttext_pieces)
1498+        def _push_shares_and_salt(results):
1499+            shares, shareids = results
1500+            dl = []
1501+            for i in xrange(len(shares)):
1502+                sharedata = shares[i]
1503+                shareid = shareids[i]
1504+                block_hash = hashutil.block_hash(salt + sharedata)
1505+                self.blockhashes[shareid].append(block_hash)
1506+                # find the writer for this share
1507+                d = self.writers[shareid].put_block(sharedata,
1508+                                                    self.num_segments - 1,
1509+                                                    salt)
1510+                dl.append(d)
1511+            # TODO: Naturally, we need to check on the results of these.
1512+            return defer.DeferredList(dl)
1513+        d.addCallback(_push_shares_and_salt)
1514+        return d
1515+
1516+
1517+    def push_encprivkey(self):
1518+        started = time.time()
1519+        encprivkey = self._encprivkey
1520+        dl = []
1521+        def _spy_on_writer(results):
1522+            print results
1523+            return results
1524+        for shnum, writer in self.writers.iteritems():
1525+            d = writer.put_encprivkey(encprivkey)
1526+            dl.append(d)
1527+        d = defer.DeferredList(dl)
1528+        return d
1529+
1530+
1531+    def push_blockhashes(self):
1532+        started = time.time()
1533+        dl = []
1534+        def _spy_on_results(results):
1535+            print results
1536+            return results
1537+        self.sharehash_leaves = [None] * len(self.blockhashes)
1538+        for shnum, blockhashes in self.blockhashes.iteritems():
1539+            t = hashtree.HashTree(blockhashes)
1540+            self.blockhashes[shnum] = list(t)
1541+            # set the leaf for future use.
1542+            self.sharehash_leaves[shnum] = t[0]
1543+            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
1544+            dl.append(d)
1545+        d = defer.DeferredList(dl)
1546+        return d
1547+
1548+
1549+    def push_sharehashes(self):
1550+        share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
1551+        share_hash_chain = {}
1552+        ds = []
1553+        def _spy_on_results(results):
1554+            print results
1555+            return results
1556+        for shnum in xrange(len(self.sharehash_leaves)):
1557+            needed_indices = share_hash_tree.needed_hashes(shnum)
1558+            self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
1559+                                             for i in needed_indices] )
1560+            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
1561+            ds.append(d)
1562+        self.root_hash = share_hash_tree[0]
1563+        d = defer.DeferredList(ds)
1564+        return d
1565+
1566+
1567+    def push_toplevel_hashes_and_signature(self):
1568+        # We need to to three things here:
1569+        #   - Push the root hash and salt hash
1570+        #   - Get the checkstring of the resulting layout; sign that.
1571+        #   - Push the signature
1572+        ds = []
1573+        def _spy_on_results(results):
1574+            print results
1575+            return results
1576+        for shnum in xrange(self.total_shares):
1577+            d = self.writers[shnum].put_root_hash(self.root_hash)
1578+            ds.append(d)
1579+        d = defer.DeferredList(ds)
1580+        def _make_and_place_signature(ignored):
1581+            signable = self.writers[0].get_signable()
1582+            self.signature = self._privkey.sign(signable)
1583+
1584+            ds = []
1585+            for (shnum, writer) in self.writers.iteritems():
1586+                d = writer.put_signature(self.signature)
1587+                ds.append(d)
1588+            return defer.DeferredList(ds)
1589+        d.addCallback(_make_and_place_signature)
1590+        return d
1591+
1592+
1593+    def finish_publishing(self):
1594+        # We're almost done -- we just need to put the verification key
1595+        # and the offsets
1596+        ds = []
1597+        verification_key = self._pubkey.serialize()
1598+
1599+        def _spy_on_results(results):
1600+            print results
1601+            return results
1602+        for (shnum, writer) in self.writers.iteritems():
1603+            d = writer.put_verification_key(verification_key)
1604+            d.addCallback(lambda ignored, writer=writer:
1605+                writer.finish_publishing())
1606+            ds.append(d)
1607+        return defer.DeferredList(ds)
1608+
1609+
1610+    def _turn_barrier(self, res):
1611+        # putting this method in a Deferred chain imposes a guaranteed
1612+        # reactor turn between the pre- and post- portions of that chain.
1613+        # This can be useful to limit memory consumption: since Deferreds do
1614+        # not do tail recursion, code which uses defer.succeed(result) for
1615+        # consistency will cause objects to live for longer than you might
1616+        # normally expect.
1617+        return fireEventually(res)
1618+
1619 
1620     def _fatal_error(self, f):
1621         self.log("error during loop", failure=f, level=log.UNUSUAL)
1622hunk ./src/allmydata/mutable/publish.py 716
1623             self.log_goal(self.goal, "after update: ")
1624 
1625 
1626-
1627     def _encrypt_and_encode(self):
1628         # this returns a Deferred that fires with a list of (sharedata,
1629         # sharenum) tuples. TODO: cache the ciphertext, only produce the
1630hunk ./src/allmydata/mutable/publish.py 757
1631         d.addCallback(_done_encoding)
1632         return d
1633 
1634+
1635     def _generate_shares(self, shares_and_shareids):
1636         # this sets self.shares and self.root_hash
1637         self.log("_generate_shares")
1638hunk ./src/allmydata/mutable/publish.py 1145
1639             self._status.set_progress(1.0)
1640         eventually(self.done_deferred.callback, res)
1641 
1642-
1643hunk ./src/allmydata/test/test_mutable.py 248
1644         d.addCallback(_created)
1645         return d
1646 
1647+
1648+    def test_create_mdmf(self):
1649+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
1650+        def _created(n):
1651+            self.failUnless(isinstance(n, MutableFileNode))
1652+            self.failUnlessEqual(n.get_storage_index(), n._storage_index)
1653+            sb = self.nodemaker.storage_broker
1654+            peer0 = sorted(sb.get_all_serverids())[0]
1655+            shnums = self._storage._peers[peer0].keys()
1656+            self.failUnlessEqual(len(shnums), 1)
1657+        d.addCallback(_created)
1658+        return d
1659+
1660+
1661     def test_serialize(self):
1662         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
1663         calls = []
1664hunk ./src/allmydata/test/test_mutable.py 334
1665         d.addCallback(_created)
1666         return d
1667 
1668+
1669+    def test_create_mdmf_with_initial_contents(self):
1670+        initial_contents = "foobarbaz" * 131072 # 900KiB
1671+        d = self.nodemaker.create_mutable_file(initial_contents,
1672+                                               version=MDMF_VERSION)
1673+        def _created(n):
1674+            d = n.download_best_version()
1675+            d.addCallback(lambda data:
1676+                self.failUnlessEqual(data, initial_contents))
1677+            d.addCallback(lambda ignored:
1678+                n.overwrite(initial_contents + "foobarbaz"))
1679+            d.addCallback(lambda ignored:
1680+                n.download_best_version())
1681+            d.addCallback(lambda data:
1682+                self.failUnlessEqual(data, initial_contents +
1683+                                           "foobarbaz"))
1684+            return d
1685+        d.addCallback(_created)
1686+        return d
1687+
1688+
1689     def test_create_with_initial_contents_function(self):
1690         data = "initial contents"
1691         def _make_contents(n):
1692hunk ./src/allmydata/test/test_mutable.py 370
1693         d.addCallback(lambda data2: self.failUnlessEqual(data2, data))
1694         return d
1695 
1696+
1697+    def test_create_mdmf_with_initial_contents_function(self):
1698+        data = "initial contents" * 100000
1699+        def _make_contents(n):
1700+            self.failUnless(isinstance(n, MutableFileNode))
1701+            key = n.get_writekey()
1702+            self.failUnless(isinstance(key, str), key)
1703+            self.failUnlessEqual(len(key), 16)
1704+            return data
1705+        d = self.nodemaker.create_mutable_file(_make_contents,
1706+                                               version=MDMF_VERSION)
1707+        d.addCallback(lambda n:
1708+            n.download_best_version())
1709+        d.addCallback(lambda data2:
1710+            self.failUnlessEqual(data2, data))
1711+        return d
1712+
1713+
1714     def test_create_with_too_large_contents(self):
1715         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
1716         d = self.nodemaker.create_mutable_file(BIG)
1717}
1718[Write a segmented mutable downloader
1719Kevan Carstensen <kevan@isnotajoke.com>**20100626234314
1720 Ignore-this: d2bef531cde1b5c38f2eb28afdd4b17c
1721 
1722 The segmented mutable downloader can deal with MDMF files (files with
1723 one or more segments in MDMF format) and SDMF files (files with one
1724 segment in SDMF format). It is backwards compatible with the old
1725 file format.
1726 
1727 This patch also contains tests for the segmented mutable downloader.
1728] {
1729hunk ./src/allmydata/mutable/retrieve.py 8
1730 from twisted.internet import defer
1731 from twisted.python import failure
1732 from foolscap.api import DeadReferenceError, eventually, fireEventually
1733-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
1734-from allmydata.util import hashutil, idlib, log
1735+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
1736+                                 MDMF_VERSION, SDMF_VERSION
1737+from allmydata.util import hashutil, idlib, log, mathutil
1738 from allmydata import hashtree, codec
1739 from allmydata.storage.server import si_b2a
1740 from pycryptopp.cipher.aes import AES
1741hunk ./src/allmydata/mutable/retrieve.py 17
1742 from pycryptopp.publickey import rsa
1743 
1744 from allmydata.mutable.common import DictOfSets, CorruptShareError, UncoordinatedWriteError
1745-from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data
1746+from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data, \
1747+                                     MDMFSlotReadProxy
1748 
1749 class RetrieveStatus:
1750     implements(IRetrieveStatus)
1751hunk ./src/allmydata/mutable/retrieve.py 104
1752         self.verinfo = verinfo
1753         # during repair, we may be called upon to grab the private key, since
1754         # it wasn't picked up during a verify=False checker run, and we'll
1755-        # need it for repair to generate the a new version.
1756+        # need it for repair to generate a new version.
1757         self._need_privkey = fetch_privkey
1758         if self._node.get_privkey():
1759             self._need_privkey = False
1760hunk ./src/allmydata/mutable/retrieve.py 109
1761 
1762+        if self._need_privkey:
1763+            # TODO: Evaluate the need for this. We'll use it if we want
1764+            # to limit how many queries are on the wire for the privkey
1765+            # at once.
1766+            self._privkey_query_markers = [] # one Marker for each time we've
1767+                                             # tried to get the privkey.
1768+
1769         self._status = RetrieveStatus()
1770         self._status.set_storage_index(self._storage_index)
1771         self._status.set_helper(False)
1772hunk ./src/allmydata/mutable/retrieve.py 125
1773          offsets_tuple) = self.verinfo
1774         self._status.set_size(datalength)
1775         self._status.set_encoding(k, N)
1776+        self.readers = {}
1777 
1778     def get_status(self):
1779         return self._status
1780hunk ./src/allmydata/mutable/retrieve.py 149
1781         self.remaining_sharemap = DictOfSets()
1782         for (shnum, peerid, timestamp) in shares:
1783             self.remaining_sharemap.add(shnum, peerid)
1784+            # If the servermap update fetched anything, it fetched at least 1
1785+            # KiB, so we ask for that much.
1786+            # TODO: Change the cache methods to allow us to fetch all of the
1787+            # data that they have, then change this method to do that.
1788+            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
1789+                                                               shnum,
1790+                                                               0,
1791+                                                               1000)
1792+            ss = self.servermap.connections[peerid]
1793+            reader = MDMFSlotReadProxy(ss,
1794+                                       self._storage_index,
1795+                                       shnum,
1796+                                       any_cache)
1797+            reader.peerid = peerid
1798+            self.readers[shnum] = reader
1799+
1800 
1801         self.shares = {} # maps shnum to validated blocks
1802hunk ./src/allmydata/mutable/retrieve.py 167
1803+        self._active_readers = [] # list of active readers for this dl.
1804+        self._validated_readers = set() # set of readers that we have
1805+                                        # validated the prefix of
1806+        self._block_hash_trees = {} # shnum => hashtree
1807+        # TODO: Make this into a file-backed consumer or something to
1808+        # conserve memory.
1809+        self._plaintext = ""
1810 
1811         # how many shares do we need?
1812hunk ./src/allmydata/mutable/retrieve.py 176
1813-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1814+        (seqnum,
1815+         root_hash,
1816+         IV,
1817+         segsize,
1818+         datalength,
1819+         k,
1820+         N,
1821+         prefix,
1822          offsets_tuple) = self.verinfo
1823hunk ./src/allmydata/mutable/retrieve.py 185
1824-        assert len(self.remaining_sharemap) >= k
1825-        # we start with the lowest shnums we have available, since FEC is
1826-        # faster if we're using "primary shares"
1827-        self.active_shnums = set(sorted(self.remaining_sharemap.keys())[:k])
1828-        for shnum in self.active_shnums:
1829-            # we use an arbitrary peer who has the share. If shares are
1830-            # doubled up (more than one share per peer), we could make this
1831-            # run faster by spreading the load among multiple peers. But the
1832-            # algorithm to do that is more complicated than I want to write
1833-            # right now, and a well-provisioned grid shouldn't have multiple
1834-            # shares per peer.
1835-            peerid = list(self.remaining_sharemap[shnum])[0]
1836-            self.get_data(shnum, peerid)
1837 
1838hunk ./src/allmydata/mutable/retrieve.py 186
1839-        # control flow beyond this point: state machine. Receiving responses
1840-        # from queries is the input. We might send out more queries, or we
1841-        # might produce a result.
1842 
1843hunk ./src/allmydata/mutable/retrieve.py 187
1844+        # We need one share hash tree for the entire file; its leaves
1845+        # are the roots of the block hash trees for the shares that
1846+        # comprise it, and its root is in the verinfo.
1847+        self.share_hash_tree = hashtree.IncompleteHashTree(N)
1848+        self.share_hash_tree.set_hashes({0: root_hash})
1849+
1850+        # This will set up both the segment decoder and the tail segment
1851+        # decoder, as well as a variety of other instance variables that
1852+        # the download process will use.
1853+        self._setup_encoding_parameters()
1854+        assert len(self.remaining_sharemap) >= k
1855+
1856+        self.log("starting download")
1857+        self._add_active_peers()
1858+        # The download process beyond this is a state machine.
1859+        # _add_active_peers will select the peers that we want to use
1860+        # for the download, and then attempt to start downloading. After
1861+        # each segment, it will check for doneness, reacting to broken
1862+        # peers and corrupt shares as necessary. If it runs out of good
1863+        # peers before downloading all of the segments, _done_deferred
1864+        # will errback.  Otherwise, it will eventually callback with the
1865+        # contents of the mutable file.
1866         return self._done_deferred
1867 
1868hunk ./src/allmydata/mutable/retrieve.py 211
1869-    def get_data(self, shnum, peerid):
1870-        self.log(format="sending sh#%(shnum)d request to [%(peerid)s]",
1871-                 shnum=shnum,
1872-                 peerid=idlib.shortnodeid_b2a(peerid),
1873-                 level=log.NOISY)
1874-        ss = self.servermap.connections[peerid]
1875-        started = time.time()
1876-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1877+
1878+    def _setup_encoding_parameters(self):
1879+        """
1880+        I set up the encoding parameters, including k, n, the number
1881+        of segments associated with this file, and the segment decoder.
1882+        """
1883+        (seqnum,
1884+         root_hash,
1885+         IV,
1886+         segsize,
1887+         datalength,
1888+         k,
1889+         n,
1890+         known_prefix,
1891          offsets_tuple) = self.verinfo
1892hunk ./src/allmydata/mutable/retrieve.py 226
1893-        offsets = dict(offsets_tuple)
1894+        self._required_shares = k
1895+        self._total_shares = n
1896+        self._segment_size = segsize
1897+        self._data_length = datalength
1898+
1899+        if not IV:
1900+            self._version = MDMF_VERSION
1901+        else:
1902+            self._version = SDMF_VERSION
1903+
1904+        if datalength and segsize:
1905+            self._num_segments = mathutil.div_ceil(datalength, segsize)
1906+            self._tail_data_size = datalength % segsize
1907+        else:
1908+            self._num_segments = 0
1909+            self._tail_data_size = 0
1910 
1911hunk ./src/allmydata/mutable/retrieve.py 243
1912-        # we read the checkstring, to make sure that the data we grab is from
1913-        # the right version.
1914-        readv = [ (0, struct.calcsize(SIGNED_PREFIX)) ]
1915+        self._segment_decoder = codec.CRSDecoder()
1916+        self._segment_decoder.set_params(segsize, k, n)
1917+        self._current_segment = 0
1918 
1919hunk ./src/allmydata/mutable/retrieve.py 247
1920-        # We also read the data, and the hashes necessary to validate them
1921-        # (share_hash_chain, block_hash_tree, share_data). We don't read the
1922-        # signature or the pubkey, since that was handled during the
1923-        # servermap phase, and we'll be comparing the share hash chain
1924-        # against the roothash that was validated back then.
1925+        if  not self._tail_data_size:
1926+            self._tail_data_size = segsize
1927 
1928hunk ./src/allmydata/mutable/retrieve.py 250
1929-        readv.append( (offsets['share_hash_chain'],
1930-                       offsets['enc_privkey'] - offsets['share_hash_chain'] ) )
1931+        self._tail_segment_size = mathutil.next_multiple(self._tail_data_size,
1932+                                                         self._required_shares)
1933+        if self._tail_segment_size == self._segment_size:
1934+            self._tail_decoder = self._segment_decoder
1935+        else:
1936+            self._tail_decoder = codec.CRSDecoder()
1937+            self._tail_decoder.set_params(self._tail_segment_size,
1938+                                          self._required_shares,
1939+                                          self._total_shares)
1940 
1941hunk ./src/allmydata/mutable/retrieve.py 260
1942-        # if we need the private key (for repair), we also fetch that
1943-        if self._need_privkey:
1944-            readv.append( (offsets['enc_privkey'],
1945-                           offsets['EOF'] - offsets['enc_privkey']) )
1946+        self.log("got encoding parameters: "
1947+                 "k: %d "
1948+                 "n: %d "
1949+                 "%d segments of %d bytes each (%d byte tail segment)" % \
1950+                 (k, n, self._num_segments, self._segment_size,
1951+                  self._tail_segment_size))
1952 
1953hunk ./src/allmydata/mutable/retrieve.py 267
1954-        m = Marker()
1955-        self._outstanding_queries[m] = (peerid, shnum, started)
1956+        for i in xrange(self._total_shares):
1957+            # So we don't have to do this later.
1958+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
1959 
1960hunk ./src/allmydata/mutable/retrieve.py 271
1961-        # ask the cache first
1962-        got_from_cache = False
1963-        datavs = []
1964-        for (offset, length) in readv:
1965-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
1966-                                                            offset, length)
1967-            if data is not None:
1968-                datavs.append(data)
1969-        if len(datavs) == len(readv):
1970-            self.log("got data from cache")
1971-            got_from_cache = True
1972-            d = fireEventually({shnum: datavs})
1973-            # datavs is a dict mapping shnum to a pair of strings
1974-        else:
1975-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
1976-        self.remaining_sharemap.discard(shnum, peerid)
1977+        # If we have more than one segment, we are an SDMF file, which
1978+        # means that we need to validate the salts as we receive them.
1979+        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
1980+        self._salt_hash_tree[0] = IV # from the prefix.
1981 
1982hunk ./src/allmydata/mutable/retrieve.py 276
1983-        d.addCallback(self._got_results, m, peerid, started, got_from_cache)
1984-        d.addErrback(self._query_failed, m, peerid)
1985-        # errors that aren't handled by _query_failed (and errors caused by
1986-        # _query_failed) get logged, but we still want to check for doneness.
1987-        def _oops(f):
1988-            self.log(format="problem in _query_failed for sh#%(shnum)d to %(peerid)s",
1989-                     shnum=shnum,
1990-                     peerid=idlib.shortnodeid_b2a(peerid),
1991-                     failure=f,
1992-                     level=log.WEIRD, umid="W0xnQA")
1993-        d.addErrback(_oops)
1994-        d.addBoth(self._check_for_done)
1995-        # any error during _check_for_done means the download fails. If the
1996-        # download is successful, _check_for_done will fire _done by itself.
1997-        d.addErrback(self._done)
1998-        d.addErrback(log.err)
1999-        return d # purely for testing convenience
2000 
2001hunk ./src/allmydata/mutable/retrieve.py 277
2002-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
2003-        # isolate the callRemote to a separate method, so tests can subclass
2004-        # Publish and override it
2005-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
2006-        return d
2007+    def _add_active_peers(self):
2008+        """
2009+        I populate self._active_readers with enough active readers to
2010+        retrieve the contents of this mutable file. I am called before
2011+        downloading starts, and (eventually) after each validation
2012+        error, connection error, or other problem in the download.
2013+        """
2014+        # TODO: It would be cool to investigate other heuristics for
2015+        # reader selection. For instance, the cost (in time the user
2016+        # spends waiting for their file) of selecting a really slow peer
2017+        # that happens to have a primary share is probably more than
2018+        # selecting a really fast peer that doesn't have a primary
2019+        # share. Maybe the servermap could be extended to provide this
2020+        # information; it could keep track of latency information while
2021+        # it gathers more important data, and then this routine could
2022+        # use that to select active readers.
2023+        #
2024+        # (these and other questions would be easier to answer with a
2025+        #  robust, configurable tahoe-lafs simulator, which modeled node
2026+        #  failures, differences in node speed, and other characteristics
2027+        #  that we expect storage servers to have.  You could have
2028+        #  presets for really stable grids (like allmydata.com),
2029+        #  friendnets, make it easy to configure your own settings, and
2030+        #  then simulate the effect of big changes on these use cases
2031+        #  instead of just reasoning about what the effect might be. Out
2032+        #  of scope for MDMF, though.)
2033 
2034hunk ./src/allmydata/mutable/retrieve.py 304
2035-    def remove_peer(self, peerid):
2036-        for shnum in list(self.remaining_sharemap.keys()):
2037-            self.remaining_sharemap.discard(shnum, peerid)
2038+        # We need at least self._required_shares readers to download a
2039+        # segment.
2040+        needed = self._required_shares - len(self._active_readers)
2041+        # XXX: Why don't format= log messages work here?
2042+        self.log("adding %d peers to the active peers list" % needed)
2043 
2044hunk ./src/allmydata/mutable/retrieve.py 310
2045-    def _got_results(self, datavs, marker, peerid, started, got_from_cache):
2046-        now = time.time()
2047-        elapsed = now - started
2048-        if not got_from_cache:
2049-            self._status.add_fetch_timing(peerid, elapsed)
2050-        self.log(format="got results (%(shares)d shares) from [%(peerid)s]",
2051-                 shares=len(datavs),
2052-                 peerid=idlib.shortnodeid_b2a(peerid),
2053-                 level=log.NOISY)
2054-        self._outstanding_queries.pop(marker, None)
2055-        if not self._running:
2056-            return
2057+        # We favor lower numbered shares, since FEC is faster with
2058+        # primary shares than with other shares, and lower-numbered
2059+        # shares are more likely to be primary than higher numbered
2060+        # shares.
2061+        active_shnums = set(sorted(self.remaining_sharemap.keys()))
2062+        # We shouldn't consider adding shares that we already have; this
2063+        # will cause problems later.
2064+        active_shnums -= set([reader.shnum for reader in self._active_readers])
2065+        active_shnums = list(active_shnums)[:needed]
2066+        if len(active_shnums) < needed:
2067+            # We don't have enough readers to retrieve the file; fail.
2068+            return self._failed()
2069 
2070hunk ./src/allmydata/mutable/retrieve.py 323
2071-        # note that we only ask for a single share per query, so we only
2072-        # expect a single share back. On the other hand, we use the extra
2073-        # shares if we get them.. seems better than an assert().
2074+        for shnum in active_shnums:
2075+            self._active_readers.append(self.readers[shnum])
2076+            self.log("added reader for share %d" % shnum)
2077+        assert len(self._active_readers) == self._required_shares
2078+        # Conceptually, this is part of the _add_active_peers step. It
2079+        # validates the prefixes of newly added readers to make sure
2080+        # that they match what we are expecting for self.verinfo. If
2081+        # validation is successful, _validate_active_prefixes will call
2082+        # _download_current_segment for us. If validation is
2083+        # unsuccessful, then _validate_prefixes will remove the peer and
2084+        # call _add_active_peers again, where we will attempt to rectify
2085+        # the problem by choosing another peer.
2086+        return self._validate_active_prefixes()
2087 
2088hunk ./src/allmydata/mutable/retrieve.py 337
2089-        for shnum,datav in datavs.items():
2090-            (prefix, hash_and_data) = datav[:2]
2091-            try:
2092-                self._got_results_one_share(shnum, peerid,
2093-                                            prefix, hash_and_data)
2094-            except CorruptShareError, e:
2095-                # log it and give the other shares a chance to be processed
2096-                f = failure.Failure()
2097-                self.log(format="bad share: %(f_value)s",
2098-                         f_value=str(f.value), failure=f,
2099-                         level=log.WEIRD, umid="7fzWZw")
2100-                self.notify_server_corruption(peerid, shnum, str(e))
2101-                self.remove_peer(peerid)
2102-                self.servermap.mark_bad_share(peerid, shnum, prefix)
2103-                self._bad_shares.add( (peerid, shnum) )
2104-                self._status.problems[peerid] = f
2105-                self._last_failure = f
2106-                pass
2107-            if self._need_privkey and len(datav) > 2:
2108-                lp = None
2109-                self._try_to_validate_privkey(datav[2], peerid, shnum, lp)
2110-        # all done!
2111 
2112hunk ./src/allmydata/mutable/retrieve.py 338
2113-    def notify_server_corruption(self, peerid, shnum, reason):
2114-        ss = self.servermap.connections[peerid]
2115-        ss.callRemoteOnly("advise_corrupt_share",
2116-                          "mutable", self._storage_index, shnum, reason)
2117+    def _validate_active_prefixes(self):
2118+        """
2119+        I check to make sure that the prefixes on the peers that I am
2120+        currently reading from match the prefix that we want to see, as
2121+        said in self.verinfo.
2122 
2123hunk ./src/allmydata/mutable/retrieve.py 344
2124-    def _got_results_one_share(self, shnum, peerid,
2125-                               got_prefix, got_hash_and_data):
2126-        self.log("_got_results: got shnum #%d from peerid %s"
2127-                 % (shnum, idlib.shortnodeid_b2a(peerid)))
2128-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2129+        If I find that all of the active peers have acceptable prefixes,
2130+        I pass control to _download_current_segment, which will use
2131+        those peers to do cool things. If I find that some of the active
2132+        peers have unacceptable prefixes, I will remove them from active
2133+        peers (and from further consideration) and call
2134+        _add_active_peers to attempt to rectify the situation. I keep
2135+        track of which peers I have already validated so that I don't
2136+        need to do so again.
2137+        """
2138+        assert self._active_readers, "No more active readers"
2139+
2140+        ds = []
2141+        new_readers = set(self._active_readers) - self._validated_readers
2142+        self.log('validating %d newly-added active readers' % len(new_readers))
2143+
2144+        for reader in new_readers:
2145+            # We force a remote read here -- otherwise, we are relying
2146+            # on cached data that we already verified as valid, and we
2147+            # won't detect an uncoordinated write that has occurred
2148+            # since the last servermap update.
2149+            d = reader.get_prefix(force_remote=True)
2150+            d.addCallback(self._try_to_validate_prefix, reader)
2151+            ds.append(d)
2152+        dl = defer.DeferredList(ds, consumeErrors=True)
2153+        def _check_results(results):
2154+            # Each result in results will be of the form (success, msg).
2155+            # We don't care about msg, but success will tell us whether
2156+            # or not the checkstring validated. If it didn't, we need to
2157+            # remove the offending (peer,share) from our active readers,
2158+            # and ensure that active readers is again populated.
2159+            bad_readers = []
2160+            for i, result in enumerate(results):
2161+                if not result[0]:
2162+                    reader = self._active_readers[i]
2163+                    f = result[1]
2164+                    assert isinstance(f, failure.Failure)
2165+
2166+                    self.log("The reader %s failed to "
2167+                             "properly validate: %s" % \
2168+                             (reader, str(f.value)))
2169+                    bad_readers.append((reader, f))
2170+                else:
2171+                    reader = self._active_readers[i]
2172+                    self.log("the reader %s checks out, so we'll use it" % \
2173+                             reader)
2174+                    self._validated_readers.add(reader)
2175+                    # Each time we validate a reader, we check to see if
2176+                    # we need the private key. If we do, we politely ask
2177+                    # for it and then continue computing. If we find
2178+                    # that we haven't gotten it at the end of
2179+                    # segment decoding, then we'll take more drastic
2180+                    # measures.
2181+                    if self._need_privkey:
2182+                        d = reader.get_encprivkey()
2183+                        d.addCallback(self._try_to_validate_privkey, reader)
2184+            if bad_readers:
2185+                # We do them all at once, or else we screw up list indexing.
2186+                for (reader, f) in bad_readers:
2187+                    self._mark_bad_share(reader, f)
2188+                return self._add_active_peers()
2189+            else:
2190+                return self._download_current_segment()
2191+            # The next step will assert that it has enough active
2192+            # readers to fetch shares; we just need to remove it.
2193+        dl.addCallback(_check_results)
2194+        return dl
2195+
2196+
2197+    def _try_to_validate_prefix(self, prefix, reader):
2198+        """
2199+        I check that the prefix returned by a candidate server for
2200+        retrieval matches the prefix that the servermap knows about
2201+        (and, hence, the prefix that was validated earlier). If it does,
2202+        I return True, which means that I approve of the use of the
2203+        candidate server for segment retrieval. If it doesn't, I return
2204+        False, which means that another server must be chosen.
2205+        """
2206+        (seqnum,
2207+         root_hash,
2208+         IV,
2209+         segsize,
2210+         datalength,
2211+         k,
2212+         N,
2213+         known_prefix,
2214          offsets_tuple) = self.verinfo
2215hunk ./src/allmydata/mutable/retrieve.py 430
2216-        assert len(got_prefix) == len(prefix), (len(got_prefix), len(prefix))
2217-        if got_prefix != prefix:
2218-            msg = "someone wrote to the data since we read the servermap: prefix changed"
2219-            raise UncoordinatedWriteError(msg)
2220-        (share_hash_chain, block_hash_tree,
2221-         share_data) = unpack_share_data(self.verinfo, got_hash_and_data)
2222+        if known_prefix != prefix:
2223+            self.log("prefix from share %d doesn't match" % reader.shnum)
2224+            raise UncoordinatedWriteError("Mismatched prefix -- this could "
2225+                                          "indicate an uncoordinated write")
2226+        # Otherwise, we're okay -- no issues.
2227 
2228hunk ./src/allmydata/mutable/retrieve.py 436
2229-        assert isinstance(share_data, str)
2230-        # build the block hash tree. SDMF has only one leaf.
2231-        leaves = [hashutil.block_hash(share_data)]
2232-        t = hashtree.HashTree(leaves)
2233-        if list(t) != block_hash_tree:
2234-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
2235-        share_hash_leaf = t[0]
2236-        t2 = hashtree.IncompleteHashTree(N)
2237-        # root_hash was checked by the signature
2238-        t2.set_hashes({0: root_hash})
2239-        try:
2240-            t2.set_hashes(hashes=share_hash_chain,
2241-                          leaves={shnum: share_hash_leaf})
2242-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
2243-                IndexError), e:
2244-            msg = "corrupt hashes: %s" % (e,)
2245-            raise CorruptShareError(peerid, shnum, msg)
2246-        self.log(" data valid! len=%d" % len(share_data))
2247-        # each query comes down to this: placing validated share data into
2248-        # self.shares
2249-        self.shares[shnum] = share_data
2250 
2251hunk ./src/allmydata/mutable/retrieve.py 437
2252-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
2253+    def _remove_reader(self, reader):
2254+        """
2255+        At various points, we will wish to remove a peer from
2256+        consideration and/or use. These include, but are not necessarily
2257+        limited to:
2258 
2259hunk ./src/allmydata/mutable/retrieve.py 443
2260-        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2261-        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2262-        if alleged_writekey != self._node.get_writekey():
2263-            self.log("invalid privkey from %s shnum %d" %
2264-                     (idlib.nodeid_b2a(peerid)[:8], shnum),
2265-                     parent=lp, level=log.WEIRD, umid="YIw4tA")
2266-            return
2267+            - A connection error.
2268+            - A mismatched prefix (that is, a prefix that does not match
2269+              our conception of the version information string).
2270+            - A failing block hash, salt hash, or share hash, which can
2271+              indicate disk failure/bit flips, or network trouble.
2272 
2273hunk ./src/allmydata/mutable/retrieve.py 449
2274-        # it's good
2275-        self.log("got valid privkey from shnum %d on peerid %s" %
2276-                 (shnum, idlib.shortnodeid_b2a(peerid)),
2277-                 parent=lp)
2278-        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2279-        self._node._populate_encprivkey(enc_privkey)
2280-        self._node._populate_privkey(privkey)
2281-        self._need_privkey = False
2282+        This method will do that. I will make sure that the
2283+        (shnum,reader) combination represented by my reader argument is
2284+        not used for anything else during this download. I will not
2285+        advise the reader of any corruption, something that my callers
2286+        may wish to do on their own.
2287+        """
2288+        # TODO: When you're done writing this, see if this is ever
2289+        # actually used for something that _mark_bad_share isn't. I have
2290+        # a feeling that they will be used for very similar things, and
2291+        # that having them both here is just going to be an epic amount
2292+        # of code duplication.
2293+        #
2294+        # (well, okay, not epic, but meaningful)
2295+        self.log("removing reader %s" % reader)
2296+        # Remove the reader from _active_readers
2297+        self._active_readers.remove(reader)
2298+        # TODO: self.readers.remove(reader)?
2299+        for shnum in list(self.remaining_sharemap.keys()):
2300+            self.remaining_sharemap.discard(shnum, reader.peerid)
2301 
2302hunk ./src/allmydata/mutable/retrieve.py 469
2303-    def _query_failed(self, f, marker, peerid):
2304-        self.log(format="query to [%(peerid)s] failed",
2305-                 peerid=idlib.shortnodeid_b2a(peerid),
2306-                 level=log.NOISY)
2307-        self._status.problems[peerid] = f
2308-        self._outstanding_queries.pop(marker, None)
2309-        if not self._running:
2310-            return
2311-        self._last_failure = f
2312-        self.remove_peer(peerid)
2313-        level = log.WEIRD
2314-        if f.check(DeadReferenceError):
2315-            level = log.UNUSUAL
2316-        self.log(format="error during query: %(f_value)s",
2317-                 f_value=str(f.value), failure=f, level=level, umid="gOJB5g")
2318 
2319hunk ./src/allmydata/mutable/retrieve.py 470
2320-    def _check_for_done(self, res):
2321-        # exit paths:
2322-        #  return : keep waiting, no new queries
2323-        #  return self._send_more_queries(outstanding) : send some more queries
2324-        #  fire self._done(plaintext) : download successful
2325-        #  raise exception : download fails
2326+    def _mark_bad_share(self, reader, f):
2327+        """
2328+        I mark the (peerid, shnum) encapsulated by my reader argument as
2329+        a bad share, which means that it will not be used anywhere else.
2330 
2331hunk ./src/allmydata/mutable/retrieve.py 475
2332-        self.log(format="_check_for_done: running=%(running)s, decoding=%(decoding)s",
2333-                 running=self._running, decoding=self._decoding,
2334-                 level=log.NOISY)
2335-        if not self._running:
2336-            return
2337-        if self._decoding:
2338-            return
2339-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2340-         offsets_tuple) = self.verinfo
2341+        There are several reasons to want to mark something as a bad
2342+        share. These include:
2343 
2344hunk ./src/allmydata/mutable/retrieve.py 478
2345-        if len(self.shares) < k:
2346-            # we don't have enough shares yet
2347-            return self._maybe_send_more_queries(k)
2348-        if self._need_privkey:
2349-            # we got k shares, but none of them had a valid privkey. TODO:
2350-            # look further. Adding code to do this is a bit complicated, and
2351-            # I want to avoid that complication, and this should be pretty
2352-            # rare (k shares with bitflips in the enc_privkey but not in the
2353-            # data blocks). If we actually do get here, the subsequent repair
2354-            # will fail for lack of a privkey.
2355-            self.log("got k shares but still need_privkey, bummer",
2356-                     level=log.WEIRD, umid="MdRHPA")
2357+            - A connection error to the peer.
2358+            - A mismatched prefix (that is, a prefix that does not match
2359+              our local conception of the version information string).
2360+            - A failing block hash, salt hash, share hash, or other
2361+              integrity check.
2362 
2363hunk ./src/allmydata/mutable/retrieve.py 484
2364-        # we have enough to finish. All the shares have had their hashes
2365-        # checked, so if something fails at this point, we don't know how
2366-        # to fix it, so the download will fail.
2367+        This method will ensure that readers that we wish to mark bad
2368+        (for these reasons or other reasons) are not used for the rest
2369+        of the download. Additionally, it will attempt to tell the
2370+        remote peer (with no guarantee of success) that its share is
2371+        corrupt.
2372+        """
2373+        self.log("marking share %d on server %s as bad" % \
2374+                 (reader.shnum, reader))
2375+        self._remove_reader(reader)
2376+        self._bad_shares.add((reader.peerid, reader.shnum))
2377+        self._status.problems[reader.peerid] = f
2378+        self._last_failure = f
2379+        self.notify_server_corruption(reader.peerid, reader.shnum,
2380+                                      str(f.value))
2381 
2382hunk ./src/allmydata/mutable/retrieve.py 499
2383-        self._decoding = True # avoid reentrancy
2384-        self._status.set_status("decoding")
2385-        now = time.time()
2386-        elapsed = now - self._started
2387-        self._status.timings["fetch"] = elapsed
2388 
2389hunk ./src/allmydata/mutable/retrieve.py 500
2390-        d = defer.maybeDeferred(self._decode)
2391-        d.addCallback(self._decrypt, IV, self._node.get_readkey())
2392-        d.addBoth(self._done)
2393-        return d # purely for test convenience
2394+    def _download_current_segment(self):
2395+        """
2396+        I download, validate, decode, decrypt, and assemble the segment
2397+        that this Retrieve is currently responsible for downloading.
2398+        """
2399+        assert len(self._active_readers) >= self._required_shares
2400+        if self._current_segment < self._num_segments:
2401+            d = self._process_segment(self._current_segment)
2402+        else:
2403+            d = defer.succeed(None)
2404+        d.addCallback(self._check_for_done)
2405+        return d
2406 
2407hunk ./src/allmydata/mutable/retrieve.py 513
2408-    def _maybe_send_more_queries(self, k):
2409-        # we don't have enough shares yet. Should we send out more queries?
2410-        # There are some number of queries outstanding, each for a single
2411-        # share. If we can generate 'needed_shares' additional queries, we do
2412-        # so. If we can't, then we know this file is a goner, and we raise
2413-        # NotEnoughSharesError.
2414-        self.log(format=("_maybe_send_more_queries, have=%(have)d, k=%(k)d, "
2415-                         "outstanding=%(outstanding)d"),
2416-                 have=len(self.shares), k=k,
2417-                 outstanding=len(self._outstanding_queries),
2418-                 level=log.NOISY)
2419 
2420hunk ./src/allmydata/mutable/retrieve.py 514
2421-        remaining_shares = k - len(self.shares)
2422-        needed = remaining_shares - len(self._outstanding_queries)
2423-        if not needed:
2424-            # we have enough queries in flight already
2425+    def _process_segment(self, segnum):
2426+        """
2427+        I download, validate, decode, and decrypt one segment of the
2428+        file that this Retrieve is retrieving. This means coordinating
2429+        the process of getting k blocks of that file, validating them,
2430+        assembling them into one segment with the decoder, and then
2431+        decrypting them.
2432+        """
2433+        self.log("processing segment %d" % segnum)
2434 
2435hunk ./src/allmydata/mutable/retrieve.py 524
2436-            # TODO: but if they've been in flight for a long time, and we
2437-            # have reason to believe that new queries might respond faster
2438-            # (i.e. we've seen other queries come back faster, then consider
2439-            # sending out new queries. This could help with peers which have
2440-            # silently gone away since the servermap was updated, for which
2441-            # we're still waiting for the 15-minute TCP disconnect to happen.
2442-            self.log("enough queries are in flight, no more are needed",
2443-                     level=log.NOISY)
2444-            return
2445+        # TODO: The old code uses a marker. Should this code do that
2446+        # too? What did the Marker do?
2447+        assert len(self._active_readers) >= self._required_shares
2448+
2449+        # We need to ask each of our active readers for its block and
2450+        # salt. We will then validate those. If validation is
2451+        # successful, we will assemble the results into plaintext.
2452+        ds = []
2453+        for reader in self._active_readers:
2454+            d = reader.get_block_and_salt(segnum, queue=True)
2455+            d2 = self._get_needed_hashes(reader, segnum)
2456+            dl = defer.DeferredList([d, d2], consumeErrors=True)
2457+            dl.addCallback(self._validate_block, segnum, reader)
2458+            dl.addErrback(self._validation_or_decoding_failed, [reader])
2459+            ds.append(dl)
2460+            reader.flush()
2461+        dl = defer.DeferredList(ds)
2462+        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
2463+        return dl
2464 
2465hunk ./src/allmydata/mutable/retrieve.py 544
2466-        outstanding_shnums = set([shnum
2467-                                  for (peerid, shnum, started)
2468-                                  in self._outstanding_queries.values()])
2469-        # prefer low-numbered shares, they are more likely to be primary
2470-        available_shnums = sorted(self.remaining_sharemap.keys())
2471-        for shnum in available_shnums:
2472-            if shnum in outstanding_shnums:
2473-                # skip ones that are already in transit
2474-                continue
2475-            if shnum not in self.remaining_sharemap:
2476-                # no servers for that shnum. note that DictOfSets removes
2477-                # empty sets from the dict for us.
2478-                continue
2479-            peerid = list(self.remaining_sharemap[shnum])[0]
2480-            # get_data will remove that peerid from the sharemap, and add the
2481-            # query to self._outstanding_queries
2482-            self._status.set_status("Retrieving More Shares")
2483-            self.get_data(shnum, peerid)
2484-            needed -= 1
2485-            if not needed:
2486+
2487+    def _maybe_decode_and_decrypt_segment(self, blocks_and_salts, segnum):
2488+        """
2489+        I take the results of fetching and validating the blocks from a
2490+        callback chain in another method. If the results are such that
2491+        they tell me that validation and fetching succeeded without
2492+        incident, I will proceed with decoding and decryption.
2493+        Otherwise, I will do nothing.
2494+        """
2495+        self.log("trying to decode and decrypt segment %d" % segnum)
2496+        failures = False
2497+        for block_and_salt in blocks_and_salts:
2498+            if not block_and_salt[0] or block_and_salt[1] == None:
2499+                self.log("some validation operations failed; not proceeding")
2500+                failures = True
2501                 break
2502hunk ./src/allmydata/mutable/retrieve.py 560
2503+        if not failures:
2504+            self.log("everything looks ok, building segment %d" % segnum)
2505+            d = self._decode_blocks(blocks_and_salts, segnum)
2506+            d.addCallback(self._decrypt_segment)
2507+            d.addErrback(self._validation_or_decoding_failed,
2508+                         self._active_readers)
2509+            d.addCallback(self._set_segment)
2510+            return d
2511+        else:
2512+            return defer.succeed(None)
2513+
2514+
2515+    def _set_segment(self, segment):
2516+        """
2517+        Given a plaintext segment, I register that segment with the
2518+        target that is handling the file download.
2519+        """
2520+        self.log("got plaintext for segment %d" % self._current_segment)
2521+        self._plaintext += segment
2522+        self._current_segment += 1
2523 
2524hunk ./src/allmydata/mutable/retrieve.py 581
2525-        # at this point, we have as many outstanding queries as we can. If
2526-        # needed!=0 then we might not have enough to recover the file.
2527-        if needed:
2528-            format = ("ran out of peers: "
2529-                      "have %(have)d shares (k=%(k)d), "
2530-                      "%(outstanding)d queries in flight, "
2531-                      "need %(need)d more, "
2532-                      "found %(bad)d bad shares")
2533-            args = {"have": len(self.shares),
2534-                    "k": k,
2535-                    "outstanding": len(self._outstanding_queries),
2536-                    "need": needed,
2537-                    "bad": len(self._bad_shares),
2538-                    }
2539-            self.log(format=format,
2540-                     level=log.WEIRD, umid="ezTfjw", **args)
2541-            err = NotEnoughSharesError("%s, last failure: %s" %
2542-                                      (format % args, self._last_failure))
2543-            if self._bad_shares:
2544-                self.log("We found some bad shares this pass. You should "
2545-                         "update the servermap and try again to check "
2546-                         "more peers",
2547-                         level=log.WEIRD, umid="EFkOlA")
2548-                err.servermap = self.servermap
2549-            raise err
2550 
2551hunk ./src/allmydata/mutable/retrieve.py 582
2552+    def _validation_or_decoding_failed(self, f, readers):
2553+        """
2554+        I am called when a block or a salt fails to correctly validate, or when
2555+        the decryption or decoding operation fails for some reason.  I react to
2556+        this failure by notifying the remote server of corruption, and then
2557+        removing the remote peer from further activity.
2558+        """
2559+        assert isinstance(readers, list)
2560+        bad_shnums = [reader.shnum for reader in readers]
2561+
2562+        self.log("validation or decoding failed on share(s) %s, peer(s) %s "
2563+                 ", segment %d: %s" % \
2564+                 (bad_shnums, readers, self._current_segment, str(f)))
2565+        for reader in readers:
2566+            self._mark_bad_share(reader, f)
2567         return
2568 
2569hunk ./src/allmydata/mutable/retrieve.py 599
2570-    def _decode(self):
2571-        started = time.time()
2572-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2573-         offsets_tuple) = self.verinfo
2574 
2575hunk ./src/allmydata/mutable/retrieve.py 600
2576-        # shares_dict is a dict mapping shnum to share data, but the codec
2577-        # wants two lists.
2578-        shareids = []; shares = []
2579-        for shareid, share in self.shares.items():
2580+    def _validate_block(self, results, segnum, reader):
2581+        """
2582+        I validate a block from one share on a remote server.
2583+        """
2584+        # Grab the part of the block hash tree that is necessary to
2585+        # validate this block, then generate the block hash root.
2586+        self.log("validating share %d for segment %d" % (reader.shnum,
2587+                                                             segnum))
2588+        # Did we fail to fetch either of the things that we were
2589+        # supposed to? Fail if so.
2590+        if not results[0][0] and results[1][0]:
2591+            # handled by the errback handler.
2592+
2593+            # These all get batched into one query, so the resulting
2594+            # failure should be the same for all of them, so we can just
2595+            # use the first one.
2596+            assert isinstance(results[0][1], failure.Failure)
2597+
2598+            f = results[0][1]
2599+            raise CorruptShareError(reader.peerid,
2600+                                    reader.shnum,
2601+                                    "Connection error: %s" % str(f))
2602+
2603+        block_and_salt, block_and_sharehashes = results
2604+        block, salt = block_and_salt[1]
2605+        blockhashes, sharehashes = block_and_sharehashes[1]
2606+
2607+        blockhashes = dict(enumerate(blockhashes[1]))
2608+        self.log("the reader gave me the following blockhashes: %s" % \
2609+                 blockhashes.keys())
2610+        self.log("the reader gave me the following sharehashes: %s" % \
2611+                 sharehashes[1].keys())
2612+        bht = self._block_hash_trees[reader.shnum]
2613+
2614+        if bht.needed_hashes(segnum, include_leaf=True):
2615+            try:
2616+                bht.set_hashes(blockhashes)
2617+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2618+                    IndexError), e:
2619+                raise CorruptShareError(reader.peerid,
2620+                                        reader.shnum,
2621+                                        "block hash tree failure: %s" % e)
2622+
2623+        if self._version == MDMF_VERSION:
2624+            blockhash = hashutil.block_hash(salt + block)
2625+        else:
2626+            blockhash = hashutil.block_hash(block)
2627+        # If this works without an error, then validation is
2628+        # successful.
2629+        try:
2630+           bht.set_hashes(leaves={segnum: blockhash})
2631+        except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2632+                IndexError), e:
2633+            raise CorruptShareError(reader.peerid,
2634+                                    reader.shnum,
2635+                                    "block hash tree failure: %s" % e)
2636+
2637+        # Reaching this point means that we know that this segment
2638+        # is correct. Now we need to check to see whether the share
2639+        # hash chain is also correct.
2640+        # SDMF wrote share hash chains that didn't contain the
2641+        # leaves, which would be produced from the block hash tree.
2642+        # So we need to validate the block hash tree first. If
2643+        # successful, then bht[0] will contain the root for the
2644+        # shnum, which will be a leaf in the share hash tree, which
2645+        # will allow us to validate the rest of the tree.
2646+        if self.share_hash_tree.needed_hashes(reader.shnum,
2647+                                               include_leaf=True):
2648+            try:
2649+                self.share_hash_tree.set_hashes(hashes=sharehashes[1],
2650+                                            leaves={reader.shnum: bht[0]})
2651+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2652+                    IndexError), e:
2653+                raise CorruptShareError(reader.peerid,
2654+                                        reader.shnum,
2655+                                        "corrupt hashes: %s" % e)
2656+
2657+        # TODO: Validate the salt, too.
2658+        self.log('share %d is valid for segment %d' % (reader.shnum,
2659+                                                       segnum))
2660+        return {reader.shnum: (block, salt)}
2661+
2662+
2663+    def _get_needed_hashes(self, reader, segnum):
2664+        """
2665+        I get the hashes needed to validate segnum from the reader, then return
2666+        to my caller when this is done.
2667+        """
2668+        bht = self._block_hash_trees[reader.shnum]
2669+        needed = bht.needed_hashes(segnum, include_leaf=True)
2670+        # The root of the block hash tree is also a leaf in the share
2671+        # hash tree. So we don't need to fetch it from the remote
2672+        # server. In the case of files with one segment, this means that
2673+        # we won't fetch any block hash tree from the remote server,
2674+        # since the hash of each share of the file is the entire block
2675+        # hash tree, and is a leaf in the share hash tree. This is fine,
2676+        # since any share corruption will be detected in the share hash
2677+        # tree.
2678+        #needed.discard(0)
2679+        self.log("getting blockhashes for segment %d, share %d: %s" % \
2680+                 (segnum, reader.shnum, str(needed)))
2681+        d1 = reader.get_blockhashes(needed, queue=True, force_remote=True)
2682+        if self.share_hash_tree.needed_hashes(reader.shnum):
2683+            need = self.share_hash_tree.needed_hashes(reader.shnum)
2684+            self.log("also need sharehashes for share %d: %s" % (reader.shnum,
2685+                                                                 str(need)))
2686+            d2 = reader.get_sharehashes(need, queue=True, force_remote=True)
2687+        else:
2688+            d2 = defer.succeed({}) # the logic in the next method
2689+                                   # expects a dict
2690+        dl = defer.DeferredList([d1, d2], consumeErrors=True)
2691+        return dl
2692+
2693+
2694+    def _decode_blocks(self, blocks_and_salts, segnum):
2695+        """
2696+        I take a list of k blocks and salts, and decode that into a
2697+        single encrypted segment.
2698+        """
2699+        d = {}
2700+        # We want to merge our dictionaries to the form
2701+        # {shnum: blocks_and_salts}
2702+        #
2703+        # The dictionaries come from validate block that way, so we just
2704+        # need to merge them.
2705+        for block_and_salt in blocks_and_salts:
2706+            d.update(block_and_salt[1])
2707+
2708+        # All of these blocks should have the same salt; in SDMF, it is
2709+        # the file-wide IV, while in MDMF it is the per-segment salt. In
2710+        # either case, we just need to get one of them and use it.
2711+        #
2712+        # d.items()[0] is like (shnum, (block, salt))
2713+        # d.items()[0][1] is like (block, salt)
2714+        # d.items()[0][1][1] is the salt.
2715+        salt = d.items()[0][1][1]
2716+        # Next, extract just the blocks from the dict. We'll use the
2717+        # salt in the next step.
2718+        share_and_shareids = [(k, v[0]) for k, v in d.items()]
2719+        d2 = dict(share_and_shareids)
2720+        shareids = []
2721+        shares = []
2722+        for shareid, share in d2.items():
2723             shareids.append(shareid)
2724             shares.append(share)
2725 
2726hunk ./src/allmydata/mutable/retrieve.py 746
2727-        assert len(shareids) >= k, len(shareids)
2728+        assert len(shareids) >= self._required_shares, len(shareids)
2729         # zfec really doesn't want extra shares
2730hunk ./src/allmydata/mutable/retrieve.py 748
2731-        shareids = shareids[:k]
2732-        shares = shares[:k]
2733-
2734-        fec = codec.CRSDecoder()
2735-        fec.set_params(segsize, k, N)
2736-
2737-        self.log("params %s, we have %d shares" % ((segsize, k, N), len(shares)))
2738-        self.log("about to decode, shareids=%s" % (shareids,))
2739-        d = defer.maybeDeferred(fec.decode, shares, shareids)
2740-        def _done(buffers):
2741-            self._status.timings["decode"] = time.time() - started
2742-            self.log(" decode done, %d buffers" % len(buffers))
2743+        shareids = shareids[:self._required_shares]
2744+        shares = shares[:self._required_shares]
2745+        self.log("decoding segment %d" % segnum)
2746+        if segnum == self._num_segments - 1:
2747+            d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids)
2748+        else:
2749+            d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids)
2750+        def _process(buffers):
2751             segment = "".join(buffers)
2752hunk ./src/allmydata/mutable/retrieve.py 757
2753+            self.log(format="now decoding segment %(segnum)s of %(numsegs)s",
2754+                     segnum=segnum,
2755+                     numsegs=self._num_segments,
2756+                     level=log.NOISY)
2757             self.log(" joined length %d, datalength %d" %
2758hunk ./src/allmydata/mutable/retrieve.py 762
2759-                     (len(segment), datalength))
2760-            segment = segment[:datalength]
2761+                     (len(segment), self._data_length))
2762+            if segnum == self._num_segments - 1:
2763+                size_to_use = self._tail_data_size
2764+            else:
2765+                size_to_use = self._segment_size
2766+            segment = segment[:size_to_use]
2767             self.log(" segment len=%d" % len(segment))
2768hunk ./src/allmydata/mutable/retrieve.py 769
2769-            return segment
2770-        def _err(f):
2771-            self.log(" decode failed: %s" % f)
2772-            return f
2773-        d.addCallback(_done)
2774-        d.addErrback(_err)
2775+            return segment, salt
2776+        d.addCallback(_process)
2777         return d
2778 
2779hunk ./src/allmydata/mutable/retrieve.py 773
2780-    def _decrypt(self, crypttext, IV, readkey):
2781+
2782+    def _decrypt_segment(self, segment_and_salt):
2783+        """
2784+        I take a single segment and its salt, and decrypt it. I return
2785+        the plaintext of the segment that is in my argument.
2786+        """
2787+        segment, salt = segment_and_salt
2788         self._status.set_status("decrypting")
2789hunk ./src/allmydata/mutable/retrieve.py 781
2790+        self.log("decrypting segment %d" % self._current_segment)
2791         started = time.time()
2792hunk ./src/allmydata/mutable/retrieve.py 783
2793-        key = hashutil.ssk_readkey_data_hash(IV, readkey)
2794+        key = hashutil.ssk_readkey_data_hash(salt, self._node.get_readkey())
2795         decryptor = AES(key)
2796hunk ./src/allmydata/mutable/retrieve.py 785
2797-        plaintext = decryptor.process(crypttext)
2798+        plaintext = decryptor.process(segment)
2799         self._status.timings["decrypt"] = time.time() - started
2800         return plaintext
2801 
2802hunk ./src/allmydata/mutable/retrieve.py 789
2803-    def _done(self, res):
2804-        if not self._running:
2805+
2806+    def notify_server_corruption(self, peerid, shnum, reason):
2807+        ss = self.servermap.connections[peerid]
2808+        ss.callRemoteOnly("advise_corrupt_share",
2809+                          "mutable", self._storage_index, shnum, reason)
2810+
2811+
2812+    def _try_to_validate_privkey(self, enc_privkey, reader):
2813+
2814+        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2815+        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2816+        if alleged_writekey != self._node.get_writekey():
2817+            self.log("invalid privkey from %s shnum %d" %
2818+                     (reader, reader.shnum),
2819+                     level=log.WEIRD, umid="YIw4tA")
2820             return
2821hunk ./src/allmydata/mutable/retrieve.py 805
2822-        self._running = False
2823-        self._status.set_active(False)
2824-        self._status.timings["total"] = time.time() - self._started
2825-        # res is either the new contents, or a Failure
2826-        if isinstance(res, failure.Failure):
2827-            self.log("Retrieve done, with failure", failure=res,
2828-                     level=log.UNUSUAL)
2829-            self._status.set_status("Failed")
2830-        else:
2831-            self.log("Retrieve done, success!")
2832-            self._status.set_status("Finished")
2833-            self._status.set_progress(1.0)
2834-            # remember the encoding parameters, use them again next time
2835-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2836-             offsets_tuple) = self.verinfo
2837-            self._node._populate_required_shares(k)
2838-            self._node._populate_total_shares(N)
2839-        eventually(self._done_deferred.callback, res)
2840 
2841hunk ./src/allmydata/mutable/retrieve.py 806
2842+        # it's good
2843+        self.log("got valid privkey from shnum %d on reader %s" %
2844+                 (reader.shnum, reader))
2845+        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2846+        self._node._populate_encprivkey(enc_privkey)
2847+        self._node._populate_privkey(privkey)
2848+        self._need_privkey = False
2849+
2850+
2851+    def _check_for_done(self, res):
2852+        """
2853+        I check to see if this Retrieve object has successfully finished
2854+        its work.
2855+
2856+        I can exit in the following ways:
2857+            - If there are no more segments to download, then I exit by
2858+              causing self._done_deferred to fire with the plaintext
2859+              content requested by the caller.
2860+            - If there are still segments to be downloaded, and there
2861+              are enough active readers (readers which have not broken
2862+              and have not given us corrupt data) to continue
2863+              downloading, I send control back to
2864+              _download_current_segment.
2865+            - If there are still segments to be downloaded but there are
2866+              not enough active peers to download them, I ask
2867+              _add_active_peers to add more peers. If it is successful,
2868+              it will call _download_current_segment. If there are not
2869+              enough peers to retrieve the file, then that will cause
2870+              _done_deferred to errback.
2871+        """
2872+        self.log("checking for doneness")
2873+        if self._current_segment == self._num_segments:
2874+            # No more segments to download, we're done.
2875+            self.log("got plaintext, done")
2876+            return self._done()
2877+
2878+        if len(self._active_readers) >= self._required_shares:
2879+            # More segments to download, but we have enough good peers
2880+            # in self._active_readers that we can do that without issue,
2881+            # so go nab the next segment.
2882+            self.log("not done yet: on segment %d of %d" % \
2883+                     (self._current_segment + 1, self._num_segments))
2884+            return self._download_current_segment()
2885+
2886+        self.log("not done yet: on segment %d of %d, need to add peers" % \
2887+                 (self._current_segment + 1, self._num_segments))
2888+        return self._add_active_peers()
2889+
2890+
2891+    def _done(self):
2892+        """
2893+        I am called by _check_for_done when the download process has
2894+        finished successfully. After making some useful logging
2895+        statements, I return the decrypted contents to the owner of this
2896+        Retrieve object through self._done_deferred.
2897+        """
2898+        eventually(self._done_deferred.callback, self._plaintext)
2899+
2900+
2901+    def _failed(self):
2902+        """
2903+        I am called by _add_active_peers when there are not enough
2904+        active peers left to complete the download. After making some
2905+        useful logging statements, I return an exception to that effect
2906+        to the caller of this Retrieve object through
2907+        self._done_deferred.
2908+        """
2909+        format = ("ran out of peers: "
2910+                  "have %(have)d of %(total)d segments "
2911+                  "found %(bad)d bad shares "
2912+                  "encoding %(k)d-of-%(n)d")
2913+        args = {"have": self._current_segment,
2914+                "total": self._num_segments,
2915+                "k": self._required_shares,
2916+                "n": self._total_shares,
2917+                "bad": len(self._bad_shares)}
2918+        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
2919+                                                        str(self._last_failure)))
2920+        f = failure.Failure(e)
2921+        eventually(self._done_deferred.callback, f)
2922hunk ./src/allmydata/test/test_mutable.py 12
2923 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
2924      ssk_pubkey_fingerprint_hash
2925 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
2926-     NotEnoughSharesError
2927+     NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
2928 from allmydata.monitor import Monitor
2929 from allmydata.test.common import ShouldFailMixin
2930 from allmydata.test.no_network import GridTestMixin
2931hunk ./src/allmydata/test/test_mutable.py 28
2932 from allmydata.mutable.retrieve import Retrieve
2933 from allmydata.mutable.publish import Publish
2934 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
2935-from allmydata.mutable.layout import unpack_header, unpack_share
2936+from allmydata.mutable.layout import unpack_header, unpack_share, \
2937+                                     MDMFSlotReadProxy
2938 from allmydata.mutable.repairer import MustForceRepairError
2939 
2940 import allmydata.test.common_util as testutil
2941hunk ./src/allmydata/test/test_mutable.py 104
2942         d = fireEventually()
2943         d.addCallback(lambda res: _call())
2944         return d
2945+
2946     def callRemoteOnly(self, methname, *args, **kwargs):
2947         d = self.callRemote(methname, *args, **kwargs)
2948         d.addBoth(lambda ignore: None)
2949hunk ./src/allmydata/test/test_mutable.py 163
2950 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
2951     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
2952     # list of shnums to corrupt.
2953+    ds = []
2954     for peerid in s._peers:
2955         shares = s._peers[peerid]
2956         for shnum in shares:
2957hunk ./src/allmydata/test/test_mutable.py 190
2958                 else:
2959                     offset1 = offset
2960                     offset2 = 0
2961-                if offset1 == "pubkey":
2962+                if offset1 == "pubkey" and IV:
2963                     real_offset = 107
2964hunk ./src/allmydata/test/test_mutable.py 192
2965+                elif offset1 == "share_data" and not IV:
2966+                    real_offset = 104
2967                 elif offset1 in o:
2968                     real_offset = o[offset1]
2969                 else:
2970hunk ./src/allmydata/test/test_mutable.py 327
2971         d.addCallback(_created)
2972         return d
2973 
2974+
2975+    def test_upload_and_download_mdmf(self):
2976+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
2977+        def _created(n):
2978+            d = defer.succeed(None)
2979+            d.addCallback(lambda ignored:
2980+                n.get_servermap(MODE_READ))
2981+            def _then(servermap):
2982+                dumped = servermap.dump(StringIO())
2983+                self.failUnlessIn("3-of-10", dumped.getvalue())
2984+            d.addCallback(_then)
2985+            # Now overwrite the contents with some new contents. We want
2986+            # to make them big enough to force the file to be uploaded
2987+            # in more than one segment.
2988+            big_contents = "contents1" * 100000 # about 900 KiB
2989+            d.addCallback(lambda ignored:
2990+                n.overwrite(big_contents))
2991+            d.addCallback(lambda ignored:
2992+                n.download_best_version())
2993+            d.addCallback(lambda data:
2994+                self.failUnlessEqual(data, big_contents))
2995+            # Overwrite the contents again with some new contents. As
2996+            # before, they need to be big enough to force multiple
2997+            # segments, so that we make the downloader deal with
2998+            # multiple segments.
2999+            bigger_contents = "contents2" * 1000000 # about 9MiB
3000+            d.addCallback(lambda ignored:
3001+                n.overwrite(bigger_contents))
3002+            d.addCallback(lambda ignored:
3003+                n.download_best_version())
3004+            d.addCallback(lambda data:
3005+                self.failUnlessEqual(data, bigger_contents))
3006+            return d
3007+        d.addCallback(_created)
3008+        return d
3009+
3010+
3011     def test_create_with_initial_contents(self):
3012         d = self.nodemaker.create_mutable_file("contents 1")
3013         def _created(n):
3014hunk ./src/allmydata/test/test_mutable.py 1147
3015 
3016 
3017     def _test_corrupt_all(self, offset, substring,
3018-                          should_succeed=False, corrupt_early=True,
3019-                          failure_checker=None):
3020+                          should_succeed=False,
3021+                          corrupt_early=True,
3022+                          failure_checker=None,
3023+                          fetch_privkey=False):
3024         d = defer.succeed(None)
3025         if corrupt_early:
3026             d.addCallback(corrupt, self._storage, offset)
3027hunk ./src/allmydata/test/test_mutable.py 1167
3028                     self.failUnlessIn(substring, "".join(allproblems))
3029                 return servermap
3030             if should_succeed:
3031-                d1 = self._fn.download_version(servermap, ver)
3032+                d1 = self._fn.download_version(servermap, ver,
3033+                                               fetch_privkey)
3034                 d1.addCallback(lambda new_contents:
3035                                self.failUnlessEqual(new_contents, self.CONTENTS))
3036             else:
3037hunk ./src/allmydata/test/test_mutable.py 1175
3038                 d1 = self.shouldFail(NotEnoughSharesError,
3039                                      "_corrupt_all(offset=%s)" % (offset,),
3040                                      substring,
3041-                                     self._fn.download_version, servermap, ver)
3042+                                     self._fn.download_version, servermap,
3043+                                                                ver,
3044+                                                                fetch_privkey)
3045             if failure_checker:
3046                 d1.addCallback(failure_checker)
3047             d1.addCallback(lambda res: servermap)
3048hunk ./src/allmydata/test/test_mutable.py 1186
3049         return d
3050 
3051     def test_corrupt_all_verbyte(self):
3052-        # when the version byte is not 0, we hit an UnknownVersionError error
3053-        # in unpack_share().
3054+        # when the version byte is not 0 or 1, we hit an UnknownVersionError
3055+        # error in unpack_share().
3056         d = self._test_corrupt_all(0, "UnknownVersionError")
3057         def _check_servermap(servermap):
3058             # and the dump should mention the problems
3059hunk ./src/allmydata/test/test_mutable.py 1193
3060             s = StringIO()
3061             dump = servermap.dump(s).getvalue()
3062-            self.failUnless("10 PROBLEMS" in dump, dump)
3063+            self.failUnless("30 PROBLEMS" in dump, dump)
3064         d.addCallback(_check_servermap)
3065         return d
3066 
3067hunk ./src/allmydata/test/test_mutable.py 1263
3068         return self._test_corrupt_all("enc_privkey", None, should_succeed=True)
3069 
3070 
3071+    def test_corrupt_all_encprivkey_late(self):
3072+        # this should work for the same reason as above, but we corrupt
3073+        # after the servermap update to exercise the error handling
3074+        # code.
3075+        # We need to remove the privkey from the node, or the retrieve
3076+        # process won't know to update it.
3077+        self._fn._privkey = None
3078+        return self._test_corrupt_all("enc_privkey",
3079+                                      None, # this shouldn't fail
3080+                                      should_succeed=True,
3081+                                      corrupt_early=False,
3082+                                      fetch_privkey=True)
3083+
3084+
3085     def test_corrupt_all_seqnum_late(self):
3086         # corrupting the seqnum between mapupdate and retrieve should result
3087         # in NotEnoughSharesError, since each share will look invalid
3088hunk ./src/allmydata/test/test_mutable.py 1283
3089         def _check(res):
3090             f = res[0]
3091             self.failUnless(f.check(NotEnoughSharesError))
3092-            self.failUnless("someone wrote to the data since we read the servermap" in str(f))
3093+            self.failUnless("uncoordinated write" in str(f))
3094         return self._test_corrupt_all(1, "ran out of peers",
3095                                       corrupt_early=False,
3096                                       failure_checker=_check)
3097hunk ./src/allmydata/test/test_mutable.py 1333
3098                       self.failUnlessEqual(new_contents, self.CONTENTS))
3099         return d
3100 
3101-    def test_corrupt_some(self):
3102-        # corrupt the data of first five shares (so the servermap thinks
3103-        # they're good but retrieve marks them as bad), so that the
3104-        # MODE_READ set of 6 will be insufficient, forcing node.download to
3105-        # retry with more servers.
3106-        corrupt(None, self._storage, "share_data", range(5))
3107-        d = self.make_servermap()
3108+
3109+    def _test_corrupt_some(self, offset, mdmf=False):
3110+        if mdmf:
3111+            d = self.publish_mdmf()
3112+        else:
3113+            d = defer.succeed(None)
3114+        d.addCallback(lambda ignored:
3115+            corrupt(None, self._storage, offset, range(5)))
3116+        d.addCallback(lambda ignored:
3117+            self.make_servermap())
3118         def _do_retrieve(servermap):
3119             ver = servermap.best_recoverable_version()
3120             self.failUnless(ver)
3121hunk ./src/allmydata/test/test_mutable.py 1349
3122             return self._fn.download_best_version()
3123         d.addCallback(_do_retrieve)
3124         d.addCallback(lambda new_contents:
3125-                      self.failUnlessEqual(new_contents, self.CONTENTS))
3126+            self.failUnlessEqual(new_contents, self.CONTENTS))
3127         return d
3128 
3129hunk ./src/allmydata/test/test_mutable.py 1352
3130+
3131+    def test_corrupt_some(self):
3132+        # corrupt the data of first five shares (so the servermap thinks
3133+        # they're good but retrieve marks them as bad), so that the
3134+        # MODE_READ set of 6 will be insufficient, forcing node.download to
3135+        # retry with more servers.
3136+        return self._test_corrupt_some("share_data")
3137+
3138+
3139     def test_download_fails(self):
3140         d = corrupt(None, self._storage, "signature")
3141         d.addCallback(lambda ignored:
3142hunk ./src/allmydata/test/test_mutable.py 1366
3143             self.shouldFail(UnrecoverableFileError, "test_download_anyway",
3144                             "no recoverable versions",
3145-                            self._fn.download_best_version)
3146+                            self._fn.download_best_version))
3147         return d
3148 
3149 
3150hunk ./src/allmydata/test/test_mutable.py 1370
3151+
3152+    def test_corrupt_mdmf_block_hash_tree(self):
3153+        d = self.publish_mdmf()
3154+        d.addCallback(lambda ignored:
3155+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3156+                                   "block hash tree failure",
3157+                                   corrupt_early=False,
3158+                                   should_succeed=False))
3159+        return d
3160+
3161+
3162+    def test_corrupt_mdmf_block_hash_tree_late(self):
3163+        d = self.publish_mdmf()
3164+        d.addCallback(lambda ignored:
3165+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3166+                                   "block hash tree failure",
3167+                                   corrupt_early=True,
3168+                                   should_succeed=False))
3169+        return d
3170+
3171+
3172+    def test_corrupt_mdmf_share_data(self):
3173+        d = self.publish_mdmf()
3174+        d.addCallback(lambda ignored:
3175+            # TODO: Find out what the block size is and corrupt a
3176+            # specific block, rather than just guessing.
3177+            self._test_corrupt_all(("share_data", 12 * 40),
3178+                                    "block hash tree failure",
3179+                                    corrupt_early=True,
3180+                                    should_succeed=False))
3181+        return d
3182+
3183+
3184+    def test_corrupt_some_mdmf(self):
3185+        return self._test_corrupt_some(("share_data", 12 * 40),
3186+                                       mdmf=True)
3187+
3188+
3189 class CheckerMixin:
3190     def check_good(self, r, where):
3191         self.failUnless(r.is_healthy(), where)
3192hunk ./src/allmydata/test/test_mutable.py 2116
3193             d.addCallback(lambda res:
3194                           self.shouldFail(NotEnoughSharesError,
3195                                           "test_retrieve_surprise",
3196-                                          "ran out of peers: have 0 shares (k=3)",
3197+                                          "ran out of peers: have 0 of 1",
3198                                           n.download_version,
3199                                           self.old_map,
3200                                           self.old_map.best_recoverable_version(),
3201hunk ./src/allmydata/test/test_mutable.py 2125
3202         d.addCallback(_created)
3203         return d
3204 
3205+
3206     def test_unexpected_shares(self):
3207         # upload the file, take a servermap, shut down one of the servers,
3208         # upload it again (causing shares to appear on a new server), then
3209hunk ./src/allmydata/test/test_mutable.py 2329
3210         self.basedir = "mutable/Problems/test_privkey_query_missing"
3211         self.set_up_grid(num_servers=20)
3212         nm = self.g.clients[0].nodemaker
3213-        LARGE = "These are Larger contents" * 2000 # about 50KB
3214+        LARGE = "These are Larger contents" * 2000 # about 50KiB
3215         nm._node_cache = DevNullDictionary() # disable the nodecache
3216 
3217         d = nm.create_mutable_file(LARGE)
3218hunk ./src/allmydata/test/test_mutable.py 2342
3219         d.addCallback(_created)
3220         d.addCallback(lambda res: self.n2.get_servermap(MODE_WRITE))
3221         return d
3222+
3223+
3224+    def test_block_and_hash_query_error(self):
3225+        # This tests for what happens when a query to a remote server
3226+        # fails in either the hash validation step or the block getting
3227+        # step (because of batching, this is the same actual query).
3228+        # We need to have the storage server persist up until the point
3229+        # that its prefix is validated, then suddenly die. This
3230+        # exercises some exception handling code in Retrieve.
3231+        self.basedir = "mutable/Problems/test_block_and_hash_query_error"
3232+        self.set_up_grid(num_servers=20)
3233+        nm = self.g.clients[0].nodemaker
3234+        CONTENTS = "contents" * 2000
3235+        d = nm.create_mutable_file(CONTENTS)
3236+        def _created(node):
3237+            self._node = node
3238+        d.addCallback(_created)
3239+        d.addCallback(lambda ignored:
3240+            self._node.get_servermap(MODE_READ))
3241+        def _then(servermap):
3242+            # we have our servermap. Now we set up the servers like the
3243+            # tests above -- the first one that gets a read call should
3244+            # start throwing errors, but only after returning its prefix
3245+            # for validation. Since we'll download without fetching the
3246+            # private key, the next query to the remote server will be
3247+            # for either a block and salt or for hashes, either of which
3248+            # will exercise the error handling code.
3249+            killer = FirstServerGetsKilled()
3250+            for (serverid, ss) in nm.storage_broker.get_all_servers():
3251+                ss.post_call_notifier = killer.notify
3252+            ver = servermap.best_recoverable_version()
3253+            assert ver
3254+            return self._node.download_version(servermap, ver)
3255+        d.addCallback(_then)
3256+        d.addCallback(lambda data:
3257+            self.failUnlessEqual(data, CONTENTS))
3258+        return d
3259}
3260[mutable/checker.py: check MDMF files
3261Kevan Carstensen <kevan@isnotajoke.com>**20100628225048
3262 Ignore-this: fb697b36285d60552df6ca5ac6a37629
3263 
3264 This patch adapts the mutable file checker and verifier to check and
3265 verify MDMF files. It does this by using the new segmented downloader,
3266 which is trained to perform verification operations on request. This
3267 removes some code duplication.
3268] {
3269hunk ./src/allmydata/mutable/checker.py 12
3270 from allmydata.mutable.common import MODE_CHECK, CorruptShareError
3271 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
3272 from allmydata.mutable.layout import unpack_share, SIGNED_PREFIX_LENGTH
3273+from allmydata.mutable.retrieve import Retrieve # for verifying
3274 
3275 class MutableChecker:
3276 
3277hunk ./src/allmydata/mutable/checker.py 29
3278 
3279     def check(self, verify=False, add_lease=False):
3280         servermap = ServerMap()
3281+        # Updating the servermap in MODE_CHECK will stand a good chance
3282+        # of finding all of the shares, and getting a good idea of
3283+        # recoverability, etc, without verifying.
3284         u = ServermapUpdater(self._node, self._storage_broker, self._monitor,
3285                              servermap, MODE_CHECK, add_lease=add_lease)
3286         if self._history:
3287hunk ./src/allmydata/mutable/checker.py 55
3288         if num_recoverable:
3289             self.best_version = servermap.best_recoverable_version()
3290 
3291+        # The file is unhealthy and needs to be repaired if:
3292+        # - There are unrecoverable versions.
3293         if servermap.unrecoverable_versions():
3294             self.need_repair = True
3295hunk ./src/allmydata/mutable/checker.py 59
3296+        # - There isn't a recoverable version.
3297         if num_recoverable != 1:
3298             self.need_repair = True
3299hunk ./src/allmydata/mutable/checker.py 62
3300+        # - The best recoverable version is missing some shares.
3301         if self.best_version:
3302             available_shares = servermap.shares_available()
3303             (num_distinct_shares, k, N) = available_shares[self.best_version]
3304hunk ./src/allmydata/mutable/checker.py 73
3305 
3306     def _verify_all_shares(self, servermap):
3307         # read every byte of each share
3308+        #
3309+        # This logic is going to be very nearly the same as the
3310+        # downloader. I bet we could pass the downloader a flag that
3311+        # makes it do this, and piggyback onto that instead of
3312+        # duplicating a bunch of code.
3313+        #
3314+        # Like:
3315+        #  r = Retrieve(blah, blah, blah, verify=True)
3316+        #  d = r.download()
3317+        #  (wait, wait, wait, d.callback)
3318+        # 
3319+        #  Then, when it has finished, we can check the servermap (which
3320+        #  we provided to Retrieve) to figure out which shares are bad,
3321+        #  since the Retrieve process will have updated the servermap as
3322+        #  it went along.
3323+        #
3324+        #  By passing the verify=True flag to the constructor, we are
3325+        #  telling the downloader a few things.
3326+        #
3327+        #  1. It needs to download all N shares, not just K shares.
3328+        #  2. It doesn't need to decrypt or decode the shares, only
3329+        #     verify them.
3330         if not self.best_version:
3331             return
3332hunk ./src/allmydata/mutable/checker.py 97
3333-        versionmap = servermap.make_versionmap()
3334-        shares = versionmap[self.best_version]
3335-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3336-         offsets_tuple) = self.best_version
3337-        offsets = dict(offsets_tuple)
3338-        readv = [ (0, offsets["EOF"]) ]
3339-        dl = []
3340-        for (shnum, peerid, timestamp) in shares:
3341-            ss = servermap.connections[peerid]
3342-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
3343-            d.addCallback(self._got_answer, peerid, servermap)
3344-            dl.append(d)
3345-        return defer.DeferredList(dl, fireOnOneErrback=True, consumeErrors=True)
3346 
3347hunk ./src/allmydata/mutable/checker.py 98
3348-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
3349-        # isolate the callRemote to a separate method, so tests can subclass
3350-        # Publish and override it
3351-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
3352+        r = Retrieve(self._node, servermap, self.best_version, verify=True)
3353+        d = r.download()
3354+        d.addCallback(self._process_bad_shares)
3355         return d
3356 
3357hunk ./src/allmydata/mutable/checker.py 103
3358-    def _got_answer(self, datavs, peerid, servermap):
3359-        for shnum,datav in datavs.items():
3360-            data = datav[0]
3361-            try:
3362-                self._got_results_one_share(shnum, peerid, data)
3363-            except CorruptShareError:
3364-                f = failure.Failure()
3365-                self.need_repair = True
3366-                self.bad_shares.append( (peerid, shnum, f) )
3367-                prefix = data[:SIGNED_PREFIX_LENGTH]
3368-                servermap.mark_bad_share(peerid, shnum, prefix)
3369-                ss = servermap.connections[peerid]
3370-                self.notify_server_corruption(ss, shnum, str(f.value))
3371-
3372-    def check_prefix(self, peerid, shnum, data):
3373-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3374-         offsets_tuple) = self.best_version
3375-        got_prefix = data[:SIGNED_PREFIX_LENGTH]
3376-        if got_prefix != prefix:
3377-            raise CorruptShareError(peerid, shnum,
3378-                                    "prefix mismatch: share changed while we were reading it")
3379-
3380-    def _got_results_one_share(self, shnum, peerid, data):
3381-        self.check_prefix(peerid, shnum, data)
3382-
3383-        # the [seqnum:signature] pieces are validated by _compare_prefix,
3384-        # which checks their signature against the pubkey known to be
3385-        # associated with this file.
3386 
3387hunk ./src/allmydata/mutable/checker.py 104
3388-        (seqnum, root_hash, IV, k, N, segsize, datalen, pubkey, signature,
3389-         share_hash_chain, block_hash_tree, share_data,
3390-         enc_privkey) = unpack_share(data)
3391-
3392-        # validate [share_hash_chain,block_hash_tree,share_data]
3393-
3394-        leaves = [hashutil.block_hash(share_data)]
3395-        t = hashtree.HashTree(leaves)
3396-        if list(t) != block_hash_tree:
3397-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
3398-        share_hash_leaf = t[0]
3399-        t2 = hashtree.IncompleteHashTree(N)
3400-        # root_hash was checked by the signature
3401-        t2.set_hashes({0: root_hash})
3402-        try:
3403-            t2.set_hashes(hashes=share_hash_chain,
3404-                          leaves={shnum: share_hash_leaf})
3405-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
3406-                IndexError), e:
3407-            msg = "corrupt hashes: %s" % (e,)
3408-            raise CorruptShareError(peerid, shnum, msg)
3409-
3410-        # validate enc_privkey: only possible if we have a write-cap
3411-        if not self._node.is_readonly():
3412-            alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3413-            alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3414-            if alleged_writekey != self._node.get_writekey():
3415-                raise CorruptShareError(peerid, shnum, "invalid privkey")
3416+    def _process_bad_shares(self, bad_shares):
3417+        if bad_shares:
3418+            self.need_repair = True
3419+        self.bad_shares = bad_shares
3420 
3421hunk ./src/allmydata/mutable/checker.py 109
3422-    def notify_server_corruption(self, ss, shnum, reason):
3423-        ss.callRemoteOnly("advise_corrupt_share",
3424-                          "mutable", self._storage_index, shnum, reason)
3425 
3426     def _count_shares(self, smap, version):
3427         available_shares = smap.shares_available()
3428hunk ./src/allmydata/test/test_mutable.py 193
3429                 if offset1 == "pubkey" and IV:
3430                     real_offset = 107
3431                 elif offset1 == "share_data" and not IV:
3432-                    real_offset = 104
3433+                    real_offset = 107
3434                 elif offset1 in o:
3435                     real_offset = o[offset1]
3436                 else:
3437hunk ./src/allmydata/test/test_mutable.py 395
3438             return d
3439         d.addCallback(_created)
3440         return d
3441+    test_create_mdmf_with_initial_contents.timeout = 20
3442 
3443 
3444     def test_create_with_initial_contents_function(self):
3445hunk ./src/allmydata/test/test_mutable.py 700
3446                                            k, N, segsize, datalen)
3447                 self.failUnless(p._pubkey.verify(sig_material, signature))
3448                 #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
3449-                self.failUnless(isinstance(share_hash_chain, dict))
3450-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3451+                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3452                 for shnum,share_hash in share_hash_chain.items():
3453                     self.failUnless(isinstance(shnum, int))
3454                     self.failUnless(isinstance(share_hash, str))
3455hunk ./src/allmydata/test/test_mutable.py 820
3456                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
3457 
3458 
3459+
3460+
3461 class Servermap(unittest.TestCase, PublishMixin):
3462     def setUp(self):
3463         return self.publish_one()
3464hunk ./src/allmydata/test/test_mutable.py 951
3465         self._storage._peers = {} # delete all shares
3466         ms = self.make_servermap
3467         d = defer.succeed(None)
3468-
3469+#
3470         d.addCallback(lambda res: ms(mode=MODE_CHECK))
3471         d.addCallback(lambda sm: self.failUnlessNoneRecoverable(sm))
3472 
3473hunk ./src/allmydata/test/test_mutable.py 1440
3474         d.addCallback(self.check_good, "test_check_good")
3475         return d
3476 
3477+    def test_check_mdmf_good(self):
3478+        d = self.publish_mdmf()
3479+        d.addCallback(lambda ignored:
3480+            self._fn.check(Monitor()))
3481+        d.addCallback(self.check_good, "test_check_mdmf_good")
3482+        return d
3483+
3484     def test_check_no_shares(self):
3485         for shares in self._storage._peers.values():
3486             shares.clear()
3487hunk ./src/allmydata/test/test_mutable.py 1454
3488         d.addCallback(self.check_bad, "test_check_no_shares")
3489         return d
3490 
3491+    def test_check_mdmf_no_shares(self):
3492+        d = self.publish_mdmf()
3493+        def _then(ignored):
3494+            for share in self._storage._peers.values():
3495+                share.clear()
3496+        d.addCallback(_then)
3497+        d.addCallback(lambda ignored:
3498+            self._fn.check(Monitor()))
3499+        d.addCallback(self.check_bad, "test_check_mdmf_no_shares")
3500+        return d
3501+
3502     def test_check_not_enough_shares(self):
3503         for shares in self._storage._peers.values():
3504             for shnum in shares.keys():
3505hunk ./src/allmydata/test/test_mutable.py 1474
3506         d.addCallback(self.check_bad, "test_check_not_enough_shares")
3507         return d
3508 
3509+    def test_check_mdmf_not_enough_shares(self):
3510+        d = self.publish_mdmf()
3511+        def _then(ignored):
3512+            for shares in self._storage._peers.values():
3513+                for shnum in shares.keys():
3514+                    if shnum > 0:
3515+                        del shares[shnum]
3516+        d.addCallback(_then)
3517+        d.addCallback(lambda ignored:
3518+            self._fn.check(Monitor()))
3519+        d.addCallback(self.check_bad, "test_check_mdmf_not_enougH_shares")
3520+        return d
3521+
3522+
3523     def test_check_all_bad_sig(self):
3524         d = corrupt(None, self._storage, 1) # bad sig
3525         d.addCallback(lambda ignored:
3526hunk ./src/allmydata/test/test_mutable.py 1495
3527         d.addCallback(self.check_bad, "test_check_all_bad_sig")
3528         return d
3529 
3530+    def test_check_mdmf_all_bad_sig(self):
3531+        d = self.publish_mdmf()
3532+        d.addCallback(lambda ignored:
3533+            corrupt(None, self._storage, 1))
3534+        d.addCallback(lambda ignored:
3535+            self._fn.check(Monitor()))
3536+        d.addCallback(self.check_bad, "test_check_mdmf_all_bad_sig")
3537+        return d
3538+
3539     def test_check_all_bad_blocks(self):
3540         d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
3541         # the Checker won't notice this.. it doesn't look at actual data
3542hunk ./src/allmydata/test/test_mutable.py 1512
3543         d.addCallback(self.check_good, "test_check_all_bad_blocks")
3544         return d
3545 
3546+
3547+    def test_check_mdmf_all_bad_blocks(self):
3548+        d = self.publish_mdmf()
3549+        d.addCallback(lambda ignored:
3550+            corrupt(None, self._storage, "share_data"))
3551+        d.addCallback(lambda ignored:
3552+            self._fn.check(Monitor()))
3553+        d.addCallback(self.check_good, "test_check_mdmf_all_bad_blocks")
3554+        return d
3555+
3556     def test_verify_good(self):
3557         d = self._fn.check(Monitor(), verify=True)
3558         d.addCallback(self.check_good, "test_verify_good")
3559hunk ./src/allmydata/test/test_mutable.py 1582
3560                       "test_verify_one_bad_encprivkey_uncheckable")
3561         return d
3562 
3563+
3564+    def test_verify_mdmf_good(self):
3565+        d = self.publish_mdmf()
3566+        d.addCallback(lambda ignored:
3567+            self._fn.check(Monitor(), verify=True))
3568+        d.addCallback(self.check_good, "test_verify_mdmf_good")
3569+        return d
3570+
3571+
3572+    def test_verify_mdmf_one_bad_block(self):
3573+        d = self.publish_mdmf()
3574+        d.addCallback(lambda ignored:
3575+            corrupt(None, self._storage, "share_data", [1]))
3576+        d.addCallback(lambda ignored:
3577+            self._fn.check(Monitor(), verify=True))
3578+        # We should find one bad block here
3579+        d.addCallback(self.check_bad, "test_verify_mdmf_one_bad_block")
3580+        d.addCallback(self.check_expected_failure,
3581+                      CorruptShareError, "block hash tree failure",
3582+                      "test_verify_mdmf_one_bad_block")
3583+        return d
3584+
3585+
3586+    def test_verify_mdmf_bad_encprivkey(self):
3587+        d = self.publish_mdmf()
3588+        d.addCallback(lambda ignored:
3589+            corrupt(None, self._storage, "enc_privkey", [1]))
3590+        d.addCallback(lambda ignored:
3591+            self._fn.check(Monitor(), verify=True))
3592+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
3593+        d.addCallback(self.check_expected_failure,
3594+                      CorruptShareError, "privkey",
3595+                      "test_verify_mdmf_bad_encprivkey")
3596+        return d
3597+
3598+
3599+    def test_verify_mdmf_bad_sig(self):
3600+        d = self.publish_mdmf()
3601+        d.addCallback(lambda ignored:
3602+            corrupt(None, self._storage, 1, [1]))
3603+        d.addCallback(lambda ignored:
3604+            self._fn.check(Monitor(), verify=True))
3605+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_sig")
3606+        return d
3607+
3608+
3609+    def test_verify_mdmf_bad_encprivkey_uncheckable(self):
3610+        d = self.publish_mdmf()
3611+        d.addCallback(lambda ignored:
3612+            corrupt(None, self._storage, "enc_privkey", [1]))
3613+        d.addCallback(lambda ignored:
3614+            self._fn.get_readonly())
3615+        d.addCallback(lambda fn:
3616+            fn.check(Monitor(), verify=True))
3617+        d.addCallback(self.check_good,
3618+                      "test_verify_mdmf_bad_encprivkey_uncheckable")
3619+        return d
3620+
3621+
3622 class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
3623 
3624     def get_shares(self, s):
3625hunk ./src/allmydata/test/test_mutable.py 1706
3626         current_shares = self.old_shares[-1]
3627         self.failUnlessEqual(old_shares, current_shares)
3628 
3629+
3630     def test_unrepairable_0shares(self):
3631         d = self.publish_one()
3632         def _delete_all_shares(ign):
3633hunk ./src/allmydata/test/test_mutable.py 1721
3634         d.addCallback(_check)
3635         return d
3636 
3637+    def test_mdmf_unrepairable_0shares(self):
3638+        d = self.publish_mdmf()
3639+        def _delete_all_shares(ign):
3640+            shares = self._storage._peers
3641+            for peerid in shares:
3642+                shares[peerid] = {}
3643+        d.addCallback(_delete_all_shares)
3644+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3645+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3646+        d.addCallback(lambda crr: self.failIf(crr.get_successful()))
3647+        return d
3648+
3649+
3650     def test_unrepairable_1share(self):
3651         d = self.publish_one()
3652         def _delete_all_shares(ign):
3653hunk ./src/allmydata/test/test_mutable.py 1750
3654         d.addCallback(_check)
3655         return d
3656 
3657+    def test_mdmf_unrepairable_1share(self):
3658+        d = self.publish_mdmf()
3659+        def _delete_all_shares(ign):
3660+            shares = self._storage._peers
3661+            for peerid in shares:
3662+                for shnum in list(shares[peerid]):
3663+                    if shnum > 0:
3664+                        del shares[peerid][shnum]
3665+        d.addCallback(_delete_all_shares)
3666+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3667+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3668+        def _check(crr):
3669+            self.failUnlessEqual(crr.get_successful(), False)
3670+        d.addCallback(_check)
3671+        return d
3672+
3673+    def test_repairable_5shares(self):
3674+        d = self.publish_mdmf()
3675+        def _delete_all_shares(ign):
3676+            shares = self._storage._peers
3677+            for peerid in shares:
3678+                for shnum in list(shares[peerid]):
3679+                    if shnum > 4:
3680+                        del shares[peerid][shnum]
3681+        d.addCallback(_delete_all_shares)
3682+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3683+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3684+        def _check(crr):
3685+            self.failUnlessEqual(crr.get_successful(), True)
3686+        d.addCallback(_check)
3687+        return d
3688+
3689+    def test_mdmf_repairable_5shares(self):
3690+        d = self.publish_mdmf()
3691+        def _delete_all_shares(ign):
3692+            shares = self._storage._peers
3693+            for peerid in shares:
3694+                for shnum in list(shares[peerid]):
3695+                    if shnum > 5:
3696+                        del shares[peerid][shnum]
3697+        d.addCallback(_delete_all_shares)
3698+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3699+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3700+        def _check(crr):
3701+            self.failUnlessEqual(crr.get_successful(), True)
3702+        d.addCallback(_check)
3703+        return d
3704+
3705+
3706     def test_merge(self):
3707         self.old_shares = []
3708         d = self.publish_multiple()
3709}
3710[mutable/retrieve.py: learn how to verify mutable files
3711Kevan Carstensen <kevan@isnotajoke.com>**20100628225201
3712 Ignore-this: 989af7800c47589620918461ec989483
3713] {
3714hunk ./src/allmydata/mutable/retrieve.py 86
3715     # Retrieve object will remain tied to a specific version of the file, and
3716     # will use a single ServerMap instance.
3717 
3718-    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False):
3719+    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
3720+                 verify=False):
3721         self._node = filenode
3722         assert self._node.get_pubkey()
3723         self._storage_index = filenode.get_storage_index()
3724hunk ./src/allmydata/mutable/retrieve.py 106
3725         # during repair, we may be called upon to grab the private key, since
3726         # it wasn't picked up during a verify=False checker run, and we'll
3727         # need it for repair to generate a new version.
3728-        self._need_privkey = fetch_privkey
3729-        if self._node.get_privkey():
3730+        self._need_privkey = fetch_privkey or verify
3731+        if self._node.get_privkey() and not verify:
3732             self._need_privkey = False
3733 
3734         if self._need_privkey:
3735hunk ./src/allmydata/mutable/retrieve.py 117
3736             self._privkey_query_markers = [] # one Marker for each time we've
3737                                              # tried to get the privkey.
3738 
3739+        # verify means that we are using the downloader logic to verify all
3740+        # of our shares. This tells the downloader a few things.
3741+        #
3742+        # 1. We need to download all of the shares.
3743+        # 2. We don't need to decode or decrypt the shares, since our
3744+        #    caller doesn't care about the plaintext, only the
3745+        #    information about which shares are or are not valid.
3746+        # 3. When we are validating readers, we need to validate the
3747+        #    signature on the prefix. Do we? We already do this in the
3748+        #    servermap update?
3749+        #
3750+        # (just work on 1 and 2 for now, I guess)
3751+        self._verify = False
3752+        if verify:
3753+            self._verify = True
3754+
3755         self._status = RetrieveStatus()
3756         self._status.set_storage_index(self._storage_index)
3757         self._status.set_helper(False)
3758hunk ./src/allmydata/mutable/retrieve.py 323
3759 
3760         # We need at least self._required_shares readers to download a
3761         # segment.
3762-        needed = self._required_shares - len(self._active_readers)
3763+        if self._verify:
3764+            needed = self._total_shares
3765+        else:
3766+            needed = self._required_shares - len(self._active_readers)
3767         # XXX: Why don't format= log messages work here?
3768         self.log("adding %d peers to the active peers list" % needed)
3769 
3770hunk ./src/allmydata/mutable/retrieve.py 339
3771         # will cause problems later.
3772         active_shnums -= set([reader.shnum for reader in self._active_readers])
3773         active_shnums = list(active_shnums)[:needed]
3774-        if len(active_shnums) < needed:
3775+        if len(active_shnums) < needed and not self._verify:
3776             # We don't have enough readers to retrieve the file; fail.
3777             return self._failed()
3778 
3779hunk ./src/allmydata/mutable/retrieve.py 346
3780         for shnum in active_shnums:
3781             self._active_readers.append(self.readers[shnum])
3782             self.log("added reader for share %d" % shnum)
3783-        assert len(self._active_readers) == self._required_shares
3784+        assert len(self._active_readers) >= self._required_shares
3785         # Conceptually, this is part of the _add_active_peers step. It
3786         # validates the prefixes of newly added readers to make sure
3787         # that they match what we are expecting for self.verinfo. If
3788hunk ./src/allmydata/mutable/retrieve.py 416
3789                     # that we haven't gotten it at the end of
3790                     # segment decoding, then we'll take more drastic
3791                     # measures.
3792-                    if self._need_privkey:
3793+                    if self._need_privkey and not self._node.is_readonly():
3794                         d = reader.get_encprivkey()
3795                         d.addCallback(self._try_to_validate_privkey, reader)
3796             if bad_readers:
3797hunk ./src/allmydata/mutable/retrieve.py 423
3798                 # We do them all at once, or else we screw up list indexing.
3799                 for (reader, f) in bad_readers:
3800                     self._mark_bad_share(reader, f)
3801-                return self._add_active_peers()
3802+                if self._verify:
3803+                    if len(self._active_readers) >= self._required_shares:
3804+                        return self._download_current_segment()
3805+                    else:
3806+                        return self._failed()
3807+                else:
3808+                    return self._add_active_peers()
3809             else:
3810                 return self._download_current_segment()
3811             # The next step will assert that it has enough active
3812hunk ./src/allmydata/mutable/retrieve.py 518
3813         """
3814         self.log("marking share %d on server %s as bad" % \
3815                  (reader.shnum, reader))
3816+        prefix = self.verinfo[-2]
3817+        self.servermap.mark_bad_share(reader.peerid,
3818+                                      reader.shnum,
3819+                                      prefix)
3820         self._remove_reader(reader)
3821hunk ./src/allmydata/mutable/retrieve.py 523
3822-        self._bad_shares.add((reader.peerid, reader.shnum))
3823+        self._bad_shares.add((reader.peerid, reader.shnum, f))
3824         self._status.problems[reader.peerid] = f
3825         self._last_failure = f
3826         self.notify_server_corruption(reader.peerid, reader.shnum,
3827hunk ./src/allmydata/mutable/retrieve.py 571
3828             ds.append(dl)
3829             reader.flush()
3830         dl = defer.DeferredList(ds)
3831-        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3832+        if self._verify:
3833+            dl.addCallback(lambda ignored: "")
3834+            dl.addCallback(self._set_segment)
3835+        else:
3836+            dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3837         return dl
3838 
3839 
3840hunk ./src/allmydata/mutable/retrieve.py 701
3841         # shnum, which will be a leaf in the share hash tree, which
3842         # will allow us to validate the rest of the tree.
3843         if self.share_hash_tree.needed_hashes(reader.shnum,
3844-                                               include_leaf=True):
3845+                                              include_leaf=True) or \
3846+                                              self._verify:
3847             try:
3848                 self.share_hash_tree.set_hashes(hashes=sharehashes[1],
3849                                             leaves={reader.shnum: bht[0]})
3850hunk ./src/allmydata/mutable/retrieve.py 832
3851 
3852 
3853     def _try_to_validate_privkey(self, enc_privkey, reader):
3854-
3855         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3856         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3857         if alleged_writekey != self._node.get_writekey():
3858hunk ./src/allmydata/mutable/retrieve.py 838
3859             self.log("invalid privkey from %s shnum %d" %
3860                      (reader, reader.shnum),
3861                      level=log.WEIRD, umid="YIw4tA")
3862+            if self._verify:
3863+                self.servermap.mark_bad_share(reader.peerid, reader.shnum,
3864+                                              self.verinfo[-2])
3865+                e = CorruptShareError(reader.peerid,
3866+                                      reader.shnum,
3867+                                      "invalid privkey")
3868+                f = failure.Failure(e)
3869+                self._bad_shares.add((reader.peerid, reader.shnum, f))
3870             return
3871 
3872         # it's good
3873hunk ./src/allmydata/mutable/retrieve.py 904
3874         statements, I return the decrypted contents to the owner of this
3875         Retrieve object through self._done_deferred.
3876         """
3877-        eventually(self._done_deferred.callback, self._plaintext)
3878+        if self._verify:
3879+            ret = list(self._bad_shares)
3880+            self.log("done verifying, found %d bad shares" % len(ret))
3881+        else:
3882+            ret = self._plaintext
3883+        eventually(self._done_deferred.callback, ret)
3884 
3885 
3886     def _failed(self):
3887hunk ./src/allmydata/mutable/retrieve.py 920
3888         to the caller of this Retrieve object through
3889         self._done_deferred.
3890         """
3891-        format = ("ran out of peers: "
3892-                  "have %(have)d of %(total)d segments "
3893-                  "found %(bad)d bad shares "
3894-                  "encoding %(k)d-of-%(n)d")
3895-        args = {"have": self._current_segment,
3896-                "total": self._num_segments,
3897-                "k": self._required_shares,
3898-                "n": self._total_shares,
3899-                "bad": len(self._bad_shares)}
3900-        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
3901-                                                        str(self._last_failure)))
3902-        f = failure.Failure(e)
3903-        eventually(self._done_deferred.callback, f)
3904+        if self._verify:
3905+            ret = list(self._bad_shares)
3906+        else:
3907+            format = ("ran out of peers: "
3908+                      "have %(have)d of %(total)d segments "
3909+                      "found %(bad)d bad shares "
3910+                      "encoding %(k)d-of-%(n)d")
3911+            args = {"have": self._current_segment,
3912+                    "total": self._num_segments,
3913+                    "k": self._required_shares,
3914+                    "n": self._total_shares,
3915+                    "bad": len(self._bad_shares)}
3916+            e = NotEnoughSharesError("%s, last failure: %s" % \
3917+                                     (format % args, str(self._last_failure)))
3918+            f = failure.Failure(e)
3919+            ret = f
3920+        eventually(self._done_deferred.callback, ret)
3921}
3922[interfaces.py: add IMutableSlotWriter
3923Kevan Carstensen <kevan@isnotajoke.com>**20100630183305
3924 Ignore-this: ff9dca96ef1a009ae85485682f81ea5
3925] hunk ./src/allmydata/interfaces.py 418
3926         """
3927 
3928 
3929+class IMutableSlotWriter(Interface):
3930+    """
3931+    The interface for a writer around a mutable slot on a remote server.
3932+    """
3933+    def set_checkstring(checkstring, *args):
3934+        """
3935+        Set the checkstring that I will pass to the remote server when
3936+        writing.
3937+
3938+            @param checkstring A packed checkstring to use.
3939+
3940+        Note that implementations can differ in which semantics they
3941+        wish to support for set_checkstring -- they can, for example,
3942+        build the checkstring themselves from its constituents, or
3943+        some other thing.
3944+        """
3945+
3946+    def get_checkstring():
3947+        """
3948+        Get the checkstring that I think currently exists on the remote
3949+        server.
3950+        """
3951+
3952+    def put_block(data, segnum, salt):
3953+        """
3954+        Add a block and salt to the share.
3955+        """
3956+
3957+    def put_encprivey(encprivkey):
3958+        """
3959+        Add the encrypted private key to the share.
3960+        """
3961+
3962+    def put_blockhashes(blockhashes=list):
3963+        """
3964+        Add the block hash tree to the share.
3965+        """
3966+
3967+    def put_sharehashes(sharehashes=dict):
3968+        """
3969+        Add the share hash chain to the share.
3970+        """
3971+
3972+    def get_signable():
3973+        """
3974+        Return the part of the share that needs to be signed.
3975+        """
3976+
3977+    def put_signature(signature):
3978+        """
3979+        Add the signature to the share.
3980+        """
3981+
3982+    def put_verification_key(verification_key):
3983+        """
3984+        Add the verification key to the share.
3985+        """
3986+
3987+    def finish_publishing():
3988+        """
3989+        Do anything necessary to finish writing the share to a remote
3990+        server. I require that no further publishing needs to take place
3991+        after this method has been called.
3992+        """
3993+
3994+
3995 class IURI(Interface):
3996     def init_from_string(uri):
3997         """Accept a string (as created by my to_string() method) and populate
3998[test/test_mutable.py: temporarily disable two tests that are now irrelevant
3999Kevan Carstensen <kevan@isnotajoke.com>**20100701232806
4000 Ignore-this: 701e143567f3954812ca6960af1d6ac7
4001] {
4002hunk ./src/allmydata/test/test_mutable.py 651
4003             self.failUnlessEqual(len(share_ids), 10)
4004         d.addCallback(_done)
4005         return d
4006+    test_encrypt.todo = "Write an equivalent of this for the new uploader"
4007 
4008     def test_generate(self):
4009         nm = make_nodemaker()
4010hunk ./src/allmydata/test/test_mutable.py 713
4011                 self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
4012         d.addCallback(_generated)
4013         return d
4014+    test_generate.todo = "Write an equivalent of this for the new uploader"
4015 
4016     # TODO: when we publish to 20 peers, we should get one share per peer on 10
4017     # when we publish to 3 peers, we should get either 3 or 4 shares per peer
4018}
4019[Add MDMF reader and writer, and SDMF writer
4020Kevan Carstensen <kevan@isnotajoke.com>**20100702225531
4021 Ignore-this: bf6276a91d27dcb4e779b0eb82ea1843
4022 
4023 The MDMF/SDMF reader MDMF writer, and SDMF writer are similar to the
4024 object proxies that exist for immutable files. They abstract away
4025 details of connection, state, and caching from their callers (in this
4026 case, the download, servermap updater, and uploader), and expose methods
4027 to get and set information on the remote server.
4028 
4029 MDMFSlotReadProxy reads a mutable file from the server, doing the right
4030 thing (in most cases) regardless of whether the file is MDMF or SDMF. It
4031 allows callers to tell it how to batch and flush reads.
4032 
4033 MDMFSlotWriteProxy writes an MDMF mutable file to a server.
4034 
4035 SDMFSlotWriteProxy writes an SDMF mutable file to a server.
4036 
4037 This patch also includes tests for MDMFSlotReadProxy,
4038 SDMFSlotWriteProxy, and MDMFSlotWriteProxy.
4039] {
4040hunk ./src/allmydata/mutable/layout.py 4
4041 
4042 import struct
4043 from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError
4044+from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \
4045+                                 MDMF_VERSION, IMutableSlotWriter
4046+from allmydata.util import mathutil, observer
4047+from twisted.python import failure
4048+from twisted.internet import defer
4049+from zope.interface import implements
4050+
4051+
4052+# These strings describe the format of the packed structs they help process
4053+# Here's what they mean:
4054+#
4055+#  PREFIX:
4056+#    >: Big-endian byte order; the most significant byte is first (leftmost).
4057+#    B: The version information; an 8 bit version identifier. Stored as
4058+#       an unsigned char. This is currently 00 00 00 00; our modifications
4059+#       will turn it into 00 00 00 01.
4060+#    Q: The sequence number; this is sort of like a revision history for
4061+#       mutable files; they start at 1 and increase as they are changed after
4062+#       being uploaded. Stored as an unsigned long long, which is 8 bytes in
4063+#       length.
4064+#  32s: The root hash of the share hash tree. We use sha-256d, so we use 32
4065+#       characters = 32 bytes to store the value.
4066+#  16s: The salt for the readkey. This is a 16-byte random value, stored as
4067+#       16 characters.
4068+#
4069+#  SIGNED_PREFIX additions, things that are covered by the signature:
4070+#    B: The "k" encoding parameter. We store this as an 8-bit character,
4071+#       which is convenient because our erasure coding scheme cannot
4072+#       encode if you ask for more than 255 pieces.
4073+#    B: The "N" encoding parameter. Stored as an 8-bit character for the
4074+#       same reasons as above.
4075+#    Q: The segment size of the uploaded file. This will essentially be the
4076+#       length of the file in SDMF. An unsigned long long, so we can store
4077+#       files of quite large size.
4078+#    Q: The data length of the uploaded file. Modulo padding, this will be
4079+#       the same of the data length field. Like the data length field, it is
4080+#       an unsigned long long and can be quite large.
4081+#
4082+#   HEADER additions:
4083+#     L: The offset of the signature of this. An unsigned long.
4084+#     L: The offset of the share hash chain. An unsigned long.
4085+#     L: The offset of the block hash tree. An unsigned long.
4086+#     L: The offset of the share data. An unsigned long.
4087+#     Q: The offset of the encrypted private key. An unsigned long long, to
4088+#        account for the possibility of a lot of share data.
4089+#     Q: The offset of the EOF. An unsigned long long, to account for the
4090+#        possibility of a lot of share data.
4091+#
4092+#  After all of these, we have the following:
4093+#    - The verification key: Occupies the space between the end of the header
4094+#      and the start of the signature (i.e.: data[HEADER_LENGTH:o['signature']].
4095+#    - The signature, which goes from the signature offset to the share hash
4096+#      chain offset.
4097+#    - The share hash chain, which goes from the share hash chain offset to
4098+#      the block hash tree offset.
4099+#    - The share data, which goes from the share data offset to the encrypted
4100+#      private key offset.
4101+#    - The encrypted private key offset, which goes until the end of the file.
4102+#
4103+#  The block hash tree in this encoding has only one share, so the offset of
4104+#  the share data will be 32 bits more than the offset of the block hash tree.
4105+#  Given this, we may need to check to see how many bytes a reasonably sized
4106+#  block hash tree will take up.
4107 
4108 PREFIX = ">BQ32s16s" # each version has a different prefix
4109 SIGNED_PREFIX = ">BQ32s16s BBQQ" # this is covered by the signature
4110hunk ./src/allmydata/mutable/layout.py 73
4111 SIGNED_PREFIX_LENGTH = struct.calcsize(SIGNED_PREFIX)
4112 HEADER = ">BQ32s16s BBQQ LLLLQQ" # includes offsets
4113 HEADER_LENGTH = struct.calcsize(HEADER)
4114+OFFSETS = ">LLLLQQ"
4115+OFFSETS_LENGTH = struct.calcsize(OFFSETS)
4116 
4117 def unpack_header(data):
4118     o = {}
4119hunk ./src/allmydata/mutable/layout.py 194
4120     return (share_hash_chain, block_hash_tree, share_data)
4121 
4122 
4123-def pack_checkstring(seqnum, root_hash, IV):
4124+def pack_checkstring(seqnum, root_hash, IV, version=0):
4125     return struct.pack(PREFIX,
4126hunk ./src/allmydata/mutable/layout.py 196
4127-                       0, # version,
4128+                       version,
4129                        seqnum,
4130                        root_hash,
4131                        IV)
4132hunk ./src/allmydata/mutable/layout.py 269
4133                            encprivkey])
4134     return final_share
4135 
4136+def pack_prefix(seqnum, root_hash, IV,
4137+                required_shares, total_shares,
4138+                segment_size, data_length):
4139+    prefix = struct.pack(SIGNED_PREFIX,
4140+                         0, # version,
4141+                         seqnum,
4142+                         root_hash,
4143+                         IV,
4144+                         required_shares,
4145+                         total_shares,
4146+                         segment_size,
4147+                         data_length,
4148+                         )
4149+    return prefix
4150+
4151+
4152+class SDMFSlotWriteProxy:
4153+    implements(IMutableSlotWriter)
4154+    """
4155+    I represent a remote write slot for an SDMF mutable file. I build a
4156+    share in memory, and then write it in one piece to the remote
4157+    server. This mimics how SDMF shares were built before MDMF (and the
4158+    new MDMF uploader), but provides that functionality in a way that
4159+    allows the MDMF uploader to be built without much special-casing for
4160+    file format, which makes the uploader code more readable.
4161+    """
4162+    def __init__(self,
4163+                 shnum,
4164+                 rref, # a remote reference to a storage server
4165+                 storage_index,
4166+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4167+                 seqnum, # the sequence number of the mutable file
4168+                 required_shares,
4169+                 total_shares,
4170+                 segment_size,
4171+                 data_length): # the length of the original file
4172+        self.shnum = shnum
4173+        self._rref = rref
4174+        self._storage_index = storage_index
4175+        self._secrets = secrets
4176+        self._seqnum = seqnum
4177+        self._required_shares = required_shares
4178+        self._total_shares = total_shares
4179+        self._segment_size = segment_size
4180+        self._data_length = data_length
4181+
4182+        # This is an SDMF file, so it should have only one segment, so,
4183+        # modulo padding of the data length, the segment size and the
4184+        # data length should be the same.
4185+        expected_segment_size = mathutil.next_multiple(data_length,
4186+                                                       self._required_shares)
4187+        assert expected_segment_size == segment_size
4188+
4189+        self._block_size = self._segment_size / self._required_shares
4190+
4191+        # This is meant to mimic how SDMF files were built before MDMF
4192+        # entered the picture: we generate each share in its entirety,
4193+        # then push it off to the storage server in one write. When
4194+        # callers call set_*, they are just populating this dict.
4195+        # finish_publishing will stitch these pieces together into a
4196+        # coherent share, and then write the coherent share to the
4197+        # storage server.
4198+        self._share_pieces = {}
4199+
4200+        # This tells the write logic what checkstring to use when
4201+        # writing remote shares.
4202+        self._testvs = []
4203+
4204+        self._readvs = [(0, struct.calcsize(PREFIX))]
4205+
4206+
4207+    def set_checkstring(self, checkstring_or_seqnum,
4208+                              root_hash=None,
4209+                              salt=None):
4210+        """
4211+        Set the checkstring that I will pass to the remote server when
4212+        writing.
4213+
4214+            @param checkstring_or_seqnum: A packed checkstring to use,
4215+                   or a sequence number. I will treat this as a checkstr
4216+
4217+        Note that implementations can differ in which semantics they
4218+        wish to support for set_checkstring -- they can, for example,
4219+        build the checkstring themselves from its constituents, or
4220+        some other thing.
4221+        """
4222+        if root_hash and salt:
4223+            checkstring = struct.pack(PREFIX,
4224+                                      0,
4225+                                      checkstring_or_seqnum,
4226+                                      root_hash,
4227+                                      salt)
4228+        else:
4229+            checkstring = checkstring_or_seqnum
4230+        self._testvs = [(0, len(checkstring), "eq", checkstring)]
4231+
4232+
4233+    def get_checkstring(self):
4234+        """
4235+        Get the checkstring that I think currently exists on the remote
4236+        server.
4237+        """
4238+        if self._testvs:
4239+            return self._testvs[0][3]
4240+        return ""
4241+
4242+
4243+    def put_block(self, data, segnum, salt):
4244+        """
4245+        Add a block and salt to the share.
4246+        """
4247+        # SDMF files have only one segment
4248+        assert segnum == 0
4249+        assert len(data) == self._block_size
4250+        assert len(salt) == SALT_SIZE
4251+
4252+        self._share_pieces['sharedata'] = data
4253+        self._share_pieces['salt'] = salt
4254+
4255+        # TODO: Figure out something intelligent to return.
4256+        return defer.succeed(None)
4257+
4258+
4259+    def put_encprivkey(self, encprivkey):
4260+        """
4261+        Add the encrypted private key to the share.
4262+        """
4263+        self._share_pieces['encprivkey'] = encprivkey
4264+
4265+        return defer.succeed(None)
4266+
4267+
4268+    def put_blockhashes(self, blockhashes):
4269+        """
4270+        Add the block hash tree to the share.
4271+        """
4272+        assert isinstance(blockhashes, list)
4273+        for h in blockhashes:
4274+            assert len(h) == HASH_SIZE
4275+
4276+        # serialize the blockhashes, then set them.
4277+        blockhashes_s = "".join(blockhashes)
4278+        self._share_pieces['block_hash_tree'] = blockhashes_s
4279+
4280+        return defer.succeed(None)
4281+
4282+
4283+    def put_sharehashes(self, sharehashes):
4284+        """
4285+        Add the share hash chain to the share.
4286+        """
4287+        assert isinstance(sharehashes, dict)
4288+        for h in sharehashes.itervalues():
4289+            assert len(h) == HASH_SIZE
4290+
4291+        # serialize the sharehashes, then set them.
4292+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4293+                                 for i in sorted(sharehashes.keys())])
4294+        self._share_pieces['share_hash_chain'] = sharehashes_s
4295+
4296+        return defer.succeed(None)
4297+
4298+
4299+    def put_root_hash(self, root_hash):
4300+        """
4301+        Add the root hash to the share.
4302+        """
4303+        assert len(root_hash) == HASH_SIZE
4304+
4305+        self._share_pieces['root_hash'] = root_hash
4306+
4307+        return defer.succeed(None)
4308+
4309+
4310+    def put_salt(self, salt):
4311+        """
4312+        Add a salt to an empty SDMF file.
4313+        """
4314+        assert len(salt) == SALT_SIZE
4315+
4316+        self._share_pieces['salt'] = salt
4317+        self._share_pieces['sharedata'] = ""
4318+
4319+
4320+    def get_signable(self):
4321+        """
4322+        Return the part of the share that needs to be signed.
4323+
4324+        SDMF writers need to sign the packed representation of the
4325+        first eight fields of the remote share, that is:
4326+            - version number (0)
4327+            - sequence number
4328+            - root of the share hash tree
4329+            - salt
4330+            - k
4331+            - n
4332+            - segsize
4333+            - datalen
4334+
4335+        This method is responsible for returning that to callers.
4336+        """
4337+        return struct.pack(SIGNED_PREFIX,
4338+                           0,
4339+                           self._seqnum,
4340+                           self._share_pieces['root_hash'],
4341+                           self._share_pieces['salt'],
4342+                           self._required_shares,
4343+                           self._total_shares,
4344+                           self._segment_size,
4345+                           self._data_length)
4346+
4347+
4348+    def put_signature(self, signature):
4349+        """
4350+        Add the signature to the share.
4351+        """
4352+        self._share_pieces['signature'] = signature
4353+
4354+        return defer.succeed(None)
4355+
4356+
4357+    def put_verification_key(self, verification_key):
4358+        """
4359+        Add the verification key to the share.
4360+        """
4361+        self._share_pieces['verification_key'] = verification_key
4362+
4363+        return defer.succeed(None)
4364+
4365+
4366+    def get_verinfo(self):
4367+        """
4368+        I return my verinfo tuple. This is used by the ServermapUpdater
4369+        to keep track of versions of mutable files.
4370+
4371+        The verinfo tuple for MDMF files contains:
4372+            - seqnum
4373+            - root hash
4374+            - a blank (nothing)
4375+            - segsize
4376+            - datalen
4377+            - k
4378+            - n
4379+            - prefix (the thing that you sign)
4380+            - a tuple of offsets
4381+
4382+        We include the nonce in MDMF to simplify processing of version
4383+        information tuples.
4384+
4385+        The verinfo tuple for SDMF files is the same, but contains a
4386+        16-byte IV instead of a hash of salts.
4387+        """
4388+        return (self._seqnum,
4389+                self._share_pieces['root_hash'],
4390+                self._share_pieces['salt'],
4391+                self._segment_size,
4392+                self._data_length,
4393+                self._required_shares,
4394+                self._total_shares,
4395+                self.get_signable(),
4396+                self._get_offsets_tuple())
4397+
4398+    def _get_offsets_dict(self):
4399+        post_offset = HEADER_LENGTH
4400+        offsets = {}
4401+
4402+        verification_key_length = len(self._share_pieces['verification_key'])
4403+        o1 = offsets['signature'] = post_offset + verification_key_length
4404+
4405+        signature_length = len(self._share_pieces['signature'])
4406+        o2 = offsets['share_hash_chain'] = o1 + signature_length
4407+
4408+        share_hash_chain_length = len(self._share_pieces['share_hash_chain'])
4409+        o3 = offsets['block_hash_tree'] = o2 + share_hash_chain_length
4410+
4411+        block_hash_tree_length = len(self._share_pieces['block_hash_tree'])
4412+        o4 = offsets['share_data'] = o3 + block_hash_tree_length
4413+
4414+        share_data_length = len(self._share_pieces['sharedata'])
4415+        o5 = offsets['enc_privkey'] = o4 + share_data_length
4416+
4417+        encprivkey_length = len(self._share_pieces['encprivkey'])
4418+        offsets['EOF'] = o5 + encprivkey_length
4419+        return offsets
4420+
4421+
4422+    def _get_offsets_tuple(self):
4423+        offsets = self._get_offsets_dict()
4424+        return tuple([(key, value) for key, value in offsets.items()])
4425+
4426+
4427+    def _pack_offsets(self):
4428+        offsets = self._get_offsets_dict()
4429+        return struct.pack(">LLLLQQ",
4430+                           offsets['signature'],
4431+                           offsets['share_hash_chain'],
4432+                           offsets['block_hash_tree'],
4433+                           offsets['share_data'],
4434+                           offsets['enc_privkey'],
4435+                           offsets['EOF'])
4436+
4437+
4438+    def finish_publishing(self):
4439+        """
4440+        Do anything necessary to finish writing the share to a remote
4441+        server. I require that no further publishing needs to take place
4442+        after this method has been called.
4443+        """
4444+        for k in ["sharedata", "encprivkey", "signature", "verification_key",
4445+                  "share_hash_chain", "block_hash_tree"]:
4446+            assert k in self._share_pieces
4447+        # This is the only method that actually writes something to the
4448+        # remote server.
4449+        # First, we need to pack the share into data that we can write
4450+        # to the remote server in one write.
4451+        offsets = self._pack_offsets()
4452+        prefix = self.get_signable()
4453+        final_share = "".join([prefix,
4454+                               offsets,
4455+                               self._share_pieces['verification_key'],
4456+                               self._share_pieces['signature'],
4457+                               self._share_pieces['share_hash_chain'],
4458+                               self._share_pieces['block_hash_tree'],
4459+                               self._share_pieces['sharedata'],
4460+                               self._share_pieces['encprivkey']])
4461+
4462+        # Our only data vector is going to be writing the final share,
4463+        # in its entirely.
4464+        datavs = [(0, final_share)]
4465+
4466+        if not self._testvs:
4467+            # Our caller has not provided us with another checkstring
4468+            # yet, so we assume that we are writing a new share, and set
4469+            # a test vector that will allow a new share to be written.
4470+            self._testvs = []
4471+            self._testvs.append(tuple([0, 1, "eq", ""]))
4472+            new_share = True
4473+
4474+        tw_vectors = {}
4475+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
4476+        return self._rref.callRemote("slot_testv_and_readv_and_writev",
4477+                                     self._storage_index,
4478+                                     self._secrets,
4479+                                     tw_vectors,
4480+                                     # TODO is it useful to read something?
4481+                                     self._readvs)
4482+
4483+
4484+MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
4485+MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
4486+MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
4487+MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
4488+MDMFCHECKSTRING = ">BQ32s"
4489+MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
4490+MDMFOFFSETS = ">QQQQQQ"
4491+MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
4492+
4493+class MDMFSlotWriteProxy:
4494+    implements(IMutableSlotWriter)
4495+
4496+    """
4497+    I represent a remote write slot for an MDMF mutable file.
4498+
4499+    I abstract away from my caller the details of block and salt
4500+    management, and the implementation of the on-disk format for MDMF
4501+    shares.
4502+    """
4503+    # Expected layout, MDMF:
4504+    # offset:     size:       name:
4505+    #-- signed part --
4506+    # 0           1           version number (01)
4507+    # 1           8           sequence number
4508+    # 9           32          share tree root hash
4509+    # 41          1           The "k" encoding parameter
4510+    # 42          1           The "N" encoding parameter
4511+    # 43          8           The segment size of the uploaded file
4512+    # 51          8           The data length of the original plaintext
4513+    #-- end signed part --
4514+    # 59          8           The offset of the encrypted private key
4515+    # 67          8           The offset of the block hash tree
4516+    # 75          8           The offset of the share hash chain
4517+    # 83          8           The offset of the signature
4518+    # 91          8           The offset of the verification key
4519+    # 99          8           The offset of the EOF
4520+    #
4521+    # followed by salts and share data, the encrypted private key, the
4522+    # block hash tree, the salt hash tree, the share hash chain, a
4523+    # signature over the first eight fields, and a verification key.
4524+    #
4525+    # The checkstring is the first three fields -- the version number,
4526+    # sequence number, root hash and root salt hash. This is consistent
4527+    # in meaning to what we have with SDMF files, except now instead of
4528+    # using the literal salt, we use a value derived from all of the
4529+    # salts -- the share hash root.
4530+    #
4531+    # The salt is stored before the block for each segment. The block
4532+    # hash tree is computed over the combination of block and salt for
4533+    # each segment. In this way, we get integrity checking for both
4534+    # block and salt with the current block hash tree arrangement.
4535+    #
4536+    # The ordering of the offsets is different to reflect the dependencies
4537+    # that we'll run into with an MDMF file. The expected write flow is
4538+    # something like this:
4539+    #
4540+    #   0: Initialize with the sequence number, encoding parameters and
4541+    #      data length. From this, we can deduce the number of segments,
4542+    #      and where they should go.. We can also figure out where the
4543+    #      encrypted private key should go, because we can figure out how
4544+    #      big the share data will be.
4545+    #
4546+    #   1: Encrypt, encode, and upload the file in chunks. Do something
4547+    #      like
4548+    #
4549+    #       put_block(data, segnum, salt)
4550+    #
4551+    #      to write a block and a salt to the disk. We can do both of
4552+    #      these operations now because we have enough of the offsets to
4553+    #      know where to put them.
4554+    #
4555+    #   2: Put the encrypted private key. Use:
4556+    #
4557+    #        put_encprivkey(encprivkey)
4558+    #
4559+    #      Now that we know the length of the private key, we can fill
4560+    #      in the offset for the block hash tree.
4561+    #
4562+    #   3: We're now in a position to upload the block hash tree for
4563+    #      a share. Put that using something like:
4564+    #       
4565+    #        put_blockhashes(block_hash_tree)
4566+    #
4567+    #      Note that block_hash_tree is a list of hashes -- we'll take
4568+    #      care of the details of serializing that appropriately. When
4569+    #      we get the block hash tree, we are also in a position to
4570+    #      calculate the offset for the share hash chain, and fill that
4571+    #      into the offsets table.
4572+    #
4573+    #   4: At the same time, we're in a position to upload the salt hash
4574+    #      tree. This is a Merkle tree over all of the salts. We use a
4575+    #      Merkle tree so that we can validate each block,salt pair as
4576+    #      we download them later. We do this using
4577+    #
4578+    #        put_salthashes(salt_hash_tree)
4579+    #
4580+    #      When you do this, I automatically put the root of the tree
4581+    #      (the hash at index 0 of the list) in its appropriate slot in
4582+    #      the signed prefix of the share.
4583+    #
4584+    #   5: We're now in a position to upload the share hash chain for
4585+    #      a share. Do that with something like:
4586+    #     
4587+    #        put_sharehashes(share_hash_chain)
4588+    #
4589+    #      share_hash_chain should be a dictionary mapping shnums to
4590+    #      32-byte hashes -- the wrapper handles serialization.
4591+    #      We'll know where to put the signature at this point, also.
4592+    #      The root of this tree will be put explicitly in the next
4593+    #      step.
4594+    #
4595+    #      TODO: Why? Why not just include it in the tree here?
4596+    #
4597+    #   6: Before putting the signature, we must first put the
4598+    #      root_hash. Do this with:
4599+    #
4600+    #        put_root_hash(root_hash).
4601+    #     
4602+    #      In terms of knowing where to put this value, it was always
4603+    #      possible to place it, but it makes sense semantically to
4604+    #      place it after the share hash tree, so that's why you do it
4605+    #      in this order.
4606+    #
4607+    #   6: With the root hash put, we can now sign the header. Use:
4608+    #
4609+    #        get_signable()
4610+    #
4611+    #      to get the part of the header that you want to sign, and use:
4612+    #       
4613+    #        put_signature(signature)
4614+    #
4615+    #      to write your signature to the remote server.
4616+    #
4617+    #   6: Add the verification key, and finish. Do:
4618+    #
4619+    #        put_verification_key(key)
4620+    #
4621+    #      and
4622+    #
4623+    #        finish_publish()
4624+    #
4625+    # Checkstring management:
4626+    #
4627+    # To write to a mutable slot, we have to provide test vectors to ensure
4628+    # that we are writing to the same data that we think we are. These
4629+    # vectors allow us to detect uncoordinated writes; that is, writes
4630+    # where both we and some other shareholder are writing to the
4631+    # mutable slot, and to report those back to the parts of the program
4632+    # doing the writing.
4633+    #
4634+    # With SDMF, this was easy -- all of the share data was written in
4635+    # one go, so it was easy to detect uncoordinated writes, and we only
4636+    # had to do it once. With MDMF, not all of the file is written at
4637+    # once.
4638+    #
4639+    # If a share is new, we write out as much of the header as we can
4640+    # before writing out anything else. This gives other writers a
4641+    # canary that they can use to detect uncoordinated writes, and, if
4642+    # they do the same thing, gives us the same canary. We them update
4643+    # the share. We won't be able to write out two fields of the header
4644+    # -- the share tree hash and the salt hash -- until we finish
4645+    # writing out the share. We only require the writer to provide the
4646+    # initial checkstring, and keep track of what it should be after
4647+    # updates ourselves.
4648+    #
4649+    # If we haven't written anything yet, then on the first write (which
4650+    # will probably be a block + salt of a share), we'll also write out
4651+    # the header. On subsequent passes, we'll expect to see the header.
4652+    # This changes in two places:
4653+    #
4654+    #   - When we write out the salt hash
4655+    #   - When we write out the root of the share hash tree
4656+    #
4657+    # since these values will change the header. It is possible that we
4658+    # can just make those be written in one operation to minimize
4659+    # disruption.
4660+    def __init__(self,
4661+                 shnum,
4662+                 rref, # a remote reference to a storage server
4663+                 storage_index,
4664+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4665+                 seqnum, # the sequence number of the mutable file
4666+                 required_shares,
4667+                 total_shares,
4668+                 segment_size,
4669+                 data_length): # the length of the original file
4670+        self.shnum = shnum
4671+        self._rref = rref
4672+        self._storage_index = storage_index
4673+        self._seqnum = seqnum
4674+        self._required_shares = required_shares
4675+        assert self.shnum >= 0 and self.shnum < total_shares
4676+        self._total_shares = total_shares
4677+        # We build up the offset table as we write things. It is the
4678+        # last thing we write to the remote server.
4679+        self._offsets = {}
4680+        self._testvs = []
4681+        self._secrets = secrets
4682+        # The segment size needs to be a multiple of the k parameter --
4683+        # any padding should have been carried out by the publisher
4684+        # already.
4685+        assert segment_size % required_shares == 0
4686+        self._segment_size = segment_size
4687+        self._data_length = data_length
4688+
4689+        # These are set later -- we define them here so that we can
4690+        # check for their existence easily
4691+
4692+        # This is the root of the share hash tree -- the Merkle tree
4693+        # over the roots of the block hash trees computed for shares in
4694+        # this upload.
4695+        self._root_hash = None
4696+
4697+        # We haven't yet written anything to the remote bucket. By
4698+        # setting this, we tell the _write method as much. The write
4699+        # method will then know that it also needs to add a write vector
4700+        # for the checkstring (or what we have of it) to the first write
4701+        # request. We'll then record that value for future use.  If
4702+        # we're expecting something to be there already, we need to call
4703+        # set_checkstring before we write anything to tell the first
4704+        # write about that.
4705+        self._written = False
4706+
4707+        # When writing data to the storage servers, we get a read vector
4708+        # for free. We'll read the checkstring, which will help us
4709+        # figure out what's gone wrong if a write fails.
4710+        self._readv = [(0, struct.calcsize(MDMFCHECKSTRING))]
4711+
4712+        # We calculate the number of segments because it tells us
4713+        # where the salt part of the file ends/share segment begins,
4714+        # and also because it provides a useful amount of bounds checking.
4715+        self._num_segments = mathutil.div_ceil(self._data_length,
4716+                                               self._segment_size)
4717+        self._block_size = self._segment_size / self._required_shares
4718+        # We also calculate the share size, to help us with block
4719+        # constraints later.
4720+        tail_size = self._data_length % self._segment_size
4721+        if not tail_size:
4722+            self._tail_block_size = self._block_size
4723+        else:
4724+            self._tail_block_size = mathutil.next_multiple(tail_size,
4725+                                                           self._required_shares)
4726+            self._tail_block_size /= self._required_shares
4727+
4728+        # We already know where the sharedata starts; right after the end
4729+        # of the header (which is defined as the signable part + the offsets)
4730+        # We can also calculate where the encrypted private key begins
4731+        # from what we know know.
4732+        self._actual_block_size = self._block_size + SALT_SIZE
4733+        data_size = self._actual_block_size * (self._num_segments - 1)
4734+        data_size += self._tail_block_size
4735+        data_size += SALT_SIZE
4736+        self._offsets['enc_privkey'] = MDMFHEADERSIZE
4737+        self._offsets['enc_privkey'] += data_size
4738+        # We'll wait for the rest. Callers can now call my "put_block" and
4739+        # "set_checkstring" methods.
4740+
4741+
4742+    def set_checkstring(self,
4743+                        seqnum_or_checkstring,
4744+                        root_hash=None,
4745+                        salt=None):
4746+        """
4747+        Set checkstring checkstring for the given shnum.
4748+
4749+        This can be invoked in one of two ways.
4750+
4751+        With one argument, I assume that you are giving me a literal
4752+        checkstring -- e.g., the output of get_checkstring. I will then
4753+        set that checkstring as it is. This form is used by unit tests.
4754+
4755+        With two arguments, I assume that you are giving me a sequence
4756+        number and root hash to make a checkstring from. In that case, I
4757+        will build a checkstring and set it for you. This form is used
4758+        by the publisher.
4759+
4760+        By default, I assume that I am writing new shares to the grid.
4761+        If you don't explcitly set your own checkstring, I will use
4762+        one that requires that the remote share not exist. You will want
4763+        to use this method if you are updating a share in-place;
4764+        otherwise, writes will fail.
4765+        """
4766+        # You're allowed to overwrite checkstrings with this method;
4767+        # I assume that users know what they are doing when they call
4768+        # it.
4769+        if root_hash:
4770+            checkstring = struct.pack(MDMFCHECKSTRING,
4771+                                      1,
4772+                                      seqnum_or_checkstring,
4773+                                      root_hash)
4774+        else:
4775+            checkstring = seqnum_or_checkstring
4776+
4777+        if checkstring == "":
4778+            # We special-case this, since len("") = 0, but we need
4779+            # length of 1 for the case of an empty share to work on the
4780+            # storage server, which is what a checkstring that is the
4781+            # empty string means.
4782+            self._testvs = []
4783+        else:
4784+            self._testvs = []
4785+            self._testvs.append((0, len(checkstring), "eq", checkstring))
4786+
4787+
4788+    def __repr__(self):
4789+        return "MDMFSlotWriteProxy for share %d" % self.shnum
4790+
4791+
4792+    def get_checkstring(self):
4793+        """
4794+        Given a share number, I return a representation of what the
4795+        checkstring for that share on the server will look like.
4796+
4797+        I am mostly used for tests.
4798+        """
4799+        if self._root_hash:
4800+            roothash = self._root_hash
4801+        else:
4802+            roothash = "\x00" * 32
4803+        return struct.pack(MDMFCHECKSTRING,
4804+                           1,
4805+                           self._seqnum,
4806+                           roothash)
4807+
4808+
4809+    def put_block(self, data, segnum, salt):
4810+        """
4811+        Put the encrypted-and-encoded data segment in the slot, along
4812+        with the salt.
4813+        """
4814+        if segnum >= self._num_segments:
4815+            raise LayoutInvalid("I won't overwrite the private key")
4816+        if len(salt) != SALT_SIZE:
4817+            raise LayoutInvalid("I was given a salt of size %d, but "
4818+                                "I wanted a salt of size %d")
4819+        if segnum + 1 == self._num_segments:
4820+            if len(data) != self._tail_block_size:
4821+                raise LayoutInvalid("I was given the wrong size block to write")
4822+        elif len(data) != self._block_size:
4823+            raise LayoutInvalid("I was given the wrong size block to write")
4824+
4825+        # We want to write at len(MDMFHEADER) + segnum * block_size.
4826+
4827+        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
4828+        data = salt + data
4829+
4830+        datavs = [tuple([offset, data])]
4831+        return self._write(datavs)
4832+
4833+
4834+    def put_encprivkey(self, encprivkey):
4835+        """
4836+        Put the encrypted private key in the remote slot.
4837+        """
4838+        assert self._offsets
4839+        assert self._offsets['enc_privkey']
4840+        # You shouldn't re-write the encprivkey after the block hash
4841+        # tree is written, since that could cause the private key to run
4842+        # into the block hash tree. Before it writes the block hash
4843+        # tree, the block hash tree writing method writes the offset of
4844+        # the salt hash tree. So that's a good indicator of whether or
4845+        # not the block hash tree has been written.
4846+        if "share_hash_chain" in self._offsets:
4847+            raise LayoutInvalid("You must write this before the block hash tree")
4848+
4849+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
4850+        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
4851+        def _on_failure():
4852+            del(self._offsets['block_hash_tree'])
4853+        return self._write(datavs, on_failure=_on_failure)
4854+
4855+
4856+    def put_blockhashes(self, blockhashes):
4857+        """
4858+        Put the block hash tree in the remote slot.
4859+
4860+        The encrypted private key must be put before the block hash
4861+        tree, since we need to know how large it is to know where the
4862+        block hash tree should go. The block hash tree must be put
4863+        before the salt hash tree, since its size determines the
4864+        offset of the share hash chain.
4865+        """
4866+        assert self._offsets
4867+        assert isinstance(blockhashes, list)
4868+        if "block_hash_tree" not in self._offsets:
4869+            raise LayoutInvalid("You must put the encrypted private key "
4870+                                "before you put the block hash tree")
4871+        # If written, the share hash chain causes the signature offset
4872+        # to be defined.
4873+        if "signature" in self._offsets:
4874+            raise LayoutInvalid("You must put the block hash tree before "
4875+                                "you put the share hash chain")
4876+        blockhashes_s = "".join(blockhashes)
4877+        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
4878+        datavs = []
4879+        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
4880+        def _on_failure():
4881+            del(self._offsets['share_hash_chain'])
4882+        return self._write(datavs, on_failure=_on_failure)
4883+
4884+
4885+    def put_sharehashes(self, sharehashes):
4886+        """
4887+        Put the share hash chain in the remote slot.
4888+
4889+        The salt hash tree must be put before the share hash chain,
4890+        since we need to know where the salt hash tree ends before we
4891+        can know where the share hash chain starts. The share hash chain
4892+        must be put before the signature, since the length of the packed
4893+        share hash chain determines the offset of the signature. Also,
4894+        semantically, you must know what the root of the salt hash tree
4895+        is before you can generate a valid signature.
4896+        """
4897+        assert isinstance(sharehashes, dict)
4898+        if "share_hash_chain" not in self._offsets:
4899+            raise LayoutInvalid("You need to put the salt hash tree before "
4900+                                "you can put the share hash chain")
4901+        # The signature comes after the share hash chain. If the
4902+        # signature has already been written, we must not write another
4903+        # share hash chain. The signature writes the verification key
4904+        # offset when it gets sent to the remote server, so we look for
4905+        # that.
4906+        if "verification_key" in self._offsets:
4907+            raise LayoutInvalid("You must write the share hash chain "
4908+                                "before you write the signature")
4909+        datavs = []
4910+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4911+                                  for i in sorted(sharehashes.keys())])
4912+        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
4913+        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
4914+        def _on_failure():
4915+            del(self._offsets['signature'])
4916+        return self._write(datavs, on_failure=_on_failure)
4917+
4918+
4919+    def put_root_hash(self, roothash):
4920+        """
4921+        Put the root hash (the root of the share hash tree) in the
4922+        remote slot.
4923+        """
4924+        # It does not make sense to be able to put the root
4925+        # hash without first putting the share hashes, since you need
4926+        # the share hashes to generate the root hash.
4927+        #
4928+        # Signature is defined by the routine that places the share hash
4929+        # chain, so it's a good thing to look for in finding out whether
4930+        # or not the share hash chain exists on the remote server.
4931+        if "signature" not in self._offsets:
4932+            raise LayoutInvalid("You need to put the share hash chain "
4933+                                "before you can put the root share hash")
4934+        if len(roothash) != HASH_SIZE:
4935+            raise LayoutInvalid("hashes and salts must be exactly %d bytes"
4936+                                 % HASH_SIZE)
4937+        datavs = []
4938+        self._root_hash = roothash
4939+        # To write both of these values, we update the checkstring on
4940+        # the remote server, which includes them
4941+        checkstring = self.get_checkstring()
4942+        datavs.append(tuple([0, checkstring]))
4943+        # This write, if successful, changes the checkstring, so we need
4944+        # to update our internal checkstring to be consistent with the
4945+        # one on the server.
4946+        def _on_success():
4947+            self._testvs = [(0, len(checkstring), "eq", checkstring)]
4948+        def _on_failure():
4949+            self._root_hash = None
4950+        return self._write(datavs,
4951+                           on_success=_on_success,
4952+                           on_failure=_on_failure)
4953+
4954+
4955+    def get_signable(self):
4956+        """
4957+        Get the first seven fields of the mutable file; the parts that
4958+        are signed.
4959+        """
4960+        if not self._root_hash:
4961+            raise LayoutInvalid("You need to set the root hash "
4962+                                "before getting something to "
4963+                                "sign")
4964+        return struct.pack(MDMFSIGNABLEHEADER,
4965+                           1,
4966+                           self._seqnum,
4967+                           self._root_hash,
4968+                           self._required_shares,
4969+                           self._total_shares,
4970+                           self._segment_size,
4971+                           self._data_length)
4972+
4973+
4974+    def put_signature(self, signature):
4975+        """
4976+        Put the signature field to the remote slot.
4977+
4978+        I require that the root hash and share hash chain have been put
4979+        to the grid before I will write the signature to the grid.
4980+        """
4981+        if "signature" not in self._offsets:
4982+            raise LayoutInvalid("You must put the share hash chain "
4983+        # It does not make sense to put a signature without first
4984+        # putting the root hash and the salt hash (since otherwise
4985+        # the signature would be incomplete), so we don't allow that.
4986+                       "before putting the signature")
4987+        if not self._root_hash:
4988+            raise LayoutInvalid("You must complete the signed prefix "
4989+                                "before computing a signature")
4990+        # If we put the signature after we put the verification key, we
4991+        # could end up running into the verification key, and will
4992+        # probably screw up the offsets as well. So we don't allow that.
4993+        # The method that writes the verification key defines the EOF
4994+        # offset before writing the verification key, so look for that.
4995+        if "EOF" in self._offsets:
4996+            raise LayoutInvalid("You must write the signature before the verification key")
4997+
4998+        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
4999+        datavs = []
5000+        datavs.append(tuple([self._offsets['signature'], signature]))
5001+        def _on_failure():
5002+            del(self._offsets['verification_key'])
5003+        return self._write(datavs, on_failure=_on_failure)
5004+
5005+
5006+    def put_verification_key(self, verification_key):
5007+        """
5008+        Put the verification key into the remote slot.
5009+
5010+        I require that the signature have been written to the storage
5011+        server before I allow the verification key to be written to the
5012+        remote server.
5013+        """
5014+        if "verification_key" not in self._offsets:
5015+            raise LayoutInvalid("You must put the signature before you "
5016+                                "can put the verification key")
5017+        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
5018+        datavs = []
5019+        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
5020+        def _on_failure():
5021+            del(self._offsets['EOF'])
5022+        return self._write(datavs, on_failure=_on_failure)
5023+
5024+    def _get_offsets_tuple(self):
5025+        return tuple([(key, value) for key, value in self._offsets.items()])
5026+
5027+    def get_verinfo(self):
5028+        return (self._seqnum,
5029+                self._root_hash,
5030+                self._required_shares,
5031+                self._total_shares,
5032+                self._segment_size,
5033+                self._data_length,
5034+                self.get_signable(),
5035+                self._get_offsets_tuple())
5036+
5037+
5038+    def finish_publishing(self):
5039+        """
5040+        Write the offset table and encoding parameters to the remote
5041+        slot, since that's the only thing we have yet to publish at this
5042+        point.
5043+        """
5044+        if "EOF" not in self._offsets:
5045+            raise LayoutInvalid("You must put the verification key before "
5046+                                "you can publish the offsets")
5047+        offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
5048+        offsets = struct.pack(MDMFOFFSETS,
5049+                              self._offsets['enc_privkey'],
5050+                              self._offsets['block_hash_tree'],
5051+                              self._offsets['share_hash_chain'],
5052+                              self._offsets['signature'],
5053+                              self._offsets['verification_key'],
5054+                              self._offsets['EOF'])
5055+        datavs = []
5056+        datavs.append(tuple([offsets_offset, offsets]))
5057+        encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
5058+        params = struct.pack(">BBQQ",
5059+                             self._required_shares,
5060+                             self._total_shares,
5061+                             self._segment_size,
5062+                             self._data_length)
5063+        datavs.append(tuple([encoding_parameters_offset, params]))
5064+        return self._write(datavs)
5065+
5066+
5067+    def _write(self, datavs, on_failure=None, on_success=None):
5068+        """I write the data vectors in datavs to the remote slot."""
5069+        tw_vectors = {}
5070+        new_share = False
5071+        if not self._testvs:
5072+            self._testvs = []
5073+            self._testvs.append(tuple([0, 1, "eq", ""]))
5074+            new_share = True
5075+        if not self._written:
5076+            # Write a new checkstring to the share when we write it, so
5077+            # that we have something to check later.
5078+            new_checkstring = self.get_checkstring()
5079+            datavs.append((0, new_checkstring))
5080+            def _first_write():
5081+                self._written = True
5082+                self._testvs = [(0, len(new_checkstring), "eq", new_checkstring)]
5083+            on_success = _first_write
5084+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
5085+        datalength = sum([len(x[1]) for x in datavs])
5086+        d = self._rref.callRemote("slot_testv_and_readv_and_writev",
5087+                                  self._storage_index,
5088+                                  self._secrets,
5089+                                  tw_vectors,
5090+                                  self._readv)
5091+        def _result(results):
5092+            if isinstance(results, failure.Failure) or not results[0]:
5093+                # Do nothing; the write was unsuccessful.
5094+                if on_failure: on_failure()
5095+            else:
5096+                if on_success: on_success()
5097+            return results
5098+        d.addCallback(_result)
5099+        return d
5100+
5101+
5102+class MDMFSlotReadProxy:
5103+    """
5104+    I read from a mutable slot filled with data written in the MDMF data
5105+    format (which is described above).
5106+
5107+    I can be initialized with some amount of data, which I will use (if
5108+    it is valid) to eliminate some of the need to fetch it from servers.
5109+    """
5110+    def __init__(self,
5111+                 rref,
5112+                 storage_index,
5113+                 shnum,
5114+                 data=""):
5115+        # Start the initialization process.
5116+        self._rref = rref
5117+        self._storage_index = storage_index
5118+        self.shnum = shnum
5119+
5120+        # Before doing anything, the reader is probably going to want to
5121+        # verify that the signature is correct. To do that, they'll need
5122+        # the verification key, and the signature. To get those, we'll
5123+        # need the offset table. So fetch the offset table on the
5124+        # assumption that that will be the first thing that a reader is
5125+        # going to do.
5126+
5127+        # The fact that these encoding parameters are None tells us
5128+        # that we haven't yet fetched them from the remote share, so we
5129+        # should. We could just not set them, but the checks will be
5130+        # easier to read if we don't have to use hasattr.
5131+        self._version_number = None
5132+        self._sequence_number = None
5133+        self._root_hash = None
5134+        # Filled in if we're dealing with an SDMF file. Unused
5135+        # otherwise.
5136+        self._salt = None
5137+        self._required_shares = None
5138+        self._total_shares = None
5139+        self._segment_size = None
5140+        self._data_length = None
5141+        self._offsets = None
5142+
5143+        # If the user has chosen to initialize us with some data, we'll
5144+        # try to satisfy subsequent data requests with that data before
5145+        # asking the storage server for it. If
5146+        self._data = data
5147+        # The way callers interact with cache in the filenode returns
5148+        # None if there isn't any cached data, but the way we index the
5149+        # cached data requires a string, so convert None to "".
5150+        if self._data == None:
5151+            self._data = ""
5152+
5153+        self._queue_observers = observer.ObserverList()
5154+        self._queue_errbacks = observer.ObserverList()
5155+        self._readvs = []
5156+
5157+
5158+    def _maybe_fetch_offsets_and_header(self, force_remote=False):
5159+        """
5160+        I fetch the offset table and the header from the remote slot if
5161+        I don't already have them. If I do have them, I do nothing and
5162+        return an empty Deferred.
5163+        """
5164+        if self._offsets:
5165+            return defer.succeed(None)
5166+        # At this point, we may be either SDMF or MDMF. Fetching 107
5167+        # bytes will be enough to get header and offsets for both SDMF and
5168+        # MDMF, though we'll be left with 4 more bytes than we
5169+        # need if this ends up being MDMF. This is probably less
5170+        # expensive than the cost of a second roundtrip.
5171+        readvs = [(0, 107)]
5172+        d = self._read(readvs, force_remote)
5173+        d.addCallback(self._process_encoding_parameters)
5174+        d.addCallback(self._process_offsets)
5175+        return d
5176+
5177+
5178+    def _process_encoding_parameters(self, encoding_parameters):
5179+        assert self.shnum in encoding_parameters
5180+        encoding_parameters = encoding_parameters[self.shnum][0]
5181+        # The first byte is the version number. It will tell us what
5182+        # to do next.
5183+        (verno,) = struct.unpack(">B", encoding_parameters[:1])
5184+        if verno == MDMF_VERSION:
5185+            read_size = MDMFHEADERWITHOUTOFFSETSSIZE
5186+            (verno,
5187+             seqnum,
5188+             root_hash,
5189+             k,
5190+             n,
5191+             segsize,
5192+             datalen) = struct.unpack(MDMFHEADERWITHOUTOFFSETS,
5193+                                      encoding_parameters[:read_size])
5194+            if segsize == 0 and datalen == 0:
5195+                # Empty file, no segments.
5196+                self._num_segments = 0
5197+            else:
5198+                self._num_segments = mathutil.div_ceil(datalen, segsize)
5199+
5200+        elif verno == SDMF_VERSION:
5201+            read_size = SIGNED_PREFIX_LENGTH
5202+            (verno,
5203+             seqnum,
5204+             root_hash,
5205+             salt,
5206+             k,
5207+             n,
5208+             segsize,
5209+             datalen) = struct.unpack(">BQ32s16s BBQQ",
5210+                                encoding_parameters[:SIGNED_PREFIX_LENGTH])
5211+            self._salt = salt
5212+            if segsize == 0 and datalen == 0:
5213+                # empty file
5214+                self._num_segments = 0
5215+            else:
5216+                # non-empty SDMF files have one segment.
5217+                self._num_segments = 1
5218+        else:
5219+            raise UnknownVersionError("You asked me to read mutable file "
5220+                                      "version %d, but I only understand "
5221+                                      "%d and %d" % (verno, SDMF_VERSION,
5222+                                                     MDMF_VERSION))
5223+
5224+        self._version_number = verno
5225+        self._sequence_number = seqnum
5226+        self._root_hash = root_hash
5227+        self._required_shares = k
5228+        self._total_shares = n
5229+        self._segment_size = segsize
5230+        self._data_length = datalen
5231+
5232+        self._block_size = self._segment_size / self._required_shares
5233+        # We can upload empty files, and need to account for this fact
5234+        # so as to avoid zero-division and zero-modulo errors.
5235+        if datalen > 0:
5236+            tail_size = self._data_length % self._segment_size
5237+        else:
5238+            tail_size = 0
5239+        if not tail_size:
5240+            self._tail_block_size = self._block_size
5241+        else:
5242+            self._tail_block_size = mathutil.next_multiple(tail_size,
5243+                                                    self._required_shares)
5244+            self._tail_block_size /= self._required_shares
5245+
5246+        return encoding_parameters
5247+
5248+
5249+    def _process_offsets(self, offsets):
5250+        if self._version_number == 0:
5251+            read_size = OFFSETS_LENGTH
5252+            read_offset = SIGNED_PREFIX_LENGTH
5253+            end = read_size + read_offset
5254+            (signature,
5255+             share_hash_chain,
5256+             block_hash_tree,
5257+             share_data,
5258+             enc_privkey,
5259+             EOF) = struct.unpack(">LLLLQQ",
5260+                                  offsets[read_offset:end])
5261+            self._offsets = {}
5262+            self._offsets['signature'] = signature
5263+            self._offsets['share_data'] = share_data
5264+            self._offsets['block_hash_tree'] = block_hash_tree
5265+            self._offsets['share_hash_chain'] = share_hash_chain
5266+            self._offsets['enc_privkey'] = enc_privkey
5267+            self._offsets['EOF'] = EOF
5268+
5269+        elif self._version_number == 1:
5270+            read_offset = MDMFHEADERWITHOUTOFFSETSSIZE
5271+            read_length = MDMFOFFSETS_LENGTH
5272+            end = read_offset + read_length
5273+            (encprivkey,
5274+             blockhashes,
5275+             sharehashes,
5276+             signature,
5277+             verification_key,
5278+             eof) = struct.unpack(MDMFOFFSETS,
5279+                                  offsets[read_offset:end])
5280+            self._offsets = {}
5281+            self._offsets['enc_privkey'] = encprivkey
5282+            self._offsets['block_hash_tree'] = blockhashes
5283+            self._offsets['share_hash_chain'] = sharehashes
5284+            self._offsets['signature'] = signature
5285+            self._offsets['verification_key'] = verification_key
5286+            self._offsets['EOF'] = eof
5287+
5288+
5289+    def get_block_and_salt(self, segnum, queue=False):
5290+        """
5291+        I return (block, salt), where block is the block data and
5292+        salt is the salt used to encrypt that segment.
5293+        """
5294+        d = self._maybe_fetch_offsets_and_header()
5295+        def _then(ignored):
5296+            if self._version_number == 1:
5297+                base_share_offset = MDMFHEADERSIZE
5298+            else:
5299+                base_share_offset = self._offsets['share_data']
5300+
5301+            if segnum + 1 > self._num_segments:
5302+                raise LayoutInvalid("Not a valid segment number")
5303+
5304+            if self._version_number == 0:
5305+                share_offset = base_share_offset + self._block_size * segnum
5306+            else:
5307+                share_offset = base_share_offset + (self._block_size + \
5308+                                                    SALT_SIZE) * segnum
5309+            if segnum + 1 == self._num_segments:
5310+                data = self._tail_block_size
5311+            else:
5312+                data = self._block_size
5313+
5314+            if self._version_number == 1:
5315+                data += SALT_SIZE
5316+
5317+            readvs = [(share_offset, data)]
5318+            return readvs
5319+        d.addCallback(_then)
5320+        d.addCallback(lambda readvs:
5321+            self._read(readvs, queue=queue))
5322+        def _process_results(results):
5323+            assert self.shnum in results
5324+            if self._version_number == 0:
5325+                # We only read the share data, but we know the salt from
5326+                # when we fetched the header
5327+                data = results[self.shnum]
5328+                if not data:
5329+                    data = ""
5330+                else:
5331+                    assert len(data) == 1
5332+                    data = data[0]
5333+                salt = self._salt
5334+            else:
5335+                data = results[self.shnum]
5336+                if not data:
5337+                    salt = data = ""
5338+                else:
5339+                    salt_and_data = results[self.shnum][0]
5340+                    salt = salt_and_data[:SALT_SIZE]
5341+                    data = salt_and_data[SALT_SIZE:]
5342+            return data, salt
5343+        d.addCallback(_process_results)
5344+        return d
5345+
5346+
5347+    def get_blockhashes(self, needed=None, queue=False, force_remote=False):
5348+        """
5349+        I return the block hash tree
5350+
5351+        I take an optional argument, needed, which is a set of indices
5352+        correspond to hashes that I should fetch. If this argument is
5353+        missing, I will fetch the entire block hash tree; otherwise, I
5354+        may attempt to fetch fewer hashes, based on what needed says
5355+        that I should do. Note that I may fetch as many hashes as I
5356+        want, so long as the set of hashes that I do fetch is a superset
5357+        of the ones that I am asked for, so callers should be prepared
5358+        to tolerate additional hashes.
5359+        """
5360+        # TODO: Return only the parts of the block hash tree necessary
5361+        # to validate the blocknum provided?
5362+        # This is a good idea, but it is hard to implement correctly. It
5363+        # is bad to fetch any one block hash more than once, so we
5364+        # probably just want to fetch the whole thing at once and then
5365+        # serve it.
5366+        if needed == set([]):
5367+            return defer.succeed([])
5368+        d = self._maybe_fetch_offsets_and_header()
5369+        def _then(ignored):
5370+            blockhashes_offset = self._offsets['block_hash_tree']
5371+            if self._version_number == 1:
5372+                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
5373+            else:
5374+                blockhashes_length = self._offsets['share_data'] - blockhashes_offset
5375+            readvs = [(blockhashes_offset, blockhashes_length)]
5376+            return readvs
5377+        d.addCallback(_then)
5378+        d.addCallback(lambda readvs:
5379+            self._read(readvs, queue=queue, force_remote=force_remote))
5380+        def _build_block_hash_tree(results):
5381+            assert self.shnum in results
5382+
5383+            rawhashes = results[self.shnum][0]
5384+            results = [rawhashes[i:i+HASH_SIZE]
5385+                       for i in range(0, len(rawhashes), HASH_SIZE)]
5386+            return results
5387+        d.addCallback(_build_block_hash_tree)
5388+        return d
5389+
5390+
5391+    def get_sharehashes(self, needed=None, queue=False, force_remote=False):
5392+        """
5393+        I return the part of the share hash chain placed to validate
5394+        this share.
5395+
5396+        I take an optional argument, needed. Needed is a set of indices
5397+        that correspond to the hashes that I should fetch. If needed is
5398+        not present, I will fetch and return the entire share hash
5399+        chain. Otherwise, I may fetch and return any part of the share
5400+        hash chain that is a superset of the part that I am asked to
5401+        fetch. Callers should be prepared to deal with more hashes than
5402+        they've asked for.
5403+        """
5404+        if needed == set([]):
5405+            return defer.succeed([])
5406+        d = self._maybe_fetch_offsets_and_header()
5407+
5408+        def _make_readvs(ignored):
5409+            sharehashes_offset = self._offsets['share_hash_chain']
5410+            if self._version_number == 0:
5411+                sharehashes_length = self._offsets['block_hash_tree'] - sharehashes_offset
5412+            else:
5413+                sharehashes_length = self._offsets['signature'] - sharehashes_offset
5414+            readvs = [(sharehashes_offset, sharehashes_length)]
5415+            return readvs
5416+        d.addCallback(_make_readvs)
5417+        d.addCallback(lambda readvs:
5418+            self._read(readvs, queue=queue, force_remote=force_remote))
5419+        def _build_share_hash_chain(results):
5420+            assert self.shnum in results
5421+
5422+            sharehashes = results[self.shnum][0]
5423+            results = [sharehashes[i:i+(HASH_SIZE + 2)]
5424+                       for i in range(0, len(sharehashes), HASH_SIZE + 2)]
5425+            results = dict([struct.unpack(">H32s", data)
5426+                            for data in results])
5427+            return results
5428+        d.addCallback(_build_share_hash_chain)
5429+        return d
5430+
5431+
5432+    def get_encprivkey(self, queue=False):
5433+        """
5434+        I return the encrypted private key.
5435+        """
5436+        d = self._maybe_fetch_offsets_and_header()
5437+
5438+        def _make_readvs(ignored):
5439+            privkey_offset = self._offsets['enc_privkey']
5440+            if self._version_number == 0:
5441+                privkey_length = self._offsets['EOF'] - privkey_offset
5442+            else:
5443+                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
5444+            readvs = [(privkey_offset, privkey_length)]
5445+            return readvs
5446+        d.addCallback(_make_readvs)
5447+        d.addCallback(lambda readvs:
5448+            self._read(readvs, queue=queue))
5449+        def _process_results(results):
5450+            assert self.shnum in results
5451+            privkey = results[self.shnum][0]
5452+            return privkey
5453+        d.addCallback(_process_results)
5454+        return d
5455+
5456+
5457+    def get_signature(self, queue=False):
5458+        """
5459+        I return the signature of my share.
5460+        """
5461+        d = self._maybe_fetch_offsets_and_header()
5462+
5463+        def _make_readvs(ignored):
5464+            signature_offset = self._offsets['signature']
5465+            if self._version_number == 1:
5466+                signature_length = self._offsets['verification_key'] - signature_offset
5467+            else:
5468+                signature_length = self._offsets['share_hash_chain'] - signature_offset
5469+            readvs = [(signature_offset, signature_length)]
5470+            return readvs
5471+        d.addCallback(_make_readvs)
5472+        d.addCallback(lambda readvs:
5473+            self._read(readvs, queue=queue))
5474+        def _process_results(results):
5475+            assert self.shnum in results
5476+            signature = results[self.shnum][0]
5477+            return signature
5478+        d.addCallback(_process_results)
5479+        return d
5480+
5481+
5482+    def get_verification_key(self, queue=False):
5483+        """
5484+        I return the verification key.
5485+        """
5486+        d = self._maybe_fetch_offsets_and_header()
5487+
5488+        def _make_readvs(ignored):
5489+            if self._version_number == 1:
5490+                vk_offset = self._offsets['verification_key']
5491+                vk_length = self._offsets['EOF'] - vk_offset
5492+            else:
5493+                vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5494+                vk_length = self._offsets['signature'] - vk_offset
5495+            readvs = [(vk_offset, vk_length)]
5496+            return readvs
5497+        d.addCallback(_make_readvs)
5498+        d.addCallback(lambda readvs:
5499+            self._read(readvs, queue=queue))
5500+        def _process_results(results):
5501+            assert self.shnum in results
5502+            verification_key = results[self.shnum][0]
5503+            return verification_key
5504+        d.addCallback(_process_results)
5505+        return d
5506+
5507+
5508+    def get_encoding_parameters(self):
5509+        """
5510+        I return (k, n, segsize, datalen)
5511+        """
5512+        d = self._maybe_fetch_offsets_and_header()
5513+        d.addCallback(lambda ignored:
5514+            (self._required_shares,
5515+             self._total_shares,
5516+             self._segment_size,
5517+             self._data_length))
5518+        return d
5519+
5520+
5521+    def get_seqnum(self):
5522+        """
5523+        I return the sequence number for this share.
5524+        """
5525+        d = self._maybe_fetch_offsets_and_header()
5526+        d.addCallback(lambda ignored:
5527+            self._sequence_number)
5528+        return d
5529+
5530+
5531+    def get_root_hash(self):
5532+        """
5533+        I return the root of the block hash tree
5534+        """
5535+        d = self._maybe_fetch_offsets_and_header()
5536+        d.addCallback(lambda ignored: self._root_hash)
5537+        return d
5538+
5539+
5540+    def get_checkstring(self):
5541+        """
5542+        I return the packed representation of the following:
5543+
5544+            - version number
5545+            - sequence number
5546+            - root hash
5547+            - salt hash
5548+
5549+        which my users use as a checkstring to detect other writers.
5550+        """
5551+        d = self._maybe_fetch_offsets_and_header()
5552+        def _build_checkstring(ignored):
5553+            if self._salt:
5554+                checkstring = strut.pack(PREFIX,
5555+                                         self._version_number,
5556+                                         self._sequence_number,
5557+                                         self._root_hash,
5558+                                         self._salt)
5559+            else:
5560+                checkstring = struct.pack(MDMFCHECKSTRING,
5561+                                          self._version_number,
5562+                                          self._sequence_number,
5563+                                          self._root_hash)
5564+
5565+            return checkstring
5566+        d.addCallback(_build_checkstring)
5567+        return d
5568+
5569+
5570+    def get_prefix(self, force_remote):
5571+        d = self._maybe_fetch_offsets_and_header(force_remote)
5572+        d.addCallback(lambda ignored:
5573+            self._build_prefix())
5574+        return d
5575+
5576+
5577+    def _build_prefix(self):
5578+        # The prefix is another name for the part of the remote share
5579+        # that gets signed. It consists of everything up to and
5580+        # including the datalength, packed by struct.
5581+        if self._version_number == SDMF_VERSION:
5582+            return struct.pack(SIGNED_PREFIX,
5583+                           self._version_number,
5584+                           self._sequence_number,
5585+                           self._root_hash,
5586+                           self._salt,
5587+                           self._required_shares,
5588+                           self._total_shares,
5589+                           self._segment_size,
5590+                           self._data_length)
5591+
5592+        else:
5593+            return struct.pack(MDMFSIGNABLEHEADER,
5594+                           self._version_number,
5595+                           self._sequence_number,
5596+                           self._root_hash,
5597+                           self._required_shares,
5598+                           self._total_shares,
5599+                           self._segment_size,
5600+                           self._data_length)
5601+
5602+
5603+    def _get_offsets_tuple(self):
5604+        # The offsets tuple is another component of the version
5605+        # information tuple. It is basically our offsets dictionary,
5606+        # itemized and in a tuple.
5607+        return self._offsets.copy()
5608+
5609+
5610+    def get_verinfo(self):
5611+        """
5612+        I return my verinfo tuple. This is used by the ServermapUpdater
5613+        to keep track of versions of mutable files.
5614+
5615+        The verinfo tuple for MDMF files contains:
5616+            - seqnum
5617+            - root hash
5618+            - a blank (nothing)
5619+            - segsize
5620+            - datalen
5621+            - k
5622+            - n
5623+            - prefix (the thing that you sign)
5624+            - a tuple of offsets
5625+
5626+        We include the nonce in MDMF to simplify processing of version
5627+        information tuples.
5628+
5629+        The verinfo tuple for SDMF files is the same, but contains a
5630+        16-byte IV instead of a hash of salts.
5631+        """
5632+        d = self._maybe_fetch_offsets_and_header()
5633+        def _build_verinfo(ignored):
5634+            if self._version_number == SDMF_VERSION:
5635+                salt_to_use = self._salt
5636+            else:
5637+                salt_to_use = None
5638+            return (self._sequence_number,
5639+                    self._root_hash,
5640+                    salt_to_use,
5641+                    self._segment_size,
5642+                    self._data_length,
5643+                    self._required_shares,
5644+                    self._total_shares,
5645+                    self._build_prefix(),
5646+                    self._get_offsets_tuple())
5647+        d.addCallback(_build_verinfo)
5648+        return d
5649+
5650+
5651+    def flush(self):
5652+        """
5653+        I flush my queue of read vectors.
5654+        """
5655+        d = self._read(self._readvs)
5656+        def _then(results):
5657+            self._readvs = []
5658+            if isinstance(results, failure.Failure):
5659+                self._queue_errbacks.notify(results)
5660+            else:
5661+                self._queue_observers.notify(results)
5662+            self._queue_observers = observer.ObserverList()
5663+            self._queue_errbacks = observer.ObserverList()
5664+        d.addBoth(_then)
5665+
5666+
5667+    def _read(self, readvs, force_remote=False, queue=False):
5668+        unsatisfiable = filter(lambda x: x[0] + x[1] > len(self._data), readvs)
5669+        # TODO: It's entirely possible to tweak this so that it just
5670+        # fulfills the requests that it can, and not demand that all
5671+        # requests are satisfiable before running it.
5672+        if not unsatisfiable and not force_remote:
5673+            results = [self._data[offset:offset+length]
5674+                       for (offset, length) in readvs]
5675+            results = {self.shnum: results}
5676+            return defer.succeed(results)
5677+        else:
5678+            if queue:
5679+                start = len(self._readvs)
5680+                self._readvs += readvs
5681+                end = len(self._readvs)
5682+                def _get_results(results, start, end):
5683+                    if not self.shnum in results:
5684+                        return {self._shnum: [""]}
5685+                    return {self.shnum: results[self.shnum][start:end]}
5686+                d = defer.Deferred()
5687+                d.addCallback(_get_results, start, end)
5688+                self._queue_observers.subscribe(d.callback)
5689+                self._queue_errbacks.subscribe(d.errback)
5690+                return d
5691+            return self._rref.callRemote("slot_readv",
5692+                                         self._storage_index,
5693+                                         [self.shnum],
5694+                                         readvs)
5695+
5696+
5697+    def is_sdmf(self):
5698+        """I tell my caller whether or not my remote file is SDMF or MDMF
5699+        """
5700+        d = self._maybe_fetch_offsets_and_header()
5701+        d.addCallback(lambda ignored:
5702+            self._version_number == 0)
5703+        return d
5704+
5705+
5706+class LayoutInvalid(Exception):
5707+    """
5708+    This isn't a valid MDMF mutable file
5709+    """
5710hunk ./src/allmydata/test/test_storage.py 2
5711 
5712-import time, os.path, stat, re, simplejson, struct
5713+import time, os.path, stat, re, simplejson, struct, shutil
5714 
5715 from twisted.trial import unittest
5716 
5717hunk ./src/allmydata/test/test_storage.py 22
5718 from allmydata.storage.expirer import LeaseCheckingCrawler
5719 from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \
5720      ReadBucketProxy
5721-from allmydata.interfaces import BadWriteEnablerError
5722-from allmydata.test.common import LoggingServiceParent
5723+from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
5724+                                     LayoutInvalid, MDMFSIGNABLEHEADER, \
5725+                                     SIGNED_PREFIX, MDMFHEADER, \
5726+                                     MDMFOFFSETS, SDMFSlotWriteProxy
5727+from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
5728+                                 SDMF_VERSION
5729+from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
5730 from allmydata.test.common_web import WebRenderingMixin
5731 from allmydata.web.storage import StorageStatus, remove_prefix
5732 
5733hunk ./src/allmydata/test/test_storage.py 106
5734 
5735 class RemoteBucket:
5736 
5737+    def __init__(self):
5738+        self.read_count = 0
5739+        self.write_count = 0
5740+
5741     def callRemote(self, methname, *args, **kwargs):
5742         def _call():
5743             meth = getattr(self.target, "remote_" + methname)
5744hunk ./src/allmydata/test/test_storage.py 114
5745             return meth(*args, **kwargs)
5746+
5747+        if methname == "slot_readv":
5748+            self.read_count += 1
5749+        if "writev" in methname:
5750+            self.write_count += 1
5751+
5752         return defer.maybeDeferred(_call)
5753 
5754hunk ./src/allmydata/test/test_storage.py 122
5755+
5756 class BucketProxy(unittest.TestCase):
5757     def make_bucket(self, name, size):
5758         basedir = os.path.join("storage", "BucketProxy", name)
5759hunk ./src/allmydata/test/test_storage.py 1313
5760         self.failUnless(os.path.exists(prefixdir), prefixdir)
5761         self.failIf(os.path.exists(bucketdir), bucketdir)
5762 
5763+
5764+class MDMFProxies(unittest.TestCase, ShouldFailMixin):
5765+    def setUp(self):
5766+        self.sparent = LoggingServiceParent()
5767+        self._lease_secret = itertools.count()
5768+        self.ss = self.create("MDMFProxies storage test server")
5769+        self.rref = RemoteBucket()
5770+        self.rref.target = self.ss
5771+        self.secrets = (self.write_enabler("we_secret"),
5772+                        self.renew_secret("renew_secret"),
5773+                        self.cancel_secret("cancel_secret"))
5774+        self.segment = "aaaaaa"
5775+        self.block = "aa"
5776+        self.salt = "a" * 16
5777+        self.block_hash = "a" * 32
5778+        self.block_hash_tree = [self.block_hash for i in xrange(6)]
5779+        self.share_hash = self.block_hash
5780+        self.share_hash_chain = dict([(i, self.share_hash) for i in xrange(6)])
5781+        self.signature = "foobarbaz"
5782+        self.verification_key = "vvvvvv"
5783+        self.encprivkey = "private"
5784+        self.root_hash = self.block_hash
5785+        self.salt_hash = self.root_hash
5786+        self.salt_hash_tree = [self.salt_hash for i in xrange(6)]
5787+        self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree)
5788+        self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain)
5789+        # blockhashes and salt hashes are serialized in the same way,
5790+        # only we lop off the first element and store that in the
5791+        # header.
5792+        self.salt_hash_tree_s = self.serialize_blockhashes(self.salt_hash_tree[1:])
5793+
5794+
5795+    def tearDown(self):
5796+        self.sparent.stopService()
5797+        shutil.rmtree(self.workdir("MDMFProxies storage test server"))
5798+
5799+
5800+    def write_enabler(self, we_tag):
5801+        return hashutil.tagged_hash("we_blah", we_tag)
5802+
5803+
5804+    def renew_secret(self, tag):
5805+        return hashutil.tagged_hash("renew_blah", str(tag))
5806+
5807+
5808+    def cancel_secret(self, tag):
5809+        return hashutil.tagged_hash("cancel_blah", str(tag))
5810+
5811+
5812+    def workdir(self, name):
5813+        basedir = os.path.join("storage", "MutableServer", name)
5814+        return basedir
5815+
5816+
5817+    def create(self, name):
5818+        workdir = self.workdir(name)
5819+        ss = StorageServer(workdir, "\x00" * 20)
5820+        ss.setServiceParent(self.sparent)
5821+        return ss
5822+
5823+
5824+    def build_test_mdmf_share(self, tail_segment=False, empty=False):
5825+        # Start with the checkstring
5826+        data = struct.pack(">BQ32s",
5827+                           1,
5828+                           0,
5829+                           self.root_hash)
5830+        self.checkstring = data
5831+        # Next, the encoding parameters
5832+        if tail_segment:
5833+            data += struct.pack(">BBQQ",
5834+                                3,
5835+                                10,
5836+                                6,
5837+                                33)
5838+        elif empty:
5839+            data += struct.pack(">BBQQ",
5840+                                3,
5841+                                10,
5842+                                0,
5843+                                0)
5844+        else:
5845+            data += struct.pack(">BBQQ",
5846+                                3,
5847+                                10,
5848+                                6,
5849+                                36)
5850+        # Now we'll build the offsets.
5851+        sharedata = ""
5852+        if not tail_segment and not empty:
5853+            for i in xrange(6):
5854+                sharedata += self.salt + self.block
5855+        elif tail_segment:
5856+            for i in xrange(5):
5857+                sharedata += self.salt + self.block
5858+            sharedata += self.salt + "a"
5859+
5860+        # The encrypted private key comes after the shares + salts
5861+        offset_size = struct.calcsize(MDMFOFFSETS)
5862+        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
5863+        # The blockhashes come after the private key
5864+        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
5865+        # The sharehashes come after the salt hashes
5866+        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
5867+        # The signature comes after the share hash chain
5868+        signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
5869+        # The verification key comes after the signature
5870+        verification_offset = signature_offset + len(self.signature)
5871+        # The EOF comes after the verification key
5872+        eof_offset = verification_offset + len(self.verification_key)
5873+        data += struct.pack(MDMFOFFSETS,
5874+                            encrypted_private_key_offset,
5875+                            blockhashes_offset,
5876+                            sharehashes_offset,
5877+                            signature_offset,
5878+                            verification_offset,
5879+                            eof_offset)
5880+        self.offsets = {}
5881+        self.offsets['enc_privkey'] = encrypted_private_key_offset
5882+        self.offsets['block_hash_tree'] = blockhashes_offset
5883+        self.offsets['share_hash_chain'] = sharehashes_offset
5884+        self.offsets['signature'] = signature_offset
5885+        self.offsets['verification_key'] = verification_offset
5886+        self.offsets['EOF'] = eof_offset
5887+        # Next, we'll add in the salts and share data,
5888+        data += sharedata
5889+        # the private key,
5890+        data += self.encprivkey
5891+        # the block hash tree,
5892+        data += self.block_hash_tree_s
5893+        # the share hash chain,
5894+        data += self.share_hash_chain_s
5895+        # the signature,
5896+        data += self.signature
5897+        # and the verification key
5898+        data += self.verification_key
5899+        return data
5900+
5901+
5902+    def write_test_share_to_server(self,
5903+                                   storage_index,
5904+                                   tail_segment=False,
5905+                                   empty=False):
5906+        """
5907+        I write some data for the read tests to read to self.ss
5908+
5909+        If tail_segment=True, then I will write a share that has a
5910+        smaller tail segment than other segments.
5911+        """
5912+        write = self.ss.remote_slot_testv_and_readv_and_writev
5913+        data = self.build_test_mdmf_share(tail_segment, empty)
5914+        # Finally, we write the whole thing to the storage server in one
5915+        # pass.
5916+        testvs = [(0, 1, "eq", "")]
5917+        tws = {}
5918+        tws[0] = (testvs, [(0, data)], None)
5919+        readv = [(0, 1)]
5920+        results = write(storage_index, self.secrets, tws, readv)
5921+        self.failUnless(results[0])
5922+
5923+
5924+    def build_test_sdmf_share(self, empty=False):
5925+        if empty:
5926+            sharedata = ""
5927+        else:
5928+            sharedata = self.segment * 6
5929+        self.sharedata = sharedata
5930+        blocksize = len(sharedata) / 3
5931+        block = sharedata[:blocksize]
5932+        self.blockdata = block
5933+        prefix = struct.pack(">BQ32s16s BBQQ",
5934+                             0, # version,
5935+                             0,
5936+                             self.root_hash,
5937+                             self.salt,
5938+                             3,
5939+                             10,
5940+                             len(sharedata),
5941+                             len(sharedata),
5942+                            )
5943+        post_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5944+        signature_offset = post_offset + len(self.verification_key)
5945+        sharehashes_offset = signature_offset + len(self.signature)
5946+        blockhashes_offset = sharehashes_offset + len(self.share_hash_chain_s)
5947+        sharedata_offset = blockhashes_offset + len(self.block_hash_tree_s)
5948+        encprivkey_offset = sharedata_offset + len(block)
5949+        eof_offset = encprivkey_offset + len(self.encprivkey)
5950+        offsets = struct.pack(">LLLLQQ",
5951+                              signature_offset,
5952+                              sharehashes_offset,
5953+                              blockhashes_offset,
5954+                              sharedata_offset,
5955+                              encprivkey_offset,
5956+                              eof_offset)
5957+        final_share = "".join([prefix,
5958+                           offsets,
5959+                           self.verification_key,
5960+                           self.signature,
5961+                           self.share_hash_chain_s,
5962+                           self.block_hash_tree_s,
5963+                           block,
5964+                           self.encprivkey])
5965+        self.offsets = {}
5966+        self.offsets['signature'] = signature_offset
5967+        self.offsets['share_hash_chain'] = sharehashes_offset
5968+        self.offsets['block_hash_tree'] = blockhashes_offset
5969+        self.offsets['share_data'] = sharedata_offset
5970+        self.offsets['enc_privkey'] = encprivkey_offset
5971+        self.offsets['EOF'] = eof_offset
5972+        return final_share
5973+
5974+
5975+    def write_sdmf_share_to_server(self,
5976+                                   storage_index,
5977+                                   empty=False):
5978+        # Some tests need SDMF shares to verify that we can still
5979+        # read them. This method writes one, which resembles but is not
5980+        assert self.rref
5981+        write = self.ss.remote_slot_testv_and_readv_and_writev
5982+        share = self.build_test_sdmf_share(empty)
5983+        testvs = [(0, 1, "eq", "")]
5984+        tws = {}
5985+        tws[0] = (testvs, [(0, share)], None)
5986+        readv = []
5987+        results = write(storage_index, self.secrets, tws, readv)
5988+        self.failUnless(results[0])
5989+
5990+
5991+    def test_read(self):
5992+        self.write_test_share_to_server("si1")
5993+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
5994+        # Check that every method equals what we expect it to.
5995+        d = defer.succeed(None)
5996+        def _check_block_and_salt((block, salt)):
5997+            self.failUnlessEqual(block, self.block)
5998+            self.failUnlessEqual(salt, self.salt)
5999+
6000+        for i in xrange(6):
6001+            d.addCallback(lambda ignored, i=i:
6002+                mr.get_block_and_salt(i))
6003+            d.addCallback(_check_block_and_salt)
6004+
6005+        d.addCallback(lambda ignored:
6006+            mr.get_encprivkey())
6007+        d.addCallback(lambda encprivkey:
6008+            self.failUnlessEqual(self.encprivkey, encprivkey))
6009+
6010+        d.addCallback(lambda ignored:
6011+            mr.get_blockhashes())
6012+        d.addCallback(lambda blockhashes:
6013+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6014+
6015+        d.addCallback(lambda ignored:
6016+            mr.get_sharehashes())
6017+        d.addCallback(lambda sharehashes:
6018+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6019+
6020+        d.addCallback(lambda ignored:
6021+            mr.get_signature())
6022+        d.addCallback(lambda signature:
6023+            self.failUnlessEqual(signature, self.signature))
6024+
6025+        d.addCallback(lambda ignored:
6026+            mr.get_verification_key())
6027+        d.addCallback(lambda verification_key:
6028+            self.failUnlessEqual(verification_key, self.verification_key))
6029+
6030+        d.addCallback(lambda ignored:
6031+            mr.get_seqnum())
6032+        d.addCallback(lambda seqnum:
6033+            self.failUnlessEqual(seqnum, 0))
6034+
6035+        d.addCallback(lambda ignored:
6036+            mr.get_root_hash())
6037+        d.addCallback(lambda root_hash:
6038+            self.failUnlessEqual(self.root_hash, root_hash))
6039+
6040+        d.addCallback(lambda ignored:
6041+            mr.get_seqnum())
6042+        d.addCallback(lambda seqnum:
6043+            self.failUnlessEqual(0, seqnum))
6044+
6045+        d.addCallback(lambda ignored:
6046+            mr.get_encoding_parameters())
6047+        def _check_encoding_parameters((k, n, segsize, datalen)):
6048+            self.failUnlessEqual(k, 3)
6049+            self.failUnlessEqual(n, 10)
6050+            self.failUnlessEqual(segsize, 6)
6051+            self.failUnlessEqual(datalen, 36)
6052+        d.addCallback(_check_encoding_parameters)
6053+
6054+        d.addCallback(lambda ignored:
6055+            mr.get_checkstring())
6056+        d.addCallback(lambda checkstring:
6057+            self.failUnlessEqual(checkstring, checkstring))
6058+        return d
6059+
6060+
6061+    def test_read_with_different_tail_segment_size(self):
6062+        self.write_test_share_to_server("si1", tail_segment=True)
6063+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6064+        d = mr.get_block_and_salt(5)
6065+        def _check_tail_segment(results):
6066+            block, salt = results
6067+            self.failUnlessEqual(len(block), 1)
6068+            self.failUnlessEqual(block, "a")
6069+        d.addCallback(_check_tail_segment)
6070+        return d
6071+
6072+
6073+    def test_get_block_with_invalid_segnum(self):
6074+        self.write_test_share_to_server("si1")
6075+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6076+        d = defer.succeed(None)
6077+        d.addCallback(lambda ignored:
6078+            self.shouldFail(LayoutInvalid, "test invalid segnum",
6079+                            None,
6080+                            mr.get_block_and_salt, 7))
6081+        return d
6082+
6083+
6084+    def test_get_encoding_parameters_first(self):
6085+        self.write_test_share_to_server("si1")
6086+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6087+        d = mr.get_encoding_parameters()
6088+        def _check_encoding_parameters((k, n, segment_size, datalen)):
6089+            self.failUnlessEqual(k, 3)
6090+            self.failUnlessEqual(n, 10)
6091+            self.failUnlessEqual(segment_size, 6)
6092+            self.failUnlessEqual(datalen, 36)
6093+        d.addCallback(_check_encoding_parameters)
6094+        return d
6095+
6096+
6097+    def test_get_seqnum_first(self):
6098+        self.write_test_share_to_server("si1")
6099+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6100+        d = mr.get_seqnum()
6101+        d.addCallback(lambda seqnum:
6102+            self.failUnlessEqual(seqnum, 0))
6103+        return d
6104+
6105+
6106+    def test_get_root_hash_first(self):
6107+        self.write_test_share_to_server("si1")
6108+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6109+        d = mr.get_root_hash()
6110+        d.addCallback(lambda root_hash:
6111+            self.failUnlessEqual(root_hash, self.root_hash))
6112+        return d
6113+
6114+
6115+    def test_get_checkstring_first(self):
6116+        self.write_test_share_to_server("si1")
6117+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6118+        d = mr.get_checkstring()
6119+        d.addCallback(lambda checkstring:
6120+            self.failUnlessEqual(checkstring, self.checkstring))
6121+        return d
6122+
6123+
6124+    def test_write_read_vectors(self):
6125+        # When writing for us, the storage server will return to us a
6126+        # read vector, along with its result. If a write fails because
6127+        # the test vectors failed, this read vector can help us to
6128+        # diagnose the problem. This test ensures that the read vector
6129+        # is working appropriately.
6130+        mw = self._make_new_mw("si1", 0)
6131+        d = defer.succeed(None)
6132+
6133+        # Write one share. This should return a checkstring of nothing,
6134+        # since there is no data there.
6135+        d.addCallback(lambda ignored:
6136+            mw.put_block(self.block, 0, self.salt))
6137+        def _check_first_write(results):
6138+            result, readvs = results
6139+            self.failUnless(result)
6140+            self.failIf(readvs)
6141+        d.addCallback(_check_first_write)
6142+        # Now, there should be a different checkstring returned when
6143+        # we write other shares
6144+        d.addCallback(lambda ignored:
6145+            mw.put_block(self.block, 1, self.salt))
6146+        def _check_next_write(results):
6147+            result, readvs = results
6148+            self.failUnless(result)
6149+            self.expected_checkstring = mw.get_checkstring()
6150+            self.failUnlessIn(0, readvs)
6151+            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
6152+        d.addCallback(_check_next_write)
6153+        # Add the other four shares
6154+        for i in xrange(2, 6):
6155+            d.addCallback(lambda ignored, i=i:
6156+                mw.put_block(self.block, i, self.salt))
6157+            d.addCallback(_check_next_write)
6158+        # Add the encrypted private key
6159+        d.addCallback(lambda ignored:
6160+            mw.put_encprivkey(self.encprivkey))
6161+        d.addCallback(_check_next_write)
6162+        # Add the block hash tree and share hash tree
6163+        d.addCallback(lambda ignored:
6164+            mw.put_blockhashes(self.block_hash_tree))
6165+        d.addCallback(_check_next_write)
6166+        d.addCallback(lambda ignored:
6167+            mw.put_sharehashes(self.share_hash_chain))
6168+        d.addCallback(_check_next_write)
6169+        # Add the root hash and the salt hash. This should change the
6170+        # checkstring, but not in a way that we'll be able to see right
6171+        # now, since the read vectors are applied before the write
6172+        # vectors.
6173+        d.addCallback(lambda ignored:
6174+            mw.put_root_hash(self.root_hash))
6175+        def _check_old_testv_after_new_one_is_written(results):
6176+            result, readvs = results
6177+            self.failUnless(result)
6178+            self.failUnlessIn(0, readvs)
6179+            self.failUnlessEqual(self.expected_checkstring,
6180+                                 readvs[0][0])
6181+            new_checkstring = mw.get_checkstring()
6182+            self.failIfEqual(new_checkstring,
6183+                             readvs[0][0])
6184+        d.addCallback(_check_old_testv_after_new_one_is_written)
6185+        # Now add the signature. This should succeed, meaning that the
6186+        # data gets written and the read vector matches what the writer
6187+        # thinks should be there.
6188+        d.addCallback(lambda ignored:
6189+            mw.put_signature(self.signature))
6190+        d.addCallback(_check_next_write)
6191+        # The checkstring remains the same for the rest of the process.
6192+        return d
6193+
6194+
6195+    def test_blockhashes_after_share_hash_chain(self):
6196+        mw = self._make_new_mw("si1", 0)
6197+        d = defer.succeed(None)
6198+        # Put everything up to and including the share hash chain
6199+        for i in xrange(6):
6200+            d.addCallback(lambda ignored, i=i:
6201+                mw.put_block(self.block, i, self.salt))
6202+        d.addCallback(lambda ignored:
6203+            mw.put_encprivkey(self.encprivkey))
6204+        d.addCallback(lambda ignored:
6205+            mw.put_blockhashes(self.block_hash_tree))
6206+        d.addCallback(lambda ignored:
6207+            mw.put_sharehashes(self.share_hash_chain))
6208+
6209+        # Now try to put the block hash tree again.
6210+        d.addCallback(lambda ignored:
6211+            self.shouldFail(LayoutInvalid, "test repeat salthashes",
6212+                            None,
6213+                            mw.put_blockhashes, self.block_hash_tree))
6214+        return d
6215+
6216+
6217+    def test_encprivkey_after_blockhashes(self):
6218+        mw = self._make_new_mw("si1", 0)
6219+        d = defer.succeed(None)
6220+        # Put everything up to and including the block hash tree
6221+        for i in xrange(6):
6222+            d.addCallback(lambda ignored, i=i:
6223+                mw.put_block(self.block, i, self.salt))
6224+        d.addCallback(lambda ignored:
6225+            mw.put_encprivkey(self.encprivkey))
6226+        d.addCallback(lambda ignored:
6227+            mw.put_blockhashes(self.block_hash_tree))
6228+        d.addCallback(lambda ignored:
6229+            self.shouldFail(LayoutInvalid, "out of order private key",
6230+                            None,
6231+                            mw.put_encprivkey, self.encprivkey))
6232+        return d
6233+
6234+
6235+    def test_share_hash_chain_after_signature(self):
6236+        mw = self._make_new_mw("si1", 0)
6237+        d = defer.succeed(None)
6238+        # Put everything up to and including the signature
6239+        for i in xrange(6):
6240+            d.addCallback(lambda ignored, i=i:
6241+                mw.put_block(self.block, i, self.salt))
6242+        d.addCallback(lambda ignored:
6243+            mw.put_encprivkey(self.encprivkey))
6244+        d.addCallback(lambda ignored:
6245+            mw.put_blockhashes(self.block_hash_tree))
6246+        d.addCallback(lambda ignored:
6247+            mw.put_sharehashes(self.share_hash_chain))
6248+        d.addCallback(lambda ignored:
6249+            mw.put_root_hash(self.root_hash))
6250+        d.addCallback(lambda ignored:
6251+            mw.put_signature(self.signature))
6252+        # Now try to put the share hash chain again. This should fail
6253+        d.addCallback(lambda ignored:
6254+            self.shouldFail(LayoutInvalid, "out of order share hash chain",
6255+                            None,
6256+                            mw.put_sharehashes, self.share_hash_chain))
6257+        return d
6258+
6259+
6260+    def test_signature_after_verification_key(self):
6261+        mw = self._make_new_mw("si1", 0)
6262+        d = defer.succeed(None)
6263+        # Put everything up to and including the verification key.
6264+        for i in xrange(6):
6265+            d.addCallback(lambda ignored, i=i:
6266+                mw.put_block(self.block, i, self.salt))
6267+        d.addCallback(lambda ignored:
6268+            mw.put_encprivkey(self.encprivkey))
6269+        d.addCallback(lambda ignored:
6270+            mw.put_blockhashes(self.block_hash_tree))
6271+        d.addCallback(lambda ignored:
6272+            mw.put_sharehashes(self.share_hash_chain))
6273+        d.addCallback(lambda ignored:
6274+            mw.put_root_hash(self.root_hash))
6275+        d.addCallback(lambda ignored:
6276+            mw.put_signature(self.signature))
6277+        d.addCallback(lambda ignored:
6278+            mw.put_verification_key(self.verification_key))
6279+        # Now try to put the signature again. This should fail
6280+        d.addCallback(lambda ignored:
6281+            self.shouldFail(LayoutInvalid, "signature after verification",
6282+                            None,
6283+                            mw.put_signature, self.signature))
6284+        return d
6285+
6286+
6287+    def test_uncoordinated_write(self):
6288+        # Make two mutable writers, both pointing to the same storage
6289+        # server, both at the same storage index, and try writing to the
6290+        # same share.
6291+        mw1 = self._make_new_mw("si1", 0)
6292+        mw2 = self._make_new_mw("si1", 0)
6293+        d = defer.succeed(None)
6294+        def _check_success(results):
6295+            result, readvs = results
6296+            self.failUnless(result)
6297+
6298+        def _check_failure(results):
6299+            result, readvs = results
6300+            self.failIf(result)
6301+
6302+        d.addCallback(lambda ignored:
6303+            mw1.put_block(self.block, 0, self.salt))
6304+        d.addCallback(_check_success)
6305+        d.addCallback(lambda ignored:
6306+            mw2.put_block(self.block, 0, self.salt))
6307+        d.addCallback(_check_failure)
6308+        return d
6309+
6310+
6311+    def test_invalid_salt_size(self):
6312+        # Salts need to be 16 bytes in size. Writes that attempt to
6313+        # write more or less than this should be rejected.
6314+        mw = self._make_new_mw("si1", 0)
6315+        invalid_salt = "a" * 17 # 17 bytes
6316+        another_invalid_salt = "b" * 15 # 15 bytes
6317+        d = defer.succeed(None)
6318+        d.addCallback(lambda ignored:
6319+            self.shouldFail(LayoutInvalid, "salt too big",
6320+                            None,
6321+                            mw.put_block, self.block, 0, invalid_salt))
6322+        d.addCallback(lambda ignored:
6323+            self.shouldFail(LayoutInvalid, "salt too small",
6324+                            None,
6325+                            mw.put_block, self.block, 0,
6326+                            another_invalid_salt))
6327+        return d
6328+
6329+
6330+    def test_write_test_vectors(self):
6331+        # If we give the write proxy a bogus test vector at
6332+        # any point during the process, it should fail to write.
6333+        mw = self._make_new_mw("si1", 0)
6334+        mw.set_checkstring("this is a lie")
6335+        # The initial write should be expecting to find the improbable
6336+        # checkstring above in place; finding nothing, it should fail.
6337+        d = defer.succeed(None)
6338+        d.addCallback(lambda ignored:
6339+            mw.put_block(self.block, 0, self.salt))
6340+        def _check_failure(results):
6341+            result, readv = results
6342+            self.failIf(result)
6343+        d.addCallback(_check_failure)
6344+        # Now set the checkstring to the empty string, which
6345+        # indicates that no share is there.
6346+        d.addCallback(lambda ignored:
6347+            mw.set_checkstring(""))
6348+        d.addCallback(lambda ignored:
6349+            mw.put_block(self.block, 0, self.salt))
6350+        def _check_success(results):
6351+            result, readv = results
6352+            self.failUnless(result)
6353+        d.addCallback(_check_success)
6354+        # Now set the checkstring to something wrong
6355+        d.addCallback(lambda ignored:
6356+            mw.set_checkstring("something wrong"))
6357+        # This should fail to do anything
6358+        d.addCallback(lambda ignored:
6359+            mw.put_block(self.block, 1, self.salt))
6360+        d.addCallback(_check_failure)
6361+        # Now set it back to what it should be.
6362+        d.addCallback(lambda ignored:
6363+            mw.set_checkstring(mw.get_checkstring()))
6364+        for i in xrange(1, 6):
6365+            d.addCallback(lambda ignored, i=i:
6366+                mw.put_block(self.block, i, self.salt))
6367+            d.addCallback(_check_success)
6368+        d.addCallback(lambda ignored:
6369+            mw.put_encprivkey(self.encprivkey))
6370+        d.addCallback(_check_success)
6371+        d.addCallback(lambda ignored:
6372+            mw.put_blockhashes(self.block_hash_tree))
6373+        d.addCallback(_check_success)
6374+        d.addCallback(lambda ignored:
6375+            mw.put_sharehashes(self.share_hash_chain))
6376+        d.addCallback(_check_success)
6377+        def _keep_old_checkstring(ignored):
6378+            self.old_checkstring = mw.get_checkstring()
6379+            mw.set_checkstring("foobarbaz")
6380+        d.addCallback(_keep_old_checkstring)
6381+        d.addCallback(lambda ignored:
6382+            mw.put_root_hash(self.root_hash))
6383+        d.addCallback(_check_failure)
6384+        d.addCallback(lambda ignored:
6385+            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
6386+        def _restore_old_checkstring(ignored):
6387+            mw.set_checkstring(self.old_checkstring)
6388+        d.addCallback(_restore_old_checkstring)
6389+        d.addCallback(lambda ignored:
6390+            mw.put_root_hash(self.root_hash))
6391+        d.addCallback(_check_success)
6392+        # The checkstring should have been set appropriately for us on
6393+        # the last write; if we try to change it to something else,
6394+        # that change should cause the verification key step to fail.
6395+        d.addCallback(lambda ignored:
6396+            mw.set_checkstring("something else"))
6397+        d.addCallback(lambda ignored:
6398+            mw.put_signature(self.signature))
6399+        d.addCallback(_check_failure)
6400+        d.addCallback(lambda ignored:
6401+            mw.set_checkstring(mw.get_checkstring()))
6402+        d.addCallback(lambda ignored:
6403+            mw.put_signature(self.signature))
6404+        d.addCallback(_check_success)
6405+        d.addCallback(lambda ignored:
6406+            mw.put_verification_key(self.verification_key))
6407+        d.addCallback(_check_success)
6408+        return d
6409+
6410+
6411+    def test_offset_only_set_on_success(self):
6412+        # The write proxy should be smart enough to detect when a write
6413+        # has failed, and to temper its definition of progress based on
6414+        # that.
6415+        mw = self._make_new_mw("si1", 0)
6416+        d = defer.succeed(None)
6417+        for i in xrange(1, 6):
6418+            d.addCallback(lambda ignored, i=i:
6419+                mw.put_block(self.block, i, self.salt))
6420+        def _break_checkstring(ignored):
6421+            self._old_checkstring = mw.get_checkstring()
6422+            mw.set_checkstring("foobarbaz")
6423+
6424+        def _fix_checkstring(ignored):
6425+            mw.set_checkstring(self._old_checkstring)
6426+
6427+        d.addCallback(_break_checkstring)
6428+
6429+        # Setting the encrypted private key shouldn't work now, which is
6430+        # to be expected and is tested elsewhere. We also want to make
6431+        # sure that we can't add the block hash tree after a failed
6432+        # write of this sort.
6433+        d.addCallback(lambda ignored:
6434+            mw.put_encprivkey(self.encprivkey))
6435+        d.addCallback(lambda ignored:
6436+            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
6437+                            None,
6438+                            mw.put_blockhashes, self.block_hash_tree))
6439+        d.addCallback(_fix_checkstring)
6440+        d.addCallback(lambda ignored:
6441+            mw.put_encprivkey(self.encprivkey))
6442+        d.addCallback(_break_checkstring)
6443+        d.addCallback(lambda ignored:
6444+            mw.put_blockhashes(self.block_hash_tree))
6445+        d.addCallback(lambda ignored:
6446+            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
6447+                            None,
6448+                            mw.put_sharehashes, self.share_hash_chain))
6449+        d.addCallback(_fix_checkstring)
6450+        d.addCallback(lambda ignored:
6451+            mw.put_blockhashes(self.block_hash_tree))
6452+        d.addCallback(_break_checkstring)
6453+        d.addCallback(lambda ignored:
6454+            mw.put_sharehashes(self.share_hash_chain))
6455+        d.addCallback(lambda ignored:
6456+            self.shouldFail(LayoutInvalid, "out-of-order root hash",
6457+                            None,
6458+                            mw.put_root_hash, self.root_hash))
6459+        d.addCallback(_fix_checkstring)
6460+        d.addCallback(lambda ignored:
6461+            mw.put_sharehashes(self.share_hash_chain))
6462+        d.addCallback(_break_checkstring)
6463+        d.addCallback(lambda ignored:
6464+            mw.put_root_hash(self.root_hash))
6465+        d.addCallback(lambda ignored:
6466+            self.shouldFail(LayoutInvalid, "out-of-order signature",
6467+                            None,
6468+                            mw.put_signature, self.signature))
6469+        d.addCallback(_fix_checkstring)
6470+        d.addCallback(lambda ignored:
6471+            mw.put_root_hash(self.root_hash))
6472+        d.addCallback(_break_checkstring)
6473+        d.addCallback(lambda ignored:
6474+            mw.put_signature(self.signature))
6475+        d.addCallback(lambda ignored:
6476+            self.shouldFail(LayoutInvalid, "out-of-order verification key",
6477+                            None,
6478+                            mw.put_verification_key,
6479+                            self.verification_key))
6480+        d.addCallback(_fix_checkstring)
6481+        d.addCallback(lambda ignored:
6482+            mw.put_signature(self.signature))
6483+        d.addCallback(_break_checkstring)
6484+        d.addCallback(lambda ignored:
6485+            mw.put_verification_key(self.verification_key))
6486+        d.addCallback(lambda ignored:
6487+            self.shouldFail(LayoutInvalid, "out-of-order finish",
6488+                            None,
6489+                            mw.finish_publishing))
6490+        return d
6491+
6492+
6493+    def serialize_blockhashes(self, blockhashes):
6494+        return "".join(blockhashes)
6495+
6496+
6497+    def serialize_sharehashes(self, sharehashes):
6498+        ret = "".join([struct.pack(">H32s", i, sharehashes[i])
6499+                        for i in sorted(sharehashes.keys())])
6500+        return ret
6501+
6502+
6503+    def test_write(self):
6504+        # This translates to a file with 6 6-byte segments, and with 2-byte
6505+        # blocks.
6506+        mw = self._make_new_mw("si1", 0)
6507+        mw2 = self._make_new_mw("si1", 1)
6508+        # Test writing some blocks.
6509+        read = self.ss.remote_slot_readv
6510+        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
6511+        written_block_size = 2 + len(self.salt)
6512+        written_block = self.block + self.salt
6513+        def _check_block_write(i, share):
6514+            self.failUnlessEqual(read("si1", [share], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
6515+                                {share: [written_block]})
6516+        d = defer.succeed(None)
6517+        for i in xrange(6):
6518+            d.addCallback(lambda ignored, i=i:
6519+                mw.put_block(self.block, i, self.salt))
6520+            d.addCallback(lambda ignored, i=i:
6521+                _check_block_write(i, 0))
6522+        # Now try the same thing, but with share 1 instead of share 0.
6523+        for i in xrange(6):
6524+            d.addCallback(lambda ignored, i=i:
6525+                mw2.put_block(self.block, i, self.salt))
6526+            d.addCallback(lambda ignored, i=i:
6527+                _check_block_write(i, 1))
6528+
6529+        # Next, we make a fake encrypted private key, and put it onto the
6530+        # storage server.
6531+        d.addCallback(lambda ignored:
6532+            mw.put_encprivkey(self.encprivkey))
6533+        expected_private_key_offset = expected_sharedata_offset + \
6534+                                      len(written_block) * 6
6535+        self.failUnlessEqual(len(self.encprivkey), 7)
6536+        d.addCallback(lambda ignored:
6537+            self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
6538+                                 {0: [self.encprivkey]}))
6539+
6540+        # Next, we put a fake block hash tree.
6541+        d.addCallback(lambda ignored:
6542+            mw.put_blockhashes(self.block_hash_tree))
6543+        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
6544+        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
6545+        d.addCallback(lambda ignored:
6546+            self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
6547+                                 {0: [self.block_hash_tree_s]}))
6548+
6549+        # Next, put a fake share hash chain
6550+        d.addCallback(lambda ignored:
6551+            mw.put_sharehashes(self.share_hash_chain))
6552+        expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
6553+        d.addCallback(lambda ignored:
6554+            self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
6555+                                 {0: [self.share_hash_chain_s]}))
6556+
6557+        # Next, we put what is supposed to be the root hash of
6558+        # our share hash tree but isn't       
6559+        d.addCallback(lambda ignored:
6560+            mw.put_root_hash(self.root_hash))
6561+        # The root hash gets inserted at byte 9 (its position is in the header,
6562+        # and is fixed).
6563+        def _check(ignored):
6564+            self.failUnlessEqual(read("si1", [0], [(9, 32)]),
6565+                                 {0: [self.root_hash]})
6566+        d.addCallback(_check)
6567+
6568+        # Next, we put a signature of the header block.
6569+        d.addCallback(lambda ignored:
6570+            mw.put_signature(self.signature))
6571+        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
6572+        self.failUnlessEqual(len(self.signature), 9)
6573+        d.addCallback(lambda ignored:
6574+            self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
6575+                                 {0: [self.signature]}))
6576+
6577+        # Next, we put the verification key
6578+        d.addCallback(lambda ignored:
6579+            mw.put_verification_key(self.verification_key))
6580+        expected_verification_key_offset = expected_signature_offset + len(self.signature)
6581+        self.failUnlessEqual(len(self.verification_key), 6)
6582+        d.addCallback(lambda ignored:
6583+            self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
6584+                                 {0: [self.verification_key]}))
6585+
6586+        def _check_signable(ignored):
6587+            # Make sure that the signable is what we think it should be.
6588+            signable = mw.get_signable()
6589+            verno, seq, roothash, k, n, segsize, datalen = \
6590+                                            struct.unpack(">BQ32sBBQQ",
6591+                                                          signable)
6592+            self.failUnlessEqual(verno, 1)
6593+            self.failUnlessEqual(seq, 0)
6594+            self.failUnlessEqual(roothash, self.root_hash)
6595+            self.failUnlessEqual(k, 3)
6596+            self.failUnlessEqual(n, 10)
6597+            self.failUnlessEqual(segsize, 6)
6598+            self.failUnlessEqual(datalen, 36)
6599+        d.addCallback(_check_signable)
6600+        # Next, we cause the offset table to be published.
6601+        d.addCallback(lambda ignored:
6602+            mw.finish_publishing())
6603+        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
6604+
6605+        def _check_offsets(ignored):
6606+            # Check the version number to make sure that it is correct.
6607+            expected_version_number = struct.pack(">B", 1)
6608+            self.failUnlessEqual(read("si1", [0], [(0, 1)]),
6609+                                 {0: [expected_version_number]})
6610+            # Check the sequence number to make sure that it is correct
6611+            expected_sequence_number = struct.pack(">Q", 0)
6612+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
6613+                                 {0: [expected_sequence_number]})
6614+            # Check that the encoding parameters (k, N, segement size, data
6615+            # length) are what they should be. These are  3, 10, 6, 36
6616+            expected_k = struct.pack(">B", 3)
6617+            self.failUnlessEqual(read("si1", [0], [(41, 1)]),
6618+                                 {0: [expected_k]})
6619+            expected_n = struct.pack(">B", 10)
6620+            self.failUnlessEqual(read("si1", [0], [(42, 1)]),
6621+                                 {0: [expected_n]})
6622+            expected_segment_size = struct.pack(">Q", 6)
6623+            self.failUnlessEqual(read("si1", [0], [(43, 8)]),
6624+                                 {0: [expected_segment_size]})
6625+            expected_data_length = struct.pack(">Q", 36)
6626+            self.failUnlessEqual(read("si1", [0], [(51, 8)]),
6627+                                 {0: [expected_data_length]})
6628+            expected_offset = struct.pack(">Q", expected_private_key_offset)
6629+            self.failUnlessEqual(read("si1", [0], [(59, 8)]),
6630+                                 {0: [expected_offset]})
6631+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
6632+            self.failUnlessEqual(read("si1", [0], [(67, 8)]),
6633+                                 {0: [expected_offset]})
6634+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
6635+            self.failUnlessEqual(read("si1", [0], [(75, 8)]),
6636+                                 {0: [expected_offset]})
6637+            expected_offset = struct.pack(">Q", expected_signature_offset)
6638+            self.failUnlessEqual(read("si1", [0], [(83, 8)]),
6639+                                 {0: [expected_offset]})
6640+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
6641+            self.failUnlessEqual(read("si1", [0], [(91, 8)]),
6642+                                 {0: [expected_offset]})
6643+            expected_offset = struct.pack(">Q", expected_eof_offset)
6644+            self.failUnlessEqual(read("si1", [0], [(99, 8)]),
6645+                                 {0: [expected_offset]})
6646+        d.addCallback(_check_offsets)
6647+        return d
6648+
6649+    def _make_new_mw(self, si, share, datalength=36):
6650+        # This is a file of size 36 bytes. Since it has a segment
6651+        # size of 6, we know that it has 6 byte segments, which will
6652+        # be split into blocks of 2 bytes because our FEC k
6653+        # parameter is 3.
6654+        mw = MDMFSlotWriteProxy(share, self.rref, si, self.secrets, 0, 3, 10,
6655+                                6, datalength)
6656+        return mw
6657+
6658+
6659+    def test_write_rejected_with_too_many_blocks(self):
6660+        mw = self._make_new_mw("si0", 0)
6661+
6662+        # Try writing too many blocks. We should not be able to write
6663+        # more than 6
6664+        # blocks into each share.
6665+        d = defer.succeed(None)
6666+        for i in xrange(6):
6667+            d.addCallback(lambda ignored, i=i:
6668+                mw.put_block(self.block, i, self.salt))
6669+        d.addCallback(lambda ignored:
6670+            self.shouldFail(LayoutInvalid, "too many blocks",
6671+                            None,
6672+                            mw.put_block, self.block, 7, self.salt))
6673+        return d
6674+
6675+
6676+    def test_write_rejected_with_invalid_salt(self):
6677+        # Try writing an invalid salt. Salts are 16 bytes -- any more or
6678+        # less should cause an error.
6679+        mw = self._make_new_mw("si1", 0)
6680+        bad_salt = "a" * 17 # 17 bytes
6681+        d = defer.succeed(None)
6682+        d.addCallback(lambda ignored:
6683+            self.shouldFail(LayoutInvalid, "test_invalid_salt",
6684+                            None, mw.put_block, self.block, 7, bad_salt))
6685+        return d
6686+
6687+
6688+    def test_write_rejected_with_invalid_root_hash(self):
6689+        # Try writing an invalid root hash. This should be SHA256d, and
6690+        # 32 bytes long as a result.
6691+        mw = self._make_new_mw("si2", 0)
6692+        # 17 bytes != 32 bytes
6693+        invalid_root_hash = "a" * 17
6694+        d = defer.succeed(None)
6695+        # Before this test can work, we need to put some blocks + salts,
6696+        # a block hash tree, and a share hash tree. Otherwise, we'll see
6697+        # failures that match what we are looking for, but are caused by
6698+        # the constraints imposed on operation ordering.
6699+        for i in xrange(6):
6700+            d.addCallback(lambda ignored, i=i:
6701+                mw.put_block(self.block, i, self.salt))
6702+        d.addCallback(lambda ignored:
6703+            mw.put_encprivkey(self.encprivkey))
6704+        d.addCallback(lambda ignored:
6705+            mw.put_blockhashes(self.block_hash_tree))
6706+        d.addCallback(lambda ignored:
6707+            mw.put_sharehashes(self.share_hash_chain))
6708+        d.addCallback(lambda ignored:
6709+            self.shouldFail(LayoutInvalid, "invalid root hash",
6710+                            None, mw.put_root_hash, invalid_root_hash))
6711+        return d
6712+
6713+
6714+    def test_write_rejected_with_invalid_blocksize(self):
6715+        # The blocksize implied by the writer that we get from
6716+        # _make_new_mw is 2bytes -- any more or any less than this
6717+        # should be cause for failure, unless it is the tail segment, in
6718+        # which case it may not be failure.
6719+        invalid_block = "a"
6720+        mw = self._make_new_mw("si3", 0, 33) # implies a tail segment with
6721+                                             # one byte blocks
6722+        # 1 bytes != 2 bytes
6723+        d = defer.succeed(None)
6724+        d.addCallback(lambda ignored, invalid_block=invalid_block:
6725+            self.shouldFail(LayoutInvalid, "test blocksize too small",
6726+                            None, mw.put_block, invalid_block, 0,
6727+                            self.salt))
6728+        invalid_block = invalid_block * 3
6729+        # 3 bytes != 2 bytes
6730+        d.addCallback(lambda ignored:
6731+            self.shouldFail(LayoutInvalid, "test blocksize too large",
6732+                            None,
6733+                            mw.put_block, invalid_block, 0, self.salt))
6734+        for i in xrange(5):
6735+            d.addCallback(lambda ignored, i=i:
6736+                mw.put_block(self.block, i, self.salt))
6737+        # Try to put an invalid tail segment
6738+        d.addCallback(lambda ignored:
6739+            self.shouldFail(LayoutInvalid, "test invalid tail segment",
6740+                            None,
6741+                            mw.put_block, self.block, 5, self.salt))
6742+        valid_block = "a"
6743+        d.addCallback(lambda ignored:
6744+            mw.put_block(valid_block, 5, self.salt))
6745+        return d
6746+
6747+
6748+    def test_write_enforces_order_constraints(self):
6749+        # We require that the MDMFSlotWriteProxy be interacted with in a
6750+        # specific way.
6751+        # That way is:
6752+        # 0: __init__
6753+        # 1: write blocks and salts
6754+        # 2: Write the encrypted private key
6755+        # 3: Write the block hashes
6756+        # 4: Write the share hashes
6757+        # 5: Write the root hash and salt hash
6758+        # 6: Write the signature and verification key
6759+        # 7: Write the file.
6760+        #
6761+        # Some of these can be performed out-of-order, and some can't.
6762+        # The dependencies that I want to test here are:
6763+        #  - Private key before block hashes
6764+        #  - share hashes and block hashes before root hash
6765+        #  - root hash before signature
6766+        #  - signature before verification key
6767+        mw0 = self._make_new_mw("si0", 0)
6768+        # Write some shares
6769+        d = defer.succeed(None)
6770+        for i in xrange(6):
6771+            d.addCallback(lambda ignored, i=i:
6772+                mw0.put_block(self.block, i, self.salt))
6773+        # Try to write the block hashes before writing the encrypted
6774+        # private key
6775+        d.addCallback(lambda ignored:
6776+            self.shouldFail(LayoutInvalid, "block hashes before key",
6777+                            None, mw0.put_blockhashes,
6778+                            self.block_hash_tree))
6779+
6780+        # Write the private key.
6781+        d.addCallback(lambda ignored:
6782+            mw0.put_encprivkey(self.encprivkey))
6783+
6784+
6785+        # Try to write the share hash chain without writing the block
6786+        # hash tree
6787+        d.addCallback(lambda ignored:
6788+            self.shouldFail(LayoutInvalid, "share hash chain before "
6789+                                           "salt hash tree",
6790+                            None,
6791+                            mw0.put_sharehashes, self.share_hash_chain))
6792+
6793+        # Try to write the root hash and without writing either the
6794+        # block hashes or the or the share hashes
6795+        d.addCallback(lambda ignored:
6796+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6797+                            None,
6798+                            mw0.put_root_hash, self.root_hash))
6799+
6800+        # Now write the block hashes and try again
6801+        d.addCallback(lambda ignored:
6802+            mw0.put_blockhashes(self.block_hash_tree))
6803+
6804+        d.addCallback(lambda ignored:
6805+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6806+                            None, mw0.put_root_hash, self.root_hash))
6807+
6808+        # We haven't yet put the root hash on the share, so we shouldn't
6809+        # be able to sign it.
6810+        d.addCallback(lambda ignored:
6811+            self.shouldFail(LayoutInvalid, "signature before root hash",
6812+                            None, mw0.put_signature, self.signature))
6813+
6814+        d.addCallback(lambda ignored:
6815+            self.failUnlessRaises(LayoutInvalid, mw0.get_signable))
6816+
6817+        # ..and, since that fails, we also shouldn't be able to put the
6818+        # verification key.
6819+        d.addCallback(lambda ignored:
6820+            self.shouldFail(LayoutInvalid, "key before signature",
6821+                            None, mw0.put_verification_key,
6822+                            self.verification_key))
6823+
6824+        # Now write the share hashes.
6825+        d.addCallback(lambda ignored:
6826+            mw0.put_sharehashes(self.share_hash_chain))
6827+        # We should be able to write the root hash now too
6828+        d.addCallback(lambda ignored:
6829+            mw0.put_root_hash(self.root_hash))
6830+
6831+        # We should still be unable to put the verification key
6832+        d.addCallback(lambda ignored:
6833+            self.shouldFail(LayoutInvalid, "key before signature",
6834+                            None, mw0.put_verification_key,
6835+                            self.verification_key))
6836+
6837+        d.addCallback(lambda ignored:
6838+            mw0.put_signature(self.signature))
6839+
6840+        # We shouldn't be able to write the offsets to the remote server
6841+        # until the offset table is finished; IOW, until we have written
6842+        # the verification key.
6843+        d.addCallback(lambda ignored:
6844+            self.shouldFail(LayoutInvalid, "offsets before verification key",
6845+                            None,
6846+                            mw0.finish_publishing))
6847+
6848+        d.addCallback(lambda ignored:
6849+            mw0.put_verification_key(self.verification_key))
6850+        return d
6851+
6852+
6853+    def test_end_to_end(self):
6854+        mw = self._make_new_mw("si1", 0)
6855+        # Write a share using the mutable writer, and make sure that the
6856+        # reader knows how to read everything back to us.
6857+        d = defer.succeed(None)
6858+        for i in xrange(6):
6859+            d.addCallback(lambda ignored, i=i:
6860+                mw.put_block(self.block, i, self.salt))
6861+        d.addCallback(lambda ignored:
6862+            mw.put_encprivkey(self.encprivkey))
6863+        d.addCallback(lambda ignored:
6864+            mw.put_blockhashes(self.block_hash_tree))
6865+        d.addCallback(lambda ignored:
6866+            mw.put_sharehashes(self.share_hash_chain))
6867+        d.addCallback(lambda ignored:
6868+            mw.put_root_hash(self.root_hash))
6869+        d.addCallback(lambda ignored:
6870+            mw.put_signature(self.signature))
6871+        d.addCallback(lambda ignored:
6872+            mw.put_verification_key(self.verification_key))
6873+        d.addCallback(lambda ignored:
6874+            mw.finish_publishing())
6875+
6876+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6877+        def _check_block_and_salt((block, salt)):
6878+            self.failUnlessEqual(block, self.block)
6879+            self.failUnlessEqual(salt, self.salt)
6880+
6881+        for i in xrange(6):
6882+            d.addCallback(lambda ignored, i=i:
6883+                mr.get_block_and_salt(i))
6884+            d.addCallback(_check_block_and_salt)
6885+
6886+        d.addCallback(lambda ignored:
6887+            mr.get_encprivkey())
6888+        d.addCallback(lambda encprivkey:
6889+            self.failUnlessEqual(self.encprivkey, encprivkey))
6890+
6891+        d.addCallback(lambda ignored:
6892+            mr.get_blockhashes())
6893+        d.addCallback(lambda blockhashes:
6894+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6895+
6896+        d.addCallback(lambda ignored:
6897+            mr.get_sharehashes())
6898+        d.addCallback(lambda sharehashes:
6899+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6900+
6901+        d.addCallback(lambda ignored:
6902+            mr.get_signature())
6903+        d.addCallback(lambda signature:
6904+            self.failUnlessEqual(signature, self.signature))
6905+
6906+        d.addCallback(lambda ignored:
6907+            mr.get_verification_key())
6908+        d.addCallback(lambda verification_key:
6909+            self.failUnlessEqual(verification_key, self.verification_key))
6910+
6911+        d.addCallback(lambda ignored:
6912+            mr.get_seqnum())
6913+        d.addCallback(lambda seqnum:
6914+            self.failUnlessEqual(seqnum, 0))
6915+
6916+        d.addCallback(lambda ignored:
6917+            mr.get_root_hash())
6918+        d.addCallback(lambda root_hash:
6919+            self.failUnlessEqual(self.root_hash, root_hash))
6920+
6921+        d.addCallback(lambda ignored:
6922+            mr.get_encoding_parameters())
6923+        def _check_encoding_parameters((k, n, segsize, datalen)):
6924+            self.failUnlessEqual(k, 3)
6925+            self.failUnlessEqual(n, 10)
6926+            self.failUnlessEqual(segsize, 6)
6927+            self.failUnlessEqual(datalen, 36)
6928+        d.addCallback(_check_encoding_parameters)
6929+
6930+        d.addCallback(lambda ignored:
6931+            mr.get_checkstring())
6932+        d.addCallback(lambda checkstring:
6933+            self.failUnlessEqual(checkstring, mw.get_checkstring()))
6934+        return d
6935+
6936+
6937+    def test_is_sdmf(self):
6938+        # The MDMFSlotReadProxy should also know how to read SDMF files,
6939+        # since it will encounter them on the grid. Callers use the
6940+        # is_sdmf method to test this.
6941+        self.write_sdmf_share_to_server("si1")
6942+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6943+        d = mr.is_sdmf()
6944+        d.addCallback(lambda issdmf:
6945+            self.failUnless(issdmf))
6946+        return d
6947+
6948+
6949+    def test_reads_sdmf(self):
6950+        # The slot read proxy should, naturally, know how to tell us
6951+        # about data in the SDMF format
6952+        self.write_sdmf_share_to_server("si1")
6953+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6954+        d = defer.succeed(None)
6955+        d.addCallback(lambda ignored:
6956+            mr.is_sdmf())
6957+        d.addCallback(lambda issdmf:
6958+            self.failUnless(issdmf))
6959+
6960+        # What do we need to read?
6961+        #  - The sharedata
6962+        #  - The salt
6963+        d.addCallback(lambda ignored:
6964+            mr.get_block_and_salt(0))
6965+        def _check_block_and_salt(results):
6966+            block, salt = results
6967+            # Our original file is 36 bytes long. Then each share is 12
6968+            # bytes in size. The share is composed entirely of the
6969+            # letter a. self.block contains 2 as, so 6 * self.block is
6970+            # what we are looking for.
6971+            self.failUnlessEqual(block, self.block * 6)
6972+            self.failUnlessEqual(salt, self.salt)
6973+        d.addCallback(_check_block_and_salt)
6974+
6975+        #  - The blockhashes
6976+        d.addCallback(lambda ignored:
6977+            mr.get_blockhashes())
6978+        d.addCallback(lambda blockhashes:
6979+            self.failUnlessEqual(self.block_hash_tree,
6980+                                 blockhashes,
6981+                                 blockhashes))
6982+        #  - The sharehashes
6983+        d.addCallback(lambda ignored:
6984+            mr.get_sharehashes())
6985+        d.addCallback(lambda sharehashes:
6986+            self.failUnlessEqual(self.share_hash_chain,
6987+                                 sharehashes))
6988+        #  - The keys
6989+        d.addCallback(lambda ignored:
6990+            mr.get_encprivkey())
6991+        d.addCallback(lambda encprivkey:
6992+            self.failUnlessEqual(encprivkey, self.encprivkey, encprivkey))
6993+        d.addCallback(lambda ignored:
6994+            mr.get_verification_key())
6995+        d.addCallback(lambda verification_key:
6996+            self.failUnlessEqual(verification_key,
6997+                                 self.verification_key,
6998+                                 verification_key))
6999+        #  - The signature
7000+        d.addCallback(lambda ignored:
7001+            mr.get_signature())
7002+        d.addCallback(lambda signature:
7003+            self.failUnlessEqual(signature, self.signature, signature))
7004+
7005+        #  - The sequence number
7006+        d.addCallback(lambda ignored:
7007+            mr.get_seqnum())
7008+        d.addCallback(lambda seqnum:
7009+            self.failUnlessEqual(seqnum, 0, seqnum))
7010+
7011+        #  - The root hash
7012+        d.addCallback(lambda ignored:
7013+            mr.get_root_hash())
7014+        d.addCallback(lambda root_hash:
7015+            self.failUnlessEqual(root_hash, self.root_hash, root_hash))
7016+        return d
7017+
7018+
7019+    def test_only_reads_one_segment_sdmf(self):
7020+        # SDMF shares have only one segment, so it doesn't make sense to
7021+        # read more segments than that. The reader should know this and
7022+        # complain if we try to do that.
7023+        self.write_sdmf_share_to_server("si1")
7024+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7025+        d = defer.succeed(None)
7026+        d.addCallback(lambda ignored:
7027+            mr.is_sdmf())
7028+        d.addCallback(lambda issdmf:
7029+            self.failUnless(issdmf))
7030+        d.addCallback(lambda ignored:
7031+            self.shouldFail(LayoutInvalid, "test bad segment",
7032+                            None,
7033+                            mr.get_block_and_salt, 1))
7034+        return d
7035+
7036+
7037+    def test_read_with_prefetched_mdmf_data(self):
7038+        # The MDMFSlotReadProxy will prefill certain fields if you pass
7039+        # it data that you have already fetched. This is useful for
7040+        # cases like the Servermap, which prefetches ~2kb of data while
7041+        # finding out which shares are on the remote peer so that it
7042+        # doesn't waste round trips.
7043+        mdmf_data = self.build_test_mdmf_share()
7044+        self.write_test_share_to_server("si1")
7045+        def _make_mr(ignored, length):
7046+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:length])
7047+            return mr
7048+
7049+        d = defer.succeed(None)
7050+        # This should be enough to fill in both the encoding parameters
7051+        # and the table of offsets, which will complete the version
7052+        # information tuple.
7053+        d.addCallback(_make_mr, 107)
7054+        d.addCallback(lambda mr:
7055+            mr.get_verinfo())
7056+        def _check_verinfo(verinfo):
7057+            self.failUnless(verinfo)
7058+            self.failUnlessEqual(len(verinfo), 9)
7059+            (seqnum,
7060+             root_hash,
7061+             salt_hash,
7062+             segsize,
7063+             datalen,
7064+             k,
7065+             n,
7066+             prefix,
7067+             offsets) = verinfo
7068+            self.failUnlessEqual(seqnum, 0)
7069+            self.failUnlessEqual(root_hash, self.root_hash)
7070+            self.failUnlessEqual(segsize, 6)
7071+            self.failUnlessEqual(datalen, 36)
7072+            self.failUnlessEqual(k, 3)
7073+            self.failUnlessEqual(n, 10)
7074+            expected_prefix = struct.pack(MDMFSIGNABLEHEADER,
7075+                                          1,
7076+                                          seqnum,
7077+                                          root_hash,
7078+                                          k,
7079+                                          n,
7080+                                          segsize,
7081+                                          datalen)
7082+            self.failUnlessEqual(expected_prefix, prefix)
7083+            self.failUnlessEqual(self.rref.read_count, 0)
7084+        d.addCallback(_check_verinfo)
7085+        # This is not enough data to read a block and a share, so the
7086+        # wrapper should attempt to read this from the remote server.
7087+        d.addCallback(_make_mr, 107)
7088+        d.addCallback(lambda mr:
7089+            mr.get_block_and_salt(0))
7090+        def _check_block_and_salt((block, salt)):
7091+            self.failUnlessEqual(block, self.block)
7092+            self.failUnlessEqual(salt, self.salt)
7093+            self.failUnlessEqual(self.rref.read_count, 1)
7094+        # This should be enough data to read one block.
7095+        d.addCallback(_make_mr, 249)
7096+        d.addCallback(lambda mr:
7097+            mr.get_block_and_salt(0))
7098+        d.addCallback(_check_block_and_salt)
7099+        return d
7100+
7101+
7102+    def test_read_with_prefetched_sdmf_data(self):
7103+        sdmf_data = self.build_test_sdmf_share()
7104+        self.write_sdmf_share_to_server("si1")
7105+        def _make_mr(ignored, length):
7106+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:length])
7107+            return mr
7108+
7109+        d = defer.succeed(None)
7110+        # This should be enough to get us the encoding parameters,
7111+        # offset table, and everything else we need to build a verinfo
7112+        # string.
7113+        d.addCallback(_make_mr, 107)
7114+        d.addCallback(lambda mr:
7115+            mr.get_verinfo())
7116+        def _check_verinfo(verinfo):
7117+            self.failUnless(verinfo)
7118+            self.failUnlessEqual(len(verinfo), 9)
7119+            (seqnum,
7120+             root_hash,
7121+             salt,
7122+             segsize,
7123+             datalen,
7124+             k,
7125+             n,
7126+             prefix,
7127+             offsets) = verinfo
7128+            self.failUnlessEqual(seqnum, 0)
7129+            self.failUnlessEqual(root_hash, self.root_hash)
7130+            self.failUnlessEqual(salt, self.salt)
7131+            self.failUnlessEqual(segsize, 36)
7132+            self.failUnlessEqual(datalen, 36)
7133+            self.failUnlessEqual(k, 3)
7134+            self.failUnlessEqual(n, 10)
7135+            expected_prefix = struct.pack(SIGNED_PREFIX,
7136+                                          0,
7137+                                          seqnum,
7138+                                          root_hash,
7139+                                          salt,
7140+                                          k,
7141+                                          n,
7142+                                          segsize,
7143+                                          datalen)
7144+            self.failUnlessEqual(expected_prefix, prefix)
7145+            self.failUnlessEqual(self.rref.read_count, 0)
7146+        d.addCallback(_check_verinfo)
7147+        # This shouldn't be enough to read any share data.
7148+        d.addCallback(_make_mr, 107)
7149+        d.addCallback(lambda mr:
7150+            mr.get_block_and_salt(0))
7151+        def _check_block_and_salt((block, salt)):
7152+            self.failUnlessEqual(block, self.block * 6)
7153+            self.failUnlessEqual(salt, self.salt)
7154+            # TODO: Fix the read routine so that it reads only the data
7155+            #       that it has cached if it can't read all of it.
7156+            self.failUnlessEqual(self.rref.read_count, 2)
7157+
7158+        # This should be enough to read share data.
7159+        d.addCallback(_make_mr, self.offsets['share_data'])
7160+        d.addCallback(lambda mr:
7161+            mr.get_block_and_salt(0))
7162+        d.addCallback(_check_block_and_salt)
7163+        return d
7164+
7165+
7166+    def test_read_with_empty_mdmf_file(self):
7167+        # Some tests upload a file with no contents to test things
7168+        # unrelated to the actual handling of the content of the file.
7169+        # The reader should behave intelligently in these cases.
7170+        self.write_test_share_to_server("si1", empty=True)
7171+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7172+        # We should be able to get the encoding parameters, and they
7173+        # should be correct.
7174+        d = defer.succeed(None)
7175+        d.addCallback(lambda ignored:
7176+            mr.get_encoding_parameters())
7177+        def _check_encoding_parameters(params):
7178+            self.failUnlessEqual(len(params), 4)
7179+            k, n, segsize, datalen = params
7180+            self.failUnlessEqual(k, 3)
7181+            self.failUnlessEqual(n, 10)
7182+            self.failUnlessEqual(segsize, 0)
7183+            self.failUnlessEqual(datalen, 0)
7184+        d.addCallback(_check_encoding_parameters)
7185+
7186+        # We should not be able to fetch a block, since there are no
7187+        # blocks to fetch
7188+        d.addCallback(lambda ignored:
7189+            self.shouldFail(LayoutInvalid, "get block on empty file",
7190+                            None,
7191+                            mr.get_block_and_salt, 0))
7192+        return d
7193+
7194+
7195+    def test_read_with_empty_sdmf_file(self):
7196+        self.write_sdmf_share_to_server("si1", empty=True)
7197+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7198+        # We should be able to get the encoding parameters, and they
7199+        # should be correct
7200+        d = defer.succeed(None)
7201+        d.addCallback(lambda ignored:
7202+            mr.get_encoding_parameters())
7203+        def _check_encoding_parameters(params):
7204+            self.failUnlessEqual(len(params), 4)
7205+            k, n, segsize, datalen = params
7206+            self.failUnlessEqual(k, 3)
7207+            self.failUnlessEqual(n, 10)
7208+            self.failUnlessEqual(segsize, 0)
7209+            self.failUnlessEqual(datalen, 0)
7210+        d.addCallback(_check_encoding_parameters)
7211+
7212+        # It does not make sense to get a block in this format, so we
7213+        # should not be able to.
7214+        d.addCallback(lambda ignored:
7215+            self.shouldFail(LayoutInvalid, "get block on an empty file",
7216+                            None,
7217+                            mr.get_block_and_salt, 0))
7218+        return d
7219+
7220+
7221+    def test_verinfo_with_sdmf_file(self):
7222+        self.write_sdmf_share_to_server("si1")
7223+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7224+        # We should be able to get the version information.
7225+        d = defer.succeed(None)
7226+        d.addCallback(lambda ignored:
7227+            mr.get_verinfo())
7228+        def _check_verinfo(verinfo):
7229+            self.failUnless(verinfo)
7230+            self.failUnlessEqual(len(verinfo), 9)
7231+            (seqnum,
7232+             root_hash,
7233+             salt,
7234+             segsize,
7235+             datalen,
7236+             k,
7237+             n,
7238+             prefix,
7239+             offsets) = verinfo
7240+            self.failUnlessEqual(seqnum, 0)
7241+            self.failUnlessEqual(root_hash, self.root_hash)
7242+            self.failUnlessEqual(salt, self.salt)
7243+            self.failUnlessEqual(segsize, 36)
7244+            self.failUnlessEqual(datalen, 36)
7245+            self.failUnlessEqual(k, 3)
7246+            self.failUnlessEqual(n, 10)
7247+            expected_prefix = struct.pack(">BQ32s16s BBQQ",
7248+                                          0,
7249+                                          seqnum,
7250+                                          root_hash,
7251+                                          salt,
7252+                                          k,
7253+                                          n,
7254+                                          segsize,
7255+                                          datalen)
7256+            self.failUnlessEqual(prefix, expected_prefix)
7257+            self.failUnlessEqual(offsets, self.offsets)
7258+        d.addCallback(_check_verinfo)
7259+        return d
7260+
7261+
7262+    def test_verinfo_with_mdmf_file(self):
7263+        self.write_test_share_to_server("si1")
7264+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7265+        d = defer.succeed(None)
7266+        d.addCallback(lambda ignored:
7267+            mr.get_verinfo())
7268+        def _check_verinfo(verinfo):
7269+            self.failUnless(verinfo)
7270+            self.failUnlessEqual(len(verinfo), 9)
7271+            (seqnum,
7272+             root_hash,
7273+             IV,
7274+             segsize,
7275+             datalen,
7276+             k,
7277+             n,
7278+             prefix,
7279+             offsets) = verinfo
7280+            self.failUnlessEqual(seqnum, 0)
7281+            self.failUnlessEqual(root_hash, self.root_hash)
7282+            self.failIf(IV)
7283+            self.failUnlessEqual(segsize, 6)
7284+            self.failUnlessEqual(datalen, 36)
7285+            self.failUnlessEqual(k, 3)
7286+            self.failUnlessEqual(n, 10)
7287+            expected_prefix = struct.pack(">BQ32s BBQQ",
7288+                                          1,
7289+                                          seqnum,
7290+                                          root_hash,
7291+                                          k,
7292+                                          n,
7293+                                          segsize,
7294+                                          datalen)
7295+            self.failUnlessEqual(prefix, expected_prefix)
7296+            self.failUnlessEqual(offsets, self.offsets)
7297+        d.addCallback(_check_verinfo)
7298+        return d
7299+
7300+
7301+    def test_reader_queue(self):
7302+        self.write_test_share_to_server('si1')
7303+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7304+        d1 = mr.get_block_and_salt(0, queue=True)
7305+        d2 = mr.get_blockhashes(queue=True)
7306+        d3 = mr.get_sharehashes(queue=True)
7307+        d4 = mr.get_signature(queue=True)
7308+        d5 = mr.get_verification_key(queue=True)
7309+        dl = defer.DeferredList([d1, d2, d3, d4, d5])
7310+        mr.flush()
7311+        def _print(results):
7312+            self.failUnlessEqual(len(results), 5)
7313+            # We have one read for version information and offsets, and
7314+            # one for everything else.
7315+            self.failUnlessEqual(self.rref.read_count, 2)
7316+            block, salt = results[0][1] # results[0] is a boolean that says
7317+                                           # whether or not the operation
7318+                                           # worked.
7319+            self.failUnlessEqual(self.block, block)
7320+            self.failUnlessEqual(self.salt, salt)
7321+
7322+            blockhashes = results[1][1]
7323+            self.failUnlessEqual(self.block_hash_tree, blockhashes)
7324+
7325+            sharehashes = results[2][1]
7326+            self.failUnlessEqual(self.share_hash_chain, sharehashes)
7327+
7328+            signature = results[3][1]
7329+            self.failUnlessEqual(self.signature, signature)
7330+
7331+            verification_key = results[4][1]
7332+            self.failUnlessEqual(self.verification_key, verification_key)
7333+        dl.addCallback(_print)
7334+        return dl
7335+
7336+
7337+    def test_sdmf_writer(self):
7338+        # Go through the motions of writing an SDMF share to the storage
7339+        # server. Then read the storage server to see that the share got
7340+        # written in the way that we think it should have.
7341+
7342+        # We do this first so that the necessary instance variables get
7343+        # set the way we want them for the tests below.
7344+        data = self.build_test_sdmf_share()
7345+        sdmfr = SDMFSlotWriteProxy(0,
7346+                                   self.rref,
7347+                                   "si1",
7348+                                   self.secrets,
7349+                                   0, 3, 10, 36, 36)
7350+        # Put the block and salt.
7351+        sdmfr.put_block(self.blockdata, 0, self.salt)
7352+
7353+        # Put the encprivkey
7354+        sdmfr.put_encprivkey(self.encprivkey)
7355+
7356+        # Put the block and share hash chains
7357+        sdmfr.put_blockhashes(self.block_hash_tree)
7358+        sdmfr.put_sharehashes(self.share_hash_chain)
7359+        sdmfr.put_root_hash(self.root_hash)
7360+
7361+        # Put the signature
7362+        sdmfr.put_signature(self.signature)
7363+
7364+        # Put the verification key
7365+        sdmfr.put_verification_key(self.verification_key)
7366+
7367+        # Now check to make sure that nothing has been written yet.
7368+        self.failUnlessEqual(self.rref.write_count, 0)
7369+
7370+        # Now finish publishing
7371+        d = sdmfr.finish_publishing()
7372+        def _then(ignored):
7373+            self.failUnlessEqual(self.rref.write_count, 1)
7374+            read = self.ss.remote_slot_readv
7375+            self.failUnlessEqual(read("si1", [0], [(0, len(data))]),
7376+                                 {0: [data]})
7377+        d.addCallback(_then)
7378+        return d
7379+
7380+
7381+    def test_sdmf_writer_preexisting_share(self):
7382+        data = self.build_test_sdmf_share()
7383+        self.write_sdmf_share_to_server("si1")
7384+
7385+        # Now there is a share on the storage server. To successfully
7386+        # write, we need to set the checkstring correctly. When we
7387+        # don't, no write should occur.
7388+        sdmfw = SDMFSlotWriteProxy(0,
7389+                                   self.rref,
7390+                                   "si1",
7391+                                   self.secrets,
7392+                                   1, 3, 10, 36, 36)
7393+        sdmfw.put_block(self.blockdata, 0, self.salt)
7394+
7395+        # Put the encprivkey
7396+        sdmfw.put_encprivkey(self.encprivkey)
7397+
7398+        # Put the block and share hash chains
7399+        sdmfw.put_blockhashes(self.block_hash_tree)
7400+        sdmfw.put_sharehashes(self.share_hash_chain)
7401+
7402+        # Put the root hash
7403+        sdmfw.put_root_hash(self.root_hash)
7404+
7405+        # Put the signature
7406+        sdmfw.put_signature(self.signature)
7407+
7408+        # Put the verification key
7409+        sdmfw.put_verification_key(self.verification_key)
7410+
7411+        # We shouldn't have a checkstring yet
7412+        self.failUnlessEqual(sdmfw.get_checkstring(), "")
7413+
7414+        d = sdmfw.finish_publishing()
7415+        def _then(results):
7416+            self.failIf(results[0])
7417+            # this is the correct checkstring
7418+            self._expected_checkstring = results[1][0][0]
7419+            return self._expected_checkstring
7420+
7421+        d.addCallback(_then)
7422+        d.addCallback(sdmfw.set_checkstring)
7423+        d.addCallback(lambda ignored:
7424+            sdmfw.get_checkstring())
7425+        d.addCallback(lambda checkstring:
7426+            self.failUnlessEqual(checkstring, self._expected_checkstring))
7427+        d.addCallback(lambda ignored:
7428+            sdmfw.finish_publishing())
7429+        def _then_again(results):
7430+            self.failUnless(results[0])
7431+            read = self.ss.remote_slot_readv
7432+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
7433+                                 {0: [struct.pack(">Q", 1)]})
7434+            self.failUnlessEqual(read("si1", [0], [(9, len(data) - 9)]),
7435+                                 {0: [data[9:]]})
7436+        d.addCallback(_then_again)
7437+        return d
7438+
7439+
7440 class Stats(unittest.TestCase):
7441 
7442     def setUp(self):
7443}
7444[mutable/publish.py: cleanup + simplification
7445Kevan Carstensen <kevan@isnotajoke.com>**20100702225554
7446 Ignore-this: 36a58424ceceffb1ddc55cc5934399e2
7447] {
7448hunk ./src/allmydata/mutable/publish.py 19
7449      UncoordinatedWriteError, NotEnoughServersError
7450 from allmydata.mutable.servermap import ServerMap
7451 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
7452-     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
7453+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy, \
7454+     SDMFSlotWriteProxy
7455 
7456 KiB = 1024
7457 DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
7458hunk ./src/allmydata/mutable/publish.py 24
7459+PUSHING_BLOCKS_STATE = 0
7460+PUSHING_EVERYTHING_ELSE_STATE = 1
7461+DONE_STATE = 2
7462 
7463 class PublishStatus:
7464     implements(IPublishStatus)
7465hunk ./src/allmydata/mutable/publish.py 229
7466 
7467         self.bad_share_checkstrings = {}
7468 
7469+        # This is set at the last step of the publishing process.
7470+        self.versioninfo = ""
7471+
7472         # we use the servermap to populate the initial goal: this way we will
7473         # try to update each existing share in place.
7474         for (peerid, shnum) in self._servermap.servermap:
7475hunk ./src/allmydata/mutable/publish.py 245
7476             self.bad_share_checkstrings[key] = old_checkstring
7477             self.connections[peerid] = self._servermap.connections[peerid]
7478 
7479-        # Now, the process dovetails -- if this is an SDMF file, we need
7480-        # to write an SDMF file. Otherwise, we need to write an MDMF
7481-        # file.
7482-        if self._version == MDMF_VERSION:
7483-            return self._publish_mdmf()
7484-        else:
7485-            return self._publish_sdmf()
7486-        #return self.done_deferred
7487-
7488-    def _publish_mdmf(self):
7489-        # Next, we find homes for all of the shares that we don't have
7490-        # homes for yet.
7491         # TODO: Make this part do peer selection.
7492         self.update_goal()
7493         self.writers = {}
7494hunk ./src/allmydata/mutable/publish.py 248
7495-        # For each (peerid, shnum) in self.goal, we make an
7496-        # MDMFSlotWriteProxy for that peer. We'll use this to write
7497+        if self._version == MDMF_VERSION:
7498+            writer_class = MDMFSlotWriteProxy
7499+        else:
7500+            writer_class = SDMFSlotWriteProxy
7501+
7502+        # For each (peerid, shnum) in self.goal, we make a
7503+        # write proxy for that peer. We'll use this to write
7504         # shares to the peer.
7505         for key in self.goal:
7506             peerid, shnum = key
7507hunk ./src/allmydata/mutable/publish.py 263
7508             cancel_secret = self._node.get_cancel_secret(peerid)
7509             secrets = (write_enabler, renew_secret, cancel_secret)
7510 
7511-            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
7512-                                                      self.connections[peerid],
7513-                                                      self._storage_index,
7514-                                                      secrets,
7515-                                                      self._new_seqnum,
7516-                                                      self.required_shares,
7517-                                                      self.total_shares,
7518-                                                      self.segment_size,
7519-                                                      len(self.newdata))
7520+            self.writers[shnum] =  writer_class(shnum,
7521+                                                self.connections[peerid],
7522+                                                self._storage_index,
7523+                                                secrets,
7524+                                                self._new_seqnum,
7525+                                                self.required_shares,
7526+                                                self.total_shares,
7527+                                                self.segment_size,
7528+                                                len(self.newdata))
7529+            self.writers[shnum].peerid = peerid
7530             if (peerid, shnum) in self._servermap.servermap:
7531                 old_versionid, old_timestamp = self._servermap.servermap[key]
7532                 (old_seqnum, old_root_hash, old_salt, old_segsize,
7533hunk ./src/allmydata/mutable/publish.py 278
7534                  old_datalength, old_k, old_N, old_prefix,
7535                  old_offsets_tuple) = old_versionid
7536-                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
7537+                self.writers[shnum].set_checkstring(old_seqnum,
7538+                                                    old_root_hash,
7539+                                                    old_salt)
7540+            elif (peerid, shnum) in self.bad_share_checkstrings:
7541+                old_checkstring = self.bad_share_checkstrings[(peerid, shnum)]
7542+                self.writers[shnum].set_checkstring(old_checkstring)
7543+
7544+        # Our remote shares will not have a complete checkstring until
7545+        # after we are done writing share data and have started to write
7546+        # blocks. In the meantime, we need to know what to look for when
7547+        # writing, so that we can detect UncoordinatedWriteErrors.
7548+        self._checkstring = self.writers.values()[0].get_checkstring()
7549 
7550         # Now, we start pushing shares.
7551         self._status.timings["setup"] = time.time() - self._started
7552hunk ./src/allmydata/mutable/publish.py 293
7553-        def _start_pushing(res):
7554-            self._started_pushing = time.time()
7555-            return res
7556-
7557         # First, we encrypt, encode, and publish the shares that we need
7558         # to encrypt, encode, and publish.
7559 
7560hunk ./src/allmydata/mutable/publish.py 306
7561 
7562         d = defer.succeed(None)
7563         self.log("Starting push")
7564-        for i in xrange(self.num_segments - 1):
7565-            d.addCallback(lambda ignored, i=i:
7566-                self.push_segment(i))
7567-            d.addCallback(self._turn_barrier)
7568-        # We have at least one segment, so we will have a tail segment
7569-        if self.num_segments > 0:
7570-            d.addCallback(lambda ignored:
7571-                self.push_tail_segment())
7572-
7573-        d.addCallback(lambda ignored:
7574-            self.push_encprivkey())
7575-        d.addCallback(lambda ignored:
7576-            self.push_blockhashes())
7577-        d.addCallback(lambda ignored:
7578-            self.push_sharehashes())
7579-        d.addCallback(lambda ignored:
7580-            self.push_toplevel_hashes_and_signature())
7581-        d.addCallback(lambda ignored:
7582-            self.finish_publishing())
7583-        return d
7584-
7585-
7586-    def _publish_sdmf(self):
7587-        self._status.timings["setup"] = time.time() - self._started
7588-        self.salt = os.urandom(16)
7589 
7590hunk ./src/allmydata/mutable/publish.py 307
7591-        d = self._encrypt_and_encode()
7592-        d.addCallback(self._generate_shares)
7593-        def _start_pushing(res):
7594-            self._started_pushing = time.time()
7595-            return res
7596-        d.addCallback(_start_pushing)
7597-        d.addCallback(self.loop) # trigger delivery
7598-        d.addErrback(self._fatal_error)
7599+        self._state = PUSHING_BLOCKS_STATE
7600+        self._push()
7601 
7602         return self.done_deferred
7603 
7604hunk ./src/allmydata/mutable/publish.py 327
7605                                                   segment_size)
7606         else:
7607             self.num_segments = 0
7608+
7609+        self.log("building encoding parameters for file")
7610+        self.log("got segsize %d" % self.segment_size)
7611+        self.log("got %d segments" % self.num_segments)
7612+
7613         if self._version == SDMF_VERSION:
7614             assert self.num_segments in (0, 1) # SDMF
7615hunk ./src/allmydata/mutable/publish.py 334
7616-            return
7617         # calculate the tail segment size.
7618hunk ./src/allmydata/mutable/publish.py 335
7619-        self.tail_segment_size = len(self.newdata) % segment_size
7620 
7621hunk ./src/allmydata/mutable/publish.py 336
7622-        if self.tail_segment_size == 0:
7623+        if segment_size and self.newdata:
7624+            self.tail_segment_size = len(self.newdata) % segment_size
7625+        else:
7626+            self.tail_segment_size = 0
7627+
7628+        if self.tail_segment_size == 0 and segment_size:
7629             # The tail segment is the same size as the other segments.
7630             self.tail_segment_size = segment_size
7631 
7632hunk ./src/allmydata/mutable/publish.py 345
7633-        # We'll make an encoder ahead-of-time for the normal-sized
7634-        # segments (defined as any segment of segment_size size.
7635-        # (the part of the code that puts the tail segment will make its
7636-        #  own encoder for that part)
7637+        # Make FEC encoders
7638         fec = codec.CRSEncoder()
7639         fec.set_params(self.segment_size,
7640                        self.required_shares, self.total_shares)
7641hunk ./src/allmydata/mutable/publish.py 352
7642         self.piece_size = fec.get_block_size()
7643         self.fec = fec
7644 
7645+        if self.tail_segment_size == self.segment_size:
7646+            self.tail_fec = self.fec
7647+        else:
7648+            tail_fec = codec.CRSEncoder()
7649+            tail_fec.set_params(self.tail_segment_size,
7650+                                self.required_shares,
7651+                                self.total_shares)
7652+            self.tail_fec = tail_fec
7653+
7654+        self._current_segment = 0
7655+
7656+
7657+    def _push(self, ignored=None):
7658+        """
7659+        I manage state transitions. In particular, I see that we still
7660+        have a good enough number of writers to complete the upload
7661+        successfully.
7662+        """
7663+        # Can we still successfully publish this file?
7664+        # TODO: Keep track of outstanding queries before aborting the
7665+        #       process.
7666+        if len(self.writers) <= self.required_shares or self.surprised:
7667+            return self._failure()
7668+
7669+        # Figure out what we need to do next. Each of these needs to
7670+        # return a deferred so that we don't block execution when this
7671+        # is first called in the upload method.
7672+        if self._state == PUSHING_BLOCKS_STATE:
7673+            return self.push_segment(self._current_segment)
7674+
7675+        # XXX: Do we want more granularity in states? Is that useful at
7676+        #      all?
7677+        #      Yes -- quicker reaction to UCW.
7678+        elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
7679+            return self.push_everything_else()
7680+
7681+        # If we make it to this point, we were successful in placing the
7682+        # file.
7683+        return self._done(None)
7684+
7685 
7686     def push_segment(self, segnum):
7687hunk ./src/allmydata/mutable/publish.py 394
7688+        if self.num_segments == 0 and self._version == SDMF_VERSION:
7689+            self._add_dummy_salts()
7690+
7691+        if segnum == self.num_segments:
7692+            # We don't have any more segments to push.
7693+            self._state = PUSHING_EVERYTHING_ELSE_STATE
7694+            return self._push()
7695+
7696+        d = self._encode_segment(segnum)
7697+        d.addCallback(self._push_segment, segnum)
7698+        def _increment_segnum(ign):
7699+            self._current_segment += 1
7700+        # XXX: I don't think we need to do addBoth here -- any errBacks
7701+        # should be handled within push_segment.
7702+        d.addBoth(_increment_segnum)
7703+        d.addBoth(self._push)
7704+
7705+
7706+    def _add_dummy_salts(self):
7707+        """
7708+        SDMF files need a salt even if they're empty, or the signature
7709+        won't make sense. This method adds a dummy salt to each of our
7710+        SDMF writers so that they can write the signature later.
7711+        """
7712+        salt = os.urandom(16)
7713+        assert self._version == SDMF_VERSION
7714+
7715+        for writer in self.writers.itervalues():
7716+            writer.put_salt(salt)
7717+
7718+
7719+    def _encode_segment(self, segnum):
7720+        """
7721+        I encrypt and encode the segment segnum.
7722+        """
7723         started = time.time()
7724hunk ./src/allmydata/mutable/publish.py 430
7725-        segsize = self.segment_size
7726+
7727+        if segnum + 1 == self.num_segments:
7728+            segsize = self.tail_segment_size
7729+        else:
7730+            segsize = self.segment_size
7731+
7732+
7733+        offset = self.segment_size * segnum
7734+        length = segsize + offset
7735         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
7736hunk ./src/allmydata/mutable/publish.py 440
7737-        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
7738+        data = self.newdata[offset:length]
7739         assert len(data) == segsize
7740 
7741         salt = os.urandom(16)
7742hunk ./src/allmydata/mutable/publish.py 455
7743         started = now
7744 
7745         # now apply FEC
7746+        if segnum + 1 == self.num_segments:
7747+            fec = self.tail_fec
7748+        else:
7749+            fec = self.fec
7750 
7751         self._status.set_status("Encoding")
7752         crypttext_pieces = [None] * self.required_shares
7753hunk ./src/allmydata/mutable/publish.py 462
7754-        piece_size = self.piece_size
7755+        piece_size = fec.get_block_size()
7756         for i in range(len(crypttext_pieces)):
7757             offset = i * piece_size
7758             piece = crypttext[offset:offset+piece_size]
7759hunk ./src/allmydata/mutable/publish.py 469
7760             piece = piece + "\x00"*(piece_size - len(piece)) # padding
7761             crypttext_pieces[i] = piece
7762             assert len(piece) == piece_size
7763-        d = self.fec.encode(crypttext_pieces)
7764+        d = fec.encode(crypttext_pieces)
7765         def _done_encoding(res):
7766             elapsed = time.time() - started
7767             self._status.timings["encode"] = elapsed
7768hunk ./src/allmydata/mutable/publish.py 473
7769-            return res
7770+            return (res, salt)
7771         d.addCallback(_done_encoding)
7772hunk ./src/allmydata/mutable/publish.py 475
7773-
7774-        def _push_shares_and_salt(results):
7775-            shares, shareids = results
7776-            dl = []
7777-            for i in xrange(len(shares)):
7778-                sharedata = shares[i]
7779-                shareid = shareids[i]
7780-                block_hash = hashutil.block_hash(salt + sharedata)
7781-                self.blockhashes[shareid].append(block_hash)
7782-
7783-                # find the writer for this share
7784-                d = self.writers[shareid].put_block(sharedata, segnum, salt)
7785-                dl.append(d)
7786-            # TODO: Naturally, we need to check on the results of these.
7787-            return defer.DeferredList(dl)
7788-        d.addCallback(_push_shares_and_salt)
7789         return d
7790 
7791 
7792hunk ./src/allmydata/mutable/publish.py 478
7793-    def push_tail_segment(self):
7794-        # This is essentially the same as push_segment, except that we
7795-        # don't use the cached encoder that we use elsewhere.
7796-        self.log("Pushing tail segment")
7797+    def _push_segment(self, encoded_and_salt, segnum):
7798+        """
7799+        I push (data, salt) as segment number segnum.
7800+        """
7801+        results, salt = encoded_and_salt
7802+        shares, shareids = results
7803         started = time.time()
7804hunk ./src/allmydata/mutable/publish.py 485
7805-        segsize = self.segment_size
7806-        data = self.newdata[segsize * (self.num_segments-1):]
7807-        assert len(data) == self.tail_segment_size
7808-        salt = os.urandom(16)
7809-
7810-        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
7811-        enc = AES(key)
7812-        crypttext = enc.process(data)
7813-        assert len(crypttext) == len(data)
7814+        dl = []
7815+        for i in xrange(len(shares)):
7816+            sharedata = shares[i]
7817+            shareid = shareids[i]
7818+            if self._version == MDMF_VERSION:
7819+                hashed = salt + sharedata
7820+            else:
7821+                hashed = sharedata
7822+            block_hash = hashutil.block_hash(hashed)
7823+            self.blockhashes[shareid].append(block_hash)
7824 
7825hunk ./src/allmydata/mutable/publish.py 496
7826-        now = time.time()
7827-        self._status.timings['encrypt'] = now - started
7828-        started = now
7829+            # find the writer for this share
7830+            writer = self.writers[shareid]
7831+            d = writer.put_block(sharedata, segnum, salt)
7832+            d.addCallback(self._got_write_answer, writer, started)
7833+            d.addErrback(self._connection_problem, writer)
7834+            dl.append(d)
7835+            # TODO: Naturally, we need to check on the results of these.
7836+        return defer.DeferredList(dl)
7837 
7838hunk ./src/allmydata/mutable/publish.py 505
7839-        self._status.set_status("Encoding")
7840-        tail_fec = codec.CRSEncoder()
7841-        tail_fec.set_params(self.tail_segment_size,
7842-                            self.required_shares,
7843-                            self.total_shares)
7844 
7845hunk ./src/allmydata/mutable/publish.py 506
7846-        crypttext_pieces = [None] * self.required_shares
7847-        piece_size = tail_fec.get_block_size()
7848-        for i in range(len(crypttext_pieces)):
7849-            offset = i * piece_size
7850-            piece = crypttext[offset:offset+piece_size]
7851-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
7852-            crypttext_pieces[i] = piece
7853-            assert len(piece) == piece_size
7854-        d = tail_fec.encode(crypttext_pieces)
7855-        def _push_shares_and_salt(results):
7856-            shares, shareids = results
7857-            dl = []
7858-            for i in xrange(len(shares)):
7859-                sharedata = shares[i]
7860-                shareid = shareids[i]
7861-                block_hash = hashutil.block_hash(salt + sharedata)
7862-                self.blockhashes[shareid].append(block_hash)
7863-                # find the writer for this share
7864-                d = self.writers[shareid].put_block(sharedata,
7865-                                                    self.num_segments - 1,
7866-                                                    salt)
7867-                dl.append(d)
7868-            # TODO: Naturally, we need to check on the results of these.
7869-            return defer.DeferredList(dl)
7870-        d.addCallback(_push_shares_and_salt)
7871+    def push_everything_else(self):
7872+        """
7873+        I put everything else associated with a share.
7874+        """
7875+        encprivkey = self._encprivkey
7876+        d = self.push_encprivkey()
7877+        d.addCallback(self.push_blockhashes)
7878+        d.addCallback(self.push_sharehashes)
7879+        d.addCallback(self.push_toplevel_hashes_and_signature)
7880+        d.addCallback(self.finish_publishing)
7881+        def _change_state(ignored):
7882+            self._state = DONE_STATE
7883+        d.addCallback(_change_state)
7884+        d.addCallback(self._push)
7885         return d
7886 
7887 
7888hunk ./src/allmydata/mutable/publish.py 527
7889         started = time.time()
7890         encprivkey = self._encprivkey
7891         dl = []
7892-        def _spy_on_writer(results):
7893-            print results
7894-            return results
7895-        for shnum, writer in self.writers.iteritems():
7896+        for writer in self.writers.itervalues():
7897             d = writer.put_encprivkey(encprivkey)
7898hunk ./src/allmydata/mutable/publish.py 529
7899+            d.addCallback(self._got_write_answer, writer, started)
7900+            d.addErrback(self._connection_problem, writer)
7901             dl.append(d)
7902         d = defer.DeferredList(dl)
7903         return d
7904hunk ./src/allmydata/mutable/publish.py 536
7905 
7906 
7907-    def push_blockhashes(self):
7908+    def push_blockhashes(self, ignored):
7909         started = time.time()
7910         dl = []
7911hunk ./src/allmydata/mutable/publish.py 539
7912-        def _spy_on_results(results):
7913-            print results
7914-            return results
7915         self.sharehash_leaves = [None] * len(self.blockhashes)
7916         for shnum, blockhashes in self.blockhashes.iteritems():
7917             t = hashtree.HashTree(blockhashes)
7918hunk ./src/allmydata/mutable/publish.py 545
7919             self.blockhashes[shnum] = list(t)
7920             # set the leaf for future use.
7921             self.sharehash_leaves[shnum] = t[0]
7922-            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
7923+            writer = self.writers[shnum]
7924+            d = writer.put_blockhashes(self.blockhashes[shnum])
7925+            d.addCallback(self._got_write_answer, writer, started)
7926+            d.addErrback(self._connection_problem, self.writers[shnum])
7927             dl.append(d)
7928         d = defer.DeferredList(dl)
7929         return d
7930hunk ./src/allmydata/mutable/publish.py 554
7931 
7932 
7933-    def push_sharehashes(self):
7934+    def push_sharehashes(self, ignored):
7935+        started = time.time()
7936         share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
7937         share_hash_chain = {}
7938         ds = []
7939hunk ./src/allmydata/mutable/publish.py 559
7940-        def _spy_on_results(results):
7941-            print results
7942-            return results
7943         for shnum in xrange(len(self.sharehash_leaves)):
7944             needed_indices = share_hash_tree.needed_hashes(shnum)
7945             self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
7946hunk ./src/allmydata/mutable/publish.py 563
7947                                              for i in needed_indices] )
7948-            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
7949+            writer = self.writers[shnum]
7950+            d = writer.put_sharehashes(self.sharehashes[shnum])
7951+            d.addCallback(self._got_write_answer, writer, started)
7952+            d.addErrback(self._connection_problem, writer)
7953             ds.append(d)
7954         self.root_hash = share_hash_tree[0]
7955         d = defer.DeferredList(ds)
7956hunk ./src/allmydata/mutable/publish.py 573
7957         return d
7958 
7959 
7960-    def push_toplevel_hashes_and_signature(self):
7961+    def push_toplevel_hashes_and_signature(self, ignored):
7962         # We need to to three things here:
7963         #   - Push the root hash and salt hash
7964         #   - Get the checkstring of the resulting layout; sign that.
7965hunk ./src/allmydata/mutable/publish.py 578
7966         #   - Push the signature
7967+        started = time.time()
7968         ds = []
7969hunk ./src/allmydata/mutable/publish.py 580
7970-        def _spy_on_results(results):
7971-            print results
7972-            return results
7973         for shnum in xrange(self.total_shares):
7974hunk ./src/allmydata/mutable/publish.py 581
7975-            d = self.writers[shnum].put_root_hash(self.root_hash)
7976+            writer = self.writers[shnum]
7977+            d = writer.put_root_hash(self.root_hash)
7978+            d.addCallback(self._got_write_answer, writer, started)
7979             ds.append(d)
7980         d = defer.DeferredList(ds)
7981hunk ./src/allmydata/mutable/publish.py 586
7982-        def _make_and_place_signature(ignored):
7983-            signable = self.writers[0].get_signable()
7984-            self.signature = self._privkey.sign(signable)
7985-
7986-            ds = []
7987-            for (shnum, writer) in self.writers.iteritems():
7988-                d = writer.put_signature(self.signature)
7989-                ds.append(d)
7990-            return defer.DeferredList(ds)
7991-        d.addCallback(_make_and_place_signature)
7992+        d.addCallback(self._update_checkstring)
7993+        d.addCallback(self._make_and_place_signature)
7994         return d
7995 
7996 
7997hunk ./src/allmydata/mutable/publish.py 591
7998-    def finish_publishing(self):
7999+    def _update_checkstring(self, ignored):
8000+        """
8001+        After putting the root hash, MDMF files will have the
8002+        checkstring written to the storage server. This means that we
8003+        can update our copy of the checkstring so we can detect
8004+        uncoordinated writes. SDMF files will have the same checkstring,
8005+        so we need not do anything.
8006+        """
8007+        self._checkstring = self.writers.values()[0].get_checkstring()
8008+
8009+
8010+    def _make_and_place_signature(self, ignored):
8011+        """
8012+        I create and place the signature.
8013+        """
8014+        started = time.time()
8015+        signable = self.writers[0].get_signable()
8016+        self.signature = self._privkey.sign(signable)
8017+
8018+        ds = []
8019+        for (shnum, writer) in self.writers.iteritems():
8020+            d = writer.put_signature(self.signature)
8021+            d.addCallback(self._got_write_answer, writer, started)
8022+            d.addErrback(self._connection_problem, writer)
8023+            ds.append(d)
8024+        return defer.DeferredList(ds)
8025+
8026+
8027+    def finish_publishing(self, ignored):
8028         # We're almost done -- we just need to put the verification key
8029         # and the offsets
8030hunk ./src/allmydata/mutable/publish.py 622
8031+        started = time.time()
8032         ds = []
8033         verification_key = self._pubkey.serialize()
8034 
8035hunk ./src/allmydata/mutable/publish.py 626
8036-        def _spy_on_results(results):
8037-            print results
8038-            return results
8039+
8040+        # TODO: Bad, since we remove from this same dict. We need to
8041+        # make a copy, or just use a non-iterated value.
8042         for (shnum, writer) in self.writers.iteritems():
8043             d = writer.put_verification_key(verification_key)
8044hunk ./src/allmydata/mutable/publish.py 631
8045+            d.addCallback(self._got_write_answer, writer, started)
8046+            d.addCallback(self._record_verinfo)
8047             d.addCallback(lambda ignored, writer=writer:
8048                 writer.finish_publishing())
8049hunk ./src/allmydata/mutable/publish.py 635
8050+            d.addCallback(self._got_write_answer, writer, started)
8051+            d.addErrback(self._connection_problem, writer)
8052             ds.append(d)
8053         return defer.DeferredList(ds)
8054 
8055hunk ./src/allmydata/mutable/publish.py 641
8056 
8057-    def _turn_barrier(self, res):
8058-        # putting this method in a Deferred chain imposes a guaranteed
8059-        # reactor turn between the pre- and post- portions of that chain.
8060-        # This can be useful to limit memory consumption: since Deferreds do
8061-        # not do tail recursion, code which uses defer.succeed(result) for
8062-        # consistency will cause objects to live for longer than you might
8063-        # normally expect.
8064-        return fireEventually(res)
8065+    def _record_verinfo(self, ignored):
8066+        self.versioninfo = self.writers.values()[0].get_verinfo()
8067 
8068 
8069hunk ./src/allmydata/mutable/publish.py 645
8070-    def _fatal_error(self, f):
8071-        self.log("error during loop", failure=f, level=log.UNUSUAL)
8072-        self._done(f)
8073+    def _connection_problem(self, f, writer):
8074+        """
8075+        We ran into a connection problem while working with writer, and
8076+        need to deal with that.
8077+        """
8078+        self.log("found problem: %s" % str(f))
8079+        self._last_failure = f
8080+        del(self.writers[writer.shnum])
8081 
8082hunk ./src/allmydata/mutable/publish.py 654
8083-    def _update_status(self):
8084-        self._status.set_status("Sending Shares: %d placed out of %d, "
8085-                                "%d messages outstanding" %
8086-                                (len(self.placed),
8087-                                 len(self.goal),
8088-                                 len(self.outstanding)))
8089-        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
8090 
8091     def loop(self, ignored=None):
8092         self.log("entering loop", level=log.NOISY)
8093hunk ./src/allmydata/mutable/publish.py 778
8094             self.log_goal(self.goal, "after update: ")
8095 
8096 
8097-    def _encrypt_and_encode(self):
8098-        # this returns a Deferred that fires with a list of (sharedata,
8099-        # sharenum) tuples. TODO: cache the ciphertext, only produce the
8100-        # shares that we care about.
8101-        self.log("_encrypt_and_encode")
8102-
8103-        self._status.set_status("Encrypting")
8104-        started = time.time()
8105+    def _got_write_answer(self, answer, writer, started):
8106+        if not answer:
8107+            # SDMF writers only pretend to write when readers set their
8108+            # blocks, salts, and so on -- they actually just write once,
8109+            # at the end of the upload process. In fake writes, they
8110+            # return defer.succeed(None). If we see that, we shouldn't
8111+            # bother checking it.
8112+            return
8113 
8114hunk ./src/allmydata/mutable/publish.py 787
8115-        key = hashutil.ssk_readkey_data_hash(self.salt, self.readkey)
8116-        enc = AES(key)
8117-        crypttext = enc.process(self.newdata)
8118-        assert len(crypttext) == len(self.newdata)
8119+        peerid = writer.peerid
8120+        lp = self.log("_got_write_answer from %s, share %d" %
8121+                      (idlib.shortnodeid_b2a(peerid), writer.shnum))
8122 
8123         now = time.time()
8124hunk ./src/allmydata/mutable/publish.py 792
8125-        self._status.timings["encrypt"] = now - started
8126-        started = now
8127-
8128-        # now apply FEC
8129-
8130-        self._status.set_status("Encoding")
8131-        fec = codec.CRSEncoder()
8132-        fec.set_params(self.segment_size,
8133-                       self.required_shares, self.total_shares)
8134-        piece_size = fec.get_block_size()
8135-        crypttext_pieces = [None] * self.required_shares
8136-        for i in range(len(crypttext_pieces)):
8137-            offset = i * piece_size
8138-            piece = crypttext[offset:offset+piece_size]
8139-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
8140-            crypttext_pieces[i] = piece
8141-            assert len(piece) == piece_size
8142-
8143-        d = fec.encode(crypttext_pieces)
8144-        def _done_encoding(res):
8145-            elapsed = time.time() - started
8146-            self._status.timings["encode"] = elapsed
8147-            return res
8148-        d.addCallback(_done_encoding)
8149-        return d
8150-
8151-
8152-    def _generate_shares(self, shares_and_shareids):
8153-        # this sets self.shares and self.root_hash
8154-        self.log("_generate_shares")
8155-        self._status.set_status("Generating Shares")
8156-        started = time.time()
8157-
8158-        # we should know these by now
8159-        privkey = self._privkey
8160-        encprivkey = self._encprivkey
8161-        pubkey = self._pubkey
8162-
8163-        (shares, share_ids) = shares_and_shareids
8164-
8165-        assert len(shares) == len(share_ids)
8166-        assert len(shares) == self.total_shares
8167-        all_shares = {}
8168-        block_hash_trees = {}
8169-        share_hash_leaves = [None] * len(shares)
8170-        for i in range(len(shares)):
8171-            share_data = shares[i]
8172-            shnum = share_ids[i]
8173-            all_shares[shnum] = share_data
8174-
8175-            # build the block hash tree. SDMF has only one leaf.
8176-            leaves = [hashutil.block_hash(share_data)]
8177-            t = hashtree.HashTree(leaves)
8178-            block_hash_trees[shnum] = list(t)
8179-            share_hash_leaves[shnum] = t[0]
8180-        for leaf in share_hash_leaves:
8181-            assert leaf is not None
8182-        share_hash_tree = hashtree.HashTree(share_hash_leaves)
8183-        share_hash_chain = {}
8184-        for shnum in range(self.total_shares):
8185-            needed_hashes = share_hash_tree.needed_hashes(shnum)
8186-            share_hash_chain[shnum] = dict( [ (i, share_hash_tree[i])
8187-                                              for i in needed_hashes ] )
8188-        root_hash = share_hash_tree[0]
8189-        assert len(root_hash) == 32
8190-        self.log("my new root_hash is %s" % base32.b2a(root_hash))
8191-        self._new_version_info = (self._new_seqnum, root_hash, self.salt)
8192-
8193-        prefix = pack_prefix(self._new_seqnum, root_hash, self.salt,
8194-                             self.required_shares, self.total_shares,
8195-                             self.segment_size, len(self.newdata))
8196-
8197-        # now pack the beginning of the share. All shares are the same up
8198-        # to the signature, then they have divergent share hash chains,
8199-        # then completely different block hash trees + salt + share data,
8200-        # then they all share the same encprivkey at the end. The sizes
8201-        # of everything are the same for all shares.
8202-
8203-        sign_started = time.time()
8204-        signature = privkey.sign(prefix)
8205-        self._status.timings["sign"] = time.time() - sign_started
8206-
8207-        verification_key = pubkey.serialize()
8208-
8209-        final_shares = {}
8210-        for shnum in range(self.total_shares):
8211-            final_share = pack_share(prefix,
8212-                                     verification_key,
8213-                                     signature,
8214-                                     share_hash_chain[shnum],
8215-                                     block_hash_trees[shnum],
8216-                                     all_shares[shnum],
8217-                                     encprivkey)
8218-            final_shares[shnum] = final_share
8219-        elapsed = time.time() - started
8220-        self._status.timings["pack"] = elapsed
8221-        self.shares = final_shares
8222-        self.root_hash = root_hash
8223-
8224-        # we also need to build up the version identifier for what we're
8225-        # pushing. Extract the offsets from one of our shares.
8226-        assert final_shares
8227-        offsets = unpack_header(final_shares.values()[0])[-1]
8228-        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
8229-        verinfo = (self._new_seqnum, root_hash, self.salt,
8230-                   self.segment_size, len(self.newdata),
8231-                   self.required_shares, self.total_shares,
8232-                   prefix, offsets_tuple)
8233-        self.versioninfo = verinfo
8234-
8235-
8236-
8237-    def _send_shares(self, needed):
8238-        self.log("_send_shares")
8239-
8240-        # we're finally ready to send out our shares. If we encounter any
8241-        # surprises here, it's because somebody else is writing at the same
8242-        # time. (Note: in the future, when we remove the _query_peers() step
8243-        # and instead speculate about [or remember] which shares are where,
8244-        # surprises here are *not* indications of UncoordinatedWriteError,
8245-        # and we'll need to respond to them more gracefully.)
8246-
8247-        # needed is a set of (peerid, shnum) tuples. The first thing we do is
8248-        # organize it by peerid.
8249-
8250-        peermap = DictOfSets()
8251-        for (peerid, shnum) in needed:
8252-            peermap.add(peerid, shnum)
8253-
8254-        # the next thing is to build up a bunch of test vectors. The
8255-        # semantics of Publish are that we perform the operation if the world
8256-        # hasn't changed since the ServerMap was constructed (more or less).
8257-        # For every share we're trying to place, we create a test vector that
8258-        # tests to see if the server*share still corresponds to the
8259-        # map.
8260-
8261-        all_tw_vectors = {} # maps peerid to tw_vectors
8262-        sm = self._servermap.servermap
8263-
8264-        for key in needed:
8265-            (peerid, shnum) = key
8266-
8267-            if key in sm:
8268-                # an old version of that share already exists on the
8269-                # server, according to our servermap. We will create a
8270-                # request that attempts to replace it.
8271-                old_versionid, old_timestamp = sm[key]
8272-                (old_seqnum, old_root_hash, old_salt, old_segsize,
8273-                 old_datalength, old_k, old_N, old_prefix,
8274-                 old_offsets_tuple) = old_versionid
8275-                old_checkstring = pack_checkstring(old_seqnum,
8276-                                                   old_root_hash,
8277-                                                   old_salt)
8278-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8279-
8280-            elif key in self.bad_share_checkstrings:
8281-                old_checkstring = self.bad_share_checkstrings[key]
8282-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8283-
8284-            else:
8285-                # add a testv that requires the share not exist
8286-
8287-                # Unfortunately, foolscap-0.2.5 has a bug in the way inbound
8288-                # constraints are handled. If the same object is referenced
8289-                # multiple times inside the arguments, foolscap emits a
8290-                # 'reference' token instead of a distinct copy of the
8291-                # argument. The bug is that these 'reference' tokens are not
8292-                # accepted by the inbound constraint code. To work around
8293-                # this, we need to prevent python from interning the
8294-                # (constant) tuple, by creating a new copy of this vector
8295-                # each time.
8296-
8297-                # This bug is fixed in foolscap-0.2.6, and even though this
8298-                # version of Tahoe requires foolscap-0.3.1 or newer, we are
8299-                # supposed to be able to interoperate with older versions of
8300-                # Tahoe which are allowed to use older versions of foolscap,
8301-                # including foolscap-0.2.5 . In addition, I've seen other
8302-                # foolscap problems triggered by 'reference' tokens (see #541
8303-                # for details). So we must keep this workaround in place.
8304-
8305-                #testv = (0, 1, 'eq', "")
8306-                testv = tuple([0, 1, 'eq', ""])
8307-
8308-            testvs = [testv]
8309-            # the write vector is simply the share
8310-            writev = [(0, self.shares[shnum])]
8311-
8312-            if peerid not in all_tw_vectors:
8313-                all_tw_vectors[peerid] = {}
8314-                # maps shnum to (testvs, writevs, new_length)
8315-            assert shnum not in all_tw_vectors[peerid]
8316-
8317-            all_tw_vectors[peerid][shnum] = (testvs, writev, None)
8318-
8319-        # we read the checkstring back from each share, however we only use
8320-        # it to detect whether there was a new share that we didn't know
8321-        # about. The success or failure of the write will tell us whether
8322-        # there was a collision or not. If there is a collision, the first
8323-        # thing we'll do is update the servermap, which will find out what
8324-        # happened. We could conceivably reduce a roundtrip by using the
8325-        # readv checkstring to populate the servermap, but really we'd have
8326-        # to read enough data to validate the signatures too, so it wouldn't
8327-        # be an overall win.
8328-        read_vector = [(0, struct.calcsize(SIGNED_PREFIX))]
8329-
8330-        # ok, send the messages!
8331-        self.log("sending %d shares" % len(all_tw_vectors), level=log.NOISY)
8332-        started = time.time()
8333-        for (peerid, tw_vectors) in all_tw_vectors.items():
8334-
8335-            write_enabler = self._node.get_write_enabler(peerid)
8336-            renew_secret = self._node.get_renewal_secret(peerid)
8337-            cancel_secret = self._node.get_cancel_secret(peerid)
8338-            secrets = (write_enabler, renew_secret, cancel_secret)
8339-            shnums = tw_vectors.keys()
8340-
8341-            for shnum in shnums:
8342-                self.outstanding.add( (peerid, shnum) )
8343-
8344-            d = self._do_testreadwrite(peerid, secrets,
8345-                                       tw_vectors, read_vector)
8346-            d.addCallbacks(self._got_write_answer, self._got_write_error,
8347-                           callbackArgs=(peerid, shnums, started),
8348-                           errbackArgs=(peerid, shnums, started))
8349-            # tolerate immediate errback, like with DeadReferenceError
8350-            d.addBoth(fireEventually)
8351-            d.addCallback(self.loop)
8352-            d.addErrback(self._fatal_error)
8353-
8354-        self._update_status()
8355-        self.log("%d shares sent" % len(all_tw_vectors), level=log.NOISY)
8356+        elapsed = now - started
8357 
8358hunk ./src/allmydata/mutable/publish.py 794
8359-    def _do_testreadwrite(self, peerid, secrets,
8360-                          tw_vectors, read_vector):
8361-        storage_index = self._storage_index
8362-        ss = self.connections[peerid]
8363+        self._status.add_per_server_time(peerid, elapsed)
8364 
8365hunk ./src/allmydata/mutable/publish.py 796
8366-        #print "SS[%s] is %s" % (idlib.shortnodeid_b2a(peerid), ss), ss.tracker.interfaceName
8367-        d = ss.callRemote("slot_testv_and_readv_and_writev",
8368-                          storage_index,
8369-                          secrets,
8370-                          tw_vectors,
8371-                          read_vector)
8372-        return d
8373+        wrote, read_data = answer
8374 
8375hunk ./src/allmydata/mutable/publish.py 798
8376-    def _got_write_answer(self, answer, peerid, shnums, started):
8377-        lp = self.log("_got_write_answer from %s" %
8378-                      idlib.shortnodeid_b2a(peerid))
8379-        for shnum in shnums:
8380-            self.outstanding.discard( (peerid, shnum) )
8381+        surprise_shares = set(read_data.keys()) - set([writer.shnum])
8382 
8383hunk ./src/allmydata/mutable/publish.py 800
8384-        now = time.time()
8385-        elapsed = now - started
8386-        self._status.add_per_server_time(peerid, elapsed)
8387+        # We need to remove from surprise_shares any shares that we are
8388+        # knowingly also writing to that peer from other writers.
8389 
8390hunk ./src/allmydata/mutable/publish.py 803
8391-        wrote, read_data = answer
8392+        # TODO: Precompute this.
8393+        known_shnums = [x.shnum for x in self.writers.values()
8394+                        if x.peerid == peerid]
8395+        surprise_shares -= set(known_shnums)
8396+        self.log("found the following surprise shares: %s" %
8397+                 str(surprise_shares))
8398 
8399hunk ./src/allmydata/mutable/publish.py 810
8400-        surprise_shares = set(read_data.keys()) - set(shnums)
8401+        # Now surprise shares contains all of the shares that we did not
8402+        # expect to be there.
8403 
8404         surprised = False
8405         for shnum in surprise_shares:
8406hunk ./src/allmydata/mutable/publish.py 817
8407             # read_data is a dict mapping shnum to checkstring (SIGNED_PREFIX)
8408             checkstring = read_data[shnum][0]
8409-            their_version_info = unpack_checkstring(checkstring)
8410-            if their_version_info == self._new_version_info:
8411+            # What we want to do here is to see if their (seqnum,
8412+            # roothash, salt) is the same as our (seqnum, roothash,
8413+            # salt), or the equivalent for MDMF. The best way to do this
8414+            # is to store a packed representation of our checkstring
8415+            # somewhere, then not bother unpacking the other
8416+            # checkstring.
8417+            if checkstring == self._checkstring:
8418                 # they have the right share, somehow
8419 
8420                 if (peerid,shnum) in self.goal:
8421hunk ./src/allmydata/mutable/publish.py 902
8422             self.log("our testv failed, so the write did not happen",
8423                      parent=lp, level=log.WEIRD, umid="8sc26g")
8424             self.surprised = True
8425-            self.bad_peers.add(peerid) # don't ask them again
8426+            # TODO: This needs to
8427+            self.bad_peers.add(writer) # don't ask them again
8428             # use the checkstring to add information to the log message
8429             for (shnum,readv) in read_data.items():
8430                 checkstring = readv[0]
8431hunk ./src/allmydata/mutable/publish.py 928
8432             # self.loop() will take care of finding new homes
8433             return
8434 
8435-        for shnum in shnums:
8436-            self.placed.add( (peerid, shnum) )
8437-            # and update the servermap
8438-            self._servermap.add_new_share(peerid, shnum,
8439+        # and update the servermap
8440+        # self.versioninfo is set during the last phase of publishing.
8441+        # If we get there, we know that responses correspond to placed
8442+        # shares, and can safely execute these statements.
8443+        if self.versioninfo:
8444+            self.log("wrote successfully: adding new share to servermap")
8445+            self._servermap.add_new_share(peerid, writer.shnum,
8446                                           self.versioninfo, started)
8447hunk ./src/allmydata/mutable/publish.py 936
8448-
8449-        # self.loop() will take care of checking to see if we're done
8450-        return
8451+            self.placed.add( (peerid, writer.shnum) )
8452 
8453hunk ./src/allmydata/mutable/publish.py 938
8454-    def _got_write_error(self, f, peerid, shnums, started):
8455-        for shnum in shnums:
8456-            self.outstanding.discard( (peerid, shnum) )
8457-        self.bad_peers.add(peerid)
8458-        if self._first_write_error is None:
8459-            self._first_write_error = f
8460-        self.log(format="error while writing shares %(shnums)s to peerid %(peerid)s",
8461-                 shnums=list(shnums), peerid=idlib.shortnodeid_b2a(peerid),
8462-                 failure=f,
8463-                 level=log.UNUSUAL)
8464         # self.loop() will take care of checking to see if we're done
8465         return
8466 
8467hunk ./src/allmydata/mutable/publish.py 949
8468         now = time.time()
8469         self._status.timings["total"] = now - self._started
8470         self._status.set_active(False)
8471-        if isinstance(res, failure.Failure):
8472-            self.log("Publish done, with failure", failure=res,
8473-                     level=log.WEIRD, umid="nRsR9Q")
8474-            self._status.set_status("Failed")
8475-        elif self.surprised:
8476-            self.log("Publish done, UncoordinatedWriteError", level=log.UNUSUAL)
8477-            self._status.set_status("UncoordinatedWriteError")
8478-            # deliver a failure
8479-            res = failure.Failure(UncoordinatedWriteError())
8480-            # TODO: recovery
8481-        else:
8482-            self.log("Publish done, success")
8483-            self._status.set_status("Finished")
8484-            self._status.set_progress(1.0)
8485+        self.log("Publish done, success")
8486+        self._status.set_status("Finished")
8487+        self._status.set_progress(1.0)
8488         eventually(self.done_deferred.callback, res)
8489 
8490hunk ./src/allmydata/mutable/publish.py 954
8491+    def _failure(self):
8492+
8493+        if not self.surprised:
8494+            # We ran out of servers
8495+            self.log("Publish ran out of good servers, "
8496+                     "last failure was: %s" % str(self._last_failure))
8497+            e = NotEnoughServersError("Ran out of non-bad servers, "
8498+                                      "last failure was %s" %
8499+                                      str(self._last_failure))
8500+        else:
8501+            # We ran into shares that we didn't recognize, which means
8502+            # that we need to return an UncoordinatedWriteError.
8503+            self.log("Publish failed with UncoordinatedWriteError")
8504+            e = UncoordinatedWriteError()
8505+        f = failure.Failure(e)
8506+        eventually(self.done_deferred.callback, f)
8507}
8508[test/test_mutable.py: remove tests that are no longer relevant
8509Kevan Carstensen <kevan@isnotajoke.com>**20100702225710
8510 Ignore-this: 90a26b4cc4b2e190a635474ba7097e21
8511] hunk ./src/allmydata/test/test_mutable.py 627
8512         return d
8513 
8514 
8515-class MakeShares(unittest.TestCase):
8516-    def test_encrypt(self):
8517-        nm = make_nodemaker()
8518-        CONTENTS = "some initial contents"
8519-        d = nm.create_mutable_file(CONTENTS)
8520-        def _created(fn):
8521-            p = Publish(fn, nm.storage_broker, None)
8522-            p.salt = "SALT" * 4
8523-            p.readkey = "\x00" * 16
8524-            p.newdata = CONTENTS
8525-            p.required_shares = 3
8526-            p.total_shares = 10
8527-            p.setup_encoding_parameters()
8528-            return p._encrypt_and_encode()
8529-        d.addCallback(_created)
8530-        def _done(shares_and_shareids):
8531-            (shares, share_ids) = shares_and_shareids
8532-            self.failUnlessEqual(len(shares), 10)
8533-            for sh in shares:
8534-                self.failUnless(isinstance(sh, str))
8535-                self.failUnlessEqual(len(sh), 7)
8536-            self.failUnlessEqual(len(share_ids), 10)
8537-        d.addCallback(_done)
8538-        return d
8539-    test_encrypt.todo = "Write an equivalent of this for the new uploader"
8540-
8541-    def test_generate(self):
8542-        nm = make_nodemaker()
8543-        CONTENTS = "some initial contents"
8544-        d = nm.create_mutable_file(CONTENTS)
8545-        def _created(fn):
8546-            self._fn = fn
8547-            p = Publish(fn, nm.storage_broker, None)
8548-            self._p = p
8549-            p.newdata = CONTENTS
8550-            p.required_shares = 3
8551-            p.total_shares = 10
8552-            p.setup_encoding_parameters()
8553-            p._new_seqnum = 3
8554-            p.salt = "SALT" * 4
8555-            # make some fake shares
8556-            shares_and_ids = ( ["%07d" % i for i in range(10)], range(10) )
8557-            p._privkey = fn.get_privkey()
8558-            p._encprivkey = fn.get_encprivkey()
8559-            p._pubkey = fn.get_pubkey()
8560-            return p._generate_shares(shares_and_ids)
8561-        d.addCallback(_created)
8562-        def _generated(res):
8563-            p = self._p
8564-            final_shares = p.shares
8565-            root_hash = p.root_hash
8566-            self.failUnlessEqual(len(root_hash), 32)
8567-            self.failUnless(isinstance(final_shares, dict))
8568-            self.failUnlessEqual(len(final_shares), 10)
8569-            self.failUnlessEqual(sorted(final_shares.keys()), range(10))
8570-            for i,sh in final_shares.items():
8571-                self.failUnless(isinstance(sh, str))
8572-                # feed the share through the unpacker as a sanity-check
8573-                pieces = unpack_share(sh)
8574-                (u_seqnum, u_root_hash, IV, k, N, segsize, datalen,
8575-                 pubkey, signature, share_hash_chain, block_hash_tree,
8576-                 share_data, enc_privkey) = pieces
8577-                self.failUnlessEqual(u_seqnum, 3)
8578-                self.failUnlessEqual(u_root_hash, root_hash)
8579-                self.failUnlessEqual(k, 3)
8580-                self.failUnlessEqual(N, 10)
8581-                self.failUnlessEqual(segsize, 21)
8582-                self.failUnlessEqual(datalen, len(CONTENTS))
8583-                self.failUnlessEqual(pubkey, p._pubkey.serialize())
8584-                sig_material = struct.pack(">BQ32s16s BBQQ",
8585-                                           0, p._new_seqnum, root_hash, IV,
8586-                                           k, N, segsize, datalen)
8587-                self.failUnless(p._pubkey.verify(sig_material, signature))
8588-                #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
8589-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
8590-                for shnum,share_hash in share_hash_chain.items():
8591-                    self.failUnless(isinstance(shnum, int))
8592-                    self.failUnless(isinstance(share_hash, str))
8593-                    self.failUnlessEqual(len(share_hash), 32)
8594-                self.failUnless(isinstance(block_hash_tree, list))
8595-                self.failUnlessEqual(len(block_hash_tree), 1) # very small tree
8596-                self.failUnlessEqual(IV, "SALT"*4)
8597-                self.failUnlessEqual(len(share_data), len("%07d" % 1))
8598-                self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
8599-        d.addCallback(_generated)
8600-        return d
8601-    test_generate.todo = "Write an equivalent of this for the new uploader"
8602-
8603-    # TODO: when we publish to 20 peers, we should get one share per peer on 10
8604-    # when we publish to 3 peers, we should get either 3 or 4 shares per peer
8605-    # when we publish to zero peers, we should get a NotEnoughSharesError
8606-
8607 class PublishMixin:
8608     def publish_one(self):
8609         # publish a file and create shares, which can then be manipulated
8610[interfaces.py: create IMutableUploadable
8611Kevan Carstensen <kevan@isnotajoke.com>**20100706215217
8612 Ignore-this: bee202ec2bfbd8e41f2d4019cce176c7
8613] hunk ./src/allmydata/interfaces.py 1693
8614         """The upload is finished, and whatever filehandle was in use may be
8615         closed."""
8616 
8617+
8618+class IMutableUploadable(Interface):
8619+    """
8620+    I represent content that is due to be uploaded to a mutable filecap.
8621+    """
8622+    # This is somewhat simpler than the IUploadable interface above
8623+    # because mutable files do not need to be concerned with possibly
8624+    # generating a CHK, nor with per-file keys. It is a subset of the
8625+    # methods in IUploadable, though, so we could just as well implement
8626+    # the mutable uploadables as IUploadables that don't happen to use
8627+    # those methods (with the understanding that the unused methods will
8628+    # never be called on such objects)
8629+    def get_size():
8630+        """
8631+        Returns a Deferred that fires with the size of the content held
8632+        by the uploadable.
8633+        """
8634+
8635+    def read(length):
8636+        """
8637+        Returns a list of strings which, when concatenated, are the next
8638+        length bytes of the file, or fewer if there are fewer bytes
8639+        between the current location and the end of the file.
8640+        """
8641+
8642+    def close():
8643+        """
8644+        The process that used the Uploadable is finished using it, so
8645+        the uploadable may be closed.
8646+        """
8647+
8648 class IUploadResults(Interface):
8649     """I am returned by upload() methods. I contain a number of public
8650     attributes which can be read to determine the results of the upload. Some
8651[mutable/publish.py: add MutableDataHandle and MutableFileHandle
8652Kevan Carstensen <kevan@isnotajoke.com>**20100706215257
8653 Ignore-this: 295ea3bc2a962fd14fb7877fc76c011c
8654] {
8655hunk ./src/allmydata/mutable/publish.py 8
8656 from zope.interface import implements
8657 from twisted.internet import defer
8658 from twisted.python import failure
8659-from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
8660+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \
8661+                                 IMutableUploadable
8662 from allmydata.util import base32, hashutil, mathutil, idlib, log
8663 from allmydata import hashtree, codec
8664 from allmydata.storage.server import si_b2a
8665hunk ./src/allmydata/mutable/publish.py 971
8666             e = UncoordinatedWriteError()
8667         f = failure.Failure(e)
8668         eventually(self.done_deferred.callback, f)
8669+
8670+
8671+class MutableFileHandle:
8672+    """
8673+    I am a mutable uploadable built around a filehandle-like object,
8674+    usually either a StringIO instance or a handle to an actual file.
8675+    """
8676+    implements(IMutableUploadable)
8677+
8678+    def __init__(self, filehandle):
8679+        # The filehandle is defined as a generally file-like object that
8680+        # has these two methods. We don't care beyond that.
8681+        assert hasattr(filehandle, "read")
8682+        assert hasattr(filehandle, "close")
8683+
8684+        self._filehandle = filehandle
8685+
8686+
8687+    def get_size(self):
8688+        """
8689+        I return the amount of data in my filehandle.
8690+        """
8691+        if not hasattr(self, "_size"):
8692+            old_position = self._filehandle.tell()
8693+            # Seek to the end of the file by seeking 0 bytes from the
8694+            # file's end
8695+            self._filehandle.seek(0, os.SEEK_END)
8696+            self._size = self._filehandle.tell()
8697+            # Restore the previous position, in case this was called
8698+            # after a read.
8699+            self._filehandle.seek(old_position)
8700+            assert self._filehandle.tell() == old_position
8701+
8702+        assert hasattr(self, "_size")
8703+        return self._size
8704+
8705+
8706+    def read(self, length):
8707+        """
8708+        I return some data (up to length bytes) from my filehandle.
8709+
8710+        In most cases, I return length bytes. If I don't, it is because
8711+        length is longer than the distance between my current position
8712+        in the file that I represent and its end. In that case, I return
8713+        as many bytes as I can before going over the EOF.
8714+        """
8715+        return [self._filehandle.read(length)]
8716+
8717+
8718+    def close(self):
8719+        """
8720+        I close the underlying filehandle. Any further operations on the
8721+        filehandle fail at this point.
8722+        """
8723+        self._filehandle.close()
8724+
8725+
8726+class MutableDataHandle(MutableFileHandle):
8727+    """
8728+    I am a mutable uploadable built around a string, which I then cast
8729+    into a StringIO and treat as a filehandle.
8730+    """
8731+
8732+    def __init__(self, s):
8733+        # Take a string and return a file-like uploadable.
8734+        assert isinstance(s, str)
8735+
8736+        MutableFileHandle.__init__(self, StringIO(s))
8737}
8738[mutable/publish.py: reorganize in preparation of file-like uploadables
8739Kevan Carstensen <kevan@isnotajoke.com>**20100706215541
8740 Ignore-this: 5346c9f919ee5b73807c8f287c64e8ce
8741] {
8742hunk ./src/allmydata/mutable/publish.py 4
8743 
8744 
8745 import os, struct, time
8746+from StringIO import StringIO
8747 from itertools import count
8748 from zope.interface import implements
8749 from twisted.internet import defer
8750hunk ./src/allmydata/mutable/publish.py 118
8751         self._status.set_helper(False)
8752         self._status.set_progress(0.0)
8753         self._status.set_active(True)
8754-        # We use this to control how the file is written.
8755-        version = self._node.get_version()
8756-        assert version in (SDMF_VERSION, MDMF_VERSION)
8757-        self._version = version
8758+        self._version = self._node.get_version()
8759+        assert self._version in (SDMF_VERSION, MDMF_VERSION)
8760+
8761 
8762     def get_status(self):
8763         return self._status
8764hunk ./src/allmydata/mutable/publish.py 141
8765 
8766         # 0. Setup encoding parameters, encoder, and other such things.
8767         # 1. Encrypt, encode, and publish segments.
8768+        self.data = StringIO(newdata)
8769+        self.datalength = len(newdata)
8770 
8771hunk ./src/allmydata/mutable/publish.py 144
8772-        self.log("starting publish, datalen is %s" % len(newdata))
8773-        self._status.set_size(len(newdata))
8774+        self.log("starting publish, datalen is %s" % self.datalength)
8775+        self._status.set_size(self.datalength)
8776         self._status.set_status("Started")
8777         self._started = time.time()
8778 
8779hunk ./src/allmydata/mutable/publish.py 193
8780         self.full_peerlist = full_peerlist # for use later, immutable
8781         self.bad_peers = set() # peerids who have errbacked/refused requests
8782 
8783-        self.newdata = newdata
8784-
8785         # This will set self.segment_size, self.num_segments, and
8786         # self.fec.
8787         self.setup_encoding_parameters()
8788hunk ./src/allmydata/mutable/publish.py 272
8789                                                 self.required_shares,
8790                                                 self.total_shares,
8791                                                 self.segment_size,
8792-                                                len(self.newdata))
8793+                                                self.datalength)
8794             self.writers[shnum].peerid = peerid
8795             if (peerid, shnum) in self._servermap.servermap:
8796                 old_versionid, old_timestamp = self._servermap.servermap[key]
8797hunk ./src/allmydata/mutable/publish.py 318
8798         if self._version == MDMF_VERSION:
8799             segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
8800         else:
8801-            segment_size = len(self.newdata) # SDMF is only one segment
8802+            segment_size = self.datalength # SDMF is only one segment
8803         # this must be a multiple of self.required_shares
8804         segment_size = mathutil.next_multiple(segment_size,
8805                                               self.required_shares)
8806hunk ./src/allmydata/mutable/publish.py 324
8807         self.segment_size = segment_size
8808         if segment_size:
8809-            self.num_segments = mathutil.div_ceil(len(self.newdata),
8810+            self.num_segments = mathutil.div_ceil(self.datalength,
8811                                                   segment_size)
8812         else:
8813             self.num_segments = 0
8814hunk ./src/allmydata/mutable/publish.py 337
8815             assert self.num_segments in (0, 1) # SDMF
8816         # calculate the tail segment size.
8817 
8818-        if segment_size and self.newdata:
8819-            self.tail_segment_size = len(self.newdata) % segment_size
8820+        if segment_size and self.datalength:
8821+            self.tail_segment_size = self.datalength % segment_size
8822         else:
8823             self.tail_segment_size = 0
8824 
8825hunk ./src/allmydata/mutable/publish.py 438
8826             segsize = self.segment_size
8827 
8828 
8829-        offset = self.segment_size * segnum
8830-        length = segsize + offset
8831         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
8832hunk ./src/allmydata/mutable/publish.py 439
8833-        data = self.newdata[offset:length]
8834+        data = self.data.read(segsize)
8835+
8836         assert len(data) == segsize
8837 
8838         salt = os.urandom(16)
8839hunk ./src/allmydata/mutable/publish.py 502
8840             d.addCallback(self._got_write_answer, writer, started)
8841             d.addErrback(self._connection_problem, writer)
8842             dl.append(d)
8843-            # TODO: Naturally, we need to check on the results of these.
8844         return defer.DeferredList(dl)
8845 
8846 
8847}
8848[test/test_mutable.py: write tests for MutableFileHandle and MutableDataHandle
8849Kevan Carstensen <kevan@isnotajoke.com>**20100706215649
8850 Ignore-this: df719a0c52b4bbe9be4fae206c7ab3e7
8851] {
8852hunk ./src/allmydata/test/test_mutable.py 2
8853 
8854-import struct
8855+import struct, os
8856 from cStringIO import StringIO
8857 from twisted.trial import unittest
8858 from twisted.internet import defer, reactor
8859hunk ./src/allmydata/test/test_mutable.py 26
8860      NeedMoreDataError, UnrecoverableFileError, UncoordinatedWriteError, \
8861      NotEnoughServersError, CorruptShareError
8862 from allmydata.mutable.retrieve import Retrieve
8863-from allmydata.mutable.publish import Publish
8864+from allmydata.mutable.publish import Publish, MutableFileHandle, \
8865+                                      MutableDataHandle
8866 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
8867 from allmydata.mutable.layout import unpack_header, unpack_share, \
8868                                      MDMFSlotReadProxy
8869hunk ./src/allmydata/test/test_mutable.py 2465
8870         d.addCallback(lambda data:
8871             self.failUnlessEqual(data, CONTENTS))
8872         return d
8873+
8874+
8875+class FileHandle(unittest.TestCase):
8876+    def setUp(self):
8877+        self.test_data = "Test Data" * 50000
8878+        self.sio = StringIO(self.test_data)
8879+        self.uploadable = MutableFileHandle(self.sio)
8880+
8881+
8882+    def test_filehandle_read(self):
8883+        self.basedir = "mutable/FileHandle/test_filehandle_read"
8884+        chunk_size = 10
8885+        for i in xrange(0, len(self.test_data), chunk_size):
8886+            data = self.uploadable.read(chunk_size)
8887+            data = "".join(data)
8888+            start = i
8889+            end = i + chunk_size
8890+            self.failUnlessEqual(data, self.test_data[start:end])
8891+
8892+
8893+    def test_filehandle_get_size(self):
8894+        self.basedir = "mutable/FileHandle/test_filehandle_get_size"
8895+        actual_size = len(self.test_data)
8896+        size = self.uploadable.get_size()
8897+        self.failUnlessEqual(size, actual_size)
8898+
8899+
8900+    def test_filehandle_get_size_out_of_order(self):
8901+        # We should be able to call get_size whenever we want without
8902+        # disturbing the location of the seek pointer.
8903+        chunk_size = 100
8904+        data = self.uploadable.read(chunk_size)
8905+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8906+
8907+        # Now get the size.
8908+        size = self.uploadable.get_size()
8909+        self.failUnlessEqual(size, len(self.test_data))
8910+
8911+        # Now get more data. We should be right where we left off.
8912+        more_data = self.uploadable.read(chunk_size)
8913+        start = chunk_size
8914+        end = chunk_size * 2
8915+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
8916+
8917+
8918+    def test_filehandle_file(self):
8919+        # Make sure that the MutableFileHandle works on a file as well
8920+        # as a StringIO object, since in some cases it will be asked to
8921+        # deal with files.
8922+        self.basedir = self.mktemp()
8923+        # necessary? What am I doing wrong here?
8924+        os.mkdir(self.basedir)
8925+        f_path = os.path.join(self.basedir, "test_file")
8926+        f = open(f_path, "w")
8927+        f.write(self.test_data)
8928+        f.close()
8929+        f = open(f_path, "r")
8930+
8931+        uploadable = MutableFileHandle(f)
8932+
8933+        data = uploadable.read(len(self.test_data))
8934+        self.failUnlessEqual("".join(data), self.test_data)
8935+        size = uploadable.get_size()
8936+        self.failUnlessEqual(size, len(self.test_data))
8937+
8938+
8939+    def test_close(self):
8940+        # Make sure that the MutableFileHandle closes its handle when
8941+        # told to do so.
8942+        self.uploadable.close()
8943+        self.failUnless(self.sio.closed)
8944+
8945+
8946+class DataHandle(unittest.TestCase):
8947+    def setUp(self):
8948+        self.test_data = "Test Data" * 50000
8949+        self.uploadable = MutableDataHandle(self.test_data)
8950+
8951+
8952+    def test_datahandle_read(self):
8953+        chunk_size = 10
8954+        for i in xrange(0, len(self.test_data), chunk_size):
8955+            data = self.uploadable.read(chunk_size)
8956+            data = "".join(data)
8957+            start = i
8958+            end = i + chunk_size
8959+            self.failUnlessEqual(data, self.test_data[start:end])
8960+
8961+
8962+    def test_datahandle_get_size(self):
8963+        actual_size = len(self.test_data)
8964+        size = self.uploadable.get_size()
8965+        self.failUnlessEqual(size, actual_size)
8966+
8967+
8968+    def test_datahandle_get_size_out_of_order(self):
8969+        # We should be able to call get_size whenever we want without
8970+        # disturbing the location of the seek pointer.
8971+        chunk_size = 100
8972+        data = self.uploadable.read(chunk_size)
8973+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8974+
8975+        # Now get the size.
8976+        size = self.uploadable.get_size()
8977+        self.failUnlessEqual(size, len(self.test_data))
8978+
8979+        # Now get more data. We should be right where we left off.
8980+        more_data = self.uploadable.read(chunk_size)
8981+        start = chunk_size
8982+        end = chunk_size * 2
8983+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
8984}
8985[Alter tests to work with the new APIs
8986Kevan Carstensen <kevan@isnotajoke.com>**20100708000031
8987 Ignore-this: 1f377904ac61ce40e9a04716fbd2ad95
8988] {
8989hunk ./src/allmydata/test/common.py 12
8990 from allmydata import uri, dirnode, client
8991 from allmydata.introducer.server import IntroducerNode
8992 from allmydata.interfaces import IMutableFileNode, IImmutableFileNode, \
8993-     FileTooLargeError, NotEnoughSharesError, ICheckable
8994+     FileTooLargeError, NotEnoughSharesError, ICheckable, \
8995+     IMutableUploadable
8996 from allmydata.check_results import CheckResults, CheckAndRepairResults, \
8997      DeepCheckResults, DeepCheckAndRepairResults
8998 from allmydata.mutable.common import CorruptShareError
8999hunk ./src/allmydata/test/common.py 18
9000 from allmydata.mutable.layout import unpack_header
9001+from allmydata.mutable.publish import MutableDataHandle
9002 from allmydata.storage.server import storage_index_to_dir
9003 from allmydata.storage.mutable import MutableShareFile
9004 from allmydata.util import hashutil, log, fileutil, pollmixin
9005hunk ./src/allmydata/test/common.py 182
9006         self.init_from_cap(make_mutable_file_cap())
9007     def create(self, contents, key_generator=None, keysize=None):
9008         initial_contents = self._get_initial_contents(contents)
9009-        if len(initial_contents) > self.MUTABLE_SIZELIMIT:
9010+        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
9011             raise FileTooLargeError("SDMF is limited to one segment, and "
9012hunk ./src/allmydata/test/common.py 184
9013-                                    "%d > %d" % (len(initial_contents),
9014+                                    "%d > %d" % (initial_contents.get_size(),
9015                                                  self.MUTABLE_SIZELIMIT))
9016hunk ./src/allmydata/test/common.py 186
9017-        self.all_contents[self.storage_index] = initial_contents
9018+        data = initial_contents.read(initial_contents.get_size())
9019+        data = "".join(data)
9020+        self.all_contents[self.storage_index] = data
9021         return defer.succeed(self)
9022     def _get_initial_contents(self, contents):
9023hunk ./src/allmydata/test/common.py 191
9024-        if isinstance(contents, str):
9025-            return contents
9026         if contents is None:
9027hunk ./src/allmydata/test/common.py 192
9028-            return ""
9029+            return MutableDataHandle("")
9030+
9031+        if IMutableUploadable.providedBy(contents):
9032+            return contents
9033+
9034         assert callable(contents), "%s should be callable, not %s" % \
9035                (contents, type(contents))
9036         return contents(self)
9037hunk ./src/allmydata/test/common.py 309
9038         return defer.succeed(self.all_contents[self.storage_index])
9039 
9040     def overwrite(self, new_contents):
9041-        if len(new_contents) > self.MUTABLE_SIZELIMIT:
9042+        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
9043             raise FileTooLargeError("SDMF is limited to one segment, and "
9044hunk ./src/allmydata/test/common.py 311
9045-                                    "%d > %d" % (len(new_contents),
9046+                                    "%d > %d" % (new_contents.get_size(),
9047                                                  self.MUTABLE_SIZELIMIT))
9048         assert not self.is_readonly()
9049hunk ./src/allmydata/test/common.py 314
9050-        self.all_contents[self.storage_index] = new_contents
9051+        new_data = new_contents.read(new_contents.get_size())
9052+        new_data = "".join(new_data)
9053+        self.all_contents[self.storage_index] = new_data
9054         return defer.succeed(None)
9055     def modify(self, modifier):
9056         # this does not implement FileTooLargeError, but the real one does
9057hunk ./src/allmydata/test/common.py 324
9058     def _modify(self, modifier):
9059         assert not self.is_readonly()
9060         old_contents = self.all_contents[self.storage_index]
9061-        self.all_contents[self.storage_index] = modifier(old_contents, None, True)
9062+        new_data = modifier(old_contents, None, True)
9063+        if new_data is not None:
9064+            new_data = new_data.read(new_data.get_size())
9065+            new_data = "".join(new_data)
9066+        self.all_contents[self.storage_index] = new_data
9067         return None
9068 
9069 def make_mutable_file_cap():
9070hunk ./src/allmydata/test/test_checker.py 11
9071 from allmydata.test.no_network import GridTestMixin
9072 from allmydata.immutable.upload import Data
9073 from allmydata.test.common_web import WebRenderingMixin
9074+from allmydata.mutable.publish import MutableDataHandle
9075 
9076 class FakeClient:
9077     def get_storage_broker(self):
9078hunk ./src/allmydata/test/test_checker.py 291
9079         def _stash_immutable(ur):
9080             self.imm = c0.create_node_from_uri(ur.uri)
9081         d.addCallback(_stash_immutable)
9082-        d.addCallback(lambda ign: c0.create_mutable_file("contents"))
9083+        d.addCallback(lambda ign:
9084+            c0.create_mutable_file(MutableDataHandle("contents")))
9085         def _stash_mutable(node):
9086             self.mut = node
9087         d.addCallback(_stash_mutable)
9088hunk ./src/allmydata/test/test_cli.py 12
9089 from allmydata.util import fileutil, hashutil, base32
9090 from allmydata import uri
9091 from allmydata.immutable import upload
9092+from allmydata.mutable.publish import MutableDataHandle
9093 from allmydata.dirnode import normalize
9094 
9095 # Test that the scripts can be imported -- although the actual tests of their
9096hunk ./src/allmydata/test/test_cli.py 1975
9097         self.set_up_grid()
9098         c0 = self.g.clients[0]
9099         DATA = "data" * 100
9100-        d = c0.create_mutable_file(DATA)
9101+        DATA_uploadable = MutableDataHandle(DATA)
9102+        d = c0.create_mutable_file(DATA_uploadable)
9103         def _stash_uri(n):
9104             self.uri = n.get_uri()
9105         d.addCallback(_stash_uri)
9106hunk ./src/allmydata/test/test_cli.py 2077
9107                                            upload.Data("literal",
9108                                                         convergence="")))
9109         d.addCallback(_stash_uri, "small")
9110-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"1"))
9111+        d.addCallback(lambda ign:
9112+            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
9113         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9114         d.addCallback(_stash_uri, "mutable")
9115 
9116hunk ./src/allmydata/test/test_cli.py 2096
9117         # root/small
9118         # root/mutable
9119 
9120+        # We haven't broken anything yet, so this should all be healthy.
9121         d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
9122                                               self.rooturi))
9123         def _check2((rc, out, err)):
9124hunk ./src/allmydata/test/test_cli.py 2111
9125                             in lines, out)
9126         d.addCallback(_check2)
9127 
9128+        # Similarly, all of these results should be as we expect them to
9129+        # be for a healthy file layout.
9130         d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
9131         def _check_stats((rc, out, err)):
9132             self.failUnlessReallyEqual(err, "")
9133hunk ./src/allmydata/test/test_cli.py 2128
9134             self.failUnlessIn(" 317-1000 : 1    (1000 B, 1000 B)", lines)
9135         d.addCallback(_check_stats)
9136 
9137+        # Now we break things.
9138         def _clobber_shares(ignored):
9139             shares = self.find_uri_shares(self.uris[u"gööd"])
9140             self.failUnlessReallyEqual(len(shares), 10)
9141hunk ./src/allmydata/test/test_cli.py 2147
9142         d.addCallback(_clobber_shares)
9143 
9144         # root
9145-        # root/gööd  [9 shares]
9146+        # root/gööd  [1 missing share]
9147         # root/small
9148         # root/mutable [1 corrupt share]
9149 
9150hunk ./src/allmydata/test/test_cli.py 2153
9151         d.addCallback(lambda ign:
9152                       self.do_cli("deep-check", "--verbose", self.rooturi))
9153+        # This should reveal the missing share, but not the corrupt
9154+        # share, since we didn't tell the deep check operation to also
9155+        # verify.
9156         def _check3((rc, out, err)):
9157             self.failUnlessReallyEqual(err, "")
9158             self.failUnlessReallyEqual(rc, 0)
9159hunk ./src/allmydata/test/test_cli.py 2204
9160                                   "--verbose", "--verify", "--repair",
9161                                   self.rooturi))
9162         def _check6((rc, out, err)):
9163+            # We've just repaired the directory. There is no reason for
9164+            # that repair to be unsuccessful.
9165             self.failUnlessReallyEqual(err, "")
9166             self.failUnlessReallyEqual(rc, 0)
9167             lines = out.splitlines()
9168hunk ./src/allmydata/test/test_deepcheck.py 9
9169 from twisted.internet import threads # CLI tests use deferToThread
9170 from allmydata.immutable import upload
9171 from allmydata.mutable.common import UnrecoverableFileError
9172+from allmydata.mutable.publish import MutableDataHandle
9173 from allmydata.util import idlib
9174 from allmydata.util import base32
9175 from allmydata.scripts import runner
9176hunk ./src/allmydata/test/test_deepcheck.py 38
9177         self.basedir = "deepcheck/MutableChecker/good"
9178         self.set_up_grid()
9179         CONTENTS = "a little bit of data"
9180-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9181+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9182+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9183         def _created(node):
9184             self.node = node
9185             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9186hunk ./src/allmydata/test/test_deepcheck.py 61
9187         self.basedir = "deepcheck/MutableChecker/corrupt"
9188         self.set_up_grid()
9189         CONTENTS = "a little bit of data"
9190-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9191+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9192+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9193         def _stash_and_corrupt(node):
9194             self.node = node
9195             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9196hunk ./src/allmydata/test/test_deepcheck.py 99
9197         self.basedir = "deepcheck/MutableChecker/delete_share"
9198         self.set_up_grid()
9199         CONTENTS = "a little bit of data"
9200-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9201+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9202+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9203         def _stash_and_delete(node):
9204             self.node = node
9205             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9206hunk ./src/allmydata/test/test_deepcheck.py 223
9207             self.root = n
9208             self.root_uri = n.get_uri()
9209         d.addCallback(_created_root)
9210-        d.addCallback(lambda ign: c0.create_mutable_file("mutable file contents"))
9211+        d.addCallback(lambda ign:
9212+            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
9213         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
9214         def _created_mutable(n):
9215             self.mutable = n
9216hunk ./src/allmydata/test/test_deepcheck.py 965
9217     def create_mangled(self, ignored, name):
9218         nodetype, mangletype = name.split("-", 1)
9219         if nodetype == "mutable":
9220-            d = self.g.clients[0].create_mutable_file("mutable file contents")
9221+            mutable_uploadable = MutableDataHandle("mutable file contents")
9222+            d = self.g.clients[0].create_mutable_file(mutable_uploadable)
9223             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
9224         elif nodetype == "large":
9225             large = upload.Data("Lots of data\n" * 1000 + name + "\n", None)
9226hunk ./src/allmydata/test/test_dirnode.py 1305
9227     implements(IMutableFileNode)
9228     counter = 0
9229     def __init__(self, initial_contents=""):
9230-        self.data = self._get_initial_contents(initial_contents)
9231+        data = self._get_initial_contents(initial_contents)
9232+        self.data = data.read(data.get_size())
9233+        self.data = "".join(self.data)
9234+
9235         counter = FakeMutableFile.counter
9236         FakeMutableFile.counter += 1
9237         writekey = hashutil.ssk_writekey_hash(str(counter))
9238hunk ./src/allmydata/test/test_dirnode.py 1355
9239         pass
9240 
9241     def modify(self, modifier):
9242-        self.data = modifier(self.data, None, True)
9243+        data = modifier(self.data, None, True)
9244+        self.data = data.read(data.get_size())
9245+        self.data = "".join(self.data)
9246         return defer.succeed(None)
9247 
9248 class FakeNodeMaker(NodeMaker):
9249hunk ./src/allmydata/test/test_hung_server.py 10
9250 from allmydata.util.consumer import download_to_data
9251 from allmydata.immutable import upload
9252 from allmydata.mutable.common import UnrecoverableFileError
9253+from allmydata.mutable.publish import MutableDataHandle
9254 from allmydata.storage.common import storage_index_to_dir
9255 from allmydata.test.no_network import GridTestMixin
9256 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
9257hunk ./src/allmydata/test/test_hung_server.py 96
9258         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
9259 
9260         if mutable:
9261-            d = nm.create_mutable_file(mutable_plaintext)
9262+            uploadable = MutableDataHandle(mutable_plaintext)
9263+            d = nm.create_mutable_file(uploadable)
9264             def _uploaded_mutable(node):
9265                 self.uri = node.get_uri()
9266                 self.shares = self.find_uri_shares(self.uri)
9267hunk ./src/allmydata/test/test_mutable.py 297
9268             d.addCallback(lambda smap: smap.dump(StringIO()))
9269             d.addCallback(lambda sio:
9270                           self.failUnless("3-of-10" in sio.getvalue()))
9271-            d.addCallback(lambda res: n.overwrite("contents 1"))
9272+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9273             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9274             d.addCallback(lambda res: n.download_best_version())
9275             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9276hunk ./src/allmydata/test/test_mutable.py 304
9277             d.addCallback(lambda res: n.get_size_of_best_version())
9278             d.addCallback(lambda size:
9279                           self.failUnlessEqual(size, len("contents 1")))
9280-            d.addCallback(lambda res: n.overwrite("contents 2"))
9281+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9282             d.addCallback(lambda res: n.download_best_version())
9283             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9284             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9285hunk ./src/allmydata/test/test_mutable.py 308
9286-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9287+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9288             d.addCallback(lambda res: n.download_best_version())
9289             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9290             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9291hunk ./src/allmydata/test/test_mutable.py 320
9292             # mapupdate-to-retrieve data caching (i.e. make the shares larger
9293             # than the default readsize, which is 2000 bytes). A 15kB file
9294             # will have 5kB shares.
9295-            d.addCallback(lambda res: n.overwrite("large size file" * 1000))
9296+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
9297             d.addCallback(lambda res: n.download_best_version())
9298             d.addCallback(lambda res:
9299                           self.failUnlessEqual(res, "large size file" * 1000))
9300hunk ./src/allmydata/test/test_mutable.py 343
9301             # to make them big enough to force the file to be uploaded
9302             # in more than one segment.
9303             big_contents = "contents1" * 100000 # about 900 KiB
9304+            big_contents_uploadable = MutableDataHandle(big_contents)
9305             d.addCallback(lambda ignored:
9306hunk ./src/allmydata/test/test_mutable.py 345
9307-                n.overwrite(big_contents))
9308+                n.overwrite(big_contents_uploadable))
9309             d.addCallback(lambda ignored:
9310                 n.download_best_version())
9311             d.addCallback(lambda data:
9312hunk ./src/allmydata/test/test_mutable.py 355
9313             # segments, so that we make the downloader deal with
9314             # multiple segments.
9315             bigger_contents = "contents2" * 1000000 # about 9MiB
9316+            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
9317             d.addCallback(lambda ignored:
9318hunk ./src/allmydata/test/test_mutable.py 357
9319-                n.overwrite(bigger_contents))
9320+                n.overwrite(bigger_contents_uploadable))
9321             d.addCallback(lambda ignored:
9322                 n.download_best_version())
9323             d.addCallback(lambda data:
9324hunk ./src/allmydata/test/test_mutable.py 368
9325 
9326 
9327     def test_create_with_initial_contents(self):
9328-        d = self.nodemaker.create_mutable_file("contents 1")
9329+        upload1 = MutableDataHandle("contents 1")
9330+        d = self.nodemaker.create_mutable_file(upload1)
9331         def _created(n):
9332             d = n.download_best_version()
9333             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9334hunk ./src/allmydata/test/test_mutable.py 373
9335-            d.addCallback(lambda res: n.overwrite("contents 2"))
9336+            upload2 = MutableDataHandle("contents 2")
9337+            d.addCallback(lambda res: n.overwrite(upload2))
9338             d.addCallback(lambda res: n.download_best_version())
9339             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9340             return d
9341hunk ./src/allmydata/test/test_mutable.py 380
9342         d.addCallback(_created)
9343         return d
9344+    test_create_with_initial_contents.timeout = 15
9345 
9346 
9347     def test_create_mdmf_with_initial_contents(self):
9348hunk ./src/allmydata/test/test_mutable.py 385
9349         initial_contents = "foobarbaz" * 131072 # 900KiB
9350-        d = self.nodemaker.create_mutable_file(initial_contents,
9351+        initial_contents_uploadable = MutableDataHandle(initial_contents)
9352+        d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
9353                                                version=MDMF_VERSION)
9354         def _created(n):
9355             d = n.download_best_version()
9356hunk ./src/allmydata/test/test_mutable.py 392
9357             d.addCallback(lambda data:
9358                 self.failUnlessEqual(data, initial_contents))
9359+            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
9360             d.addCallback(lambda ignored:
9361hunk ./src/allmydata/test/test_mutable.py 394
9362-                n.overwrite(initial_contents + "foobarbaz"))
9363+                n.overwrite(uploadable2))
9364             d.addCallback(lambda ignored:
9365                 n.download_best_version())
9366             d.addCallback(lambda data:
9367hunk ./src/allmydata/test/test_mutable.py 413
9368             key = n.get_writekey()
9369             self.failUnless(isinstance(key, str), key)
9370             self.failUnlessEqual(len(key), 16) # AES key size
9371-            return data
9372+            return MutableDataHandle(data)
9373         d = self.nodemaker.create_mutable_file(_make_contents)
9374         def _created(n):
9375             return n.download_best_version()
9376hunk ./src/allmydata/test/test_mutable.py 429
9377             key = n.get_writekey()
9378             self.failUnless(isinstance(key, str), key)
9379             self.failUnlessEqual(len(key), 16)
9380-            return data
9381+            return MutableDataHandle(data)
9382         d = self.nodemaker.create_mutable_file(_make_contents,
9383                                                version=MDMF_VERSION)
9384         d.addCallback(lambda n:
9385hunk ./src/allmydata/test/test_mutable.py 441
9386 
9387     def test_create_with_too_large_contents(self):
9388         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9389-        d = self.nodemaker.create_mutable_file(BIG)
9390+        BIG_uploadable = MutableDataHandle(BIG)
9391+        d = self.nodemaker.create_mutable_file(BIG_uploadable)
9392         def _created(n):
9393hunk ./src/allmydata/test/test_mutable.py 444
9394-            d = n.overwrite(BIG)
9395+            other_BIG_uploadable = MutableDataHandle(BIG)
9396+            d = n.overwrite(other_BIG_uploadable)
9397             return d
9398         d.addCallback(_created)
9399         return d
9400hunk ./src/allmydata/test/test_mutable.py 459
9401 
9402     def test_modify(self):
9403         def _modifier(old_contents, servermap, first_time):
9404-            return old_contents + "line2"
9405+            new_contents = old_contents + "line2"
9406+            return MutableDataHandle(new_contents)
9407         def _non_modifier(old_contents, servermap, first_time):
9408hunk ./src/allmydata/test/test_mutable.py 462
9409-            return old_contents
9410+            return MutableDataHandle(old_contents)
9411         def _none_modifier(old_contents, servermap, first_time):
9412             return None
9413         def _error_modifier(old_contents, servermap, first_time):
9414hunk ./src/allmydata/test/test_mutable.py 468
9415             raise ValueError("oops")
9416         def _toobig_modifier(old_contents, servermap, first_time):
9417-            return "b" * (self.OLD_MAX_SEGMENT_SIZE+1)
9418+            new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9419+            return MutableDataHandle(new_content)
9420         calls = []
9421         def _ucw_error_modifier(old_contents, servermap, first_time):
9422             # simulate an UncoordinatedWriteError once
9423hunk ./src/allmydata/test/test_mutable.py 476
9424             calls.append(1)
9425             if len(calls) <= 1:
9426                 raise UncoordinatedWriteError("simulated")
9427-            return old_contents + "line3"
9428+            new_contents = old_contents + "line3"
9429+            return MutableDataHandle(new_contents)
9430         def _ucw_error_non_modifier(old_contents, servermap, first_time):
9431             # simulate an UncoordinatedWriteError once, and don't actually
9432             # modify the contents on subsequent invocations
9433hunk ./src/allmydata/test/test_mutable.py 484
9434             calls.append(1)
9435             if len(calls) <= 1:
9436                 raise UncoordinatedWriteError("simulated")
9437-            return old_contents
9438+            return MutableDataHandle(old_contents)
9439 
9440hunk ./src/allmydata/test/test_mutable.py 486
9441-        d = self.nodemaker.create_mutable_file("line1")
9442+        initial_contents = "line1"
9443+        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
9444         def _created(n):
9445             d = n.modify(_modifier)
9446             d.addCallback(lambda res: n.download_best_version())
9447hunk ./src/allmydata/test/test_mutable.py 548
9448 
9449     def test_modify_backoffer(self):
9450         def _modifier(old_contents, servermap, first_time):
9451-            return old_contents + "line2"
9452+            return MutableDataHandle(old_contents + "line2")
9453         calls = []
9454         def _ucw_error_modifier(old_contents, servermap, first_time):
9455             # simulate an UncoordinatedWriteError once
9456hunk ./src/allmydata/test/test_mutable.py 555
9457             calls.append(1)
9458             if len(calls) <= 1:
9459                 raise UncoordinatedWriteError("simulated")
9460-            return old_contents + "line3"
9461+            return MutableDataHandle(old_contents + "line3")
9462         def _always_ucw_error_modifier(old_contents, servermap, first_time):
9463             raise UncoordinatedWriteError("simulated")
9464         def _backoff_stopper(node, f):
9465hunk ./src/allmydata/test/test_mutable.py 570
9466         giveuper._delay = 0.1
9467         giveuper.factor = 1
9468 
9469-        d = self.nodemaker.create_mutable_file("line1")
9470+        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
9471         def _created(n):
9472             d = n.modify(_modifier)
9473             d.addCallback(lambda res: n.download_best_version())
9474hunk ./src/allmydata/test/test_mutable.py 620
9475             d.addCallback(lambda smap: smap.dump(StringIO()))
9476             d.addCallback(lambda sio:
9477                           self.failUnless("3-of-10" in sio.getvalue()))
9478-            d.addCallback(lambda res: n.overwrite("contents 1"))
9479+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9480             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9481             d.addCallback(lambda res: n.download_best_version())
9482             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9483hunk ./src/allmydata/test/test_mutable.py 624
9484-            d.addCallback(lambda res: n.overwrite("contents 2"))
9485+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9486             d.addCallback(lambda res: n.download_best_version())
9487             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9488             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9489hunk ./src/allmydata/test/test_mutable.py 628
9490-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9491+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9492             d.addCallback(lambda res: n.download_best_version())
9493             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9494             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9495hunk ./src/allmydata/test/test_mutable.py 646
9496         # publish a file and create shares, which can then be manipulated
9497         # later.
9498         self.CONTENTS = "New contents go here" * 1000
9499+        self.uploadable = MutableDataHandle(self.CONTENTS)
9500         self._storage = FakeStorage()
9501         self._nodemaker = make_nodemaker(self._storage)
9502         self._storage_broker = self._nodemaker.storage_broker
9503hunk ./src/allmydata/test/test_mutable.py 650
9504-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9505+        d = self._nodemaker.create_mutable_file(self.uploadable)
9506         def _created(node):
9507             self._fn = node
9508             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9509hunk ./src/allmydata/test/test_mutable.py 662
9510         # an MDMF file.
9511         # self.CONTENTS should have more than one segment.
9512         self.CONTENTS = "This is an MDMF file" * 100000
9513+        self.uploadable = MutableDataHandle(self.CONTENTS)
9514         self._storage = FakeStorage()
9515         self._nodemaker = make_nodemaker(self._storage)
9516         self._storage_broker = self._nodemaker.storage_broker
9517hunk ./src/allmydata/test/test_mutable.py 666
9518-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
9519+        d = self._nodemaker.create_mutable_file(self.uploadable, version=MDMF_VERSION)
9520         def _created(node):
9521             self._fn = node
9522             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9523hunk ./src/allmydata/test/test_mutable.py 678
9524         # like publish_one, except that the result is guaranteed to be
9525         # an SDMF file
9526         self.CONTENTS = "This is an SDMF file" * 1000
9527+        self.uploadable = MutableDataHandle(self.CONTENTS)
9528         self._storage = FakeStorage()
9529         self._nodemaker = make_nodemaker(self._storage)
9530         self._storage_broker = self._nodemaker.storage_broker
9531hunk ./src/allmydata/test/test_mutable.py 682
9532-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
9533+        d = self._nodemaker.create_mutable_file(self.uploadable, version=SDMF_VERSION)
9534         def _created(node):
9535             self._fn = node
9536             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9537hunk ./src/allmydata/test/test_mutable.py 696
9538                          "Contents 2",
9539                          "Contents 3a",
9540                          "Contents 3b"]
9541+        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
9542         self._copied_shares = {}
9543         self._storage = FakeStorage()
9544         self._nodemaker = make_nodemaker(self._storage)
9545hunk ./src/allmydata/test/test_mutable.py 700
9546-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
9547+        d = self._nodemaker.create_mutable_file(self.uploadables[0], version=version) # seqnum=1
9548         def _created(node):
9549             self._fn = node
9550             # now create multiple versions of the same file, and accumulate
9551hunk ./src/allmydata/test/test_mutable.py 707
9552             # their shares, so we can mix and match them later.
9553             d = defer.succeed(None)
9554             d.addCallback(self._copy_shares, 0)
9555-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[1])) #s2
9556+            d.addCallback(lambda res: node.overwrite(self.uploadables[1])) #s2
9557             d.addCallback(self._copy_shares, 1)
9558hunk ./src/allmydata/test/test_mutable.py 709
9559-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[2])) #s3
9560+            d.addCallback(lambda res: node.overwrite(self.uploadables[2])) #s3
9561             d.addCallback(self._copy_shares, 2)
9562hunk ./src/allmydata/test/test_mutable.py 711
9563-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[3])) #s4a
9564+            d.addCallback(lambda res: node.overwrite(self.uploadables[3])) #s4a
9565             d.addCallback(self._copy_shares, 3)
9566             # now we replace all the shares with version s3, and upload a new
9567             # version to get s4b.
9568hunk ./src/allmydata/test/test_mutable.py 717
9569             rollback = dict([(i,2) for i in range(10)])
9570             d.addCallback(lambda res: self._set_versions(rollback))
9571-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[4])) #s4b
9572+            d.addCallback(lambda res: node.overwrite(self.uploadables[4])) #s4b
9573             d.addCallback(self._copy_shares, 4)
9574             # we leave the storage in state 4
9575             return d
9576hunk ./src/allmydata/test/test_mutable.py 826
9577         # create a new file, which is large enough to knock the privkey out
9578         # of the early part of the file
9579         LARGE = "These are Larger contents" * 200 # about 5KB
9580-        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE))
9581+        LARGE_uploadable = MutableDataHandle(LARGE)
9582+        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
9583         def _created(large_fn):
9584             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
9585             return self.make_servermap(MODE_WRITE, large_fn2)
9586hunk ./src/allmydata/test/test_mutable.py 1842
9587 class MultipleEncodings(unittest.TestCase):
9588     def setUp(self):
9589         self.CONTENTS = "New contents go here"
9590+        self.uploadable = MutableDataHandle(self.CONTENTS)
9591         self._storage = FakeStorage()
9592         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
9593         self._storage_broker = self._nodemaker.storage_broker
9594hunk ./src/allmydata/test/test_mutable.py 1846
9595-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9596+        d = self._nodemaker.create_mutable_file(self.uploadable)
9597         def _created(node):
9598             self._fn = node
9599         d.addCallback(_created)
9600hunk ./src/allmydata/test/test_mutable.py 1872
9601         s = self._storage
9602         s._peers = {} # clear existing storage
9603         p2 = Publish(fn2, self._storage_broker, None)
9604-        d = p2.publish(data)
9605+        uploadable = MutableDataHandle(data)
9606+        d = p2.publish(uploadable)
9607         def _published(res):
9608             shares = s._peers
9609             s._peers = {}
9610hunk ./src/allmydata/test/test_mutable.py 2049
9611         self._set_versions(target)
9612 
9613         def _modify(oldversion, servermap, first_time):
9614-            return oldversion + " modified"
9615+            return MutableDataHandle(oldversion + " modified")
9616         d = self._fn.modify(_modify)
9617         d.addCallback(lambda res: self._fn.download_best_version())
9618         expected = self.CONTENTS[2] + " modified"
9619hunk ./src/allmydata/test/test_mutable.py 2175
9620         self.basedir = "mutable/Problems/test_publish_surprise"
9621         self.set_up_grid()
9622         nm = self.g.clients[0].nodemaker
9623-        d = nm.create_mutable_file("contents 1")
9624+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9625         def _created(n):
9626             d = defer.succeed(None)
9627             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9628hunk ./src/allmydata/test/test_mutable.py 2185
9629             d.addCallback(_got_smap1)
9630             # then modify the file, leaving the old map untouched
9631             d.addCallback(lambda res: log.msg("starting winning write"))
9632-            d.addCallback(lambda res: n.overwrite("contents 2"))
9633+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9634             # now attempt to modify the file with the old servermap. This
9635             # will look just like an uncoordinated write, in which every
9636             # single share got updated between our mapupdate and our publish
9637hunk ./src/allmydata/test/test_mutable.py 2194
9638                           self.shouldFail(UncoordinatedWriteError,
9639                                           "test_publish_surprise", None,
9640                                           n.upload,
9641-                                          "contents 2a", self.old_map))
9642+                                          MutableDataHandle("contents 2a"), self.old_map))
9643             return d
9644         d.addCallback(_created)
9645         return d
9646hunk ./src/allmydata/test/test_mutable.py 2203
9647         self.basedir = "mutable/Problems/test_retrieve_surprise"
9648         self.set_up_grid()
9649         nm = self.g.clients[0].nodemaker
9650-        d = nm.create_mutable_file("contents 1")
9651+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9652         def _created(n):
9653             d = defer.succeed(None)
9654             d.addCallback(lambda res: n.get_servermap(MODE_READ))
9655hunk ./src/allmydata/test/test_mutable.py 2213
9656             d.addCallback(_got_smap1)
9657             # then modify the file, leaving the old map untouched
9658             d.addCallback(lambda res: log.msg("starting winning write"))
9659-            d.addCallback(lambda res: n.overwrite("contents 2"))
9660+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9661             # now attempt to retrieve the old version with the old servermap.
9662             # This will look like someone has changed the file since we
9663             # updated the servermap.
9664hunk ./src/allmydata/test/test_mutable.py 2241
9665         self.basedir = "mutable/Problems/test_unexpected_shares"
9666         self.set_up_grid()
9667         nm = self.g.clients[0].nodemaker
9668-        d = nm.create_mutable_file("contents 1")
9669+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9670         def _created(n):
9671             d = defer.succeed(None)
9672             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9673hunk ./src/allmydata/test/test_mutable.py 2253
9674                 self.g.remove_server(peer0)
9675                 # then modify the file, leaving the old map untouched
9676                 log.msg("starting winning write")
9677-                return n.overwrite("contents 2")
9678+                return n.overwrite(MutableDataHandle("contents 2"))
9679             d.addCallback(_got_smap1)
9680             # now attempt to modify the file with the old servermap. This
9681             # will look just like an uncoordinated write, in which every
9682hunk ./src/allmydata/test/test_mutable.py 2263
9683                           self.shouldFail(UncoordinatedWriteError,
9684                                           "test_surprise", None,
9685                                           n.upload,
9686-                                          "contents 2a", self.old_map))
9687+                                          MutableDataHandle("contents 2a"), self.old_map))
9688             return d
9689         d.addCallback(_created)
9690         return d
9691hunk ./src/allmydata/test/test_mutable.py 2267
9692+    test_unexpected_shares.timeout = 15
9693 
9694     def test_bad_server(self):
9695         # Break one server, then create the file: the initial publish should
9696hunk ./src/allmydata/test/test_mutable.py 2303
9697         d.addCallback(_break_peer0)
9698         # now "create" the file, using the pre-established key, and let the
9699         # initial publish finally happen
9700-        d.addCallback(lambda res: nm.create_mutable_file("contents 1"))
9701+        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
9702         # that ought to work
9703         def _got_node(n):
9704             d = n.download_best_version()
9705hunk ./src/allmydata/test/test_mutable.py 2312
9706             def _break_peer1(res):
9707                 self.connection1.broken = True
9708             d.addCallback(_break_peer1)
9709-            d.addCallback(lambda res: n.overwrite("contents 2"))
9710+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9711             # that ought to work too
9712             d.addCallback(lambda res: n.download_best_version())
9713             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9714hunk ./src/allmydata/test/test_mutable.py 2344
9715         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
9716         self.g.break_server(peerids[0])
9717 
9718-        d = nm.create_mutable_file("contents 1")
9719+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9720         def _created(n):
9721             d = n.download_best_version()
9722             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9723hunk ./src/allmydata/test/test_mutable.py 2352
9724             def _break_second_server(res):
9725                 self.g.break_server(peerids[1])
9726             d.addCallback(_break_second_server)
9727-            d.addCallback(lambda res: n.overwrite("contents 2"))
9728+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9729             # that ought to work too
9730             d.addCallback(lambda res: n.download_best_version())
9731             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9732hunk ./src/allmydata/test/test_mutable.py 2371
9733         d = self.shouldFail(NotEnoughServersError,
9734                             "test_publish_all_servers_bad",
9735                             "Ran out of non-bad servers",
9736-                            nm.create_mutable_file, "contents")
9737+                            nm.create_mutable_file, MutableDataHandle("contents"))
9738         return d
9739 
9740     def test_publish_no_servers(self):
9741hunk ./src/allmydata/test/test_mutable.py 2383
9742         d = self.shouldFail(NotEnoughServersError,
9743                             "test_publish_no_servers",
9744                             "Ran out of non-bad servers",
9745-                            nm.create_mutable_file, "contents")
9746+                            nm.create_mutable_file, MutableDataHandle("contents"))
9747         return d
9748     test_publish_no_servers.timeout = 30
9749 
9750hunk ./src/allmydata/test/test_mutable.py 2401
9751         # we need some contents that are large enough to push the privkey out
9752         # of the early part of the file
9753         LARGE = "These are Larger contents" * 2000 # about 50KB
9754-        d = nm.create_mutable_file(LARGE)
9755+        LARGE_uploadable = MutableDataHandle(LARGE)
9756+        d = nm.create_mutable_file(LARGE_uploadable)
9757         def _created(n):
9758             self.uri = n.get_uri()
9759             self.n2 = nm.create_from_cap(self.uri)
9760hunk ./src/allmydata/test/test_mutable.py 2438
9761         self.set_up_grid(num_servers=20)
9762         nm = self.g.clients[0].nodemaker
9763         LARGE = "These are Larger contents" * 2000 # about 50KiB
9764+        LARGE_uploadable = MutableDataHandle(LARGE)
9765         nm._node_cache = DevNullDictionary() # disable the nodecache
9766 
9767hunk ./src/allmydata/test/test_mutable.py 2441
9768-        d = nm.create_mutable_file(LARGE)
9769+        d = nm.create_mutable_file(LARGE_uploadable)
9770         def _created(n):
9771             self.uri = n.get_uri()
9772             self.n2 = nm.create_from_cap(self.uri)
9773hunk ./src/allmydata/test/test_mutable.py 2464
9774         self.set_up_grid(num_servers=20)
9775         nm = self.g.clients[0].nodemaker
9776         CONTENTS = "contents" * 2000
9777-        d = nm.create_mutable_file(CONTENTS)
9778+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9779+        d = nm.create_mutable_file(CONTENTS_uploadable)
9780         def _created(node):
9781             self._node = node
9782         d.addCallback(_created)
9783hunk ./src/allmydata/test/test_system.py 22
9784 from allmydata.monitor import Monitor
9785 from allmydata.mutable.common import NotWriteableError
9786 from allmydata.mutable import layout as mutable_layout
9787+from allmydata.mutable.publish import MutableDataHandle
9788 from foolscap.api import DeadReferenceError
9789 from twisted.python.failure import Failure
9790 from twisted.web.client import getPage
9791hunk ./src/allmydata/test/test_system.py 460
9792     def test_mutable(self):
9793         self.basedir = "system/SystemTest/test_mutable"
9794         DATA = "initial contents go here."  # 25 bytes % 3 != 0
9795+        DATA_uploadable = MutableDataHandle(DATA)
9796         NEWDATA = "new contents yay"
9797hunk ./src/allmydata/test/test_system.py 462
9798+        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
9799         NEWERDATA = "this is getting old"
9800hunk ./src/allmydata/test/test_system.py 464
9801+        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
9802 
9803         d = self.set_up_nodes(use_key_generator=True)
9804 
9805hunk ./src/allmydata/test/test_system.py 471
9806         def _create_mutable(res):
9807             c = self.clients[0]
9808             log.msg("starting create_mutable_file")
9809-            d1 = c.create_mutable_file(DATA)
9810+            d1 = c.create_mutable_file(DATA_uploadable)
9811             def _done(res):
9812                 log.msg("DONE: %s" % (res,))
9813                 self._mutable_node_1 = res
9814hunk ./src/allmydata/test/test_system.py 558
9815             self.failUnlessEqual(res, DATA)
9816             # replace the data
9817             log.msg("starting replace1")
9818-            d1 = newnode.overwrite(NEWDATA)
9819+            d1 = newnode.overwrite(NEWDATA_uploadable)
9820             d1.addCallback(lambda res: newnode.download_best_version())
9821             return d1
9822         d.addCallback(_check_download_3)
9823hunk ./src/allmydata/test/test_system.py 572
9824             newnode2 = self.clients[3].create_node_from_uri(uri)
9825             self._newnode3 = self.clients[3].create_node_from_uri(uri)
9826             log.msg("starting replace2")
9827-            d1 = newnode1.overwrite(NEWERDATA)
9828+            d1 = newnode1.overwrite(NEWERDATA_uploadable)
9829             d1.addCallback(lambda res: newnode2.download_best_version())
9830             return d1
9831         d.addCallback(_check_download_4)
9832hunk ./src/allmydata/test/test_system.py 642
9833         def _check_empty_file(res):
9834             # make sure we can create empty files, this usually screws up the
9835             # segsize math
9836-            d1 = self.clients[2].create_mutable_file("")
9837+            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
9838             d1.addCallback(lambda newnode: newnode.download_best_version())
9839             d1.addCallback(lambda res: self.failUnlessEqual("", res))
9840             return d1
9841hunk ./src/allmydata/test/test_system.py 673
9842                                  self.key_generator_svc.key_generator.pool_size + size_delta)
9843 
9844         d.addCallback(check_kg_poolsize, 0)
9845-        d.addCallback(lambda junk: self.clients[3].create_mutable_file('hello, world'))
9846+        d.addCallback(lambda junk:
9847+            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
9848         d.addCallback(check_kg_poolsize, -1)
9849         d.addCallback(lambda junk: self.clients[3].create_dirnode())
9850         d.addCallback(check_kg_poolsize, -2)
9851hunk ./src/allmydata/test/test_web.py 3183
9852         def _stash_mutable_uri(n, which):
9853             self.uris[which] = n.get_uri()
9854             assert isinstance(self.uris[which], str)
9855-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9856+        d.addCallback(lambda ign:
9857+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9858         d.addCallback(_stash_mutable_uri, "corrupt")
9859         d.addCallback(lambda ign:
9860                       c0.upload(upload.Data("literal", convergence="")))
9861hunk ./src/allmydata/test/test_web.py 3330
9862         def _stash_mutable_uri(n, which):
9863             self.uris[which] = n.get_uri()
9864             assert isinstance(self.uris[which], str)
9865-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9866+        d.addCallback(lambda ign:
9867+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9868         d.addCallback(_stash_mutable_uri, "corrupt")
9869 
9870         def _compute_fileurls(ignored):
9871hunk ./src/allmydata/test/test_web.py 3993
9872         def _stash_mutable_uri(n, which):
9873             self.uris[which] = n.get_uri()
9874             assert isinstance(self.uris[which], str)
9875-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
9876+        d.addCallback(lambda ign:
9877+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
9878         d.addCallback(_stash_mutable_uri, "mutable")
9879 
9880         def _compute_fileurls(ignored):
9881hunk ./src/allmydata/test/test_web.py 4093
9882                                                         convergence="")))
9883         d.addCallback(_stash_uri, "small")
9884 
9885-        d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
9886+        d.addCallback(lambda ign:
9887+            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
9888         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9889         d.addCallback(_stash_uri, "mutable")
9890 
9891}
9892[Alter mutable files to use file-like objects for publishing instead of strings.
9893Kevan Carstensen <kevan@isnotajoke.com>**20100708000732
9894 Ignore-this: 8dd07d95386b6d540bc21289f981ebd0
9895] {
9896hunk ./src/allmydata/dirnode.py 11
9897 from allmydata.mutable.common import NotWriteableError
9898 from allmydata.mutable.filenode import MutableFileNode
9899 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
9900+from allmydata.mutable.publish import MutableDataHandle
9901 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
9902      IImmutableFileNode, IMutableFileNode, \
9903      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
9904hunk ./src/allmydata/dirnode.py 104
9905 
9906         del children[self.name]
9907         new_contents = self.node._pack_contents(children)
9908-        return new_contents
9909+        uploadable = MutableDataHandle(new_contents)
9910+        return uploadable
9911 
9912 
9913 class MetadataSetter:
9914hunk ./src/allmydata/dirnode.py 130
9915 
9916         children[name] = (child, metadata)
9917         new_contents = self.node._pack_contents(children)
9918-        return new_contents
9919+        uploadable = MutableDataHandle(new_contents)
9920+        return uploadable
9921 
9922 
9923 class Adder:
9924hunk ./src/allmydata/dirnode.py 175
9925 
9926             children[name] = (child, metadata)
9927         new_contents = self.node._pack_contents(children)
9928-        return new_contents
9929+        uploadable = MutableDataHandle(new_contents)
9930+        return uploadable
9931 
9932 def _encrypt_rw_uri(writekey, rw_uri):
9933     precondition(isinstance(rw_uri, str), rw_uri)
9934hunk ./src/allmydata/mutable/filenode.py 7
9935 from zope.interface import implements
9936 from twisted.internet import defer, reactor
9937 from foolscap.api import eventually
9938-from allmydata.interfaces import IMutableFileNode, \
9939-     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
9940+from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
9941+                                 NotEnoughSharesError, \
9942+                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
9943 from allmydata.util import hashutil, log
9944 from allmydata.util.assertutil import precondition
9945 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
9946hunk ./src/allmydata/mutable/filenode.py 16
9947 from allmydata.monitor import Monitor
9948 from pycryptopp.cipher.aes import AES
9949 
9950-from allmydata.mutable.publish import Publish
9951+from allmydata.mutable.publish import Publish, MutableFileHandle, \
9952+                                      MutableDataHandle
9953 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
9954      ResponseCache, UncoordinatedWriteError
9955 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
9956hunk ./src/allmydata/mutable/filenode.py 133
9957         return self._upload(initial_contents, None)
9958 
9959     def _get_initial_contents(self, contents):
9960-        if isinstance(contents, str):
9961-            return contents
9962         if contents is None:
9963hunk ./src/allmydata/mutable/filenode.py 134
9964-            return ""
9965+            return MutableDataHandle("")
9966+
9967+        if IMutableUploadable.providedBy(contents):
9968+            return contents
9969+
9970         assert callable(contents), "%s should be callable, not %s" % \
9971                (contents, type(contents))
9972         return contents(self)
9973hunk ./src/allmydata/mutable/filenode.py 353
9974     def overwrite(self, new_contents):
9975         return self._do_serialized(self._overwrite, new_contents)
9976     def _overwrite(self, new_contents):
9977+        assert IMutableUploadable.providedBy(new_contents)
9978+
9979         servermap = ServerMap()
9980         d = self._update_servermap(servermap, mode=MODE_WRITE)
9981         d.addCallback(lambda ignored: self._upload(new_contents, servermap))
9982hunk ./src/allmydata/mutable/filenode.py 431
9983                 # recovery when it observes UCWE, we need to do a second
9984                 # publish. See #551 for details. We'll basically loop until
9985                 # we managed an uncontested publish.
9986-                new_contents = old_contents
9987-            precondition(isinstance(new_contents, str),
9988-                         "Modifier function must return a string or None")
9989+                old_uploadable = MutableDataHandle(old_contents)
9990+                new_contents = old_uploadable
9991+            precondition((IMutableUploadable.providedBy(new_contents) or
9992+                          new_contents is None),
9993+                         "Modifier function must return an IMutableUploadable "
9994+                         "or None")
9995             return self._upload(new_contents, servermap)
9996         d.addCallback(_apply)
9997         return d
9998hunk ./src/allmydata/mutable/filenode.py 472
9999         return self._do_serialized(self._upload, new_contents, servermap)
10000     def _upload(self, new_contents, servermap):
10001         assert self._pubkey, "update_servermap must be called before publish"
10002+        assert IMutableUploadable.providedBy(new_contents)
10003+
10004         p = Publish(self, self._storage_broker, servermap)
10005         if self._history:
10006hunk ./src/allmydata/mutable/filenode.py 476
10007-            self._history.notify_publish(p.get_status(), len(new_contents))
10008+            self._history.notify_publish(p.get_status(), new_contents.get_size())
10009         d = p.publish(new_contents)
10010hunk ./src/allmydata/mutable/filenode.py 478
10011-        d.addCallback(self._did_upload, len(new_contents))
10012+        d.addCallback(self._did_upload, new_contents.get_size())
10013         return d
10014     def _did_upload(self, res, size):
10015         self._most_recent_size = size
10016hunk ./src/allmydata/mutable/publish.py 141
10017 
10018         # 0. Setup encoding parameters, encoder, and other such things.
10019         # 1. Encrypt, encode, and publish segments.
10020-        self.data = StringIO(newdata)
10021-        self.datalength = len(newdata)
10022+        assert IMutableUploadable.providedBy(newdata)
10023+
10024+        self.data = newdata
10025+        self.datalength = newdata.get_size()
10026 
10027         self.log("starting publish, datalen is %s" % self.datalength)
10028         self._status.set_size(self.datalength)
10029hunk ./src/allmydata/mutable/publish.py 442
10030 
10031         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
10032         data = self.data.read(segsize)
10033+        # XXX: This is dumb. Why return a list?
10034+        data = "".join(data)
10035 
10036         assert len(data) == segsize
10037 
10038hunk ./src/allmydata/mutable/repairer.py 5
10039 from zope.interface import implements
10040 from twisted.internet import defer
10041 from allmydata.interfaces import IRepairResults, ICheckResults
10042+from allmydata.mutable.publish import MutableDataHandle
10043 
10044 class RepairResults:
10045     implements(IRepairResults)
10046hunk ./src/allmydata/mutable/repairer.py 108
10047             raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.")
10048 
10049         d = self.node.download_version(smap, best_version, fetch_privkey=True)
10050+        d.addCallback(lambda data:
10051+            MutableDataHandle(data))
10052         d.addCallback(self.node.upload, smap)
10053         d.addCallback(self.get_results, smap)
10054         return d
10055hunk ./src/allmydata/nodemaker.py 9
10056 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
10057 from allmydata.immutable.upload import Data
10058 from allmydata.mutable.filenode import MutableFileNode
10059+from allmydata.mutable.publish import MutableDataHandle
10060 from allmydata.dirnode import DirectoryNode, pack_children
10061 from allmydata.unknown import UnknownNode
10062 from allmydata import uri
10063merger 0.0 (
10064merger 0.0 (
10065hunk ./src/allmydata/nodemaker.py 107
10066-                                     pack_children(n, initial_children))
10067+                                     pack_children(n, initial_children),
10068+                                     version)
10069hunk ./src/allmydata/nodemaker.py 107
10070-                                     pack_children(n, initial_children))
10071+                                     pack_children(initial_children, n.get_writekey()))
10072)
10073hunk ./src/allmydata/nodemaker.py 107
10074-                                     pack_children(n, initial_children),
10075+                                     MutableDataHandle(
10076+                                        pack_children(n, initial_children)),
10077)
10078hunk ./src/allmydata/web/filenode.py 12
10079 from allmydata.interfaces import ExistingChildError
10080 from allmydata.monitor import Monitor
10081 from allmydata.immutable.upload import FileHandle
10082+from allmydata.mutable.publish import MutableFileHandle
10083 from allmydata.util import log, base32
10084 
10085 from allmydata.web.common import text_plain, WebError, RenderMixin, \
10086hunk ./src/allmydata/web/filenode.py 27
10087         # a new file is being uploaded in our place.
10088         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10089         if mutable:
10090-            req.content.seek(0)
10091-            data = req.content.read()
10092+            data = MutableFileHandle(req.content)
10093             d = client.create_mutable_file(data)
10094             def _uploaded(newnode):
10095                 d2 = self.parentnode.set_node(self.name, newnode,
10096hunk ./src/allmydata/web/filenode.py 61
10097         d.addCallback(lambda res: childnode.get_uri())
10098         return d
10099 
10100-    def _read_data_from_formpost(self, req):
10101-        # SDMF: files are small, and we can only upload data, so we read
10102-        # the whole file into memory before uploading.
10103-        contents = req.fields["file"]
10104-        contents.file.seek(0)
10105-        data = contents.file.read()
10106-        return data
10107 
10108     def replace_me_with_a_formpost(self, req, client, replace):
10109         # create a new file, maybe mutable, maybe immutable
10110hunk ./src/allmydata/web/filenode.py 66
10111         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10112 
10113+        # create an immutable file
10114+        contents = req.fields["file"]
10115         if mutable:
10116hunk ./src/allmydata/web/filenode.py 69
10117-            data = self._read_data_from_formpost(req)
10118-            d = client.create_mutable_file(data)
10119+            uploadable = MutableFileHandle(contents.file)
10120+            d = client.create_mutable_file(uploadable)
10121             def _uploaded(newnode):
10122                 d2 = self.parentnode.set_node(self.name, newnode,
10123                                               overwrite=replace)
10124hunk ./src/allmydata/web/filenode.py 78
10125                 return d2
10126             d.addCallback(_uploaded)
10127             return d
10128-        # create an immutable file
10129-        contents = req.fields["file"]
10130+
10131         uploadable = FileHandle(contents.file, convergence=client.convergence)
10132         d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
10133         d.addCallback(lambda newnode: newnode.get_uri())
10134hunk ./src/allmydata/web/filenode.py 84
10135         return d
10136 
10137+
10138 class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
10139     def __init__(self, client, parentnode, name):
10140         rend.Page.__init__(self)
10141hunk ./src/allmydata/web/filenode.py 278
10142 
10143     def replace_my_contents(self, req):
10144         req.content.seek(0)
10145-        new_contents = req.content.read()
10146+        new_contents = MutableFileHandle(req.content)
10147         d = self.node.overwrite(new_contents)
10148         d.addCallback(lambda res: self.node.get_uri())
10149         return d
10150hunk ./src/allmydata/web/filenode.py 286
10151     def replace_my_contents_with_a_formpost(self, req):
10152         # we have a mutable file. Get the data from the formpost, and replace
10153         # the mutable file's contents with it.
10154-        new_contents = self._read_data_from_formpost(req)
10155+        new_contents = req.fields['file']
10156+        new_contents = MutableFileHandle(new_contents.file)
10157+
10158         d = self.node.overwrite(new_contents)
10159         d.addCallback(lambda res: self.node.get_uri())
10160         return d
10161hunk ./src/allmydata/web/unlinked.py 7
10162 from twisted.internet import defer
10163 from nevow import rend, url, tags as T
10164 from allmydata.immutable.upload import FileHandle
10165+from allmydata.mutable.publish import MutableFileHandle
10166 from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
10167      convert_children_json, WebError
10168 from allmydata.web import status
10169hunk ./src/allmydata/web/unlinked.py 23
10170 def PUTUnlinkedSSK(req, client):
10171     # SDMF: files are small, and we can only upload data
10172     req.content.seek(0)
10173-    data = req.content.read()
10174+    data = MutableFileHandle(req.content)
10175     d = client.create_mutable_file(data)
10176     d.addCallback(lambda n: n.get_uri())
10177     return d
10178hunk ./src/allmydata/web/unlinked.py 87
10179     # "POST /uri", to create an unlinked file.
10180     # SDMF: files are small, and we can only upload data
10181     contents = req.fields["file"]
10182-    contents.file.seek(0)
10183-    data = contents.file.read()
10184+    data = MutableFileHandle(contents.file)
10185     d = client.create_mutable_file(data)
10186     d.addCallback(lambda n: n.get_uri())
10187     return d
10188}
10189[test/test_sftp.py: alter a setup routine to work with new mutable file APIs.
10190Kevan Carstensen <kevan@isnotajoke.com>**20100708193522
10191 Ignore-this: 434bbe1347072076c0836d26fca8ac8a
10192] {
10193hunk ./src/allmydata/test/test_sftp.py 32
10194 
10195 from allmydata.util.consumer import download_to_data
10196 from allmydata.immutable import upload
10197+from allmydata.mutable import publish
10198 from allmydata.test.no_network import GridTestMixin
10199 from allmydata.test.common import ShouldFailMixin
10200 from allmydata.test.common_util import ReallyEqualMixin
10201hunk ./src/allmydata/test/test_sftp.py 84
10202         return d
10203 
10204     def _set_up_tree(self):
10205-        d = self.client.create_mutable_file("mutable file contents")
10206+        u = publish.MutableDataHandle("mutable file contents")
10207+        d = self.client.create_mutable_file(u)
10208         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
10209         def _created_mutable(n):
10210             self.mutable = n
10211}
10212[mutable/publish.py: make MutableFileHandle seek to the beginning of its file handle before reading.
10213Kevan Carstensen <kevan@isnotajoke.com>**20100708193600
10214 Ignore-this: 453a737dc62a79c77b3d360fed9000ab
10215] hunk ./src/allmydata/mutable/publish.py 989
10216         assert hasattr(filehandle, "close")
10217 
10218         self._filehandle = filehandle
10219+        # We must start reading at the beginning of the file, or we risk
10220+        # encountering errors when the data read does not match the size
10221+        # reported to the uploader.
10222+        self._filehandle.seek(0)
10223 
10224 
10225     def get_size(self):
10226[Refactor download interfaces to be more uniform, per #993
10227Kevan Carstensen <kevan@isnotajoke.com>**20100709232912
10228 Ignore-this: 277c5699c4a2dd7c03ecfa0a28458f5b
10229] {
10230hunk ./src/allmydata/immutable/filenode.py 10
10231 from foolscap.api import eventually
10232 from allmydata.interfaces import IImmutableFileNode, ICheckable, \
10233      IDownloadTarget, IUploadResults
10234-from allmydata.util import dictutil, log, base32
10235+from allmydata.util import dictutil, log, base32, consumer
10236 from allmydata.uri import CHKFileURI, LiteralFileURI
10237 from allmydata.immutable.checker import Checker
10238 from allmydata.check_results import CheckResults, CheckAndRepairResults
10239hunk ./src/allmydata/immutable/filenode.py 318
10240                       self.download_cache.read(consumer, offset, size))
10241         return d
10242 
10243+    # IReadable, IFileNode
10244+
10245+    def get_best_readable_version(self):
10246+        """
10247+        Return an IReadable of the best version of this file. Since
10248+        immutable files can have only one version, we just return the
10249+        current filenode.
10250+        """
10251+        return self
10252+
10253+
10254+    def download_best_version(self):
10255+        """
10256+        Download the best version of this file, returning its contents
10257+        as a bytestring. Since there is only one version of an immutable
10258+        file, we download and return the contents of this file.
10259+        """
10260+        d = consumer.download_to_data(self)
10261+        return d
10262+
10263+    # for an immutable file, download_to_data (specified in IReadable)
10264+    # is the same as download_best_version (specified in IFileNode). For
10265+    # mutable files, the difference is more meaningful, since they can
10266+    # have multiple versions.
10267+    download_to_data = download_best_version
10268+
10269+
10270+    # get_size() (IReadable), get_current_size() (IFilesystemNode), and
10271+    # get_size_of_best_version(IFileNode) are all the same for immutable
10272+    # files.
10273+    get_size_of_best_version = get_current_size
10274+
10275+
10276 class LiteralProducer:
10277     implements(IPushProducer)
10278     def resumeProducing(self):
10279hunk ./src/allmydata/immutable/filenode.py 409
10280         d = basic.FileSender().beginFileTransfer(StringIO(data), consumer)
10281         d.addCallback(lambda lastSent: consumer)
10282         return d
10283+
10284+    # IReadable, IFileNode, IFilesystemNode
10285+    def get_best_readable_version(self):
10286+        return self
10287+
10288+
10289+    def download_best_version(self):
10290+        return defer.succeed(self.u.data)
10291+
10292+
10293+    download_to_data = download_best_version
10294+    get_size_of_best_version = get_current_size
10295hunk ./src/allmydata/interfaces.py 563
10296 class MustNotBeUnknownRWError(CapConstraintError):
10297     """Cannot add an unknown child cap specified in a rw_uri field."""
10298 
10299+
10300+class IReadable(Interface):
10301+    """I represent a readable object -- either an immutable file, or a
10302+    specific version of a mutable file.
10303+    """
10304+
10305+    def is_readonly():
10306+        """Return True if this reference provides mutable access to the given
10307+        file or directory (i.e. if you can modify it), or False if not. Note
10308+        that even if this reference is read-only, someone else may hold a
10309+        read-write reference to it.
10310+
10311+        For an IReadable returned by get_best_readable_version(), this will
10312+        always return True, but for instances of subinterfaces such as
10313+        IMutableFileVersion, it may return False."""
10314+
10315+    def is_mutable():
10316+        """Return True if this file or directory is mutable (by *somebody*,
10317+        not necessarily you), False if it is is immutable. Note that a file
10318+        might be mutable overall, but your reference to it might be
10319+        read-only. On the other hand, all references to an immutable file
10320+        will be read-only; there are no read-write references to an immutable
10321+        file."""
10322+
10323+    def get_storage_index():
10324+        """Return the storage index of the file."""
10325+
10326+    def get_size():
10327+        """Return the length (in bytes) of this readable object."""
10328+
10329+    def download_to_data():
10330+        """Download all of the file contents. I return a Deferred that fires
10331+        with the contents as a byte string."""
10332+
10333+    def read(consumer, offset=0, size=None):
10334+        """Download a portion (possibly all) of the file's contents, making
10335+        them available to the given IConsumer. Return a Deferred that fires
10336+        (with the consumer) when the consumer is unregistered (either because
10337+        the last byte has been given to it, or because the consumer threw an
10338+        exception during write(), possibly because it no longer wants to
10339+        receive data). The portion downloaded will start at 'offset' and
10340+        contain 'size' bytes (or the remainder of the file if size==None).
10341+
10342+        The consumer will be used in non-streaming mode: an IPullProducer
10343+        will be attached to it.
10344+
10345+        The consumer will not receive data right away: several network trips
10346+        must occur first. The order of events will be::
10347+
10348+         consumer.registerProducer(p, streaming)
10349+          (if streaming == False)::
10350+           consumer does p.resumeProducing()
10351+            consumer.write(data)
10352+           consumer does p.resumeProducing()
10353+            consumer.write(data).. (repeat until all data is written)
10354+         consumer.unregisterProducer()
10355+         deferred.callback(consumer)
10356+
10357+        If a download error occurs, or an exception is raised by
10358+        consumer.registerProducer() or consumer.write(), I will call
10359+        consumer.unregisterProducer() and then deliver the exception via
10360+        deferred.errback(). To cancel the download, the consumer should call
10361+        p.stopProducing(), which will result in an exception being delivered
10362+        via deferred.errback().
10363+
10364+        See src/allmydata/util/consumer.py for an example of a simple
10365+        download-to-memory consumer.
10366+        """
10367+
10368+
10369+class IMutableFileVersion(IReadable):
10370+    """I provide access to a particular version of a mutable file. The
10371+    access is read/write if I was obtained from a filenode derived from
10372+    a write cap, or read-only if the filenode was derived from a read cap.
10373+    """
10374+
10375+    def get_sequence_number():
10376+        """Return the sequence number of this version."""
10377+
10378+    def get_servermap():
10379+        """Return the IMutableFileServerMap instance that was used to create
10380+        this object.
10381+        """
10382+
10383+    def get_writekey():
10384+        """Return this filenode's writekey, or None if the node does not have
10385+        write-capability. This may be used to assist with data structures
10386+        that need to make certain data available only to writers, such as the
10387+        read-write child caps in dirnodes. The recommended process is to have
10388+        reader-visible data be submitted to the filenode in the clear (where
10389+        it will be encrypted by the filenode using the readkey), but encrypt
10390+        writer-visible data using this writekey.
10391+        """
10392+
10393+    # TODO: Can this be overwrite instead of replace?
10394+    def replace(new_contents):
10395+        """Replace the contents of the mutable file, provided that no other
10396+        node has published (or is attempting to publish, concurrently) a
10397+        newer version of the file than this one.
10398+
10399+        I will avoid modifying any share that is different than the version
10400+        given by get_sequence_number(). However, if another node is writing
10401+        to the file at the same time as me, I may manage to update some shares
10402+        while they update others. If I see any evidence of this, I will signal
10403+        UncoordinatedWriteError, and the file will be left in an inconsistent
10404+        state (possibly the version you provided, possibly the old version,
10405+        possibly somebody else's version, and possibly a mix of shares from
10406+        all of these).
10407+
10408+        The recommended response to UncoordinatedWriteError is to either
10409+        return it to the caller (since they failed to coordinate their
10410+        writes), or to attempt some sort of recovery. It may be sufficient to
10411+        wait a random interval (with exponential backoff) and repeat your
10412+        operation. If I do not signal UncoordinatedWriteError, then I was
10413+        able to write the new version without incident.
10414+
10415+        I return a Deferred that fires (with a PublishStatus object) when the
10416+        update has completed.
10417+        """
10418+
10419+    def modify(modifier_cb):
10420+        """Modify the contents of the file, by downloading this version,
10421+        applying the modifier function (or bound method), then uploading
10422+        the new version. This will succeed as long as no other node
10423+        publishes a version between the download and the upload.
10424+        I return a Deferred that fires (with a PublishStatus object) when
10425+        the update is complete.
10426+
10427+        The modifier callable will be given three arguments: a string (with
10428+        the old contents), a 'first_time' boolean, and a servermap. As with
10429+        download_to_data(), the old contents will be from this version,
10430+        but the modifier can use the servermap to make other decisions
10431+        (such as refusing to apply the delta if there are multiple parallel
10432+        versions, or if there is evidence of a newer unrecoverable version).
10433+        'first_time' will be True the first time the modifier is called,
10434+        and False on any subsequent calls.
10435+
10436+        The callable should return a string with the new contents. The
10437+        callable must be prepared to be called multiple times, and must
10438+        examine the input string to see if the change that it wants to make
10439+        is already present in the old version. If it does not need to make
10440+        any changes, it can either return None, or return its input string.
10441+
10442+        If the modifier raises an exception, it will be returned in the
10443+        errback.
10444+        """
10445+
10446+
10447 # The hierarchy looks like this:
10448 #  IFilesystemNode
10449 #   IFileNode
10450hunk ./src/allmydata/interfaces.py 801
10451     def raise_error():
10452         """Raise any error associated with this node."""
10453 
10454+    # XXX: These may not be appropriate outside the context of an IReadable.
10455     def get_size():
10456         """Return the length (in bytes) of the data this node represents. For
10457         directory nodes, I return the size of the backing store. I return
10458hunk ./src/allmydata/interfaces.py 818
10459 class IFileNode(IFilesystemNode):
10460     """I am a node which represents a file: a sequence of bytes. I am not a
10461     container, like IDirectoryNode."""
10462+    def get_best_readable_version():
10463+        """Return a Deferred that fires with an IReadable for the 'best'
10464+        available version of the file. The IReadable provides only read
10465+        access, even if this filenode was derived from a write cap.
10466 
10467hunk ./src/allmydata/interfaces.py 823
10468-class IImmutableFileNode(IFileNode):
10469-    def read(consumer, offset=0, size=None):
10470-        """Download a portion (possibly all) of the file's contents, making
10471-        them available to the given IConsumer. Return a Deferred that fires
10472-        (with the consumer) when the consumer is unregistered (either because
10473-        the last byte has been given to it, or because the consumer threw an
10474-        exception during write(), possibly because it no longer wants to
10475-        receive data). The portion downloaded will start at 'offset' and
10476-        contain 'size' bytes (or the remainder of the file if size==None).
10477-
10478-        The consumer will be used in non-streaming mode: an IPullProducer
10479-        will be attached to it.
10480+        For an immutable file, there is only one version. For a mutable
10481+        file, the 'best' version is the recoverable version with the
10482+        highest sequence number. If no uncoordinated writes have occurred,
10483+        and if enough shares are available, then this will be the most
10484+        recent version that has been uploaded. If no version is recoverable,
10485+        the Deferred will errback with an UnrecoverableFileError.
10486+        """
10487 
10488hunk ./src/allmydata/interfaces.py 831
10489-        The consumer will not receive data right away: several network trips
10490-        must occur first. The order of events will be::
10491+    def download_best_version():
10492+        """Download the contents of the version that would be returned
10493+        by get_best_readable_version(). This is equivalent to calling
10494+        download_to_data() on the IReadable given by that method.
10495 
10496hunk ./src/allmydata/interfaces.py 836
10497-         consumer.registerProducer(p, streaming)
10498-          (if streaming == False)::
10499-           consumer does p.resumeProducing()
10500-            consumer.write(data)
10501-           consumer does p.resumeProducing()
10502-            consumer.write(data).. (repeat until all data is written)
10503-         consumer.unregisterProducer()
10504-         deferred.callback(consumer)
10505+        I return a Deferred that fires with a byte string when the file
10506+        has been fully downloaded. To support streaming download, use
10507+        the 'read' method of IReadable. If no version is recoverable,
10508+        the Deferred will errback with an UnrecoverableFileError.
10509+        """
10510 
10511hunk ./src/allmydata/interfaces.py 842
10512-        If a download error occurs, or an exception is raised by
10513-        consumer.registerProducer() or consumer.write(), I will call
10514-        consumer.unregisterProducer() and then deliver the exception via
10515-        deferred.errback(). To cancel the download, the consumer should call
10516-        p.stopProducing(), which will result in an exception being delivered
10517-        via deferred.errback().
10518+    def get_size_of_best_version():
10519+        """Find the size of the version that would be returned by
10520+        get_best_readable_version().
10521 
10522hunk ./src/allmydata/interfaces.py 846
10523-        See src/allmydata/util/consumer.py for an example of a simple
10524-        download-to-memory consumer.
10525+        I return a Deferred that fires with an integer. If no version
10526+        is recoverable, the Deferred will errback with an
10527+        UnrecoverableFileError.
10528         """
10529 
10530hunk ./src/allmydata/interfaces.py 851
10531+
10532+class IImmutableFileNode(IFileNode, IReadable):
10533+    """I am a node representing an immutable file. Immutable files have
10534+    only one version"""
10535+
10536+
10537 class IMutableFileNode(IFileNode):
10538     """I provide access to a 'mutable file', which retains its identity
10539     regardless of what contents are put in it.
10540hunk ./src/allmydata/interfaces.py 916
10541     only be retrieved and updated all-at-once, as a single big string. Future
10542     versions of our mutable files will remove this restriction.
10543     """
10544-
10545-    def download_best_version():
10546-        """Download the 'best' available version of the file, meaning one of
10547-        the recoverable versions with the highest sequence number. If no
10548+    def get_best_mutable_version():
10549+        """Return a Deferred that fires with an IMutableFileVersion for
10550+        the 'best' available version of the file. The best version is
10551+        the recoverable version with the highest sequence number. If no
10552         uncoordinated writes have occurred, and if enough shares are
10553hunk ./src/allmydata/interfaces.py 921
10554-        available, then this will be the most recent version that has been
10555-        uploaded.
10556-
10557-        I update an internal servermap with MODE_READ, determine which
10558-        version of the file is indicated by
10559-        servermap.best_recoverable_version(), and return a Deferred that
10560-        fires with its contents. If no version is recoverable, the Deferred
10561-        will errback with UnrecoverableFileError.
10562-        """
10563-
10564-    def get_size_of_best_version():
10565-        """Find the size of the version that would be downloaded with
10566-        download_best_version(), without actually downloading the whole file.
10567+        available, then this will be the most recent version that has
10568+        been uploaded.
10569 
10570hunk ./src/allmydata/interfaces.py 924
10571-        I return a Deferred that fires with an integer.
10572+        If no version is recoverable, the Deferred will errback with an
10573+        UnrecoverableFileError.
10574         """
10575 
10576     def overwrite(new_contents):
10577hunk ./src/allmydata/interfaces.py 964
10578         errback.
10579         """
10580 
10581-
10582     def get_servermap(mode):
10583         """Return a Deferred that fires with an IMutableFileServerMap
10584         instance, updated using the given mode.
10585hunk ./src/allmydata/test/test_filenode.py 98
10586         def _check_segment(res):
10587             self.failUnlessEqual(res, DATA[1:1+5])
10588         d.addCallback(_check_segment)
10589+        d.addCallback(lambda ignored:
10590+            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
10591+        d.addCallback(lambda ignored:
10592+            fn1.get_size_of_best_version())
10593+        d.addCallback(lambda size:
10594+            self.failUnlessEqual(size, len(DATA)))
10595+        d.addCallback(lambda ignored:
10596+            fn1.download_to_data())
10597+        d.addCallback(lambda data:
10598+            self.failUnlessEqual(data, DATA))
10599+        d.addCallback(lambda ignored:
10600+            fn1.download_best_version())
10601+        d.addCallback(lambda data:
10602+            self.failUnlessEqual(data, DATA))
10603 
10604         return d
10605 
10606hunk ./src/allmydata/test/test_immutable.py 153
10607         return d
10608 
10609 
10610+    def test_download_to_data(self):
10611+        d = self.n.download_to_data()
10612+        d.addCallback(lambda data:
10613+            self.failUnlessEqual(data, common.TEST_DATA))
10614+        return d
10615+
10616+
10617+    def test_download_best_version(self):
10618+        d = self.n.download_best_version()
10619+        d.addCallback(lambda data:
10620+            self.failUnlessEqual(data, common.TEST_DATA))
10621+        return d
10622+
10623+
10624+    def test_get_best_readable_version(self):
10625+        n = self.n.get_best_readable_version()
10626+        self.failUnlessEqual(n, self.n)
10627+
10628+    def test_get_size_of_best_version(self):
10629+        d = self.n.get_size_of_best_version()
10630+        d.addCallback(lambda size:
10631+            self.failUnlessEqual(size, len(common.TEST_DATA)))
10632+        return d
10633+
10634+
10635 # XXX extend these tests to show bad behavior of various kinds from servers: raising exception from each remove_foo() method, for example
10636 
10637 # XXX test disconnect DeadReferenceError from get_buckets and get_block_whatsit
10638}
10639[frontends/sftpd.py: alter a mutable file overwrite to work with the new API
10640Kevan Carstensen <kevan@isnotajoke.com>**20100717014446
10641 Ignore-this: 2c57bbc8d9ab97a0e9af0634c00efc86
10642] {
10643hunk ./src/allmydata/frontends/sftpd.py 33
10644 from allmydata.interfaces import IFileNode, IDirectoryNode, ExistingChildError, \
10645      NoSuchChildError, ChildOfWrongTypeError
10646 from allmydata.mutable.common import NotWriteableError
10647+from allmydata.mutable.publish import MutableFileHandle
10648 from allmydata.immutable.upload import FileHandle
10649 from allmydata.dirnode import update_metadata
10650 from allmydata.util.fileutil import EncryptedTemporaryFile
10651merger 0.0 (
10652hunk ./src/allmydata/frontends/sftpd.py 664
10653-            # TODO: use download interface described in #993 when implemented.
10654hunk ./src/allmydata/frontends/sftpd.py 664
10655-            # TODO: use download interface described in #993 when implemented.
10656-            if filenode.is_mutable():
10657-                self.async.addCallback(lambda ign: filenode.download_best_version())
10658-                def _downloaded(data):
10659-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
10660-                    self.consumer.write(data)
10661-                    self.consumer.finish()
10662-                    return None
10663-                self.async.addCallback(_downloaded)
10664-            else:
10665-                download_size = filenode.get_size()
10666-                assert download_size is not None, "download_size is None"
10667+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
10668+
10669+            def _read(version):
10670+                download_size = version.get_size()
10671+                assert download_size is not None
10672+
10673)
10674hunk ./src/allmydata/frontends/sftpd.py 677
10675                 download_size = filenode.get_size()
10676                 assert download_size is not None, "download_size is None"
10677                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
10678-                def _read(ign):
10679-                    if noisy: self.log("_read immutable", level=NOISY)
10680-                    filenode.read(self.consumer, 0, None)
10681-                self.async.addCallback(_read)
10682+
10683+                if noisy: self.log("_read", level=NOISY)
10684+                version.read(self.consumer, 0, None)
10685+            self.async.addCallback(_read)
10686 
10687         eventually(self.async.callback, None)
10688 
10689hunk ./src/allmydata/frontends/sftpd.py 824
10690                     assert parent and childname, (parent, childname, self.metadata)
10691                     d2.addCallback(lambda ign: parent.set_metadata_for(childname, self.metadata))
10692 
10693-                d2.addCallback(lambda ign: self.consumer.get_current_size())
10694-                d2.addCallback(lambda size: self.consumer.read(0, size))
10695-                d2.addCallback(lambda new_contents: self.filenode.overwrite(new_contents))
10696+                d2.addCallback(lambda ign: self.filenode.overwrite(MutableFileHandle(self.consumer.get_file())))
10697             else:
10698                 def _add_file(ign):
10699                     self.log("_add_file childname=%r" % (childname,), level=OPERATIONAL)
10700}
10701[mutable/filenode.py: implement most of IVersion, per #993
10702Kevan Carstensen <kevan@isnotajoke.com>**20100717014516
10703 Ignore-this: d4551142b32ea97040ce0e98a394fde5
10704] {
10705hunk ./src/allmydata/mutable/filenode.py 8
10706 from twisted.internet import defer, reactor
10707 from foolscap.api import eventually
10708 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
10709-                                 NotEnoughSharesError, \
10710-                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
10711-from allmydata.util import hashutil, log
10712+     NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
10713+     IMutableFileVersion
10714+from allmydata.util import hashutil, log, consumer
10715 from allmydata.util.assertutil import precondition
10716 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
10717 from allmydata.monitor import Monitor
10718hunk ./src/allmydata/mutable/filenode.py 17
10719 from pycryptopp.cipher.aes import AES
10720 
10721 from allmydata.mutable.publish import Publish, MutableFileHandle, \
10722-                                      MutableDataHandle
10723+                                      MutableData
10724 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
10725      ResponseCache, UncoordinatedWriteError
10726 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
10727hunk ./src/allmydata/mutable/filenode.py 134
10728 
10729     def _get_initial_contents(self, contents):
10730         if contents is None:
10731-            return MutableDataHandle("")
10732+            return MutableData("")
10733 
10734         if IMutableUploadable.providedBy(contents):
10735             return contents
10736hunk ./src/allmydata/mutable/filenode.py 208
10737 
10738     def get_size(self):
10739         return self._most_recent_size
10740+
10741     def get_current_size(self):
10742         d = self.get_size_of_best_version()
10743         d.addCallback(self._stash_size)
10744hunk ./src/allmydata/mutable/filenode.py 213
10745         return d
10746+
10747     def _stash_size(self, size):
10748         self._most_recent_size = size
10749         return size
10750hunk ./src/allmydata/mutable/filenode.py 272
10751             return cmp(self.__class__, them.__class__)
10752         return cmp(self._uri, them._uri)
10753 
10754-    def _do_serialized(self, cb, *args, **kwargs):
10755-        # note: to avoid deadlock, this callable is *not* allowed to invoke
10756-        # other serialized methods within this (or any other)
10757-        # MutableFileNode. The callable should be a bound method of this same
10758-        # MFN instance.
10759-        d = defer.Deferred()
10760-        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
10761-        # we need to put off d.callback until this Deferred is finished being
10762-        # processed. Otherwise the caller's subsequent activities (like,
10763-        # doing other things with this node) can cause reentrancy problems in
10764-        # the Deferred code itself
10765-        self._serializer.addBoth(lambda res: eventually(d.callback, res))
10766-        # add a log.err just in case something really weird happens, because
10767-        # self._serializer stays around forever, therefore we won't see the
10768-        # usual Unhandled Error in Deferred that would give us a hint.
10769-        self._serializer.addErrback(log.err)
10770-        return d
10771 
10772     #################################
10773     # ICheckable
10774hunk ./src/allmydata/mutable/filenode.py 297
10775 
10776 
10777     #################################
10778-    # IMutableFileNode
10779+    # IFileNode
10780+
10781+    def get_best_readable_version(self):
10782+        """
10783+        I return a Deferred that fires with a MutableFileVersion
10784+        representing the best readable version of the file that I
10785+        represent
10786+        """
10787+        return self.get_readable_version()
10788+
10789+
10790+    def get_readable_version(self, servermap=None, version=None):
10791+        """
10792+        I return a Deferred that fires with an MutableFileVersion for my
10793+        version argument, if there is a recoverable file of that version
10794+        on the grid. If there is no recoverable version, I fire with an
10795+        UnrecoverableFileError.
10796+
10797+        If a servermap is provided, I look in there for the requested
10798+        version. If no servermap is provided, I create and update a new
10799+        one.
10800+
10801+        If no version is provided, then I return a MutableFileVersion
10802+        representing the best recoverable version of the file.
10803+        """
10804+        d = self._get_version_from_servermap(MODE_READ, servermap, version)
10805+        def _build_version((servermap, their_version)):
10806+            assert their_version in servermap.recoverable_versions()
10807+            assert their_version in servermap.make_versionmap()
10808+
10809+            mfv = MutableFileVersion(self,
10810+                                     servermap,
10811+                                     their_version,
10812+                                     self._storage_index,
10813+                                     self._storage_broker,
10814+                                     self._readkey,
10815+                                     history=self._history)
10816+            assert mfv.is_readonly()
10817+            # our caller can use this to download the contents of the
10818+            # mutable file.
10819+            return mfv
10820+        return d.addCallback(_build_version)
10821+
10822+
10823+    def _get_version_from_servermap(self,
10824+                                    mode,
10825+                                    servermap=None,
10826+                                    version=None):
10827+        """
10828+        I return a Deferred that fires with (servermap, version).
10829+
10830+        This function performs validation and a servermap update. If it
10831+        returns (servermap, version), the caller can assume that:
10832+            - servermap was last updated in mode.
10833+            - version is recoverable, and corresponds to the servermap.
10834+
10835+        If version and servermap are provided to me, I will validate
10836+        that version exists in the servermap, and that the servermap was
10837+        updated correctly.
10838+
10839+        If version is not provided, but servermap is, I will validate
10840+        the servermap and return the best recoverable version that I can
10841+        find in the servermap.
10842+
10843+        If the version is provided but the servermap isn't, I will
10844+        obtain a servermap that has been updated in the correct mode and
10845+        validate that version is found and recoverable.
10846+
10847+        If neither servermap nor version are provided, I will obtain a
10848+        servermap updated in the correct mode, and return the best
10849+        recoverable version that I can find in there.
10850+        """
10851+        # XXX: wording ^^^^
10852+        if servermap and servermap.last_update_mode == mode:
10853+            d = defer.succeed(servermap)
10854+        else:
10855+            d = self._get_servermap(mode)
10856+
10857+        def _get_version(servermap, version):
10858+            if version and version not in servermap.recoverable_versions():
10859+                version = None
10860+            else:
10861+                version = servermap.best_recoverable_version()
10862+            if not version:
10863+                raise UnrecoverableFileError("no recoverable versions")
10864+            return (servermap, version)
10865+        return d.addCallback(_get_version, version)
10866+
10867 
10868     def download_best_version(self):
10869hunk ./src/allmydata/mutable/filenode.py 387
10870+        """
10871+        I return a Deferred that fires with the contents of the best
10872+        version of this mutable file.
10873+        """
10874         return self._do_serialized(self._download_best_version)
10875hunk ./src/allmydata/mutable/filenode.py 392
10876+
10877+
10878     def _download_best_version(self):
10879hunk ./src/allmydata/mutable/filenode.py 395
10880-        servermap = ServerMap()
10881-        d = self._try_once_to_download_best_version(servermap, MODE_READ)
10882-        def _maybe_retry(f):
10883-            f.trap(NotEnoughSharesError)
10884-            # the download is worth retrying once. Make sure to use the
10885-            # old servermap, since it is what remembers the bad shares,
10886-            # but use MODE_WRITE to make it look for even more shares.
10887-            # TODO: consider allowing this to retry multiple times.. this
10888-            # approach will let us tolerate about 8 bad shares, I think.
10889-            return self._try_once_to_download_best_version(servermap,
10890-                                                           MODE_WRITE)
10891+        """
10892+        I am the serialized sibling of download_best_version.
10893+        """
10894+        d = self.get_best_readable_version()
10895+        d.addCallback(self._record_size)
10896+        d.addCallback(lambda version: version.download_to_data())
10897+
10898+        # It is possible that the download will fail because there
10899+        # aren't enough shares to be had. If so, we will try again after
10900+        # updating the servermap in MODE_WRITE, which may find more
10901+        # shares than updating in MODE_READ, as we just did. We can do
10902+        # this by getting the best mutable version and downloading from
10903+        # that -- the best mutable version will be a MutableFileVersion
10904+        # with a servermap that was last updated in MODE_WRITE, as we
10905+        # want. If this fails, then we give up.
10906+        def _maybe_retry(failure):
10907+            failure.trap(NotEnoughSharesError)
10908+
10909+            d = self.get_best_mutable_version()
10910+            d.addCallback(self._record_size)
10911+            d.addCallback(lambda version: version.download_to_data())
10912+            return d
10913+
10914         d.addErrback(_maybe_retry)
10915         return d
10916hunk ./src/allmydata/mutable/filenode.py 420
10917-    def _try_once_to_download_best_version(self, servermap, mode):
10918-        d = self._update_servermap(servermap, mode)
10919-        d.addCallback(self._once_updated_download_best_version, servermap)
10920-        return d
10921-    def _once_updated_download_best_version(self, ignored, servermap):
10922-        goal = servermap.best_recoverable_version()
10923-        if not goal:
10924-            raise UnrecoverableFileError("no recoverable versions")
10925-        return self._try_once_to_download_version(servermap, goal)
10926+
10927+
10928+    def _record_size(self, mfv):
10929+        """
10930+        I record the size of a mutable file version.
10931+        """
10932+        self._most_recent_size = mfv.get_size()
10933+        return mfv
10934+
10935 
10936     def get_size_of_best_version(self):
10937hunk ./src/allmydata/mutable/filenode.py 431
10938-        d = self.get_servermap(MODE_READ)
10939-        def _got_servermap(smap):
10940-            ver = smap.best_recoverable_version()
10941-            if not ver:
10942-                raise UnrecoverableFileError("no recoverable version")
10943-            return smap.size_of_version(ver)
10944-        d.addCallback(_got_servermap)
10945-        return d
10946+        """
10947+        I return the size of the best version of this mutable file.
10948+
10949+        This is equivalent to calling get_size() on the result of
10950+        get_best_readable_version().
10951+        """
10952+        d = self.get_best_readable_version()
10953+        return d.addCallback(lambda mfv: mfv.get_size())
10954+
10955+
10956+    #################################
10957+    # IMutableFileNode
10958+
10959+    def get_best_mutable_version(self, servermap=None):
10960+        """
10961+        I return a Deferred that fires with a MutableFileVersion
10962+        representing the best readable version of the file that I
10963+        represent. I am like get_best_readable_version, except that I
10964+        will try to make a writable version if I can.
10965+        """
10966+        return self.get_mutable_version(servermap=servermap)
10967+
10968+
10969+    def get_mutable_version(self, servermap=None, version=None):
10970+        """
10971+        I return a version of this mutable file. I return a Deferred
10972+        that fires with a MutableFileVersion
10973+
10974+        If version is provided, the Deferred will fire with a
10975+        MutableFileVersion initailized with that version. Otherwise, it
10976+        will fire with the best version that I can recover.
10977+
10978+        If servermap is provided, I will use that to find versions
10979+        instead of performing my own servermap update.
10980+        """
10981+        if self.is_readonly():
10982+            return self.get_readable_version(servermap=servermap,
10983+                                             version=version)
10984+
10985+        # get_mutable_version => write intent, so we require that the
10986+        # servermap is updated in MODE_WRITE
10987+        d = self._get_version_from_servermap(MODE_WRITE, servermap, version)
10988+        def _build_version((servermap, smap_version)):
10989+            # these should have been set by the servermap update.
10990+            assert self._secret_holder
10991+            assert self._writekey
10992+
10993+            mfv = MutableFileVersion(self,
10994+                                     servermap,
10995+                                     smap_version,
10996+                                     self._storage_index,
10997+                                     self._storage_broker,
10998+                                     self._readkey,
10999+                                     self._writekey,
11000+                                     self._secret_holder,
11001+                                     history=self._history)
11002+            assert not mfv.is_readonly()
11003+            return mfv
11004+
11005+        return d.addCallback(_build_version)
11006+
11007+
11008+    # XXX: I'm uncomfortable with the difference between upload and
11009+    #      overwrite, which, FWICT, is basically that you don't have to
11010+    #      do a servermap update before you overwrite. We split them up
11011+    #      that way anyway, so I guess there's no real difficulty in
11012+    #      offering both ways to callers, but it also makes the
11013+    #      public-facing API cluttery, and makes it hard to discern the
11014+    #      right way of doing things.
11015 
11016hunk ./src/allmydata/mutable/filenode.py 501
11017+    # In general, we leave it to callers to ensure that they aren't
11018+    # going to cause UncoordinatedWriteErrors when working with
11019+    # MutableFileVersions. We know that the next three operations
11020+    # (upload, overwrite, and modify) will all operate on the same
11021+    # version, so we say that only one of them can be going on at once,
11022+    # and serialize them to ensure that that actually happens, since as
11023+    # the caller in this situation it is our job to do that.
11024     def overwrite(self, new_contents):
11025hunk ./src/allmydata/mutable/filenode.py 509
11026+        """
11027+        I overwrite the contents of the best recoverable version of this
11028+        mutable file with new_contents. This is equivalent to calling
11029+        overwrite on the result of get_best_mutable_version with
11030+        new_contents as an argument. I return a Deferred that eventually
11031+        fires with the results of my replacement process.
11032+        """
11033         return self._do_serialized(self._overwrite, new_contents)
11034hunk ./src/allmydata/mutable/filenode.py 517
11035+
11036+
11037     def _overwrite(self, new_contents):
11038hunk ./src/allmydata/mutable/filenode.py 520
11039-        assert IMutableUploadable.providedBy(new_contents)
11040+        """
11041+        I am the serialized sibling of overwrite.
11042+        """
11043+        d = self.get_best_mutable_version()
11044+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
11045+
11046+
11047+
11048+    def upload(self, new_contents, servermap):
11049+        """
11050+        I overwrite the contents of the best recoverable version of this
11051+        mutable file with new_contents, using servermap instead of
11052+        creating/updating our own servermap. I return a Deferred that
11053+        fires with the results of my upload.
11054+        """
11055+        return self._do_serialized(self._upload, new_contents, servermap)
11056+
11057+
11058+    def _upload(self, new_contents, servermap):
11059+        """
11060+        I am the serialized sibling of upload.
11061+        """
11062+        d = self.get_best_mutable_version(servermap)
11063+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
11064+
11065+
11066+    def modify(self, modifier, backoffer=None):
11067+        """
11068+        I modify the contents of the best recoverable version of this
11069+        mutable file with the modifier. This is equivalent to calling
11070+        modify on the result of get_best_mutable_version. I return a
11071+        Deferred that eventually fires with an UploadResults instance
11072+        describing this process.
11073+        """
11074+        return self._do_serialized(self._modify, modifier, backoffer)
11075+
11076+
11077+    def _modify(self, modifier, backoffer):
11078+        """
11079+        I am the serialized sibling of modify.
11080+        """
11081+        d = self.get_best_mutable_version()
11082+        return d.addCallback(lambda mfv: mfv.modify(modifier, backoffer))
11083+
11084+
11085+    def download_version(self, servermap, version, fetch_privkey=False):
11086+        """
11087+        Download the specified version of this mutable file. I return a
11088+        Deferred that fires with the contents of the specified version
11089+        as a bytestring, or errbacks if the file is not recoverable.
11090+        """
11091+        d = self.get_readable_version(servermap, version)
11092+        return d.addCallback(lambda mfv: mfv.download_to_data(fetch_privkey))
11093+
11094+
11095+    def get_servermap(self, mode):
11096+        """
11097+        I return a servermap that has been updated in mode.
11098+
11099+        mode should be one of MODE_READ, MODE_WRITE, MODE_CHECK or
11100+        MODE_ANYTHING. See servermap.py for more on what these mean.
11101+        """
11102+        return self._do_serialized(self._get_servermap, mode)
11103+
11104 
11105hunk ./src/allmydata/mutable/filenode.py 585
11106+    def _get_servermap(self, mode):
11107+        """
11108+        I am a serialized twin to get_servermap.
11109+        """
11110         servermap = ServerMap()
11111hunk ./src/allmydata/mutable/filenode.py 590
11112-        d = self._update_servermap(servermap, mode=MODE_WRITE)
11113-        d.addCallback(lambda ignored: self._upload(new_contents, servermap))
11114+        return self._update_servermap(servermap, mode)
11115+
11116+
11117+    def _update_servermap(self, servermap, mode):
11118+        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11119+                             mode)
11120+        if self._history:
11121+            self._history.notify_mapupdate(u.get_status())
11122+        return u.update()
11123+
11124+
11125+    def set_version(self, version):
11126+        # I can be set in two ways:
11127+        #  1. When the node is created.
11128+        #  2. (for an existing share) when the Servermap is updated
11129+        #     before I am read.
11130+        assert version in (MDMF_VERSION, SDMF_VERSION)
11131+        self._protocol_version = version
11132+
11133+
11134+    def get_version(self):
11135+        return self._protocol_version
11136+
11137+
11138+    def _do_serialized(self, cb, *args, **kwargs):
11139+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11140+        # other serialized methods within this (or any other)
11141+        # MutableFileNode. The callable should be a bound method of this same
11142+        # MFN instance.
11143+        d = defer.Deferred()
11144+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11145+        # we need to put off d.callback until this Deferred is finished being
11146+        # processed. Otherwise the caller's subsequent activities (like,
11147+        # doing other things with this node) can cause reentrancy problems in
11148+        # the Deferred code itself
11149+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11150+        # add a log.err just in case something really weird happens, because
11151+        # self._serializer stays around forever, therefore we won't see the
11152+        # usual Unhandled Error in Deferred that would give us a hint.
11153+        self._serializer.addErrback(log.err)
11154         return d
11155 
11156 
11157hunk ./src/allmydata/mutable/filenode.py 633
11158+    def _upload(self, new_contents, servermap):
11159+        """
11160+        A MutableFileNode still has to have some way of getting
11161+        published initially, which is what I am here for. After that,
11162+        all publishing, updating, modifying and so on happens through
11163+        MutableFileVersions.
11164+        """
11165+        assert self._pubkey, "update_servermap must be called before publish"
11166+
11167+        p = Publish(self, self._storage_broker, servermap)
11168+        if self._history:
11169+            self._history.notify_publish(p.get_status(),
11170+                                         new_contents.get_size())
11171+        d = p.publish(new_contents)
11172+        d.addCallback(self._did_upload, new_contents.get_size())
11173+        return d
11174+
11175+
11176+    def _did_upload(self, res, size):
11177+        self._most_recent_size = size
11178+        return res
11179+
11180+
11181+class MutableFileVersion:
11182+    """
11183+    I represent a specific version (most likely the best version) of a
11184+    mutable file.
11185+
11186+    Since I implement IReadable, instances which hold a
11187+    reference to an instance of me are guaranteed the ability (absent
11188+    connection difficulties or unrecoverable versions) to read the file
11189+    that I represent. Depending on whether I was initialized with a
11190+    write capability or not, I may also provide callers the ability to
11191+    overwrite or modify the contents of the mutable file that I
11192+    reference.
11193+    """
11194+    implements(IMutableFileVersion)
11195+
11196+    def __init__(self,
11197+                 node,
11198+                 servermap,
11199+                 version,
11200+                 storage_index,
11201+                 storage_broker,
11202+                 readcap,
11203+                 writekey=None,
11204+                 write_secrets=None,
11205+                 history=None):
11206+
11207+        self._node = node
11208+        self._servermap = servermap
11209+        self._version = version
11210+        self._storage_index = storage_index
11211+        self._write_secrets = write_secrets
11212+        self._history = history
11213+        self._storage_broker = storage_broker
11214+
11215+        #assert isinstance(readcap, IURI)
11216+        self._readcap = readcap
11217+
11218+        self._writekey = writekey
11219+        self._serializer = defer.succeed(None)
11220+        self._size = None
11221+
11222+
11223+    def get_sequence_number(self):
11224+        """
11225+        Get the sequence number of the mutable version that I represent.
11226+        """
11227+        return 0
11228+
11229+
11230+    # TODO: Terminology?
11231+    def get_writekey(self):
11232+        """
11233+        I return a writekey or None if I don't have a writekey.
11234+        """
11235+        return self._writekey
11236+
11237+
11238+    def overwrite(self, new_contents):
11239+        """
11240+        I overwrite the contents of this mutable file version with the
11241+        data in new_contents.
11242+        """
11243+        assert not self.is_readonly()
11244+
11245+        return self._do_serialized(self._overwrite, new_contents)
11246+
11247+
11248+    def _overwrite(self, new_contents):
11249+        assert IMutableUploadable.providedBy(new_contents)
11250+        assert self._servermap.last_update_mode == MODE_WRITE
11251+
11252+        return self._upload(new_contents)
11253+
11254+
11255     def modify(self, modifier, backoffer=None):
11256         """I use a modifier callback to apply a change to the mutable file.
11257         I implement the following pseudocode::
11258hunk ./src/allmydata/mutable/filenode.py 770
11259         backoffer should not invoke any methods on this MutableFileNode
11260         instance, and it needs to be highly conscious of deadlock issues.
11261         """
11262+        assert not self.is_readonly()
11263+
11264         return self._do_serialized(self._modify, modifier, backoffer)
11265hunk ./src/allmydata/mutable/filenode.py 773
11266+
11267+
11268     def _modify(self, modifier, backoffer):
11269hunk ./src/allmydata/mutable/filenode.py 776
11270-        servermap = ServerMap()
11271         if backoffer is None:
11272             backoffer = BackoffAgent().delay
11273hunk ./src/allmydata/mutable/filenode.py 778
11274-        return self._modify_and_retry(servermap, modifier, backoffer, True)
11275-    def _modify_and_retry(self, servermap, modifier, backoffer, first_time):
11276-        d = self._modify_once(servermap, modifier, first_time)
11277+        return self._modify_and_retry(modifier, backoffer, True)
11278+
11279+
11280+    def _modify_and_retry(self, modifier, backoffer, first_time):
11281+        """
11282+        I try to apply modifier to the contents of this version of the
11283+        mutable file. If I succeed, I return an UploadResults instance
11284+        describing my success. If I fail, I try again after waiting for
11285+        a little bit.
11286+        """
11287+        log.msg("doing modify")
11288+        d = self._modify_once(modifier, first_time)
11289         def _retry(f):
11290             f.trap(UncoordinatedWriteError)
11291             d2 = defer.maybeDeferred(backoffer, self, f)
11292hunk ./src/allmydata/mutable/filenode.py 794
11293             d2.addCallback(lambda ignored:
11294-                           self._modify_and_retry(servermap, modifier,
11295+                           self._modify_and_retry(modifier,
11296                                                   backoffer, False))
11297             return d2
11298         d.addErrback(_retry)
11299hunk ./src/allmydata/mutable/filenode.py 799
11300         return d
11301-    def _modify_once(self, servermap, modifier, first_time):
11302-        d = self._update_servermap(servermap, MODE_WRITE)
11303-        d.addCallback(self._once_updated_download_best_version, servermap)
11304+
11305+
11306+    def _modify_once(self, modifier, first_time):
11307+        """
11308+        I attempt to apply a modifier to the contents of the mutable
11309+        file.
11310+        """
11311+        assert self._servermap.last_update_mode == MODE_WRITE
11312+
11313+        # download_to_data is serialized, so we have to call this to
11314+        # avoid deadlock.
11315+        d = self._try_to_download_data()
11316         def _apply(old_contents):
11317hunk ./src/allmydata/mutable/filenode.py 812
11318-            new_contents = modifier(old_contents, servermap, first_time)
11319+            new_contents = modifier(old_contents, self._servermap, first_time)
11320             if new_contents is None or new_contents == old_contents:
11321hunk ./src/allmydata/mutable/filenode.py 814
11322+                log.msg("no changes")
11323                 # no changes need to be made
11324                 if first_time:
11325                     return
11326hunk ./src/allmydata/mutable/filenode.py 822
11327                 # recovery when it observes UCWE, we need to do a second
11328                 # publish. See #551 for details. We'll basically loop until
11329                 # we managed an uncontested publish.
11330-                old_uploadable = MutableDataHandle(old_contents)
11331+                old_uploadable = MutableData(old_contents)
11332                 new_contents = old_uploadable
11333             precondition((IMutableUploadable.providedBy(new_contents) or
11334                           new_contents is None),
11335hunk ./src/allmydata/mutable/filenode.py 828
11336                          "Modifier function must return an IMutableUploadable "
11337                          "or None")
11338-            return self._upload(new_contents, servermap)
11339+            return self._upload(new_contents)
11340         d.addCallback(_apply)
11341         return d
11342 
11343hunk ./src/allmydata/mutable/filenode.py 832
11344-    def get_servermap(self, mode):
11345-        return self._do_serialized(self._get_servermap, mode)
11346-    def _get_servermap(self, mode):
11347-        servermap = ServerMap()
11348-        return self._update_servermap(servermap, mode)
11349-    def _update_servermap(self, servermap, mode):
11350-        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11351-                             mode)
11352-        if self._history:
11353-            self._history.notify_mapupdate(u.get_status())
11354-        return u.update()
11355 
11356hunk ./src/allmydata/mutable/filenode.py 833
11357-    def download_version(self, servermap, version, fetch_privkey=False):
11358-        return self._do_serialized(self._try_once_to_download_version,
11359-                                   servermap, version, fetch_privkey)
11360-    def _try_once_to_download_version(self, servermap, version,
11361-                                      fetch_privkey=False):
11362-        r = Retrieve(self, servermap, version, fetch_privkey)
11363+    def is_readonly(self):
11364+        """
11365+        I return True if this MutableFileVersion provides no write
11366+        access to the file that it encapsulates, and False if it
11367+        provides the ability to modify the file.
11368+        """
11369+        return self._writekey is None
11370+
11371+
11372+    def is_mutable(self):
11373+        """
11374+        I return True, since mutable files are always mutable by
11375+        somebody.
11376+        """
11377+        return True
11378+
11379+
11380+    def get_storage_index(self):
11381+        """
11382+        I return the storage index of the reference that I encapsulate.
11383+        """
11384+        return self._storage_index
11385+
11386+
11387+    def get_size(self):
11388+        """
11389+        I return the length, in bytes, of this readable object.
11390+        """
11391+        return self._servermap.size_of_version(self._version)
11392+
11393+
11394+    def download_to_data(self, fetch_privkey=False):
11395+        """
11396+        I return a Deferred that fires with the contents of this
11397+        readable object as a byte string.
11398+
11399+        """
11400+        c = consumer.MemoryConsumer()
11401+        d = self.read(c, fetch_privkey=fetch_privkey)
11402+        d.addCallback(lambda mc: "".join(mc.chunks))
11403+        return d
11404+
11405+
11406+    def _try_to_download_data(self):
11407+        """
11408+        I am an unserialized cousin of download_to_data; I am called
11409+        from the children of modify() to download the data associated
11410+        with this mutable version.
11411+        """
11412+        c = consumer.MemoryConsumer()
11413+        # modify will almost certainly write, so we need the privkey.
11414+        d = self._read(c, fetch_privkey=True)
11415+        d.addCallback(lambda mc: "".join(mc.chunks))
11416+        return d
11417+
11418+
11419+    def _update_servermap(self, mode=MODE_READ):
11420+        """
11421+        I update our Servermap according to my mode argument. I return a
11422+        Deferred that fires with None when this has finished. The
11423+        updated Servermap will be at self._servermap in that case.
11424+        """
11425+        d = self._node.get_servermap(mode)
11426+
11427+        def _got_servermap(servermap):
11428+            assert servermap.last_update_mode == mode
11429+
11430+            self._servermap = servermap
11431+        d.addCallback(_got_servermap)
11432+        return d
11433+
11434+
11435+    def read(self, consumer, offset=0, size=None, fetch_privkey=False):
11436+        """
11437+        I read a portion (possibly all) of the mutable file that I
11438+        reference into consumer.
11439+        """
11440+        return self._do_serialized(self._read, consumer, offset, size,
11441+                                   fetch_privkey)
11442+
11443+
11444+    def _read(self, consumer, offset=0, size=None, fetch_privkey=False):
11445+        """
11446+        I am the serialized companion of read.
11447+        """
11448+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
11449         if self._history:
11450             self._history.notify_retrieve(r.get_status())
11451hunk ./src/allmydata/mutable/filenode.py 921
11452-        d = r.download()
11453-        d.addCallback(self._downloaded_version)
11454+        d = r.download(consumer, offset, size)
11455+        return d
11456+
11457+
11458+    def _do_serialized(self, cb, *args, **kwargs):
11459+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11460+        # other serialized methods within this (or any other)
11461+        # MutableFileNode. The callable should be a bound method of this same
11462+        # MFN instance.
11463+        d = defer.Deferred()
11464+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11465+        # we need to put off d.callback until this Deferred is finished being
11466+        # processed. Otherwise the caller's subsequent activities (like,
11467+        # doing other things with this node) can cause reentrancy problems in
11468+        # the Deferred code itself
11469+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11470+        # add a log.err just in case something really weird happens, because
11471+        # self._serializer stays around forever, therefore we won't see the
11472+        # usual Unhandled Error in Deferred that would give us a hint.
11473+        self._serializer.addErrback(log.err)
11474         return d
11475hunk ./src/allmydata/mutable/filenode.py 942
11476-    def _downloaded_version(self, data):
11477-        self._most_recent_size = len(data)
11478-        return data
11479 
11480hunk ./src/allmydata/mutable/filenode.py 943
11481-    def upload(self, new_contents, servermap):
11482-        return self._do_serialized(self._upload, new_contents, servermap)
11483-    def _upload(self, new_contents, servermap):
11484-        assert self._pubkey, "update_servermap must be called before publish"
11485-        assert IMutableUploadable.providedBy(new_contents)
11486 
11487hunk ./src/allmydata/mutable/filenode.py 944
11488-        p = Publish(self, self._storage_broker, servermap)
11489+    def _upload(self, new_contents):
11490+        #assert self._pubkey, "update_servermap must be called before publish"
11491+        p = Publish(self._node, self._storage_broker, self._servermap)
11492         if self._history:
11493hunk ./src/allmydata/mutable/filenode.py 948
11494-            self._history.notify_publish(p.get_status(), new_contents.get_size())
11495+            self._history.notify_publish(p.get_status(),
11496+                                         new_contents.get_size())
11497         d = p.publish(new_contents)
11498         d.addCallback(self._did_upload, new_contents.get_size())
11499         return d
11500hunk ./src/allmydata/mutable/filenode.py 953
11501-    def _did_upload(self, res, size):
11502-        self._most_recent_size = size
11503-        return res
11504-
11505-
11506-    def set_version(self, version):
11507-        # I can be set in two ways:
11508-        #  1. When the node is created.
11509-        #  2. (for an existing share) when the Servermap is updated
11510-        #     before I am read.
11511-        assert version in (MDMF_VERSION, SDMF_VERSION)
11512-        self._protocol_version = version
11513 
11514 
11515hunk ./src/allmydata/mutable/filenode.py 955
11516-    def get_version(self):
11517-        return self._protocol_version
11518+    def _did_upload(self, res, size):
11519+        self._size = size
11520+        return res
11521}
11522[mutable/publish.py: enable segmented uploading for big files, change constants and wording, change MutableDataHandle to MutableData
11523Kevan Carstensen <kevan@isnotajoke.com>**20100717014549
11524 Ignore-this: f736c60c90ff09c98544af17146cf654
11525] {
11526hunk ./src/allmydata/mutable/publish.py 145
11527 
11528         self.data = newdata
11529         self.datalength = newdata.get_size()
11530+        if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
11531+            self._version = MDMF_VERSION
11532+        else:
11533+            self._version = SDMF_VERSION
11534 
11535         self.log("starting publish, datalen is %s" % self.datalength)
11536         self._status.set_size(self.datalength)
11537hunk ./src/allmydata/mutable/publish.py 1007
11538             old_position = self._filehandle.tell()
11539             # Seek to the end of the file by seeking 0 bytes from the
11540             # file's end
11541-            self._filehandle.seek(0, os.SEEK_END)
11542+            self._filehandle.seek(0, 2) # 2 == os.SEEK_END in 2.5+
11543             self._size = self._filehandle.tell()
11544             # Restore the previous position, in case this was called
11545             # after a read.
11546hunk ./src/allmydata/mutable/publish.py 1022
11547         """
11548         I return some data (up to length bytes) from my filehandle.
11549 
11550-        In most cases, I return length bytes. If I don't, it is because
11551-        length is longer than the distance between my current position
11552-        in the file that I represent and its end. In that case, I return
11553-        as many bytes as I can before going over the EOF.
11554+        In most cases, I return length bytes, but sometimes I won't --
11555+        for example, if I am asked to read beyond the end of a file, or
11556+        an error occurs.
11557         """
11558         return [self._filehandle.read(length)]
11559 
11560hunk ./src/allmydata/mutable/publish.py 1037
11561         self._filehandle.close()
11562 
11563 
11564-class MutableDataHandle(MutableFileHandle):
11565+class MutableData(MutableFileHandle):
11566     """
11567     I am a mutable uploadable built around a string, which I then cast
11568     into a StringIO and treat as a filehandle.
11569}
11570[immutable/filenode.py: fix broken implementation of #993 interfaces
11571Kevan Carstensen <kevan@isnotajoke.com>**20100717015049
11572 Ignore-this: 19ac8cf5d31ac88d4a1998ac342db004
11573] {
11574hunk ./src/allmydata/immutable/filenode.py 326
11575         immutable files can have only one version, we just return the
11576         current filenode.
11577         """
11578-        return self
11579+        return defer.succeed(self)
11580 
11581 
11582     def download_best_version(self):
11583hunk ./src/allmydata/immutable/filenode.py 412
11584 
11585     # IReadable, IFileNode, IFilesystemNode
11586     def get_best_readable_version(self):
11587-        return self
11588+        return defer.succeed(self)
11589 
11590 
11591     def download_best_version(self):
11592}
11593[mutable/retrieve.py: alter Retrieve so that it can download parts of mutable files
11594Kevan Carstensen <kevan@isnotajoke.com>**20100717015123
11595 Ignore-this: f49a6d3c05afc784aff0a4c63964a3e5
11596] {
11597hunk ./src/allmydata/mutable/retrieve.py 7
11598 from zope.interface import implements
11599 from twisted.internet import defer
11600 from twisted.python import failure
11601+from twisted.internet.interfaces import IPushProducer, IConsumer
11602 from foolscap.api import DeadReferenceError, eventually, fireEventually
11603 from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
11604                                  MDMF_VERSION, SDMF_VERSION
11605hunk ./src/allmydata/mutable/retrieve.py 86
11606     # times, and each will have a separate response chain. However the
11607     # Retrieve object will remain tied to a specific version of the file, and
11608     # will use a single ServerMap instance.
11609+    implements(IPushProducer)
11610 
11611     def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
11612                  verify=False):
11613hunk ./src/allmydata/mutable/retrieve.py 129
11614         # 3. When we are validating readers, we need to validate the
11615         #    signature on the prefix. Do we? We already do this in the
11616         #    servermap update?
11617-        #
11618-        # (just work on 1 and 2 for now, I guess)
11619         self._verify = False
11620         if verify:
11621             self._verify = True
11622hunk ./src/allmydata/mutable/retrieve.py 143
11623         self._status.set_size(datalength)
11624         self._status.set_encoding(k, N)
11625         self.readers = {}
11626+        self._paused = False
11627+        self._paused_deferred = None
11628+
11629 
11630     def get_status(self):
11631         return self._status
11632hunk ./src/allmydata/mutable/retrieve.py 157
11633             kwargs["facility"] = "tahoe.mutable.retrieve"
11634         return log.msg(*args, **kwargs)
11635 
11636-    def download(self):
11637+
11638+    ###################
11639+    # IPushProducer
11640+
11641+    def pauseProducing(self):
11642+        """
11643+        I am called by my download target if we have produced too much
11644+        data for it to handle. I make the downloader stop producing new
11645+        data until my resumeProducing method is called.
11646+        """
11647+        if self._paused:
11648+            return
11649+
11650+        # fired when the download is unpaused.
11651+        self._pause_deferred = defer.Deferred()
11652+        self._paused = True
11653+
11654+
11655+    def resumeProducing(self):
11656+        """
11657+        I am called by my download target once it is ready to begin
11658+        receiving data again.
11659+        """
11660+        if not self._paused:
11661+            return
11662+
11663+        self._paused = False
11664+        p = self._pause_deferred
11665+        self._pause_deferred = None
11666+        eventually(p.callback, None)
11667+
11668+
11669+    def _check_for_paused(self, res):
11670+        """
11671+        I am called just before a write to the consumer. I return a
11672+        Deferred that eventually fires with the data that is to be
11673+        written to the consumer. If the download has not been paused,
11674+        the Deferred fires immediately. Otherwise, the Deferred fires
11675+        when the downloader is unpaused.
11676+        """
11677+        if self._paused:
11678+            d = defer.Deferred()
11679+            self._pause_defered.addCallback(lambda ignored: d.callback(res))
11680+            return d
11681+        return defer.succeed(res)
11682+
11683+
11684+    def download(self, consumer=None, offset=0, size=None):
11685+        assert IConsumer.providedBy(consumer) or self._verify
11686+
11687+        if consumer:
11688+            self._consumer = consumer
11689+            # we provide IPushProducer, so streaming=True, per
11690+            # IConsumer.
11691+            self._consumer.registerProducer(self, streaming=True)
11692+
11693         self._done_deferred = defer.Deferred()
11694         self._started = time.time()
11695         self._status.set_status("Retrieving Shares")
11696hunk ./src/allmydata/mutable/retrieve.py 217
11697 
11698+        self._offset = offset
11699+        self._read_length = size
11700+
11701         # first, which servers can we use?
11702         versionmap = self.servermap.make_versionmap()
11703         shares = versionmap[self.verinfo]
11704hunk ./src/allmydata/mutable/retrieve.py 278
11705         assert len(self.remaining_sharemap) >= k
11706 
11707         self.log("starting download")
11708+        self._paused = False
11709         self._add_active_peers()
11710         # The download process beyond this is a state machine.
11711         # _add_active_peers will select the peers that we want to use
11712hunk ./src/allmydata/mutable/retrieve.py 324
11713 
11714         self._segment_decoder = codec.CRSDecoder()
11715         self._segment_decoder.set_params(segsize, k, n)
11716-        self._current_segment = 0
11717 
11718         if  not self._tail_data_size:
11719             self._tail_data_size = segsize
11720hunk ./src/allmydata/mutable/retrieve.py 349
11721             # So we don't have to do this later.
11722             self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
11723 
11724-        # If we have more than one segment, we are an SDMF file, which
11725-        # means that we need to validate the salts as we receive them.
11726-        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
11727-        self._salt_hash_tree[0] = IV # from the prefix.
11728+        # Our last task is to tell the downloader where to start and
11729+        # where to stop. We use three parameters for that:
11730+        #   - self._start_segment: the segment that we need to start
11731+        #     downloading from.
11732+        #   - self._current_segment: the next segment that we need to
11733+        #     download.
11734+        #   - self._last_segment: The last segment that we were asked to
11735+        #     download.
11736+        #
11737+        #  We say that the download is complete when
11738+        #  self._current_segment > self._last_segment. We use
11739+        #  self._start_segment and self._last_segment to know when to
11740+        #  strip things off of segments, and how much to strip.
11741+        if self._offset:
11742+            self.log("got offset: %d" % self._offset)
11743+            # our start segment is the first segment containing the
11744+            # offset we were given.
11745+            start = mathutil.div_ceil(self._offset,
11746+                                      self._segment_size)
11747+            # this gets us the first segment after self._offset. Then
11748+            # our start segment is the one before it.
11749+            start -= 1
11750+
11751+            assert start < self._num_segments
11752+            self._start_segment = start
11753+            self.log("got start segment: %d" % self._start_segment)
11754+        else:
11755+            self._start_segment = 0
11756+
11757+
11758+        if self._read_length:
11759+            # our end segment is the last segment containing part of the
11760+            # segment that we were asked to read.
11761+            self.log("got read length %d" % self._read_length)
11762+            end_data = self._offset + self._read_length
11763+            end = mathutil.div_ceil(end_data,
11764+                                    self._segment_size)
11765+            end -= 1
11766+            assert end < self._num_segments
11767+            self._last_segment = end
11768+            self.log("got end segment: %d" % self._last_segment)
11769+        else:
11770+            self._last_segment = self._num_segments - 1
11771 
11772hunk ./src/allmydata/mutable/retrieve.py 393
11773+        self._current_segment = self._start_segment
11774 
11775     def _add_active_peers(self):
11776         """
11777hunk ./src/allmydata/mutable/retrieve.py 637
11778         that this Retrieve is currently responsible for downloading.
11779         """
11780         assert len(self._active_readers) >= self._required_shares
11781-        if self._current_segment < self._num_segments:
11782+        if self._current_segment <= self._last_segment:
11783             d = self._process_segment(self._current_segment)
11784         else:
11785             d = defer.succeed(None)
11786hunk ./src/allmydata/mutable/retrieve.py 701
11787             d.addCallback(self._decrypt_segment)
11788             d.addErrback(self._validation_or_decoding_failed,
11789                          self._active_readers)
11790+            # check to see whether we've been paused before writing
11791+            # anything.
11792+            d.addCallback(self._check_for_paused)
11793             d.addCallback(self._set_segment)
11794             return d
11795         else:
11796hunk ./src/allmydata/mutable/retrieve.py 716
11797         target that is handling the file download.
11798         """
11799         self.log("got plaintext for segment %d" % self._current_segment)
11800-        self._plaintext += segment
11801+        if self._current_segment == self._start_segment:
11802+            # We're on the first segment. It's possible that we want
11803+            # only some part of the end of this segment, and that we
11804+            # just downloaded the whole thing to get that part. If so,
11805+            # we need to account for that and give the reader just the
11806+            # data that they want.
11807+            n = self._offset % self._segment_size
11808+            self.log("stripping %d bytes off of the first segment" % n)
11809+            self.log("original segment length: %d" % len(segment))
11810+            segment = segment[n:]
11811+            self.log("new segment length: %d" % len(segment))
11812+
11813+        if self._current_segment == self._last_segment and self._read_length is not None:
11814+            # We're on the last segment. It's possible that we only want
11815+            # part of the beginning of this segment, and that we
11816+            # downloaded the whole thing anyway. Make sure to give the
11817+            # caller only the portion of the segment that they want to
11818+            # receive.
11819+            extra = self._read_length
11820+            if self._start_segment != self._last_segment:
11821+                extra -= self._segment_size - \
11822+                            (self._offset % self._segment_size)
11823+            extra %= self._segment_size
11824+            self.log("original segment length: %d" % len(segment))
11825+            segment = segment[:extra]
11826+            self.log("new segment length: %d" % len(segment))
11827+            self.log("only taking %d bytes of the last segment" % extra)
11828+
11829+        if not self._verify:
11830+            self._consumer.write(segment)
11831+        else:
11832+            # we don't care about the plaintext if we are doing a verify.
11833+            segment = None
11834         self._current_segment += 1
11835 
11836 
11837hunk ./src/allmydata/mutable/retrieve.py 848
11838                                         reader.shnum,
11839                                         "corrupt hashes: %s" % e)
11840 
11841-        # TODO: Validate the salt, too.
11842         self.log('share %d is valid for segment %d' % (reader.shnum,
11843                                                        segnum))
11844         return {reader.shnum: (block, salt)}
11845hunk ./src/allmydata/mutable/retrieve.py 1014
11846               _done_deferred to errback.
11847         """
11848         self.log("checking for doneness")
11849-        if self._current_segment == self._num_segments:
11850+        if self._current_segment > self._last_segment:
11851             # No more segments to download, we're done.
11852             self.log("got plaintext, done")
11853             return self._done()
11854hunk ./src/allmydata/mutable/retrieve.py 1043
11855             ret = list(self._bad_shares)
11856             self.log("done verifying, found %d bad shares" % len(ret))
11857         else:
11858-            ret = self._plaintext
11859+            # TODO: upload status here?
11860+            ret = self._consumer
11861+            self._consumer.unregisterProducer()
11862         eventually(self._done_deferred.callback, ret)
11863 
11864 
11865hunk ./src/allmydata/mutable/retrieve.py 1066
11866                       "encoding %(k)d-of-%(n)d")
11867             args = {"have": self._current_segment,
11868                     "total": self._num_segments,
11869+                    "need": self._last_segment,
11870                     "k": self._required_shares,
11871                     "n": self._total_shares,
11872                     "bad": len(self._bad_shares)}
11873}
11874[change MutableDataHandle to MutableData in code.
11875Kevan Carstensen <kevan@isnotajoke.com>**20100717015210
11876 Ignore-this: f85ae425eabc21b47ad60bd6bf1f7dec
11877] {
11878hunk ./src/allmydata/dirnode.py 11
11879 from allmydata.mutable.common import NotWriteableError
11880 from allmydata.mutable.filenode import MutableFileNode
11881 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
11882-from allmydata.mutable.publish import MutableDataHandle
11883+from allmydata.mutable.publish import MutableData
11884 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
11885      IImmutableFileNode, IMutableFileNode, \
11886      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
11887hunk ./src/allmydata/dirnode.py 104
11888 
11889         del children[self.name]
11890         new_contents = self.node._pack_contents(children)
11891-        uploadable = MutableDataHandle(new_contents)
11892+        uploadable = MutableData(new_contents)
11893         return uploadable
11894 
11895 
11896hunk ./src/allmydata/dirnode.py 130
11897 
11898         children[name] = (child, metadata)
11899         new_contents = self.node._pack_contents(children)
11900-        uploadable = MutableDataHandle(new_contents)
11901+        uploadable = MutableData(new_contents)
11902         return uploadable
11903 
11904 
11905hunk ./src/allmydata/dirnode.py 175
11906 
11907             children[name] = (child, metadata)
11908         new_contents = self.node._pack_contents(children)
11909-        uploadable = MutableDataHandle(new_contents)
11910+        uploadable = MutableData(new_contents)
11911         return uploadable
11912 
11913 def _encrypt_rw_uri(writekey, rw_uri):
11914hunk ./src/allmydata/mutable/repairer.py 5
11915 from zope.interface import implements
11916 from twisted.internet import defer
11917 from allmydata.interfaces import IRepairResults, ICheckResults
11918-from allmydata.mutable.publish import MutableDataHandle
11919+from allmydata.mutable.publish import MutableData
11920 
11921 class RepairResults:
11922     implements(IRepairResults)
11923hunk ./src/allmydata/mutable/repairer.py 109
11924 
11925         d = self.node.download_version(smap, best_version, fetch_privkey=True)
11926         d.addCallback(lambda data:
11927-            MutableDataHandle(data))
11928+            MutableData(data))
11929         d.addCallback(self.node.upload, smap)
11930         d.addCallback(self.get_results, smap)
11931         return d
11932hunk ./src/allmydata/nodemaker.py 9
11933 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
11934 from allmydata.immutable.upload import Data
11935 from allmydata.mutable.filenode import MutableFileNode
11936-from allmydata.mutable.publish import MutableDataHandle
11937+from allmydata.mutable.publish import MutableData
11938 from allmydata.dirnode import DirectoryNode, pack_children
11939 from allmydata.unknown import UnknownNode
11940 from allmydata import uri
11941merger 0.0 (
11942merger 0.0 (
11943hunk ./src/allmydata/nodemaker.py 107
11944-                                     pack_children(n, initial_children),
11945+                                     MutableDataHandle(
11946+                                        pack_children(n, initial_children)),
11947merger 0.0 (
11948hunk ./src/allmydata/nodemaker.py 107
11949-                                     pack_children(n, initial_children))
11950+                                     pack_children(n, initial_children),
11951+                                     version)
11952hunk ./src/allmydata/nodemaker.py 107
11953-                                     pack_children(n, initial_children))
11954+                                     pack_children(initial_children, n.get_writekey()))
11955)
11956)
11957hunk ./src/allmydata/nodemaker.py 107
11958-                                     MutableDataHandle(
11959+                                     MutableData(
11960)
11961hunk ./src/allmydata/test/common.py 18
11962      DeepCheckResults, DeepCheckAndRepairResults
11963 from allmydata.mutable.common import CorruptShareError
11964 from allmydata.mutable.layout import unpack_header
11965-from allmydata.mutable.publish import MutableDataHandle
11966+from allmydata.mutable.publish import MutableData
11967 from allmydata.storage.server import storage_index_to_dir
11968 from allmydata.storage.mutable import MutableShareFile
11969 from allmydata.util import hashutil, log, fileutil, pollmixin
11970hunk ./src/allmydata/test/common.py 192
11971         return defer.succeed(self)
11972     def _get_initial_contents(self, contents):
11973         if contents is None:
11974-            return MutableDataHandle("")
11975+            return MutableData("")
11976 
11977         if IMutableUploadable.providedBy(contents):
11978             return contents
11979hunk ./src/allmydata/test/test_checker.py 11
11980 from allmydata.test.no_network import GridTestMixin
11981 from allmydata.immutable.upload import Data
11982 from allmydata.test.common_web import WebRenderingMixin
11983-from allmydata.mutable.publish import MutableDataHandle
11984+from allmydata.mutable.publish import MutableData
11985 
11986 class FakeClient:
11987     def get_storage_broker(self):
11988hunk ./src/allmydata/test/test_checker.py 292
11989             self.imm = c0.create_node_from_uri(ur.uri)
11990         d.addCallback(_stash_immutable)
11991         d.addCallback(lambda ign:
11992-            c0.create_mutable_file(MutableDataHandle("contents")))
11993+            c0.create_mutable_file(MutableData("contents")))
11994         def _stash_mutable(node):
11995             self.mut = node
11996         d.addCallback(_stash_mutable)
11997hunk ./src/allmydata/test/test_cli.py 12
11998 from allmydata.util import fileutil, hashutil, base32
11999 from allmydata import uri
12000 from allmydata.immutable import upload
12001-from allmydata.mutable.publish import MutableDataHandle
12002+from allmydata.mutable.publish import MutableData
12003 from allmydata.dirnode import normalize
12004 
12005 # Test that the scripts can be imported -- although the actual tests of their
12006hunk ./src/allmydata/test/test_cli.py 1975
12007         self.set_up_grid()
12008         c0 = self.g.clients[0]
12009         DATA = "data" * 100
12010-        DATA_uploadable = MutableDataHandle(DATA)
12011+        DATA_uploadable = MutableData(DATA)
12012         d = c0.create_mutable_file(DATA_uploadable)
12013         def _stash_uri(n):
12014             self.uri = n.get_uri()
12015hunk ./src/allmydata/test/test_cli.py 2078
12016                                                         convergence="")))
12017         d.addCallback(_stash_uri, "small")
12018         d.addCallback(lambda ign:
12019-            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
12020+            c0.create_mutable_file(MutableData(DATA+"1")))
12021         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
12022         d.addCallback(_stash_uri, "mutable")
12023 
12024hunk ./src/allmydata/test/test_deepcheck.py 9
12025 from twisted.internet import threads # CLI tests use deferToThread
12026 from allmydata.immutable import upload
12027 from allmydata.mutable.common import UnrecoverableFileError
12028-from allmydata.mutable.publish import MutableDataHandle
12029+from allmydata.mutable.publish import MutableData
12030 from allmydata.util import idlib
12031 from allmydata.util import base32
12032 from allmydata.scripts import runner
12033hunk ./src/allmydata/test/test_deepcheck.py 38
12034         self.basedir = "deepcheck/MutableChecker/good"
12035         self.set_up_grid()
12036         CONTENTS = "a little bit of data"
12037-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12038+        CONTENTS_uploadable = MutableData(CONTENTS)
12039         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12040         def _created(node):
12041             self.node = node
12042hunk ./src/allmydata/test/test_deepcheck.py 61
12043         self.basedir = "deepcheck/MutableChecker/corrupt"
12044         self.set_up_grid()
12045         CONTENTS = "a little bit of data"
12046-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12047+        CONTENTS_uploadable = MutableData(CONTENTS)
12048         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12049         def _stash_and_corrupt(node):
12050             self.node = node
12051hunk ./src/allmydata/test/test_deepcheck.py 99
12052         self.basedir = "deepcheck/MutableChecker/delete_share"
12053         self.set_up_grid()
12054         CONTENTS = "a little bit of data"
12055-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12056+        CONTENTS_uploadable = MutableData(CONTENTS)
12057         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12058         def _stash_and_delete(node):
12059             self.node = node
12060hunk ./src/allmydata/test/test_deepcheck.py 224
12061             self.root_uri = n.get_uri()
12062         d.addCallback(_created_root)
12063         d.addCallback(lambda ign:
12064-            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
12065+            c0.create_mutable_file(MutableData("mutable file contents")))
12066         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
12067         def _created_mutable(n):
12068             self.mutable = n
12069hunk ./src/allmydata/test/test_deepcheck.py 965
12070     def create_mangled(self, ignored, name):
12071         nodetype, mangletype = name.split("-", 1)
12072         if nodetype == "mutable":
12073-            mutable_uploadable = MutableDataHandle("mutable file contents")
12074+            mutable_uploadable = MutableData("mutable file contents")
12075             d = self.g.clients[0].create_mutable_file(mutable_uploadable)
12076             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
12077         elif nodetype == "large":
12078hunk ./src/allmydata/test/test_hung_server.py 10
12079 from allmydata.util.consumer import download_to_data
12080 from allmydata.immutable import upload
12081 from allmydata.mutable.common import UnrecoverableFileError
12082-from allmydata.mutable.publish import MutableDataHandle
12083+from allmydata.mutable.publish import MutableData
12084 from allmydata.storage.common import storage_index_to_dir
12085 from allmydata.test.no_network import GridTestMixin
12086 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
12087hunk ./src/allmydata/test/test_hung_server.py 96
12088         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
12089 
12090         if mutable:
12091-            uploadable = MutableDataHandle(mutable_plaintext)
12092+            uploadable = MutableData(mutable_plaintext)
12093             d = nm.create_mutable_file(uploadable)
12094             def _uploaded_mutable(node):
12095                 self.uri = node.get_uri()
12096hunk ./src/allmydata/test/test_mutable.py 27
12097      NotEnoughServersError, CorruptShareError
12098 from allmydata.mutable.retrieve import Retrieve
12099 from allmydata.mutable.publish import Publish, MutableFileHandle, \
12100-                                      MutableDataHandle
12101+                                      MutableData
12102 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
12103 from allmydata.mutable.layout import unpack_header, unpack_share, \
12104                                      MDMFSlotReadProxy
12105hunk ./src/allmydata/test/test_mutable.py 297
12106             d.addCallback(lambda smap: smap.dump(StringIO()))
12107             d.addCallback(lambda sio:
12108                           self.failUnless("3-of-10" in sio.getvalue()))
12109-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
12110+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
12111             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
12112             d.addCallback(lambda res: n.download_best_version())
12113             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12114hunk ./src/allmydata/test/test_mutable.py 304
12115             d.addCallback(lambda res: n.get_size_of_best_version())
12116             d.addCallback(lambda size:
12117                           self.failUnlessEqual(size, len("contents 1")))
12118-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12119+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12120             d.addCallback(lambda res: n.download_best_version())
12121             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12122             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12123hunk ./src/allmydata/test/test_mutable.py 308
12124-            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
12125+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
12126             d.addCallback(lambda res: n.download_best_version())
12127             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
12128             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
12129hunk ./src/allmydata/test/test_mutable.py 320
12130             # mapupdate-to-retrieve data caching (i.e. make the shares larger
12131             # than the default readsize, which is 2000 bytes). A 15kB file
12132             # will have 5kB shares.
12133-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
12134+            d.addCallback(lambda res: n.overwrite(MutableData("large size file" * 1000)))
12135             d.addCallback(lambda res: n.download_best_version())
12136             d.addCallback(lambda res:
12137                           self.failUnlessEqual(res, "large size file" * 1000))
12138hunk ./src/allmydata/test/test_mutable.py 343
12139             # to make them big enough to force the file to be uploaded
12140             # in more than one segment.
12141             big_contents = "contents1" * 100000 # about 900 KiB
12142-            big_contents_uploadable = MutableDataHandle(big_contents)
12143+            big_contents_uploadable = MutableData(big_contents)
12144             d.addCallback(lambda ignored:
12145                 n.overwrite(big_contents_uploadable))
12146             d.addCallback(lambda ignored:
12147hunk ./src/allmydata/test/test_mutable.py 355
12148             # segments, so that we make the downloader deal with
12149             # multiple segments.
12150             bigger_contents = "contents2" * 1000000 # about 9MiB
12151-            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
12152+            bigger_contents_uploadable = MutableData(bigger_contents)
12153             d.addCallback(lambda ignored:
12154                 n.overwrite(bigger_contents_uploadable))
12155             d.addCallback(lambda ignored:
12156hunk ./src/allmydata/test/test_mutable.py 368
12157 
12158 
12159     def test_create_with_initial_contents(self):
12160-        upload1 = MutableDataHandle("contents 1")
12161+        upload1 = MutableData("contents 1")
12162         d = self.nodemaker.create_mutable_file(upload1)
12163         def _created(n):
12164             d = n.download_best_version()
12165hunk ./src/allmydata/test/test_mutable.py 373
12166             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12167-            upload2 = MutableDataHandle("contents 2")
12168+            upload2 = MutableData("contents 2")
12169             d.addCallback(lambda res: n.overwrite(upload2))
12170             d.addCallback(lambda res: n.download_best_version())
12171             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12172hunk ./src/allmydata/test/test_mutable.py 385
12173 
12174     def test_create_mdmf_with_initial_contents(self):
12175         initial_contents = "foobarbaz" * 131072 # 900KiB
12176-        initial_contents_uploadable = MutableDataHandle(initial_contents)
12177+        initial_contents_uploadable = MutableData(initial_contents)
12178         d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
12179                                                version=MDMF_VERSION)
12180         def _created(n):
12181hunk ./src/allmydata/test/test_mutable.py 392
12182             d = n.download_best_version()
12183             d.addCallback(lambda data:
12184                 self.failUnlessEqual(data, initial_contents))
12185-            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
12186+            uploadable2 = MutableData(initial_contents + "foobarbaz")
12187             d.addCallback(lambda ignored:
12188                 n.overwrite(uploadable2))
12189             d.addCallback(lambda ignored:
12190hunk ./src/allmydata/test/test_mutable.py 413
12191             key = n.get_writekey()
12192             self.failUnless(isinstance(key, str), key)
12193             self.failUnlessEqual(len(key), 16) # AES key size
12194-            return MutableDataHandle(data)
12195+            return MutableData(data)
12196         d = self.nodemaker.create_mutable_file(_make_contents)
12197         def _created(n):
12198             return n.download_best_version()
12199hunk ./src/allmydata/test/test_mutable.py 429
12200             key = n.get_writekey()
12201             self.failUnless(isinstance(key, str), key)
12202             self.failUnlessEqual(len(key), 16)
12203-            return MutableDataHandle(data)
12204+            return MutableData(data)
12205         d = self.nodemaker.create_mutable_file(_make_contents,
12206                                                version=MDMF_VERSION)
12207         d.addCallback(lambda n:
12208hunk ./src/allmydata/test/test_mutable.py 441
12209 
12210     def test_create_with_too_large_contents(self):
12211         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
12212-        BIG_uploadable = MutableDataHandle(BIG)
12213+        BIG_uploadable = MutableData(BIG)
12214         d = self.nodemaker.create_mutable_file(BIG_uploadable)
12215         def _created(n):
12216hunk ./src/allmydata/test/test_mutable.py 444
12217-            other_BIG_uploadable = MutableDataHandle(BIG)
12218+            other_BIG_uploadable = MutableData(BIG)
12219             d = n.overwrite(other_BIG_uploadable)
12220             return d
12221         d.addCallback(_created)
12222hunk ./src/allmydata/test/test_mutable.py 460
12223     def test_modify(self):
12224         def _modifier(old_contents, servermap, first_time):
12225             new_contents = old_contents + "line2"
12226-            return MutableDataHandle(new_contents)
12227+            return MutableData(new_contents)
12228         def _non_modifier(old_contents, servermap, first_time):
12229hunk ./src/allmydata/test/test_mutable.py 462
12230-            return MutableDataHandle(old_contents)
12231+            return MutableData(old_contents)
12232         def _none_modifier(old_contents, servermap, first_time):
12233             return None
12234         def _error_modifier(old_contents, servermap, first_time):
12235hunk ./src/allmydata/test/test_mutable.py 469
12236             raise ValueError("oops")
12237         def _toobig_modifier(old_contents, servermap, first_time):
12238             new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
12239-            return MutableDataHandle(new_content)
12240+            return MutableData(new_content)
12241         calls = []
12242         def _ucw_error_modifier(old_contents, servermap, first_time):
12243             # simulate an UncoordinatedWriteError once
12244hunk ./src/allmydata/test/test_mutable.py 477
12245             if len(calls) <= 1:
12246                 raise UncoordinatedWriteError("simulated")
12247             new_contents = old_contents + "line3"
12248-            return MutableDataHandle(new_contents)
12249+            return MutableData(new_contents)
12250         def _ucw_error_non_modifier(old_contents, servermap, first_time):
12251             # simulate an UncoordinatedWriteError once, and don't actually
12252             # modify the contents on subsequent invocations
12253hunk ./src/allmydata/test/test_mutable.py 484
12254             calls.append(1)
12255             if len(calls) <= 1:
12256                 raise UncoordinatedWriteError("simulated")
12257-            return MutableDataHandle(old_contents)
12258+            return MutableData(old_contents)
12259 
12260         initial_contents = "line1"
12261hunk ./src/allmydata/test/test_mutable.py 487
12262-        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
12263+        d = self.nodemaker.create_mutable_file(MutableData(initial_contents))
12264         def _created(n):
12265             d = n.modify(_modifier)
12266             d.addCallback(lambda res: n.download_best_version())
12267hunk ./src/allmydata/test/test_mutable.py 548
12268 
12269     def test_modify_backoffer(self):
12270         def _modifier(old_contents, servermap, first_time):
12271-            return MutableDataHandle(old_contents + "line2")
12272+            return MutableData(old_contents + "line2")
12273         calls = []
12274         def _ucw_error_modifier(old_contents, servermap, first_time):
12275             # simulate an UncoordinatedWriteError once
12276hunk ./src/allmydata/test/test_mutable.py 555
12277             calls.append(1)
12278             if len(calls) <= 1:
12279                 raise UncoordinatedWriteError("simulated")
12280-            return MutableDataHandle(old_contents + "line3")
12281+            return MutableData(old_contents + "line3")
12282         def _always_ucw_error_modifier(old_contents, servermap, first_time):
12283             raise UncoordinatedWriteError("simulated")
12284         def _backoff_stopper(node, f):
12285hunk ./src/allmydata/test/test_mutable.py 570
12286         giveuper._delay = 0.1
12287         giveuper.factor = 1
12288 
12289-        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
12290+        d = self.nodemaker.create_mutable_file(MutableData("line1"))
12291         def _created(n):
12292             d = n.modify(_modifier)
12293             d.addCallback(lambda res: n.download_best_version())
12294hunk ./src/allmydata/test/test_mutable.py 620
12295             d.addCallback(lambda smap: smap.dump(StringIO()))
12296             d.addCallback(lambda sio:
12297                           self.failUnless("3-of-10" in sio.getvalue()))
12298-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
12299+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
12300             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
12301             d.addCallback(lambda res: n.download_best_version())
12302             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12303hunk ./src/allmydata/test/test_mutable.py 624
12304-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12305+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12306             d.addCallback(lambda res: n.download_best_version())
12307             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12308             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12309hunk ./src/allmydata/test/test_mutable.py 628
12310-            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
12311+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
12312             d.addCallback(lambda res: n.download_best_version())
12313             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
12314             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
12315hunk ./src/allmydata/test/test_mutable.py 646
12316         # publish a file and create shares, which can then be manipulated
12317         # later.
12318         self.CONTENTS = "New contents go here" * 1000
12319-        self.uploadable = MutableDataHandle(self.CONTENTS)
12320+        self.uploadable = MutableData(self.CONTENTS)
12321         self._storage = FakeStorage()
12322         self._nodemaker = make_nodemaker(self._storage)
12323         self._storage_broker = self._nodemaker.storage_broker
12324hunk ./src/allmydata/test/test_mutable.py 662
12325         # an MDMF file.
12326         # self.CONTENTS should have more than one segment.
12327         self.CONTENTS = "This is an MDMF file" * 100000
12328-        self.uploadable = MutableDataHandle(self.CONTENTS)
12329+        self.uploadable = MutableData(self.CONTENTS)
12330         self._storage = FakeStorage()
12331         self._nodemaker = make_nodemaker(self._storage)
12332         self._storage_broker = self._nodemaker.storage_broker
12333hunk ./src/allmydata/test/test_mutable.py 678
12334         # like publish_one, except that the result is guaranteed to be
12335         # an SDMF file
12336         self.CONTENTS = "This is an SDMF file" * 1000
12337-        self.uploadable = MutableDataHandle(self.CONTENTS)
12338+        self.uploadable = MutableData(self.CONTENTS)
12339         self._storage = FakeStorage()
12340         self._nodemaker = make_nodemaker(self._storage)
12341         self._storage_broker = self._nodemaker.storage_broker
12342hunk ./src/allmydata/test/test_mutable.py 696
12343                          "Contents 2",
12344                          "Contents 3a",
12345                          "Contents 3b"]
12346-        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
12347+        self.uploadables = [MutableData(d) for d in self.CONTENTS]
12348         self._copied_shares = {}
12349         self._storage = FakeStorage()
12350         self._nodemaker = make_nodemaker(self._storage)
12351hunk ./src/allmydata/test/test_mutable.py 826
12352         # create a new file, which is large enough to knock the privkey out
12353         # of the early part of the file
12354         LARGE = "These are Larger contents" * 200 # about 5KB
12355-        LARGE_uploadable = MutableDataHandle(LARGE)
12356+        LARGE_uploadable = MutableData(LARGE)
12357         d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
12358         def _created(large_fn):
12359             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
12360hunk ./src/allmydata/test/test_mutable.py 1842
12361 class MultipleEncodings(unittest.TestCase):
12362     def setUp(self):
12363         self.CONTENTS = "New contents go here"
12364-        self.uploadable = MutableDataHandle(self.CONTENTS)
12365+        self.uploadable = MutableData(self.CONTENTS)
12366         self._storage = FakeStorage()
12367         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
12368         self._storage_broker = self._nodemaker.storage_broker
12369hunk ./src/allmydata/test/test_mutable.py 1872
12370         s = self._storage
12371         s._peers = {} # clear existing storage
12372         p2 = Publish(fn2, self._storage_broker, None)
12373-        uploadable = MutableDataHandle(data)
12374+        uploadable = MutableData(data)
12375         d = p2.publish(uploadable)
12376         def _published(res):
12377             shares = s._peers
12378hunk ./src/allmydata/test/test_mutable.py 2049
12379         self._set_versions(target)
12380 
12381         def _modify(oldversion, servermap, first_time):
12382-            return MutableDataHandle(oldversion + " modified")
12383+            return MutableData(oldversion + " modified")
12384         d = self._fn.modify(_modify)
12385         d.addCallback(lambda res: self._fn.download_best_version())
12386         expected = self.CONTENTS[2] + " modified"
12387hunk ./src/allmydata/test/test_mutable.py 2175
12388         self.basedir = "mutable/Problems/test_publish_surprise"
12389         self.set_up_grid()
12390         nm = self.g.clients[0].nodemaker
12391-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12392+        d = nm.create_mutable_file(MutableData("contents 1"))
12393         def _created(n):
12394             d = defer.succeed(None)
12395             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12396hunk ./src/allmydata/test/test_mutable.py 2185
12397             d.addCallback(_got_smap1)
12398             # then modify the file, leaving the old map untouched
12399             d.addCallback(lambda res: log.msg("starting winning write"))
12400-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12401+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12402             # now attempt to modify the file with the old servermap. This
12403             # will look just like an uncoordinated write, in which every
12404             # single share got updated between our mapupdate and our publish
12405hunk ./src/allmydata/test/test_mutable.py 2194
12406                           self.shouldFail(UncoordinatedWriteError,
12407                                           "test_publish_surprise", None,
12408                                           n.upload,
12409-                                          MutableDataHandle("contents 2a"), self.old_map))
12410+                                          MutableData("contents 2a"), self.old_map))
12411             return d
12412         d.addCallback(_created)
12413         return d
12414hunk ./src/allmydata/test/test_mutable.py 2203
12415         self.basedir = "mutable/Problems/test_retrieve_surprise"
12416         self.set_up_grid()
12417         nm = self.g.clients[0].nodemaker
12418-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12419+        d = nm.create_mutable_file(MutableData("contents 1"))
12420         def _created(n):
12421             d = defer.succeed(None)
12422             d.addCallback(lambda res: n.get_servermap(MODE_READ))
12423hunk ./src/allmydata/test/test_mutable.py 2213
12424             d.addCallback(_got_smap1)
12425             # then modify the file, leaving the old map untouched
12426             d.addCallback(lambda res: log.msg("starting winning write"))
12427-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12428+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12429             # now attempt to retrieve the old version with the old servermap.
12430             # This will look like someone has changed the file since we
12431             # updated the servermap.
12432hunk ./src/allmydata/test/test_mutable.py 2241
12433         self.basedir = "mutable/Problems/test_unexpected_shares"
12434         self.set_up_grid()
12435         nm = self.g.clients[0].nodemaker
12436-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12437+        d = nm.create_mutable_file(MutableData("contents 1"))
12438         def _created(n):
12439             d = defer.succeed(None)
12440             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12441hunk ./src/allmydata/test/test_mutable.py 2253
12442                 self.g.remove_server(peer0)
12443                 # then modify the file, leaving the old map untouched
12444                 log.msg("starting winning write")
12445-                return n.overwrite(MutableDataHandle("contents 2"))
12446+                return n.overwrite(MutableData("contents 2"))
12447             d.addCallback(_got_smap1)
12448             # now attempt to modify the file with the old servermap. This
12449             # will look just like an uncoordinated write, in which every
12450hunk ./src/allmydata/test/test_mutable.py 2263
12451                           self.shouldFail(UncoordinatedWriteError,
12452                                           "test_surprise", None,
12453                                           n.upload,
12454-                                          MutableDataHandle("contents 2a"), self.old_map))
12455+                                          MutableData("contents 2a"), self.old_map))
12456             return d
12457         d.addCallback(_created)
12458         return d
12459hunk ./src/allmydata/test/test_mutable.py 2303
12460         d.addCallback(_break_peer0)
12461         # now "create" the file, using the pre-established key, and let the
12462         # initial publish finally happen
12463-        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
12464+        d.addCallback(lambda res: nm.create_mutable_file(MutableData("contents 1")))
12465         # that ought to work
12466         def _got_node(n):
12467             d = n.download_best_version()
12468hunk ./src/allmydata/test/test_mutable.py 2312
12469             def _break_peer1(res):
12470                 self.connection1.broken = True
12471             d.addCallback(_break_peer1)
12472-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12473+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12474             # that ought to work too
12475             d.addCallback(lambda res: n.download_best_version())
12476             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12477hunk ./src/allmydata/test/test_mutable.py 2344
12478         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
12479         self.g.break_server(peerids[0])
12480 
12481-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12482+        d = nm.create_mutable_file(MutableData("contents 1"))
12483         def _created(n):
12484             d = n.download_best_version()
12485             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12486hunk ./src/allmydata/test/test_mutable.py 2352
12487             def _break_second_server(res):
12488                 self.g.break_server(peerids[1])
12489             d.addCallback(_break_second_server)
12490-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12491+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12492             # that ought to work too
12493             d.addCallback(lambda res: n.download_best_version())
12494             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12495hunk ./src/allmydata/test/test_mutable.py 2371
12496         d = self.shouldFail(NotEnoughServersError,
12497                             "test_publish_all_servers_bad",
12498                             "Ran out of non-bad servers",
12499-                            nm.create_mutable_file, MutableDataHandle("contents"))
12500+                            nm.create_mutable_file, MutableData("contents"))
12501         return d
12502 
12503     def test_publish_no_servers(self):
12504hunk ./src/allmydata/test/test_mutable.py 2383
12505         d = self.shouldFail(NotEnoughServersError,
12506                             "test_publish_no_servers",
12507                             "Ran out of non-bad servers",
12508-                            nm.create_mutable_file, MutableDataHandle("contents"))
12509+                            nm.create_mutable_file, MutableData("contents"))
12510         return d
12511     test_publish_no_servers.timeout = 30
12512 
12513hunk ./src/allmydata/test/test_mutable.py 2401
12514         # we need some contents that are large enough to push the privkey out
12515         # of the early part of the file
12516         LARGE = "These are Larger contents" * 2000 # about 50KB
12517-        LARGE_uploadable = MutableDataHandle(LARGE)
12518+        LARGE_uploadable = MutableData(LARGE)
12519         d = nm.create_mutable_file(LARGE_uploadable)
12520         def _created(n):
12521             self.uri = n.get_uri()
12522hunk ./src/allmydata/test/test_mutable.py 2438
12523         self.set_up_grid(num_servers=20)
12524         nm = self.g.clients[0].nodemaker
12525         LARGE = "These are Larger contents" * 2000 # about 50KiB
12526-        LARGE_uploadable = MutableDataHandle(LARGE)
12527+        LARGE_uploadable = MutableData(LARGE)
12528         nm._node_cache = DevNullDictionary() # disable the nodecache
12529 
12530         d = nm.create_mutable_file(LARGE_uploadable)
12531hunk ./src/allmydata/test/test_mutable.py 2464
12532         self.set_up_grid(num_servers=20)
12533         nm = self.g.clients[0].nodemaker
12534         CONTENTS = "contents" * 2000
12535-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12536+        CONTENTS_uploadable = MutableData(CONTENTS)
12537         d = nm.create_mutable_file(CONTENTS_uploadable)
12538         def _created(node):
12539             self._node = node
12540hunk ./src/allmydata/test/test_mutable.py 2565
12541 class DataHandle(unittest.TestCase):
12542     def setUp(self):
12543         self.test_data = "Test Data" * 50000
12544-        self.uploadable = MutableDataHandle(self.test_data)
12545+        self.uploadable = MutableData(self.test_data)
12546 
12547 
12548     def test_datahandle_read(self):
12549hunk ./src/allmydata/test/test_sftp.py 84
12550         return d
12551 
12552     def _set_up_tree(self):
12553-        u = publish.MutableDataHandle("mutable file contents")
12554+        u = publish.MutableData("mutable file contents")
12555         d = self.client.create_mutable_file(u)
12556         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
12557         def _created_mutable(n):
12558hunk ./src/allmydata/test/test_system.py 22
12559 from allmydata.monitor import Monitor
12560 from allmydata.mutable.common import NotWriteableError
12561 from allmydata.mutable import layout as mutable_layout
12562-from allmydata.mutable.publish import MutableDataHandle
12563+from allmydata.mutable.publish import MutableData
12564 from foolscap.api import DeadReferenceError
12565 from twisted.python.failure import Failure
12566 from twisted.web.client import getPage
12567hunk ./src/allmydata/test/test_system.py 460
12568     def test_mutable(self):
12569         self.basedir = "system/SystemTest/test_mutable"
12570         DATA = "initial contents go here."  # 25 bytes % 3 != 0
12571-        DATA_uploadable = MutableDataHandle(DATA)
12572+        DATA_uploadable = MutableData(DATA)
12573         NEWDATA = "new contents yay"
12574hunk ./src/allmydata/test/test_system.py 462
12575-        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
12576+        NEWDATA_uploadable = MutableData(NEWDATA)
12577         NEWERDATA = "this is getting old"
12578hunk ./src/allmydata/test/test_system.py 464
12579-        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
12580+        NEWERDATA_uploadable = MutableData(NEWERDATA)
12581 
12582         d = self.set_up_nodes(use_key_generator=True)
12583 
12584hunk ./src/allmydata/test/test_system.py 642
12585         def _check_empty_file(res):
12586             # make sure we can create empty files, this usually screws up the
12587             # segsize math
12588-            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
12589+            d1 = self.clients[2].create_mutable_file(MutableData(""))
12590             d1.addCallback(lambda newnode: newnode.download_best_version())
12591             d1.addCallback(lambda res: self.failUnlessEqual("", res))
12592             return d1
12593hunk ./src/allmydata/test/test_system.py 674
12594 
12595         d.addCallback(check_kg_poolsize, 0)
12596         d.addCallback(lambda junk:
12597-            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
12598+            self.clients[3].create_mutable_file(MutableData('hello, world')))
12599         d.addCallback(check_kg_poolsize, -1)
12600         d.addCallback(lambda junk: self.clients[3].create_dirnode())
12601         d.addCallback(check_kg_poolsize, -2)
12602hunk ./src/allmydata/test/test_web.py 3184
12603             self.uris[which] = n.get_uri()
12604             assert isinstance(self.uris[which], str)
12605         d.addCallback(lambda ign:
12606-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
12607+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12608         d.addCallback(_stash_mutable_uri, "corrupt")
12609         d.addCallback(lambda ign:
12610                       c0.upload(upload.Data("literal", convergence="")))
12611hunk ./src/allmydata/test/test_web.py 3331
12612             self.uris[which] = n.get_uri()
12613             assert isinstance(self.uris[which], str)
12614         d.addCallback(lambda ign:
12615-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
12616+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12617         d.addCallback(_stash_mutable_uri, "corrupt")
12618 
12619         def _compute_fileurls(ignored):
12620hunk ./src/allmydata/test/test_web.py 3994
12621             self.uris[which] = n.get_uri()
12622             assert isinstance(self.uris[which], str)
12623         d.addCallback(lambda ign:
12624-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
12625+            c0.create_mutable_file(publish.MutableData(DATA+"2")))
12626         d.addCallback(_stash_mutable_uri, "mutable")
12627 
12628         def _compute_fileurls(ignored):
12629hunk ./src/allmydata/test/test_web.py 4094
12630         d.addCallback(_stash_uri, "small")
12631 
12632         d.addCallback(lambda ign:
12633-            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
12634+            c0.create_mutable_file(publish.MutableData("mutable")))
12635         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
12636         d.addCallback(_stash_uri, "mutable")
12637 
12638}
12639[tests: fix tests that were broken by #993
12640Kevan Carstensen <kevan@isnotajoke.com>**20100717015230
12641 Ignore-this: f0ace6538c6d824b7fe271c40a7ebf8d
12642] {
12643hunk ./src/allmydata/test/common.py 152
12644         consumer.write(data[start:end])
12645         return consumer
12646 
12647+
12648+    def get_best_readable_version(self):
12649+        return defer.succeed(self)
12650+
12651+
12652+    download_best_version = download_to_data
12653+
12654+
12655+    def download_to_data(self):
12656+        return download_to_data(self)
12657+
12658+
12659+    def get_size_of_best_version(self):
12660+        return defer.succeed(self.get_size)
12661+
12662+
12663 def make_chk_file_cap(size):
12664     return uri.CHKFileURI(key=os.urandom(16),
12665                           uri_extension_hash=os.urandom(32),
12666hunk ./src/allmydata/test/common.py 318
12667         return d
12668 
12669     def download_best_version(self):
12670+        return defer.succeed(self._download_best_version())
12671+
12672+
12673+    def _download_best_version(self, ignored=None):
12674         if isinstance(self.my_uri, uri.LiteralFileURI):
12675hunk ./src/allmydata/test/common.py 323
12676-            return defer.succeed(self.my_uri.data)
12677+            return self.my_uri.data
12678         if self.storage_index not in self.all_contents:
12679hunk ./src/allmydata/test/common.py 325
12680-            return defer.fail(NotEnoughSharesError(None, 0, 3))
12681-        return defer.succeed(self.all_contents[self.storage_index])
12682+            raise NotEnoughSharesError(None, 0, 3)
12683+        return self.all_contents[self.storage_index]
12684+
12685 
12686     def overwrite(self, new_contents):
12687         if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
12688hunk ./src/allmydata/test/common.py 352
12689         self.all_contents[self.storage_index] = new_data
12690         return None
12691 
12692+    # As actually implemented, MutableFilenode and MutableFileVersion
12693+    # are distinct. However, nothing in the webapi uses (yet) that
12694+    # distinction -- it just uses the unified download interface
12695+    # provided by get_best_readable_version and read. When we start
12696+    # doing cooler things like LDMF, we will want to revise this code to
12697+    # be less simplistic.
12698+    def get_best_readable_version(self):
12699+        return defer.succeed(self)
12700+
12701+
12702+    def read(self, consumer, offset=0, size=None):
12703+        data = self._download_best_version()
12704+        if size:
12705+            data = data[offset:offset+size]
12706+        consumer.write(data)
12707+        return defer.succeed(consumer)
12708+
12709+
12710 def make_mutable_file_cap():
12711     return uri.WriteableSSKFileURI(writekey=os.urandom(16),
12712                                    fingerprint=os.urandom(32))
12713hunk ./src/allmydata/test/test_filenode.py 98
12714         def _check_segment(res):
12715             self.failUnlessEqual(res, DATA[1:1+5])
12716         d.addCallback(_check_segment)
12717-        d.addCallback(lambda ignored:
12718-            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
12719+        d.addCallback(lambda ignored: fn1.get_best_readable_version())
12720+        d.addCallback(lambda fn2: self.failUnlessEqual(fn1, fn2))
12721         d.addCallback(lambda ignored:
12722             fn1.get_size_of_best_version())
12723         d.addCallback(lambda size:
12724hunk ./src/allmydata/test/test_immutable.py 168
12725 
12726 
12727     def test_get_best_readable_version(self):
12728-        n = self.n.get_best_readable_version()
12729-        self.failUnlessEqual(n, self.n)
12730+        d = self.n.get_best_readable_version()
12731+        d.addCallback(lambda n2:
12732+            self.failUnlessEqual(n2, self.n))
12733+        return d
12734 
12735     def test_get_size_of_best_version(self):
12736         d = self.n.get_size_of_best_version()
12737hunk ./src/allmydata/test/test_mutable.py 8
12738 from twisted.internet import defer, reactor
12739 from allmydata import uri, client
12740 from allmydata.nodemaker import NodeMaker
12741-from allmydata.util import base32
12742+from allmydata.util import base32, consumer
12743 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
12744      ssk_pubkey_fingerprint_hash
12745hunk ./src/allmydata/test/test_mutable.py 11
12746+from allmydata.util.deferredutil import gatherResults
12747 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
12748      NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
12749 from allmydata.monitor import Monitor
12750hunk ./src/allmydata/test/test_mutable.py 1000
12751         if version is None:
12752             version = servermap.best_recoverable_version()
12753         r = Retrieve(self._fn, servermap, version)
12754-        return r.download()
12755+        c = consumer.MemoryConsumer()
12756+        d = r.download(consumer=c)
12757+        d.addCallback(lambda mc: "".join(mc.chunks))
12758+        return d
12759+
12760 
12761     def test_basic(self):
12762         d = self.make_servermap()
12763hunk ./src/allmydata/test/test_mutable.py 1263
12764                             in str(servermap.problems[0]))
12765             ver = servermap.best_recoverable_version()
12766             r = Retrieve(self._fn, servermap, ver)
12767-            return r.download()
12768+            c = consumer.MemoryConsumer()
12769+            return r.download(c)
12770         d.addCallback(_do_retrieve)
12771hunk ./src/allmydata/test/test_mutable.py 1266
12772+        d.addCallback(lambda mc: "".join(mc.chunks))
12773         d.addCallback(lambda new_contents:
12774                       self.failUnlessEqual(new_contents, self.CONTENTS))
12775         return d
12776}
12777[test/test_immutable.py: add tests for #993-related modifications
12778Kevan Carstensen <kevan@isnotajoke.com>**20100717015402
12779 Ignore-this: d94ad98bd0d322ead85af2e0aa95be38
12780] hunk ./src/allmydata/test/test_mutable.py 2607
12781         start = chunk_size
12782         end = chunk_size * 2
12783         self.failUnlessEqual("".join(more_data), self.test_data[start:end])
12784+
12785+
12786+class Version(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
12787+    def setUp(self):
12788+        GridTestMixin.setUp(self)
12789+        self.basedir = self.mktemp()
12790+        self.set_up_grid()
12791+        self.c = self.g.clients[0]
12792+        self.nm = self.c.nodemaker
12793+        self.data = "test data" * 100000 # about 900 KiB; MDMF
12794+        self.small_data = "test data" * 10 # about 90 B; SDMF
12795+        return self.do_upload()
12796+
12797+
12798+    def do_upload(self):
12799+        d1 = self.nm.create_mutable_file(MutableData(self.data),
12800+                                         version=MDMF_VERSION)
12801+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
12802+        dl = gatherResults([d1, d2])
12803+        def _then((n1, n2)):
12804+            assert isinstance(n1, MutableFileNode)
12805+            assert isinstance(n2, MutableFileNode)
12806+
12807+            self.mdmf_node = n1
12808+            self.sdmf_node = n2
12809+        dl.addCallback(_then)
12810+        return dl
12811+
12812+
12813+    def test_get_readonly_mutable_version(self):
12814+        # Attempting to get a mutable version of a mutable file from a
12815+        # filenode initialized with a readcap should return a readonly
12816+        # version of that same node.
12817+        ro = self.mdmf_node.get_readonly()
12818+        d = ro.get_best_mutable_version()
12819+        d.addCallback(lambda version:
12820+            self.failUnless(version.is_readonly()))
12821+        d.addCallback(lambda ignored:
12822+            self.sdmf_node.get_readonly())
12823+        d.addCallback(lambda version:
12824+            self.failUnless(version.is_readonly()))
12825+        return d
12826+
12827+
12828+    def test_get_sequence_number(self):
12829+        d = self.mdmf_node.get_best_readable_version()
12830+        d.addCallback(lambda bv:
12831+            self.failUnlessEqual(bv.get_sequence_number(), 0))
12832+        d.addCallback(lambda ignored:
12833+            self.sdmf_node.get_best_readable_version())
12834+        d.addCallback(lambda bv:
12835+            self.failUnlessEqual(bv.get_sequence_number(), 0))
12836+        # Now update. The sequence number in both cases should be 1 in
12837+        # both cases.
12838+        def _do_update(ignored):
12839+            new_data = MutableData("foo bar baz" * 100000)
12840+            new_small_data = MutableData("foo bar baz" * 10)
12841+            d1 = self.mdmf_node.overwrite(new_data)
12842+            d2 = self.sdmf_node.overwrite(new_small_data)
12843+            dl = gatherResults([d1, d2])
12844+            return dl
12845+        d.addCallback(_do_update)
12846+        d.addCallback(lambda ignored:
12847+            self.mdmf_node.get_best_readable_version())
12848+        d.addCallback(lambda bv:
12849+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12850+        d.addCallback(lambda ignored:
12851+            self.sdmf_node.get_best_readable_version())
12852+        d.addCallback(lambda bv:
12853+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12854+        return d
12855+
12856+
12857+    def test_get_writekey(self):
12858+        d = self.mdmf_node.get_best_mutable_version()
12859+        d.addCallback(lambda bv:
12860+            self.failUnlessEqual(bv.get_writekey(),
12861+                                 self.mdmf_node.get_writekey()))
12862+        d.addCallback(lambda ignored:
12863+            self.sdmf_node.get_best_mutable_version())
12864+        d.addCallback(lambda bv:
12865+            self.failUnlessEqual(bv.get_writekey(),
12866+                                 self.sdmf_node.get_writekey()))
12867+        return d
12868+
12869+
12870+    def test_get_storage_index(self):
12871+        d = self.mdmf_node.get_best_mutable_version()
12872+        d.addCallback(lambda bv:
12873+            self.failUnlessEqual(bv.get_storage_index(),
12874+                                 self.mdmf_node.get_storage_index()))
12875+        d.addCallback(lambda ignored:
12876+            self.sdmf_node.get_best_mutable_version())
12877+        d.addCallback(lambda bv:
12878+            self.failUnlessEqual(bv.get_storage_index(),
12879+                                 self.sdmf_node.get_storage_index()))
12880+        return d
12881+
12882+
12883+    def test_get_readonly_version(self):
12884+        d = self.mdmf_node.get_best_readable_version()
12885+        d.addCallback(lambda bv:
12886+            self.failUnless(bv.is_readonly()))
12887+        d.addCallback(lambda ignored:
12888+            self.sdmf_node.get_best_readable_version())
12889+        d.addCallback(lambda bv:
12890+            self.failUnless(bv.is_readonly()))
12891+        return d
12892+
12893+
12894+    def test_get_mutable_version(self):
12895+        d = self.mdmf_node.get_best_mutable_version()
12896+        d.addCallback(lambda bv:
12897+            self.failIf(bv.is_readonly()))
12898+        d.addCallback(lambda ignored:
12899+            self.sdmf_node.get_best_mutable_version())
12900+        d.addCallback(lambda bv:
12901+            self.failIf(bv.is_readonly()))
12902+        return d
12903+
12904+
12905+    def test_toplevel_overwrite(self):
12906+        new_data = MutableData("foo bar baz" * 100000)
12907+        new_small_data = MutableData("foo bar baz" * 10)
12908+        d = self.mdmf_node.overwrite(new_data)
12909+        d.addCallback(lambda ignored:
12910+            self.mdmf_node.download_best_version())
12911+        d.addCallback(lambda data:
12912+            self.failUnlessEqual(data, "foo bar baz" * 100000))
12913+        d.addCallback(lambda ignored:
12914+            self.sdmf_node.overwrite(new_small_data))
12915+        d.addCallback(lambda ignored:
12916+            self.sdmf_node.download_best_version())
12917+        d.addCallback(lambda data:
12918+            self.failUnlessEqual(data, "foo bar baz" * 10))
12919+        return d
12920+
12921+
12922+    def test_toplevel_modify(self):
12923+        def modifier(old_contents, servermap, first_time):
12924+            return MutableData(old_contents + "modified")
12925+        d = self.mdmf_node.modify(modifier)
12926+        d.addCallback(lambda ignored:
12927+            self.mdmf_node.download_best_version())
12928+        d.addCallback(lambda data:
12929+            self.failUnlessIn("modified", data))
12930+        d.addCallback(lambda ignored:
12931+            self.sdmf_node.modify(modifier))
12932+        d.addCallback(lambda ignored:
12933+            self.sdmf_node.download_best_version())
12934+        d.addCallback(lambda data:
12935+            self.failUnlessIn("modified", data))
12936+        return d
12937+
12938+
12939+    def test_version_modify(self):
12940+        # TODO: When we can publish multiple versions, alter this test
12941+        # to modify a version other than the best usable version, then
12942+        # test to see that the best recoverable version is that.
12943+        def modifier(old_contents, servermap, first_time):
12944+            return MutableData(old_contents + "modified")
12945+        d = self.mdmf_node.modify(modifier)
12946+        d.addCallback(lambda ignored:
12947+            self.mdmf_node.download_best_version())
12948+        d.addCallback(lambda data:
12949+            self.failUnlessIn("modified", data))
12950+        d.addCallback(lambda ignored:
12951+            self.sdmf_node.modify(modifier))
12952+        d.addCallback(lambda ignored:
12953+            self.sdmf_node.download_best_version())
12954+        d.addCallback(lambda data:
12955+            self.failUnlessIn("modified", data))
12956+        return d
12957+
12958+
12959+    def test_download_version(self):
12960+        # This will only pass once we get the ability to publish
12961+        # multiple recoverable versions.
12962+        self.failUnless(False)
12963+
12964+
12965+    def test_partial_read(self):
12966+        # read only a few bytes at a time, and see that the results are
12967+        # what we expect.
12968+        d = self.mdmf_node.get_best_readable_version()
12969+        def _read_data(version):
12970+            c = consumer.MemoryConsumer()
12971+            d2 = defer.succeed(None)
12972+            for i in xrange(0, len(self.data), 10000):
12973+                d2.addCallback(lambda ignored, i=i: version.read(c, i, 10000))
12974+            d2.addCallback(lambda ignored:
12975+                self.failUnlessEqual(self.data, "".join(c.chunks)))
12976+            return d2
12977+        d.addCallback(_read_data)
12978+        return d
12979+
12980+
12981+    def test_read(self):
12982+        d = self.mdmf_node.get_best_readable_version()
12983+        def _read_data(version):
12984+            c = consumer.MemoryConsumer()
12985+            d2 = defer.succeed(None)
12986+            d2.addCallback(lambda ignored: version.read(c))
12987+            d2.addCallback(lambda ignored:
12988+                self.failUnlessEqual("".join(c.chunks), self.data))
12989+            return d2
12990+        d.addCallback(_read_data)
12991+        return d
12992+
12993+
12994+    def test_download_best_version(self):
12995+        d = self.mdmf_node.download_best_version()
12996+        d.addCallback(lambda data:
12997+            self.failUnlessEqual(data, self.data))
12998+        d.addCallback(lambda ignored:
12999+            self.sdmf_node.download_best_version())
13000+        d.addCallback(lambda data:
13001+            self.failUnlessEqual(data, self.small_data))
13002+        return d
13003[web/filenode.py: alter download code to use the new #993 interface.
13004Kevan Carstensen <kevan@isnotajoke.com>**20100717015426
13005 Ignore-this: 4e8b89f9b616755f1f644ecea4cda0bb
13006] {
13007hunk ./src/allmydata/web/filenode.py 167
13008             # properly. So we assume that at least the browser will agree
13009             # with itself, and echo back the same bytes that we were given.
13010             filename = get_arg(req, "filename", self.name) or "unknown"
13011-            if self.node.is_mutable():
13012-                # some day: d = self.node.get_best_version()
13013-                d = makeMutableDownloadable(self.node)
13014-            else:
13015-                d = defer.succeed(self.node)
13016+            d = self.node.get_best_readable_version()
13017             d.addCallback(lambda dn: FileDownloader(dn, filename))
13018             return d
13019         if t == "json":
13020hunk ./src/allmydata/web/filenode.py 191
13021         if t:
13022             raise WebError("GET file: bad t=%s" % t)
13023         filename = get_arg(req, "filename", self.name) or "unknown"
13024-        if self.node.is_mutable():
13025-            # some day: d = self.node.get_best_version()
13026-            d = makeMutableDownloadable(self.node)
13027-        else:
13028-            d = defer.succeed(self.node)
13029+        d = self.node.get_best_readable_version()
13030         d.addCallback(lambda dn: FileDownloader(dn, filename))
13031         return d
13032 
13033hunk ./src/allmydata/web/filenode.py 285
13034         d.addCallback(lambda res: self.node.get_uri())
13035         return d
13036 
13037-class MutableDownloadable:
13038-    #implements(IDownloadable)
13039-    def __init__(self, size, node):
13040-        self.size = size
13041-        self.node = node
13042-    def get_size(self):
13043-        return self.size
13044-    def is_mutable(self):
13045-        return True
13046-    def read(self, consumer, offset=0, size=None):
13047-        d = self.node.download_best_version()
13048-        d.addCallback(self._got_data, consumer, offset, size)
13049-        return d
13050-    def _got_data(self, contents, consumer, offset, size):
13051-        start = offset
13052-        if size is not None:
13053-            end = offset+size
13054-        else:
13055-            end = self.size
13056-        # SDMF: we can write the whole file in one big chunk
13057-        consumer.write(contents[start:end])
13058-        return consumer
13059-
13060-def makeMutableDownloadable(n):
13061-    d = defer.maybeDeferred(n.get_size_of_best_version)
13062-    d.addCallback(MutableDownloadable, n)
13063-    return d
13064 
13065 class FileDownloader(rend.Page):
13066     # since we override the rendering process (to let the tahoe Downloader
13067}
13068[test/common.py: remove FileTooLargeErrors that tested for an SDMF limitation that no longer exists
13069Kevan Carstensen <kevan@isnotajoke.com>**20100717015501
13070 Ignore-this: 8b17689d9391a4870a327c1d7c0b3225
13071] {
13072hunk ./src/allmydata/test/common.py 198
13073         self.init_from_cap(make_mutable_file_cap())
13074     def create(self, contents, key_generator=None, keysize=None):
13075         initial_contents = self._get_initial_contents(contents)
13076-        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
13077-            raise FileTooLargeError("SDMF is limited to one segment, and "
13078-                                    "%d > %d" % (initial_contents.get_size(),
13079-                                                 self.MUTABLE_SIZELIMIT))
13080         data = initial_contents.read(initial_contents.get_size())
13081         data = "".join(data)
13082         self.all_contents[self.storage_index] = data
13083hunk ./src/allmydata/test/common.py 326
13084 
13085 
13086     def overwrite(self, new_contents):
13087-        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
13088-            raise FileTooLargeError("SDMF is limited to one segment, and "
13089-                                    "%d > %d" % (new_contents.get_size(),
13090-                                                 self.MUTABLE_SIZELIMIT))
13091         assert not self.is_readonly()
13092         new_data = new_contents.read(new_contents.get_size())
13093         new_data = "".join(new_data)
13094}
13095[nodemaker.py: resolve a conflict introduced in one of the 1.7.1 patches
13096Kevan Carstensen <kevan@isnotajoke.com>**20100720213109
13097 Ignore-this: 4e7d4e611f4cdf04824e9040167aa11
13098] hunk ./src/allmydata/nodemaker.py 107
13099                          "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
13100             node.raise_error()
13101         d = self.create_mutable_file(lambda n:
13102-                                     pack_children(n, initial_children))
13103+                                     MutableData(pack_children(initial_children,
13104+                                                    n.get_writekey())),
13105+                                     version)
13106         d.addCallback(self._create_dirnode)
13107         return d
13108 
13109[frontends/sftpd.py: fix conflicts with trunk
13110Kevan Carstensen <kevan@isnotajoke.com>**20100727224651
13111 Ignore-this: 5636e7a27162bf3ca14d6c9dc07a015
13112] {
13113hunk ./src/allmydata/frontends/sftpd.py 664
13114         else:
13115             assert IFileNode.providedBy(filenode), filenode
13116 
13117-            # TODO: use download interface described in #993 when implemented.
13118-            if filenode.is_mutable():
13119-                self.async.addCallback(lambda ign: filenode.download_best_version())
13120-                def _downloaded(data):
13121-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
13122-                    self.consumer.write(data)
13123-                    self.consumer.finish()
13124-                    return None
13125-                self.async.addCallback(_downloaded)
13126-            else:
13127-                download_size = filenode.get_size()
13128-                assert download_size is not None, "download_size is None"
13129+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
13130+
13131+            def _read(version):
13132+                if noisy: self.log("_read", level=NOISY)
13133+                download_size = version.get_size()
13134+                assert download_size is not None
13135+
13136                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
13137 
13138hunk ./src/allmydata/frontends/sftpd.py 673
13139-                if noisy: self.log("_read", level=NOISY)
13140                 version.read(self.consumer, 0, None)
13141             self.async.addCallback(_read)
13142 
13143}
13144[interfaces.py: Create an IWritable interface
13145Kevan Carstensen <kevan@isnotajoke.com>**20100727224703
13146 Ignore-this: 3fd15da701c31c024963d7ee5c896124
13147] hunk ./src/allmydata/interfaces.py 633
13148         """
13149 
13150 
13151+class IWritable(Interface):
13152+    """
13153+    I define methods that callers can use to update SDMF and MDMF
13154+    mutable files on a Tahoe-LAFS grid.
13155+    """
13156+    # XXX: For the moment, we have only this. It is possible that we
13157+    #      want to move overwrite() and modify() in here too.
13158+    def update(data, offset):
13159+        """
13160+        I write the data from my data argument to the MDMF file,
13161+        starting at offset. I continue writing data until my data
13162+        argument is exhausted, appending data to the file as necessary.
13163+        """
13164+        # assert IMutableUploadable.providedBy(data)
13165+        # to append data: offset=node.get_size_of_best_version()
13166+        # do we want to support compacting MDMF?
13167+        # for an MDMF file, this can be done with O(data.get_size())
13168+        # memory. For an SDMF file, any modification takes
13169+        # O(node.get_size_of_best_version()).
13170+
13171+
13172 class IMutableFileVersion(IReadable):
13173     """I provide access to a particular version of a mutable file. The
13174     access is read/write if I was obtained from a filenode derived from
13175[mutable/layout.py: Alter MDMFSlotWriteProxy to perform all write operations in one actual write operation
13176Kevan Carstensen <kevan@isnotajoke.com>**20100727224725
13177 Ignore-this: 41d577e9d65eba9a38a4051c2a05d4be
13178] {
13179hunk ./src/allmydata/mutable/layout.py 814
13180         # last thing we write to the remote server.
13181         self._offsets = {}
13182         self._testvs = []
13183+        # This is a list of write vectors that will be sent to our
13184+        # remote server once we are directed to write things there.
13185+        self._writevs = []
13186         self._secrets = secrets
13187         # The segment size needs to be a multiple of the k parameter --
13188         # any padding should have been carried out by the publisher
13189hunk ./src/allmydata/mutable/layout.py 947
13190 
13191     def put_block(self, data, segnum, salt):
13192         """
13193-        Put the encrypted-and-encoded data segment in the slot, along
13194-        with the salt.
13195+        I queue a write vector for the data, salt, and segment number
13196+        provided to me. I return None, as I do not actually cause
13197+        anything to be written yet.
13198         """
13199         if segnum >= self._num_segments:
13200             raise LayoutInvalid("I won't overwrite the private key")
13201hunk ./src/allmydata/mutable/layout.py 967
13202         offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
13203         data = salt + data
13204 
13205-        datavs = [tuple([offset, data])]
13206-        return self._write(datavs)
13207+        self._writevs.append(tuple([offset, data]))
13208 
13209 
13210     def put_encprivkey(self, encprivkey):
13211hunk ./src/allmydata/mutable/layout.py 972
13212         """
13213-        Put the encrypted private key in the remote slot.
13214+        I queue a write vector for the encrypted private key provided to
13215+        me.
13216         """
13217         assert self._offsets
13218         assert self._offsets['enc_privkey']
13219hunk ./src/allmydata/mutable/layout.py 986
13220         if "share_hash_chain" in self._offsets:
13221             raise LayoutInvalid("You must write this before the block hash tree")
13222 
13223-        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
13224-        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
13225-        def _on_failure():
13226-            del(self._offsets['block_hash_tree'])
13227-        return self._write(datavs, on_failure=_on_failure)
13228+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + \
13229+            len(encprivkey)
13230+        self._writevs.append(tuple([self._offsets['enc_privkey'], encprivkey]))
13231 
13232 
13233     def put_blockhashes(self, blockhashes):
13234hunk ./src/allmydata/mutable/layout.py 993
13235         """
13236-        Put the block hash tree in the remote slot.
13237+        I queue a write vector to put the block hash tree in blockhashes
13238+        onto the remote server.
13239 
13240hunk ./src/allmydata/mutable/layout.py 996
13241-        The encrypted private key must be put before the block hash
13242+        The encrypted private key must be queued before the block hash
13243         tree, since we need to know how large it is to know where the
13244         block hash tree should go. The block hash tree must be put
13245         before the salt hash tree, since its size determines the
13246hunk ./src/allmydata/mutable/layout.py 1014
13247                                 "you put the share hash chain")
13248         blockhashes_s = "".join(blockhashes)
13249         self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
13250-        datavs = []
13251-        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
13252-        def _on_failure():
13253-            del(self._offsets['share_hash_chain'])
13254-        return self._write(datavs, on_failure=_on_failure)
13255+
13256+        self._writevs.append(tuple([self._offsets['block_hash_tree'],
13257+                                  blockhashes_s]))
13258 
13259 
13260     def put_sharehashes(self, sharehashes):
13261hunk ./src/allmydata/mutable/layout.py 1021
13262         """
13263-        Put the share hash chain in the remote slot.
13264+        I queue a write vector to put the share hash chain in my
13265+        argument onto the remote server.
13266 
13267hunk ./src/allmydata/mutable/layout.py 1024
13268-        The salt hash tree must be put before the share hash chain,
13269+        The salt hash tree must be queued before the share hash chain,
13270         since we need to know where the salt hash tree ends before we
13271         can know where the share hash chain starts. The share hash chain
13272         must be put before the signature, since the length of the packed
13273hunk ./src/allmydata/mutable/layout.py 1044
13274         if "verification_key" in self._offsets:
13275             raise LayoutInvalid("You must write the share hash chain "
13276                                 "before you write the signature")
13277-        datavs = []
13278         sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
13279                                   for i in sorted(sharehashes.keys())])
13280         self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
13281hunk ./src/allmydata/mutable/layout.py 1047
13282-        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
13283-        def _on_failure():
13284-            del(self._offsets['signature'])
13285-        return self._write(datavs, on_failure=_on_failure)
13286+        self._writevs.append(tuple([self._offsets['share_hash_chain'],
13287+                            sharehashes_s]))
13288 
13289 
13290     def put_root_hash(self, roothash):
13291hunk ./src/allmydata/mutable/layout.py 1069
13292         if len(roothash) != HASH_SIZE:
13293             raise LayoutInvalid("hashes and salts must be exactly %d bytes"
13294                                  % HASH_SIZE)
13295-        datavs = []
13296         self._root_hash = roothash
13297         # To write both of these values, we update the checkstring on
13298         # the remote server, which includes them
13299hunk ./src/allmydata/mutable/layout.py 1073
13300         checkstring = self.get_checkstring()
13301-        datavs.append(tuple([0, checkstring]))
13302+        self._writevs.append(tuple([0, checkstring]))
13303         # This write, if successful, changes the checkstring, so we need
13304         # to update our internal checkstring to be consistent with the
13305         # one on the server.
13306hunk ./src/allmydata/mutable/layout.py 1077
13307-        def _on_success():
13308-            self._testvs = [(0, len(checkstring), "eq", checkstring)]
13309-        def _on_failure():
13310-            self._root_hash = None
13311-        return self._write(datavs,
13312-                           on_success=_on_success,
13313-                           on_failure=_on_failure)
13314 
13315 
13316     def get_signable(self):
13317hunk ./src/allmydata/mutable/layout.py 1100
13318 
13319     def put_signature(self, signature):
13320         """
13321-        Put the signature field to the remote slot.
13322+        I queue a write vector for the signature of the MDMF share.
13323 
13324         I require that the root hash and share hash chain have been put
13325         to the grid before I will write the signature to the grid.
13326hunk ./src/allmydata/mutable/layout.py 1123
13327             raise LayoutInvalid("You must write the signature before the verification key")
13328 
13329         self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
13330-        datavs = []
13331-        datavs.append(tuple([self._offsets['signature'], signature]))
13332-        def _on_failure():
13333-            del(self._offsets['verification_key'])
13334-        return self._write(datavs, on_failure=_on_failure)
13335+        self._writevs.append(tuple([self._offsets['signature'], signature]))
13336 
13337 
13338     def put_verification_key(self, verification_key):
13339hunk ./src/allmydata/mutable/layout.py 1128
13340         """
13341-        Put the verification key into the remote slot.
13342+        I queue a write vector for the verification key.
13343 
13344         I require that the signature have been written to the storage
13345         server before I allow the verification key to be written to the
13346hunk ./src/allmydata/mutable/layout.py 1138
13347             raise LayoutInvalid("You must put the signature before you "
13348                                 "can put the verification key")
13349         self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
13350-        datavs = []
13351-        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
13352-        def _on_failure():
13353-            del(self._offsets['EOF'])
13354-        return self._write(datavs, on_failure=_on_failure)
13355+        self._writevs.append(tuple([self._offsets['verification_key'],
13356+                            verification_key]))
13357+
13358 
13359     def _get_offsets_tuple(self):
13360         return tuple([(key, value) for key, value in self._offsets.items()])
13361hunk ./src/allmydata/mutable/layout.py 1145
13362 
13363+
13364     def get_verinfo(self):
13365         return (self._seqnum,
13366                 self._root_hash,
13367hunk ./src/allmydata/mutable/layout.py 1159
13368 
13369     def finish_publishing(self):
13370         """
13371-        Write the offset table and encoding parameters to the remote
13372-        slot, since that's the only thing we have yet to publish at this
13373-        point.
13374+        I add a write vector for the offsets table, and then cause all
13375+        of the write vectors that I've dealt with so far to be published
13376+        to the remote server, ending the write process.
13377         """
13378         if "EOF" not in self._offsets:
13379             raise LayoutInvalid("You must put the verification key before "
13380hunk ./src/allmydata/mutable/layout.py 1174
13381                               self._offsets['signature'],
13382                               self._offsets['verification_key'],
13383                               self._offsets['EOF'])
13384-        datavs = []
13385-        datavs.append(tuple([offsets_offset, offsets]))
13386+        self._writevs.append(tuple([offsets_offset, offsets]))
13387         encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
13388         params = struct.pack(">BBQQ",
13389                              self._required_shares,
13390hunk ./src/allmydata/mutable/layout.py 1181
13391                              self._total_shares,
13392                              self._segment_size,
13393                              self._data_length)
13394-        datavs.append(tuple([encoding_parameters_offset, params]))
13395-        return self._write(datavs)
13396+        self._writevs.append(tuple([encoding_parameters_offset, params]))
13397+        return self._write(self._writevs)
13398 
13399 
13400     def _write(self, datavs, on_failure=None, on_success=None):
13401}
13402[test/test_mutable.py: test that write operations occur all at once
13403Kevan Carstensen <kevan@isnotajoke.com>**20100727224817
13404 Ignore-this: 44cb37c6887ee9baa3e67645ece9555d
13405] {
13406hunk ./src/allmydata/test/test_mutable.py 100
13407         self.storage = storage
13408         self.queries = 0
13409     def callRemote(self, methname, *args, **kwargs):
13410+        self.queries += 1
13411         def _call():
13412             meth = getattr(self, methname)
13413             return meth(*args, **kwargs)
13414hunk ./src/allmydata/test/test_mutable.py 109
13415         return d
13416 
13417     def callRemoteOnly(self, methname, *args, **kwargs):
13418+        self.queries += 1
13419         d = self.callRemote(methname, *args, **kwargs)
13420         d.addBoth(lambda ignore: None)
13421         pass
13422hunk ./src/allmydata/test/test_mutable.py 370
13423         return d
13424 
13425 
13426+    def test_mdmf_write_count(self):
13427+        # Publishing an MDMF file should only cause one write for each
13428+        # share that is to be published. Otherwise, we introduce
13429+        # undesirable semantics that are a regression from SDMF
13430+        upload = MutableData("MDMF" * 100000) # about 400 KiB
13431+        d = self.nodemaker.create_mutable_file(upload,
13432+                                               version=MDMF_VERSION)
13433+        def _check_server_write_counts(ignored):
13434+            sb = self.nodemaker.storage_broker
13435+            peers = sb.test_servers.values()
13436+            for peer in peers:
13437+                self.failUnlessEqual(peer.queries, 1)
13438+        d.addCallback(_check_server_write_counts)
13439+        return d
13440+
13441+
13442     def test_create_with_initial_contents(self):
13443         upload1 = MutableData("contents 1")
13444         d = self.nodemaker.create_mutable_file(upload1)
13445}
13446[test/test_storage.py: modify proxy tests to work with the new writing semantics
13447Kevan Carstensen <kevan@isnotajoke.com>**20100727224853
13448 Ignore-this: 2b6bdde6dc9d8e4e7f096cdb725b40cf
13449] {
13450hunk ./src/allmydata/test/test_storage.py 1681
13451         # diagnose the problem. This test ensures that the read vector
13452         # is working appropriately.
13453         mw = self._make_new_mw("si1", 0)
13454-        d = defer.succeed(None)
13455 
13456hunk ./src/allmydata/test/test_storage.py 1682
13457-        # Write one share. This should return a checkstring of nothing,
13458-        # since there is no data there.
13459-        d.addCallback(lambda ignored:
13460-            mw.put_block(self.block, 0, self.salt))
13461-        def _check_first_write(results):
13462-            result, readvs = results
13463-            self.failUnless(result)
13464-            self.failIf(readvs)
13465-        d.addCallback(_check_first_write)
13466-        # Now, there should be a different checkstring returned when
13467-        # we write other shares
13468-        d.addCallback(lambda ignored:
13469-            mw.put_block(self.block, 1, self.salt))
13470-        def _check_next_write(results):
13471-            result, readvs = results
13472+        for i in xrange(6):
13473+            mw.put_block(self.block, i, self.salt)
13474+        mw.put_encprivkey(self.encprivkey)
13475+        mw.put_blockhashes(self.block_hash_tree)
13476+        mw.put_sharehashes(self.share_hash_chain)
13477+        mw.put_root_hash(self.root_hash)
13478+        mw.put_signature(self.signature)
13479+        mw.put_verification_key(self.verification_key)
13480+        d = mw.finish_publishing()
13481+        def _then(results):
13482+            self.failUnless(len(results), 2)
13483+            result, readv = results
13484             self.failUnless(result)
13485hunk ./src/allmydata/test/test_storage.py 1695
13486-            self.expected_checkstring = mw.get_checkstring()
13487-            self.failUnlessIn(0, readvs)
13488-            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
13489-        d.addCallback(_check_next_write)
13490-        # Add the other four shares
13491-        for i in xrange(2, 6):
13492-            d.addCallback(lambda ignored, i=i:
13493-                mw.put_block(self.block, i, self.salt))
13494-            d.addCallback(_check_next_write)
13495-        # Add the encrypted private key
13496-        d.addCallback(lambda ignored:
13497-            mw.put_encprivkey(self.encprivkey))
13498-        d.addCallback(_check_next_write)
13499-        # Add the block hash tree and share hash tree
13500-        d.addCallback(lambda ignored:
13501-            mw.put_blockhashes(self.block_hash_tree))
13502-        d.addCallback(_check_next_write)
13503-        d.addCallback(lambda ignored:
13504-            mw.put_sharehashes(self.share_hash_chain))
13505-        d.addCallback(_check_next_write)
13506-        # Add the root hash and the salt hash. This should change the
13507-        # checkstring, but not in a way that we'll be able to see right
13508-        # now, since the read vectors are applied before the write
13509-        # vectors.
13510+            self.failIf(readv)
13511+            self.old_checkstring = mw.get_checkstring()
13512+            mw.set_checkstring("")
13513+        d.addCallback(_then)
13514         d.addCallback(lambda ignored:
13515hunk ./src/allmydata/test/test_storage.py 1700
13516-            mw.put_root_hash(self.root_hash))
13517-        def _check_old_testv_after_new_one_is_written(results):
13518+            mw.finish_publishing())
13519+        def _then_again(results):
13520+            self.failUnlessEqual(len(results), 2)
13521             result, readvs = results
13522hunk ./src/allmydata/test/test_storage.py 1704
13523-            self.failUnless(result)
13524+            self.failIf(result)
13525             self.failUnlessIn(0, readvs)
13526hunk ./src/allmydata/test/test_storage.py 1706
13527-            self.failUnlessEqual(self.expected_checkstring,
13528-                                 readvs[0][0])
13529-            new_checkstring = mw.get_checkstring()
13530-            self.failIfEqual(new_checkstring,
13531-                             readvs[0][0])
13532-        d.addCallback(_check_old_testv_after_new_one_is_written)
13533-        # Now add the signature. This should succeed, meaning that the
13534-        # data gets written and the read vector matches what the writer
13535-        # thinks should be there.
13536-        d.addCallback(lambda ignored:
13537-            mw.put_signature(self.signature))
13538-        d.addCallback(_check_next_write)
13539+            readv = readvs[0][0]
13540+            self.failUnlessEqual(readv, self.old_checkstring)
13541+        d.addCallback(_then_again)
13542         # The checkstring remains the same for the rest of the process.
13543         return d
13544 
13545hunk ./src/allmydata/test/test_storage.py 1811
13546         # same share.
13547         mw1 = self._make_new_mw("si1", 0)
13548         mw2 = self._make_new_mw("si1", 0)
13549-        d = defer.succeed(None)
13550+
13551         def _check_success(results):
13552             result, readvs = results
13553             self.failUnless(result)
13554hunk ./src/allmydata/test/test_storage.py 1820
13555             result, readvs = results
13556             self.failIf(result)
13557 
13558-        d.addCallback(lambda ignored:
13559-            mw1.put_block(self.block, 0, self.salt))
13560+        def _write_share(mw):
13561+            for i in xrange(6):
13562+                mw.put_block(self.block, i, self.salt)
13563+            mw.put_encprivkey(self.encprivkey)
13564+            mw.put_blockhashes(self.block_hash_tree)
13565+            mw.put_sharehashes(self.share_hash_chain)
13566+            mw.put_root_hash(self.root_hash)
13567+            mw.put_signature(self.signature)
13568+            mw.put_verification_key(self.verification_key)
13569+            return mw.finish_publishing()
13570+        d = _write_share(mw1)
13571         d.addCallback(_check_success)
13572         d.addCallback(lambda ignored:
13573hunk ./src/allmydata/test/test_storage.py 1833
13574-            mw2.put_block(self.block, 0, self.salt))
13575+            _write_share(mw2))
13576         d.addCallback(_check_failure)
13577         return d
13578 
13579hunk ./src/allmydata/test/test_storage.py 1859
13580 
13581     def test_write_test_vectors(self):
13582         # If we give the write proxy a bogus test vector at
13583-        # any point during the process, it should fail to write.
13584+        # any point during the process, it should fail to write when we
13585+        # tell it to write.
13586+        def _check_failure(results):
13587+            self.failUnlessEqual(len(results), 2)
13588+            res, d = results
13589+            self.failIf(res)
13590+
13591+        def _check_success(results):
13592+            self.failUnlessEqual(len(results), 2)
13593+            res, d = results
13594+            self.failUnless(results)
13595+
13596         mw = self._make_new_mw("si1", 0)
13597         mw.set_checkstring("this is a lie")
13598hunk ./src/allmydata/test/test_storage.py 1873
13599-        # The initial write should be expecting to find the improbable
13600-        # checkstring above in place; finding nothing, it should fail.
13601-        d = defer.succeed(None)
13602-        d.addCallback(lambda ignored:
13603-            mw.put_block(self.block, 0, self.salt))
13604-        def _check_failure(results):
13605-            result, readv = results
13606-            self.failIf(result)
13607+        for i in xrange(6):
13608+            mw.put_block(self.block, i, self.salt)
13609+        mw.put_encprivkey(self.encprivkey)
13610+        mw.put_blockhashes(self.block_hash_tree)
13611+        mw.put_sharehashes(self.share_hash_chain)
13612+        mw.put_root_hash(self.root_hash)
13613+        mw.put_signature(self.signature)
13614+        mw.put_verification_key(self.verification_key)
13615+        d = mw.finish_publishing()
13616         d.addCallback(_check_failure)
13617hunk ./src/allmydata/test/test_storage.py 1883
13618-        # Now set the checkstring to the empty string, which
13619-        # indicates that no share is there.
13620         d.addCallback(lambda ignored:
13621             mw.set_checkstring(""))
13622         d.addCallback(lambda ignored:
13623hunk ./src/allmydata/test/test_storage.py 1886
13624-            mw.put_block(self.block, 0, self.salt))
13625-        def _check_success(results):
13626-            result, readv = results
13627-            self.failUnless(result)
13628-        d.addCallback(_check_success)
13629-        # Now set the checkstring to something wrong
13630-        d.addCallback(lambda ignored:
13631-            mw.set_checkstring("something wrong"))
13632-        # This should fail to do anything
13633-        d.addCallback(lambda ignored:
13634-            mw.put_block(self.block, 1, self.salt))
13635-        d.addCallback(_check_failure)
13636-        # Now set it back to what it should be.
13637-        d.addCallback(lambda ignored:
13638-            mw.set_checkstring(mw.get_checkstring()))
13639-        for i in xrange(1, 6):
13640-            d.addCallback(lambda ignored, i=i:
13641-                mw.put_block(self.block, i, self.salt))
13642-            d.addCallback(_check_success)
13643-        d.addCallback(lambda ignored:
13644-            mw.put_encprivkey(self.encprivkey))
13645-        d.addCallback(_check_success)
13646-        d.addCallback(lambda ignored:
13647-            mw.put_blockhashes(self.block_hash_tree))
13648-        d.addCallback(_check_success)
13649-        d.addCallback(lambda ignored:
13650-            mw.put_sharehashes(self.share_hash_chain))
13651-        d.addCallback(_check_success)
13652-        def _keep_old_checkstring(ignored):
13653-            self.old_checkstring = mw.get_checkstring()
13654-            mw.set_checkstring("foobarbaz")
13655-        d.addCallback(_keep_old_checkstring)
13656-        d.addCallback(lambda ignored:
13657-            mw.put_root_hash(self.root_hash))
13658-        d.addCallback(_check_failure)
13659-        d.addCallback(lambda ignored:
13660-            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
13661-        def _restore_old_checkstring(ignored):
13662-            mw.set_checkstring(self.old_checkstring)
13663-        d.addCallback(_restore_old_checkstring)
13664-        d.addCallback(lambda ignored:
13665-            mw.put_root_hash(self.root_hash))
13666-        d.addCallback(_check_success)
13667-        # The checkstring should have been set appropriately for us on
13668-        # the last write; if we try to change it to something else,
13669-        # that change should cause the verification key step to fail.
13670-        d.addCallback(lambda ignored:
13671-            mw.set_checkstring("something else"))
13672-        d.addCallback(lambda ignored:
13673-            mw.put_signature(self.signature))
13674-        d.addCallback(_check_failure)
13675-        d.addCallback(lambda ignored:
13676-            mw.set_checkstring(mw.get_checkstring()))
13677-        d.addCallback(lambda ignored:
13678-            mw.put_signature(self.signature))
13679-        d.addCallback(_check_success)
13680-        d.addCallback(lambda ignored:
13681-            mw.put_verification_key(self.verification_key))
13682+            mw.finish_publishing())
13683         d.addCallback(_check_success)
13684         return d
13685 
13686hunk ./src/allmydata/test/test_storage.py 1891
13687 
13688-    def test_offset_only_set_on_success(self):
13689-        # The write proxy should be smart enough to detect when a write
13690-        # has failed, and to temper its definition of progress based on
13691-        # that.
13692-        mw = self._make_new_mw("si1", 0)
13693-        d = defer.succeed(None)
13694-        for i in xrange(1, 6):
13695-            d.addCallback(lambda ignored, i=i:
13696-                mw.put_block(self.block, i, self.salt))
13697-        def _break_checkstring(ignored):
13698-            self._old_checkstring = mw.get_checkstring()
13699-            mw.set_checkstring("foobarbaz")
13700-
13701-        def _fix_checkstring(ignored):
13702-            mw.set_checkstring(self._old_checkstring)
13703-
13704-        d.addCallback(_break_checkstring)
13705-
13706-        # Setting the encrypted private key shouldn't work now, which is
13707-        # to be expected and is tested elsewhere. We also want to make
13708-        # sure that we can't add the block hash tree after a failed
13709-        # write of this sort.
13710-        d.addCallback(lambda ignored:
13711-            mw.put_encprivkey(self.encprivkey))
13712-        d.addCallback(lambda ignored:
13713-            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
13714-                            None,
13715-                            mw.put_blockhashes, self.block_hash_tree))
13716-        d.addCallback(_fix_checkstring)
13717-        d.addCallback(lambda ignored:
13718-            mw.put_encprivkey(self.encprivkey))
13719-        d.addCallback(_break_checkstring)
13720-        d.addCallback(lambda ignored:
13721-            mw.put_blockhashes(self.block_hash_tree))
13722-        d.addCallback(lambda ignored:
13723-            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
13724-                            None,
13725-                            mw.put_sharehashes, self.share_hash_chain))
13726-        d.addCallback(_fix_checkstring)
13727-        d.addCallback(lambda ignored:
13728-            mw.put_blockhashes(self.block_hash_tree))
13729-        d.addCallback(_break_checkstring)
13730-        d.addCallback(lambda ignored:
13731-            mw.put_sharehashes(self.share_hash_chain))
13732-        d.addCallback(lambda ignored:
13733-            self.shouldFail(LayoutInvalid, "out-of-order root hash",
13734-                            None,
13735-                            mw.put_root_hash, self.root_hash))
13736-        d.addCallback(_fix_checkstring)
13737-        d.addCallback(lambda ignored:
13738-            mw.put_sharehashes(self.share_hash_chain))
13739-        d.addCallback(_break_checkstring)
13740-        d.addCallback(lambda ignored:
13741-            mw.put_root_hash(self.root_hash))
13742-        d.addCallback(lambda ignored:
13743-            self.shouldFail(LayoutInvalid, "out-of-order signature",
13744-                            None,
13745-                            mw.put_signature, self.signature))
13746-        d.addCallback(_fix_checkstring)
13747-        d.addCallback(lambda ignored:
13748-            mw.put_root_hash(self.root_hash))
13749-        d.addCallback(_break_checkstring)
13750-        d.addCallback(lambda ignored:
13751-            mw.put_signature(self.signature))
13752-        d.addCallback(lambda ignored:
13753-            self.shouldFail(LayoutInvalid, "out-of-order verification key",
13754-                            None,
13755-                            mw.put_verification_key,
13756-                            self.verification_key))
13757-        d.addCallback(_fix_checkstring)
13758-        d.addCallback(lambda ignored:
13759-            mw.put_signature(self.signature))
13760-        d.addCallback(_break_checkstring)
13761-        d.addCallback(lambda ignored:
13762-            mw.put_verification_key(self.verification_key))
13763-        d.addCallback(lambda ignored:
13764-            self.shouldFail(LayoutInvalid, "out-of-order finish",
13765-                            None,
13766-                            mw.finish_publishing))
13767-        return d
13768-
13769-
13770     def serialize_blockhashes(self, blockhashes):
13771         return "".join(blockhashes)
13772 
13773hunk ./src/allmydata/test/test_storage.py 1905
13774         # This translates to a file with 6 6-byte segments, and with 2-byte
13775         # blocks.
13776         mw = self._make_new_mw("si1", 0)
13777-        mw2 = self._make_new_mw("si1", 1)
13778         # Test writing some blocks.
13779         read = self.ss.remote_slot_readv
13780         expected_sharedata_offset = struct.calcsize(MDMFHEADER)
13781hunk ./src/allmydata/test/test_storage.py 1910
13782         written_block_size = 2 + len(self.salt)
13783         written_block = self.block + self.salt
13784-        def _check_block_write(i, share):
13785-            self.failUnlessEqual(read("si1", [share], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
13786-                                {share: [written_block]})
13787-        d = defer.succeed(None)
13788         for i in xrange(6):
13789hunk ./src/allmydata/test/test_storage.py 1911
13790-            d.addCallback(lambda ignored, i=i:
13791-                mw.put_block(self.block, i, self.salt))
13792-            d.addCallback(lambda ignored, i=i:
13793-                _check_block_write(i, 0))
13794-        # Now try the same thing, but with share 1 instead of share 0.
13795-        for i in xrange(6):
13796-            d.addCallback(lambda ignored, i=i:
13797-                mw2.put_block(self.block, i, self.salt))
13798-            d.addCallback(lambda ignored, i=i:
13799-                _check_block_write(i, 1))
13800+            mw.put_block(self.block, i, self.salt)
13801 
13802hunk ./src/allmydata/test/test_storage.py 1913
13803-        # Next, we make a fake encrypted private key, and put it onto the
13804-        # storage server.
13805-        d.addCallback(lambda ignored:
13806-            mw.put_encprivkey(self.encprivkey))
13807-        expected_private_key_offset = expected_sharedata_offset + \
13808+        mw.put_encprivkey(self.encprivkey)
13809+        mw.put_blockhashes(self.block_hash_tree)
13810+        mw.put_sharehashes(self.share_hash_chain)
13811+        mw.put_root_hash(self.root_hash)
13812+        mw.put_signature(self.signature)
13813+        mw.put_verification_key(self.verification_key)
13814+        d = mw.finish_publishing()
13815+        def _check_publish(results):
13816+            self.failUnlessEqual(len(results), 2)
13817+            result, ign = results
13818+            self.failUnless(result, "publish failed")
13819+            for i in xrange(6):
13820+                self.failUnlessEqual(read("si1", [0], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
13821+                                {0: [written_block]})
13822+
13823+            expected_private_key_offset = expected_sharedata_offset + \
13824                                       len(written_block) * 6
13825hunk ./src/allmydata/test/test_storage.py 1930
13826-        self.failUnlessEqual(len(self.encprivkey), 7)
13827-        d.addCallback(lambda ignored:
13828+            self.failUnlessEqual(len(self.encprivkey), 7)
13829             self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
13830hunk ./src/allmydata/test/test_storage.py 1932
13831-                                 {0: [self.encprivkey]}))
13832+                                 {0: [self.encprivkey]})
13833 
13834hunk ./src/allmydata/test/test_storage.py 1934
13835-        # Next, we put a fake block hash tree.
13836-        d.addCallback(lambda ignored:
13837-            mw.put_blockhashes(self.block_hash_tree))
13838-        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
13839-        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
13840-        d.addCallback(lambda ignored:
13841+            expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
13842+            self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
13843             self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
13844hunk ./src/allmydata/test/test_storage.py 1937
13845-                                 {0: [self.block_hash_tree_s]}))
13846+                                 {0: [self.block_hash_tree_s]})
13847 
13848hunk ./src/allmydata/test/test_storage.py 1939
13849-        # Next, put a fake share hash chain
13850-        d.addCallback(lambda ignored:
13851-            mw.put_sharehashes(self.share_hash_chain))
13852-        expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
13853-        d.addCallback(lambda ignored:
13854+            expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
13855             self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
13856hunk ./src/allmydata/test/test_storage.py 1941
13857-                                 {0: [self.share_hash_chain_s]}))
13858+                                 {0: [self.share_hash_chain_s]})
13859 
13860hunk ./src/allmydata/test/test_storage.py 1943
13861-        # Next, we put what is supposed to be the root hash of
13862-        # our share hash tree but isn't       
13863-        d.addCallback(lambda ignored:
13864-            mw.put_root_hash(self.root_hash))
13865-        # The root hash gets inserted at byte 9 (its position is in the header,
13866-        # and is fixed).
13867-        def _check(ignored):
13868             self.failUnlessEqual(read("si1", [0], [(9, 32)]),
13869                                  {0: [self.root_hash]})
13870hunk ./src/allmydata/test/test_storage.py 1945
13871-        d.addCallback(_check)
13872-
13873-        # Next, we put a signature of the header block.
13874-        d.addCallback(lambda ignored:
13875-            mw.put_signature(self.signature))
13876-        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
13877-        self.failUnlessEqual(len(self.signature), 9)
13878-        d.addCallback(lambda ignored:
13879+            expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
13880+            self.failUnlessEqual(len(self.signature), 9)
13881             self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
13882hunk ./src/allmydata/test/test_storage.py 1948
13883-                                 {0: [self.signature]}))
13884+                                 {0: [self.signature]})
13885 
13886hunk ./src/allmydata/test/test_storage.py 1950
13887-        # Next, we put the verification key
13888-        d.addCallback(lambda ignored:
13889-            mw.put_verification_key(self.verification_key))
13890-        expected_verification_key_offset = expected_signature_offset + len(self.signature)
13891-        self.failUnlessEqual(len(self.verification_key), 6)
13892-        d.addCallback(lambda ignored:
13893+            expected_verification_key_offset = expected_signature_offset + len(self.signature)
13894+            self.failUnlessEqual(len(self.verification_key), 6)
13895             self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
13896hunk ./src/allmydata/test/test_storage.py 1953
13897-                                 {0: [self.verification_key]}))
13898+                                 {0: [self.verification_key]})
13899 
13900hunk ./src/allmydata/test/test_storage.py 1955
13901-        def _check_signable(ignored):
13902-            # Make sure that the signable is what we think it should be.
13903             signable = mw.get_signable()
13904             verno, seq, roothash, k, n, segsize, datalen = \
13905                                             struct.unpack(">BQ32sBBQQ",
13906hunk ./src/allmydata/test/test_storage.py 1966
13907             self.failUnlessEqual(n, 10)
13908             self.failUnlessEqual(segsize, 6)
13909             self.failUnlessEqual(datalen, 36)
13910-        d.addCallback(_check_signable)
13911-        # Next, we cause the offset table to be published.
13912-        d.addCallback(lambda ignored:
13913-            mw.finish_publishing())
13914-        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
13915+            expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
13916 
13917hunk ./src/allmydata/test/test_storage.py 1968
13918-        def _check_offsets(ignored):
13919             # Check the version number to make sure that it is correct.
13920             expected_version_number = struct.pack(">B", 1)
13921             self.failUnlessEqual(read("si1", [0], [(0, 1)]),
13922hunk ./src/allmydata/test/test_storage.py 2008
13923             expected_offset = struct.pack(">Q", expected_eof_offset)
13924             self.failUnlessEqual(read("si1", [0], [(99, 8)]),
13925                                  {0: [expected_offset]})
13926-        d.addCallback(_check_offsets)
13927+        d.addCallback(_check_publish)
13928         return d
13929 
13930     def _make_new_mw(self, si, share, datalength=36):
13931}
13932[mutable/publish.py: alter mutable publisher to work with new writing semantics
13933Kevan Carstensen <kevan@isnotajoke.com>**20100727225001
13934 Ignore-this: a6b4628e749e09bfcddf3309271b5831
13935] {
13936hunk ./src/allmydata/mutable/publish.py 389
13937         if self._state == PUSHING_BLOCKS_STATE:
13938             return self.push_segment(self._current_segment)
13939 
13940-        # XXX: Do we want more granularity in states? Is that useful at
13941-        #      all?
13942-        #      Yes -- quicker reaction to UCW.
13943         elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
13944             return self.push_everything_else()
13945 
13946hunk ./src/allmydata/mutable/publish.py 490
13947         results, salt = encoded_and_salt
13948         shares, shareids = results
13949         started = time.time()
13950-        dl = []
13951         for i in xrange(len(shares)):
13952             sharedata = shares[i]
13953             shareid = shareids[i]
13954hunk ./src/allmydata/mutable/publish.py 502
13955 
13956             # find the writer for this share
13957             writer = self.writers[shareid]
13958-            d = writer.put_block(sharedata, segnum, salt)
13959-            d.addCallback(self._got_write_answer, writer, started)
13960-            d.addErrback(self._connection_problem, writer)
13961-            dl.append(d)
13962-        return defer.DeferredList(dl)
13963+            writer.put_block(sharedata, segnum, salt)
13964 
13965 
13966     def push_everything_else(self):
13967hunk ./src/allmydata/mutable/publish.py 510
13968         I put everything else associated with a share.
13969         """
13970         encprivkey = self._encprivkey
13971-        d = self.push_encprivkey()
13972-        d.addCallback(self.push_blockhashes)
13973-        d.addCallback(self.push_sharehashes)
13974-        d.addCallback(self.push_toplevel_hashes_and_signature)
13975-        d.addCallback(self.finish_publishing)
13976+        self.push_encprivkey()
13977+        self.push_blockhashes()
13978+        self.push_sharehashes()
13979+        self.push_toplevel_hashes_and_signature()
13980+        d = self.finish_publishing()
13981         def _change_state(ignored):
13982             self._state = DONE_STATE
13983         d.addCallback(_change_state)
13984hunk ./src/allmydata/mutable/publish.py 525
13985     def push_encprivkey(self):
13986         started = time.time()
13987         encprivkey = self._encprivkey
13988-        dl = []
13989         for writer in self.writers.itervalues():
13990hunk ./src/allmydata/mutable/publish.py 526
13991-            d = writer.put_encprivkey(encprivkey)
13992-            d.addCallback(self._got_write_answer, writer, started)
13993-            d.addErrback(self._connection_problem, writer)
13994-            dl.append(d)
13995-        d = defer.DeferredList(dl)
13996-        return d
13997+            writer.put_encprivkey(encprivkey)
13998 
13999 
14000hunk ./src/allmydata/mutable/publish.py 529
14001-    def push_blockhashes(self, ignored):
14002+    def push_blockhashes(self):
14003         started = time.time()
14004hunk ./src/allmydata/mutable/publish.py 531
14005-        dl = []
14006         self.sharehash_leaves = [None] * len(self.blockhashes)
14007         for shnum, blockhashes in self.blockhashes.iteritems():
14008             t = hashtree.HashTree(blockhashes)
14009hunk ./src/allmydata/mutable/publish.py 538
14010             # set the leaf for future use.
14011             self.sharehash_leaves[shnum] = t[0]
14012             writer = self.writers[shnum]
14013-            d = writer.put_blockhashes(self.blockhashes[shnum])
14014-            d.addCallback(self._got_write_answer, writer, started)
14015-            d.addErrback(self._connection_problem, self.writers[shnum])
14016-            dl.append(d)
14017-        d = defer.DeferredList(dl)
14018-        return d
14019+            writer.put_blockhashes(self.blockhashes[shnum])
14020 
14021 
14022hunk ./src/allmydata/mutable/publish.py 541
14023-    def push_sharehashes(self, ignored):
14024+    def push_sharehashes(self):
14025         started = time.time()
14026         share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
14027         share_hash_chain = {}
14028hunk ./src/allmydata/mutable/publish.py 545
14029-        ds = []
14030         for shnum in xrange(len(self.sharehash_leaves)):
14031             needed_indices = share_hash_tree.needed_hashes(shnum)
14032             self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
14033hunk ./src/allmydata/mutable/publish.py 550
14034                                              for i in needed_indices] )
14035             writer = self.writers[shnum]
14036-            d = writer.put_sharehashes(self.sharehashes[shnum])
14037-            d.addCallback(self._got_write_answer, writer, started)
14038-            d.addErrback(self._connection_problem, writer)
14039-            ds.append(d)
14040+            writer.put_sharehashes(self.sharehashes[shnum])
14041         self.root_hash = share_hash_tree[0]
14042hunk ./src/allmydata/mutable/publish.py 552
14043-        d = defer.DeferredList(ds)
14044-        return d
14045 
14046 
14047hunk ./src/allmydata/mutable/publish.py 554
14048-    def push_toplevel_hashes_and_signature(self, ignored):
14049+    def push_toplevel_hashes_and_signature(self):
14050         # We need to to three things here:
14051         #   - Push the root hash and salt hash
14052         #   - Get the checkstring of the resulting layout; sign that.
14053hunk ./src/allmydata/mutable/publish.py 560
14054         #   - Push the signature
14055         started = time.time()
14056-        ds = []
14057         for shnum in xrange(self.total_shares):
14058             writer = self.writers[shnum]
14059hunk ./src/allmydata/mutable/publish.py 562
14060-            d = writer.put_root_hash(self.root_hash)
14061-            d.addCallback(self._got_write_answer, writer, started)
14062-            ds.append(d)
14063-        d = defer.DeferredList(ds)
14064-        d.addCallback(self._update_checkstring)
14065-        d.addCallback(self._make_and_place_signature)
14066-        return d
14067+            writer.put_root_hash(self.root_hash)
14068+        self._update_checkstring()
14069+        self._make_and_place_signature()
14070 
14071 
14072hunk ./src/allmydata/mutable/publish.py 567
14073-    def _update_checkstring(self, ignored):
14074+    def _update_checkstring(self):
14075         """
14076         After putting the root hash, MDMF files will have the
14077         checkstring written to the storage server. This means that we
14078hunk ./src/allmydata/mutable/publish.py 578
14079         self._checkstring = self.writers.values()[0].get_checkstring()
14080 
14081 
14082-    def _make_and_place_signature(self, ignored):
14083+    def _make_and_place_signature(self):
14084         """
14085         I create and place the signature.
14086         """
14087hunk ./src/allmydata/mutable/publish.py 586
14088         signable = self.writers[0].get_signable()
14089         self.signature = self._privkey.sign(signable)
14090 
14091-        ds = []
14092         for (shnum, writer) in self.writers.iteritems():
14093hunk ./src/allmydata/mutable/publish.py 587
14094-            d = writer.put_signature(self.signature)
14095-            d.addCallback(self._got_write_answer, writer, started)
14096-            d.addErrback(self._connection_problem, writer)
14097-            ds.append(d)
14098-        return defer.DeferredList(ds)
14099+            writer.put_signature(self.signature)
14100 
14101 
14102hunk ./src/allmydata/mutable/publish.py 590
14103-    def finish_publishing(self, ignored):
14104+    def finish_publishing(self):
14105         # We're almost done -- we just need to put the verification key
14106         # and the offsets
14107         started = time.time()
14108hunk ./src/allmydata/mutable/publish.py 601
14109         # TODO: Bad, since we remove from this same dict. We need to
14110         # make a copy, or just use a non-iterated value.
14111         for (shnum, writer) in self.writers.iteritems():
14112-            d = writer.put_verification_key(verification_key)
14113-            d.addCallback(self._got_write_answer, writer, started)
14114-            d.addCallback(self._record_verinfo)
14115-            d.addCallback(lambda ignored, writer=writer:
14116-                writer.finish_publishing())
14117+            writer.put_verification_key(verification_key)
14118+            d = writer.finish_publishing()
14119             d.addCallback(self._got_write_answer, writer, started)
14120             d.addErrback(self._connection_problem, writer)
14121             ds.append(d)
14122hunk ./src/allmydata/mutable/publish.py 606
14123+        self._record_verinfo()
14124         return defer.DeferredList(ds)
14125 
14126 
14127hunk ./src/allmydata/mutable/publish.py 610
14128-    def _record_verinfo(self, ignored):
14129+    def _record_verinfo(self):
14130         self.versioninfo = self.writers.values()[0].get_verinfo()
14131 
14132 
14133}
14134[mutable/servermap.py: lay some groundwork for IWritable
14135Kevan Carstensen <kevan@isnotajoke.com>**20100727225046
14136 Ignore-this: 4b2f48e38c4cef85fa85666bb4570eec
14137] {
14138hunk ./src/allmydata/mutable/servermap.py 388
14139             # we use unpack_prefix_and_signature, so we need 1k
14140             self._read_size = 1000
14141         self._need_privkey = False
14142+
14143         if mode == MODE_WRITE and not self._node.get_privkey():
14144             self._need_privkey = True
14145         # check+repair: repair requires the privkey, so if we didn't happen
14146hunk ./src/allmydata/mutable/servermap.py 649
14147             #     public key. We use this to validate the signature.
14148             if not self._node.get_pubkey():
14149                 # fetch and set the public key.
14150-                d = reader.get_verification_key()
14151+                d = reader.get_verification_key(queue=True)
14152                 d.addCallback(lambda results, shnum=shnum, peerid=peerid:
14153                     self._try_to_set_pubkey(results, peerid, shnum, lp))
14154                 # XXX: Make self._pubkey_query_failed?
14155hunk ./src/allmydata/mutable/servermap.py 658
14156             else:
14157                 # we already have the public key.
14158                 d = defer.succeed(None)
14159+
14160             # Neither of these two branches return anything of
14161             # consequence, so the first entry in our deferredlist will
14162             # be None.
14163hunk ./src/allmydata/mutable/servermap.py 675
14164             #   to get the version information. In MDMF, this lives at
14165             #   the end of the share, so unless the file is quite small,
14166             #   we'll need to do a remote fetch to get it.
14167-            d3 = reader.get_signature()
14168+            d3 = reader.get_signature(queue=True)
14169             d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
14170                 self._got_corrupt_share(error, shnum, peerid, data, lp))
14171             #  Once we have all three of these responses, we can move on
14172hunk ./src/allmydata/mutable/servermap.py 684
14173             # Does the node already have a privkey? If not, we'll try to
14174             # fetch it here.
14175             if self._need_privkey:
14176-                d4 = reader.get_encprivkey()
14177+                d4 = reader.get_encprivkey(queue=True)
14178                 d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
14179                     self._try_to_validate_privkey(results, peerid, shnum, lp))
14180                 d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
14181hunk ./src/allmydata/mutable/servermap.py 692
14182             else:
14183                 d4 = defer.succeed(None)
14184 
14185-            dl = defer.DeferredList([d, d2, d3, d4])
14186+
14187+            if False:
14188+                # fetch the block hash tree and first + last segment, as
14189+                # configured earlier.
14190+                # Then set them in wherever we happen to want to set
14191+                # them.
14192+                ds = []
14193+                ds.append(reader.get_blockhashes(queue=True))
14194+                #ds.append(reader.get_block_and_salt(start)
14195+                #ds.append(reader.get_block_and_salt(end)
14196+                #ds.append(self._got_update_params)
14197+                d5 = defer.DeferredList(ds)
14198+                d5.addCallback(self._got_update_results_one_share, shnum)
14199+            else:
14200+                d5 = defer.succeed(None)
14201+
14202+            dl = defer.DeferredList([d, d2, d3, d4, d5])
14203+            reader.flush()
14204             dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
14205                 self._got_signature_one_share(results, shnum, peerid, lp))
14206             dl.addErrback(lambda error, shnum=shnum, data=data:
14207hunk ./src/allmydata/mutable/servermap.py 763
14208                  peerid=idlib.shortnodeid_b2a(peerid),
14209                  level=log.NOISY,
14210                  parent=lp)
14211-        _, verinfo, signature, __ = results
14212+        _, verinfo, signature, __, ___ = results
14213         (seqnum,
14214          root_hash,
14215          saltish,
14216hunk ./src/allmydata/mutable/servermap.py 828
14217         return verinfo
14218 
14219 
14220+    def _got_update_data_one_share(self, results, share):
14221+        pass
14222+
14223+
14224     def _deserialize_pubkey(self, pubkey_s):
14225         verifier = rsa.create_verifying_key_from_string(pubkey_s)
14226         return verifier
14227}
14228
14229Context:
14230
14231[docs/specifications/dirnodes.txt: 'mesh'->'grid'.
14232david-sarah@jacaranda.org**20100723061616
14233 Ignore-this: 887bcf921ef00afba8e05e9239035bca
14234] 
14235[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'.
14236david-sarah@jacaranda.org**20100723054703
14237 Ignore-this: f3b98183e7d0a0f391225b8b93ac6c37
14238] 
14239[docs: use current cap to Zooko's wiki page in example text
14240zooko@zooko.com**20100721010543
14241 Ignore-this: 4f36f36758f9fdbaf9eb73eac23b6652
14242 fixes #1134
14243] 
14244[__init__.py: silence DeprecationWarning about BaseException.message globally. fixes #1129
14245david-sarah@jacaranda.org**20100720011939
14246 Ignore-this: 38808986ba79cb2786b010504a22f89
14247] 
14248[test_runner: test that 'tahoe --version' outputs no noise (e.g. DeprecationWarnings).
14249david-sarah@jacaranda.org**20100720011345
14250 Ignore-this: dd358b7b2e5d57282cbe133e8069702e
14251] 
14252[TAG allmydata-tahoe-1.7.1
14253zooko@zooko.com**20100719131352
14254 Ignore-this: 6942056548433dc653a746703819ad8c
14255] 
14256[relnotes.txt: updated for v1.7.1 release!
14257zooko@zooko.com**20100719083059
14258 Ignore-this: 9f10eb19b65a39d652b546c57481da45
14259] 
14260[immutable: add test case of #1128, fix test case of #1118
14261zooko@zooko.com**20100719081612
14262 Ignore-this: 8f9f742e7dac2bd9b49c19bd10f1c204
14263] 
14264[NEWS: add #1118 and reflow
14265zooko@zooko.com**20100719081248
14266 Ignore-this: 37a2e39d58c7b584b3c7f193bc1b30df
14267] 
14268[immutable: fix bug in which preexisting_shares and merged were shallowly referencing the same sets
14269zooko@zooko.com**20100719075426
14270 Ignore-this: 90827f8ce7ff0fc0c3c7f819399b8cf0
14271 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.
14272] 
14273[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.
14274david-sarah@jacaranda.org**20100719044655
14275 Ignore-this: 142d182c0739986812140bb8387077d5
14276] 
14277[docs/known_issues.txt: update release version and date.
14278david-sarah@jacaranda.org**20100718235940
14279 Ignore-this: dbbb42dbfa6c0d205a0b8e6e58eee9c7
14280] 
14281[relnotes.txt, docs/quickstart.html: prepare for 1.7.1 release. Don't claim to work on Cygwin (this might work but is untested).
14282david-sarah@jacaranda.org**20100718235437
14283 Ignore-this: dfc7334ee4bb76c04ee19304a7f1024b
14284] 
14285[immutable: extend the tests to check that the shares that got uploaded really do make a sufficiently Happy distribution
14286zooko@zooko.com**20100719045047
14287 Ignore-this: 89c33a7b795e23018667351045a8d5d0
14288 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.
14289] 
14290[immutable: test for #1118
14291zooko@zooko.com**20100718221537
14292 Ignore-this: 8882aabe2aaec6a0148c87e735d817ad
14293] 
14294[immutable: test for #1124
14295zooko@zooko.com**20100718222907
14296 Ignore-this: 1766e3cbab92ea2a9e246f40eb6e770b
14297] 
14298[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>.
14299david-sarah@jacaranda.org**20100718230420
14300 Ignore-this: aef40f2e74ddeabee5e122e8d80893a1
14301] 
14302[trivial: fix unused import (sorry about that, pyflakes)
14303zooko@zooko.com**20100718215133
14304 Ignore-this: c2414e443405072b51d552295f2c0e8c
14305] 
14306[tests, NEWS, CREDITS re: #1117
14307zooko@zooko.com**20100718203225
14308 Ignore-this: 1f08be2c692fb72cc0dd023259f11354
14309 Give Brian and Kevan promotions, move release date in NEWS to the 18th, commit Brian's test for #1117.
14310 fixes #1117
14311] 
14312[test/test_upload.py: test to see that aborted buckets are ignored by the storage server
14313Kevan Carstensen <kevan@isnotajoke.com>**20100716001046
14314 Ignore-this: cc075c24b1c86d737f3199af894cc780
14315] 
14316[test/test_storage.py: test for the new remote_abort semantics.
14317Kevan Carstensen <kevan@isnotajoke.com>**20100715232148
14318 Ignore-this: d3d6491f17bf670e770ca4b385007515
14319] 
14320[storage/immutable.py: make remote_abort btell the storage server about aborted buckets.
14321Kevan Carstensen <kevan@isnotajoke.com>**20100715232105
14322 Ignore-this: 16ab0090676355abdd5600ed44ff19c9
14323] 
14324[test/test_upload.py: changes to test plumbing for #1117 tests
14325Kevan Carstensen <kevan@isnotajoke.com>**20100715231820
14326 Ignore-this: 78a6d359d7bf8529d283e2815bf1e2de
14327 
14328     - Add a callRemoteOnly method to FakeBucketWriter.
14329     - Change the abort method in FakeBucketWriter to not return a
14330       RuntimeError.
14331] 
14332[immutable/upload.py: abort buckets if peer selection fails
14333Kevan Carstensen <kevan@isnotajoke.com>**20100715231714
14334 Ignore-this: 2a0b643a22284df292d8ed9d91b1fd37
14335] 
14336[test_encodingutil: correct an error in the previous patch to StdlibUnicode.test_open_representable.
14337david-sarah@jacaranda.org**20100718151420
14338 Ignore-this: af050955f623fbc0e4d78e15a0a8a144
14339] 
14340[NEWS: Forward-compatibility improvements for non-ASCII caps (#1051).
14341david-sarah@jacaranda.org**20100718143622
14342 Ignore-this: 1edfebc4bd38a3b5c35e75c99588153f
14343] 
14344[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.
14345david-sarah@jacaranda.org**20100718142915
14346 Ignore-this: c4e78ef4b1478dd400da71cf077ffa4a
14347] 
14348[test_encodingutil: StdlibUnicode.test_open_representable no longer uses a mock.
14349david-sarah@jacaranda.org**20100718125412
14350 Ignore-this: 4bf373a5e2dfe4209e5e364124af29a3
14351] 
14352[docs: add comment clarifying #1051
14353zooko@zooko.com**20100718053250
14354 Ignore-this: 6cfc0930434cbdbbc262dabb58f1505d
14355] 
14356[docs: update NEWS
14357zooko@zooko.com**20100718053225
14358 Ignore-this: 63d5c782ef84812e6d010f0590866831
14359] 
14360[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.
14361david-sarah@jacaranda.org**20100711200252
14362 Ignore-this: c2f193352369d32e06865f8f3e951894
14363] 
14364[Debian documentation update
14365jacob@appelbaum.net**20100305003004] 
14366[debian-docs-patch-final
14367jacob@appelbaum.net**20100304085955] 
14368[M-x whitespace-cleanup
14369zooko@zooko.com**20100718032739
14370 Ignore-this: babfd4af6ad2fc885c957fd5c8b10c3f
14371] 
14372[docs: tidy up NEWS a little
14373zooko@zooko.com**20100718032434
14374 Ignore-this: 54f2820fd1a37c8967609f6bfc4e5e18
14375] 
14376[benchmarking: update bench_dirnode.py to reflect the new directory interfaces
14377zooko@zooko.com**20100718031710
14378 Ignore-this: 368ba523dd3de80d9da29cd58afbe827
14379] 
14380[test_encodingutil: fix test_open_representable, which is only valid when run on a platform for which we know an unrepresentable filename.
14381david-sarah@jacaranda.org**20100718030333
14382 Ignore-this: c114d92c17714a5d4ae005c15267d60c
14383] 
14384[iputil.py: Add support for FreeBSD 7,8 and 9
14385francois@ctrlaltdel.ch**20100718022832
14386 Ignore-this: 1829b4cf4b91107f4cf87841e6167e99
14387 committed by: zooko@zooko.com
14388 date: 2010-07-17
14389 and I also patched: NEWS and CREDITS
14390] 
14391[NEWS: add snippet about #1083
14392zooko@zooko.com**20100718020653
14393 Ignore-this: d353a9d93cbc5a5e6ba4671f78d1e22b
14394] 
14395[fileutil: docstrings for non-obvious usage restrictions on methods of EncryptedTemporaryFile.
14396david-sarah@jacaranda.org**20100717054647
14397 Ignore-this: 46d8fc10782fa8ec2b6c5b168c841943
14398] 
14399[Move EncryptedTemporaryFile from SFTP frontend to allmydata.util.fileutil, and make the FTP frontend also use it (fixing #1083).
14400david-sarah@jacaranda.org**20100711213721
14401 Ignore-this: e452e8ca66391aa2a1a49afe0114f317
14402] 
14403[NEWS: reorder NEWS snippets to be in descending order of interestingness
14404zooko@zooko.com**20100718015929
14405 Ignore-this: 146c42e88a9555a868a04a69dd0e5326
14406] 
14407[Correct stringutils->encodingutil patch to be the newer version, rather than the old version that was committed in error.
14408david-sarah@jacaranda.org**20100718013435
14409 Ignore-this: c8940c4e1aa2e9acc80cd4fe54753cd8
14410] 
14411[test_cli.py: fix error that crept in when rebasing the patch for #1072.
14412david-sarah@jacaranda.org**20100718000123
14413 Ignore-this: 3e8f6cc3a27b747c708221dd581934f4
14414] 
14415[stringutils: add test for when sys.stdout has no encoding attribute (fixes #1099).
14416david-sarah@jacaranda.org**20100717045816
14417 Ignore-this: f28dce6940e909f12f354086d17db54f
14418] 
14419[CLI: add 'tahoe unlink' as an alias to 'tahoe rm', for forward-compatibility.
14420david-sarah@jacaranda.org**20100717220411
14421 Ignore-this: 3ecdde7f2d0498514cef32e118e0b855
14422] 
14423[minor code clean-up in dirnode.py
14424zooko@zooko.com**20100714060255
14425 Ignore-this: bb0ab2783203e605024b3e2f798256a1
14426 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.
14427 fixes #967
14428] 
14429[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.
14430david-sarah@jacaranda.org**20100712003015
14431 Ignore-this: 103b809d180df17a7283077c3104c7be
14432] 
14433[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.
14434david-sarah@jacaranda.org**20100711195525
14435 Ignore-this: deac32d8b91ba26ede18905d3f7d2b93
14436] 
14437[docs: CREDITS and NEWS
14438zooko@zooko.com**20100714060150
14439 Ignore-this: dc83e612f77d69e50ee975f07f6b16fe
14440] 
14441[CREDITS: more creds for Kevan, plus utf-8 BOM
14442zooko@zooko.com**20100619045503
14443 Ignore-this: 72d02bdd7a0f324f1cee8cd399c7c6de
14444] 
14445[cli.py: make command descriptions consistently end with a full stop.
14446david-sarah@jacaranda.org**20100714014538
14447 Ignore-this: 9ee7fa29ca2d1631db4049c2a389a97a
14448] 
14449[SFTP: address some of the comments in zooko's review (#1106).
14450david-sarah@jacaranda.org**20100712025537
14451 Ignore-this: c3921638a2d4f1de2a776ae78e4dc37e
14452] 
14453[docs/logging.txt: note that setting flogging vars might affect tests with race conditions.
14454david-sarah@jacaranda.org**20100712050721
14455 Ignore-this: fc1609d215fcd5561a57fd1226206f27
14456] 
14457[test_storage.py: potential fix for failures when logging is enabled.
14458david-sarah@jacaranda.org**19700713040546
14459 Ignore-this: 5815693a0df3e64c52c3c6b7be2846c7
14460] 
14461[upcase_since_on_welcome
14462terrellrussell@gmail.com**20100708193903] 
14463[server_version_on_welcome_page.dpatch.txt
14464freestorm77@gmail.com**20100605191721
14465 Ignore-this: b450c76dc875f5ac8cca229a666cbd0a
14466 
14467 
14468 - The storage server version is 0 for all storage nodes in the Welcome Page
14469 
14470 
14471] 
14472[NEWS: add NEWS snippets about two recent patches
14473zooko@zooko.com**20100708162058
14474 Ignore-this: 6c9da6a0ad7351a960bdd60f81532899
14475] 
14476[directory_html_top_banner.dpatch
14477freestorm77@gmail.com**20100622205301
14478 Ignore-this: 1d770d975e0c414c996564774f049bca
14479 
14480 The div tag with the link "Return to Welcome page" on the directory.xhtml page is not correct
14481 
14482] 
14483[tahoe_css_toolbar.dpatch
14484freestorm77@gmail.com**20100622210046
14485 Ignore-this: 5b3ebb2e0f52bbba718a932f80c246c0
14486 
14487 CSS modification to be correctly diplayed with Internet Explorer 8
14488 
14489 The links on the top of page directory.xhtml are not diplayed in the same line as display with Firefox.
14490 
14491] 
14492[runnin_test_tahoe_css.dpatch
14493freestorm77@gmail.com**20100622214714
14494 Ignore-this: e0db73d68740aad09a7b9ae60a08c05c
14495 
14496 Runnin test for changes in tahoe.css file
14497 
14498] 
14499[runnin_test_directory_xhtml.dpatch
14500freestorm77@gmail.com**20100622201403
14501 Ignore-this: f8962463fce50b9466405cb59fe11d43
14502 
14503 Runnin test for diretory.xhtml top banner
14504 
14505] 
14506[stringutils.py: tolerate sys.stdout having no 'encoding' attribute.
14507david-sarah@jacaranda.org**20100626040817
14508 Ignore-this: f42cad81cef645ee38ac1df4660cc850
14509] 
14510[quickstart.html: python 2.5 -> 2.6 as recommended version
14511david-sarah@jacaranda.org**20100705175858
14512 Ignore-this: bc3a14645ea1d5435002966ae903199f
14513] 
14514[SFTP: don't call .stopProducing on the producer registered with OverwriteableFileConsumer (which breaks with warner's new downloader).
14515david-sarah@jacaranda.org**20100628231926
14516 Ignore-this: 131b7a5787bc85a9a356b5740d9d996f
14517] 
14518[docs/how_to_make_a_tahoe-lafs_release.txt: trivial correction, install.html should now be quickstart.html.
14519david-sarah@jacaranda.org**20100625223929
14520 Ignore-this: 99a5459cac51bd867cc11ad06927ff30
14521] 
14522[setup: in the Makefile, refuse to upload tarballs unless someone has passed the environment variable "BB_BRANCH" with value "trunk"
14523zooko@zooko.com**20100619034928
14524 Ignore-this: 276ddf9b6ad7ec79e27474862e0f7d6
14525] 
14526[trivial: tiny update to in-line comment
14527zooko@zooko.com**20100614045715
14528 Ignore-this: 10851b0ed2abfed542c97749e5d280bc
14529 (I'm actually committing this patch as a test of the new eager-annotation-computation of trac-darcs.)
14530] 
14531[docs: about.html link to home page early on, and be decentralized storage instead of cloud storage this time around
14532zooko@zooko.com**20100619065318
14533 Ignore-this: dc6db03f696e5b6d2848699e754d8053
14534] 
14535[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"
14536zooko@zooko.com**20100619065124
14537 Ignore-this: e292c7f51c337a84ebfeb366fbd24d6c
14538] 
14539[TAG allmydata-tahoe-1.7.0
14540zooko@zooko.com**20100619052631
14541 Ignore-this: d21e27afe6d85e2e3ba6a3292ba2be1
14542] 
14543Patch bundle hash:
14544cafcba4ff0a13e639071fe8a24982dd9dd5c1b9f