Ticket #393: 393status22.dpatch

File 393status22.dpatch, 626.7 KB (added by kevan, at 2010-07-28T23:44:09Z)
Line 
1Thu Jun 24 16:46:37 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
2  * Misc. changes to support the work I'm doing
3 
4      - Add a notion of file version number to interfaces.py
5      - Alter mutable file node interfaces to have a notion of version,
6        though this may be changed later.
7      - Alter mutable/filenode.py to conform to these changes.
8      - Add a salt hasher to util/hashutil.py
9
10Thu Jun 24 16:48:33 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
11  * nodemaker.py: create MDMF files when asked to
12
13Thu Jun 24 16:49:05 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
14  * storage/server.py: minor code cleanup
15
16Thu Jun 24 16:49:24 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
17  * test/test_mutable.py: alter some tests that were failing due to MDMF; minor code cleanup.
18
19Fri Jun 25 17:35:20 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
20  * test/test_mutable.py: change the definition of corrupt() to work with MDMF as well as SDMF files, change users of corrupt to use the new definition
21
22Sat Jun 26 16:41:18 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
23  * Alter the ServermapUpdater to find MDMF files
24 
25  The servermapupdater should find MDMF files on a grid in the same way
26  that it finds SDMF files. This patch makes it do that.
27
28Sat Jun 26 16:42:04 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
29  * Make a segmented mutable uploader
30 
31  The mutable file uploader should be able to publish files with one
32  segment and files with multiple segments. This patch makes it do that.
33  This is still incomplete, and rather ugly -- I need to flesh out error
34  handling, I need to write tests, and I need to remove some of the uglier
35  kludges in the process before I can call this done.
36
37Sat Jun 26 16:43:14 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
38  * Write a segmented mutable downloader
39 
40  The segmented mutable downloader can deal with MDMF files (files with
41  one or more segments in MDMF format) and SDMF files (files with one
42  segment in SDMF format). It is backwards compatible with the old
43  file format.
44 
45  This patch also contains tests for the segmented mutable downloader.
46
47Mon Jun 28 15:50:48 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
48  * mutable/checker.py: check MDMF files
49 
50  This patch adapts the mutable file checker and verifier to check and
51  verify MDMF files. It does this by using the new segmented downloader,
52  which is trained to perform verification operations on request. This
53  removes some code duplication.
54
55Mon Jun 28 15:52:01 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
56  * mutable/retrieve.py: learn how to verify mutable files
57
58Wed Jun 30 11:33:05 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
59  * interfaces.py: add IMutableSlotWriter
60
61Thu Jul  1 16:28:06 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
62  * test/test_mutable.py: temporarily disable two tests that are now irrelevant
63
64Fri Jul  2 15:55:31 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
65  * Add MDMF reader and writer, and SDMF writer
66 
67  The MDMF/SDMF reader MDMF writer, and SDMF writer are similar to the
68  object proxies that exist for immutable files. They abstract away
69  details of connection, state, and caching from their callers (in this
70  case, the download, servermap updater, and uploader), and expose methods
71  to get and set information on the remote server.
72 
73  MDMFSlotReadProxy reads a mutable file from the server, doing the right
74  thing (in most cases) regardless of whether the file is MDMF or SDMF. It
75  allows callers to tell it how to batch and flush reads.
76 
77  MDMFSlotWriteProxy writes an MDMF mutable file to a server.
78 
79  SDMFSlotWriteProxy writes an SDMF mutable file to a server.
80 
81  This patch also includes tests for MDMFSlotReadProxy,
82  SDMFSlotWriteProxy, and MDMFSlotWriteProxy.
83
84Fri Jul  2 15:55:54 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
85  * mutable/publish.py: cleanup + simplification
86
87Fri Jul  2 15:57:10 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
88  * test/test_mutable.py: remove tests that are no longer relevant
89
90Tue Jul  6 14:52:17 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
91  * interfaces.py: create IMutableUploadable
92
93Tue Jul  6 14:52:57 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
94  * mutable/publish.py: add MutableDataHandle and MutableFileHandle
95
96Tue Jul  6 14:55:41 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
97  * mutable/publish.py: reorganize in preparation of file-like uploadables
98
99Tue Jul  6 14:56:49 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
100  * test/test_mutable.py: write tests for MutableFileHandle and MutableDataHandle
101
102Wed Jul  7 17:00:31 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
103  * Alter tests to work with the new APIs
104
105Wed Jul  7 17:07:32 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
106  * Alter mutable files to use file-like objects for publishing instead of strings.
107
108Thu Jul  8 12:35:22 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
109  * test/test_sftp.py: alter a setup routine to work with new mutable file APIs.
110
111Thu Jul  8 12:36:00 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
112  * mutable/publish.py: make MutableFileHandle seek to the beginning of its file handle before reading.
113
114Fri Jul  9 16:29:12 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
115  * Refactor download interfaces to be more uniform, per #993
116
117Fri Jul 16 18:44:46 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
118  * frontends/sftpd.py: alter a mutable file overwrite to work with the new API
119
120Fri Jul 16 18:45:16 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
121  * mutable/filenode.py: implement most of IVersion, per #993
122
123Fri Jul 16 18:45:49 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
124  * mutable/publish.py: enable segmented uploading for big files, change constants and wording, change MutableDataHandle to MutableData
125
126Fri Jul 16 18:50:49 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
127  * immutable/filenode.py: fix broken implementation of #993 interfaces
128
129Fri Jul 16 18:51:23 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
130  * mutable/retrieve.py: alter Retrieve so that it can download parts of mutable files
131
132Fri Jul 16 18:52:10 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
133  * change MutableDataHandle to MutableData in code.
134
135Fri Jul 16 18:52:30 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
136  * tests: fix tests that were broken by #993
137
138Fri Jul 16 18:54:02 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
139  * test/test_immutable.py: add tests for #993-related modifications
140
141Fri Jul 16 18:54:26 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
142  * web/filenode.py: alter download code to use the new #993 interface.
143
144Fri Jul 16 18:55:01 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
145  * test/common.py: remove FileTooLargeErrors that tested for an SDMF limitation that no longer exists
146
147Tue Jul 20 14:31:09 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
148  * nodemaker.py: resolve a conflict introduced in one of the 1.7.1 patches
149
150Tue Jul 27 15:46:51 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
151  * frontends/sftpd.py: fix conflicts with trunk
152
153Tue Jul 27 15:47:03 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
154  * interfaces.py: Create an IWritable interface
155
156Tue Jul 27 15:47:25 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
157  * mutable/layout.py: Alter MDMFSlotWriteProxy to perform all write operations in one actual write operation
158
159Tue Jul 27 15:48:17 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
160  * test/test_mutable.py: test that write operations occur all at once
161
162Tue Jul 27 15:48:53 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
163  * test/test_storage.py: modify proxy tests to work with the new writing semantics
164
165Tue Jul 27 15:50:01 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
166  * mutable/publish.py: alter mutable publisher to work with new writing semantics
167
168Wed Jul 28 16:23:45 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
169  * mutable/servermap.py: lay some groundwork for IWritable
170
171Wed Jul 28 16:24:34 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
172  * test/test_mutable.py: Add tests for new servermap behavior
173
174New patches:
175
176[Misc. changes to support the work I'm doing
177Kevan Carstensen <kevan@isnotajoke.com>**20100624234637
178 Ignore-this: fdd18fa8cc05f4b4b15ff53ee24a1819
179 
180     - Add a notion of file version number to interfaces.py
181     - Alter mutable file node interfaces to have a notion of version,
182       though this may be changed later.
183     - Alter mutable/filenode.py to conform to these changes.
184     - Add a salt hasher to util/hashutil.py
185] {
186hunk ./src/allmydata/interfaces.py 7
187      ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
188 
189 HASH_SIZE=32
190+SALT_SIZE=16
191+
192+SDMF_VERSION=0
193+MDMF_VERSION=1
194 
195 Hash = StringConstraint(maxLength=HASH_SIZE,
196                         minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
197hunk ./src/allmydata/interfaces.py 811
198         writer-visible data using this writekey.
199         """
200 
201+    def set_version(version):
202+        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
203+        we upload in SDMF for reasons of compatibility. If you want to
204+        change this, set_version will let you do that.
205+
206+        To say that this file should be uploaded in SDMF, pass in a 0. To
207+        say that the file should be uploaded as MDMF, pass in a 1.
208+        """
209+
210+    def get_version():
211+        """Returns the mutable file protocol version."""
212+
213 class NotEnoughSharesError(Exception):
214     """Download was unable to get enough shares"""
215 
216hunk ./src/allmydata/mutable/filenode.py 8
217 from twisted.internet import defer, reactor
218 from foolscap.api import eventually
219 from allmydata.interfaces import IMutableFileNode, \
220-     ICheckable, ICheckResults, NotEnoughSharesError
221+     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
222 from allmydata.util import hashutil, log
223 from allmydata.util.assertutil import precondition
224 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
225hunk ./src/allmydata/mutable/filenode.py 67
226         self._sharemap = {} # known shares, shnum-to-[nodeids]
227         self._cache = ResponseCache()
228         self._most_recent_size = None
229+        # filled in after __init__ if we're being created for the first time;
230+        # filled in by the servermap updater before publishing, otherwise.
231+        # set to this default value in case neither of those things happen,
232+        # or in case the servermap can't find any shares to tell us what
233+        # to publish as.
234+        # TODO: Set this back to None, and find out why the tests fail
235+        #       with it set to None.
236+        self._protocol_version = SDMF_VERSION
237 
238         # all users of this MutableFileNode go through the serializer. This
239         # takes advantage of the fact that Deferreds discard the callbacks
240hunk ./src/allmydata/mutable/filenode.py 472
241     def _did_upload(self, res, size):
242         self._most_recent_size = size
243         return res
244+
245+
246+    def set_version(self, version):
247+        # I can be set in two ways:
248+        #  1. When the node is created.
249+        #  2. (for an existing share) when the Servermap is updated
250+        #     before I am read.
251+        assert version in (MDMF_VERSION, SDMF_VERSION)
252+        self._protocol_version = version
253+
254+
255+    def get_version(self):
256+        return self._protocol_version
257hunk ./src/allmydata/util/hashutil.py 90
258 MUTABLE_READKEY_TAG = "allmydata_mutable_writekey_to_readkey_v1"
259 MUTABLE_DATAKEY_TAG = "allmydata_mutable_readkey_to_datakey_v1"
260 MUTABLE_STORAGEINDEX_TAG = "allmydata_mutable_readkey_to_storage_index_v1"
261+MUTABLE_SALT_TAG = "allmydata_mutable_segment_salt_v1"
262 
263 # dirnodes
264 DIRNODE_CHILD_WRITECAP_TAG = "allmydata_mutable_writekey_and_salt_to_dirnode_child_capkey_v1"
265hunk ./src/allmydata/util/hashutil.py 134
266 def plaintext_segment_hasher():
267     return tagged_hasher(PLAINTEXT_SEGMENT_TAG)
268 
269+def mutable_salt_hash(data):
270+    return tagged_hash(MUTABLE_SALT_TAG, data)
271+def mutable_salt_hasher():
272+    return tagged_hasher(MUTABLE_SALT_TAG)
273+
274 KEYLEN = 16
275 IVLEN = 16
276 
277}
278[nodemaker.py: create MDMF files when asked to
279Kevan Carstensen <kevan@isnotajoke.com>**20100624234833
280 Ignore-this: 26c16aaca9ddab7a7ce37a4530bc970
281] {
282hunk ./src/allmydata/nodemaker.py 3
283 import weakref
284 from zope.interface import implements
285-from allmydata.interfaces import INodeMaker
286+from allmydata.util.assertutil import precondition
287+from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError, \
288+                                 SDMF_VERSION, MDMF_VERSION
289 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
290 from allmydata.immutable.upload import Data
291 from allmydata.mutable.filenode import MutableFileNode
292hunk ./src/allmydata/nodemaker.py 88
293             return self._create_dirnode(filenode)
294         return None
295 
296-    def create_mutable_file(self, contents=None, keysize=None):
297+    def create_mutable_file(self, contents=None, keysize=None,
298+                            version=SDMF_VERSION):
299         n = MutableFileNode(self.storage_broker, self.secret_holder,
300                             self.default_encoding_parameters, self.history)
301hunk ./src/allmydata/nodemaker.py 92
302+        n.set_version(version)
303         d = self.key_generator.generate(keysize)
304         d.addCallback(n.create_with_keys, contents)
305         d.addCallback(lambda res: n)
306hunk ./src/allmydata/nodemaker.py 98
307         return d
308 
309-    def create_new_mutable_directory(self, initial_children={}):
310+    def create_new_mutable_directory(self, initial_children={},
311+                                     version=SDMF_VERSION):
312+        # initial_children must have metadata (i.e. {} instead of None)
313+        for (name, (node, metadata)) in initial_children.iteritems():
314+            precondition(isinstance(metadata, dict),
315+                         "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
316+            node.raise_error()
317         d = self.create_mutable_file(lambda n:
318                                      pack_children(initial_children, n.get_writekey()))
319         d.addCallback(self._create_dirnode)
320merger 0.0 (
321hunk ./src/allmydata/nodemaker.py 106
322-                                     pack_children(n, initial_children))
323+                                     pack_children(initial_children, n.get_writekey()))
324hunk ./src/allmydata/nodemaker.py 106
325-                                     pack_children(n, initial_children))
326+                                     pack_children(n, initial_children),
327+                                     version)
328)
329}
330[storage/server.py: minor code cleanup
331Kevan Carstensen <kevan@isnotajoke.com>**20100624234905
332 Ignore-this: 2358c531c39e48d3c8e56b62b5768228
333] {
334hunk ./src/allmydata/storage/server.py 569
335                                          self)
336         return share
337 
338-    def remote_slot_readv(self, storage_index, shares, readv):
339+    def remote_slot_readv(self, storage_index, shares, readvs):
340         start = time.time()
341         self.count("readv")
342         si_s = si_b2a(storage_index)
343hunk ./src/allmydata/storage/server.py 590
344             if sharenum in shares or not shares:
345                 filename = os.path.join(bucketdir, sharenum_s)
346                 msf = MutableShareFile(filename, self)
347-                datavs[sharenum] = msf.readv(readv)
348+                datavs[sharenum] = msf.readv(readvs)
349         log.msg("returning shares %s" % (datavs.keys(),),
350                 facility="tahoe.storage", level=log.NOISY, parent=lp)
351         self.add_latency("readv", time.time() - start)
352}
353[test/test_mutable.py: alter some tests that were failing due to MDMF; minor code cleanup.
354Kevan Carstensen <kevan@isnotajoke.com>**20100624234924
355 Ignore-this: afb86ec1fbdbfe1a5ef6f46f350273c0
356] {
357hunk ./src/allmydata/test/test_mutable.py 151
358             chr(ord(original[byte_offset]) ^ 0x01) +
359             original[byte_offset+1:])
360 
361+def add_two(original, byte_offset):
362+    # It isn't enough to simply flip the bit for the version number,
363+    # because 1 is a valid version number. So we add two instead.
364+    return (original[:byte_offset] +
365+            chr(ord(original[byte_offset]) ^ 0x02) +
366+            original[byte_offset+1:])
367+
368 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
369     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
370     # list of shnums to corrupt.
371hunk ./src/allmydata/test/test_mutable.py 187
372                 real_offset = offset1
373             real_offset = int(real_offset) + offset2 + offset_offset
374             assert isinstance(real_offset, int), offset
375-            shares[shnum] = flip_bit(data, real_offset)
376+            if offset1 == 0: # verbyte
377+                f = add_two
378+            else:
379+                f = flip_bit
380+            shares[shnum] = f(data, real_offset)
381     return res
382 
383 def make_storagebroker(s=None, num_peers=10):
384hunk ./src/allmydata/test/test_mutable.py 423
385         d.addCallback(_created)
386         return d
387 
388+
389     def test_modify_backoffer(self):
390         def _modifier(old_contents, servermap, first_time):
391             return old_contents + "line2"
392hunk ./src/allmydata/test/test_mutable.py 658
393         d.addCallback(_created)
394         return d
395 
396+
397     def _copy_shares(self, ignored, index):
398         shares = self._storage._peers
399         # we need a deep copy
400}
401[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
402Kevan Carstensen <kevan@isnotajoke.com>**20100626003520
403 Ignore-this: 836e59e2fde0535f6b4bea3468dc8244
404] {
405hunk ./src/allmydata/test/test_mutable.py 168
406                 and shnum not in shnums_to_corrupt):
407                 continue
408             data = shares[shnum]
409-            (version,
410-             seqnum,
411-             root_hash,
412-             IV,
413-             k, N, segsize, datalen,
414-             o) = unpack_header(data)
415-            if isinstance(offset, tuple):
416-                offset1, offset2 = offset
417-            else:
418-                offset1 = offset
419-                offset2 = 0
420-            if offset1 == "pubkey":
421-                real_offset = 107
422-            elif offset1 in o:
423-                real_offset = o[offset1]
424-            else:
425-                real_offset = offset1
426-            real_offset = int(real_offset) + offset2 + offset_offset
427-            assert isinstance(real_offset, int), offset
428-            if offset1 == 0: # verbyte
429-                f = add_two
430-            else:
431-                f = flip_bit
432-            shares[shnum] = f(data, real_offset)
433-    return res
434+            # We're feeding the reader all of the share data, so it
435+            # won't need to use the rref that we didn't provide, nor the
436+            # storage index that we didn't provide. We do this because
437+            # the reader will work for both MDMF and SDMF.
438+            reader = MDMFSlotReadProxy(None, None, shnum, data)
439+            # We need to get the offsets for the next part.
440+            d = reader.get_verinfo()
441+            def _do_corruption(verinfo, data, shnum):
442+                (seqnum,
443+                 root_hash,
444+                 IV,
445+                 segsize,
446+                 datalen,
447+                 k, n, prefix, o) = verinfo
448+                if isinstance(offset, tuple):
449+                    offset1, offset2 = offset
450+                else:
451+                    offset1 = offset
452+                    offset2 = 0
453+                if offset1 == "pubkey":
454+                    real_offset = 107
455+                elif offset1 in o:
456+                    real_offset = o[offset1]
457+                else:
458+                    real_offset = offset1
459+                real_offset = int(real_offset) + offset2 + offset_offset
460+                assert isinstance(real_offset, int), offset
461+                if offset1 == 0: # verbyte
462+                    f = add_two
463+                else:
464+                    f = flip_bit
465+                shares[shnum] = f(data, real_offset)
466+            d.addCallback(_do_corruption, data, shnum)
467+            ds.append(d)
468+    dl = defer.DeferredList(ds)
469+    dl.addCallback(lambda ignored: res)
470+    return dl
471 
472 def make_storagebroker(s=None, num_peers=10):
473     if not s:
474hunk ./src/allmydata/test/test_mutable.py 1177
475         return d
476 
477     def test_download_fails(self):
478-        corrupt(None, self._storage, "signature")
479-        d = self.shouldFail(UnrecoverableFileError, "test_download_anyway",
480+        d = corrupt(None, self._storage, "signature")
481+        d.addCallback(lambda ignored:
482+            self.shouldFail(UnrecoverableFileError, "test_download_anyway",
483                             "no recoverable versions",
484                             self._fn.download_best_version)
485         return d
486hunk ./src/allmydata/test/test_mutable.py 1232
487         return d
488 
489     def test_check_all_bad_sig(self):
490-        corrupt(None, self._storage, 1) # bad sig
491-        d = self._fn.check(Monitor())
492+        d = corrupt(None, self._storage, 1) # bad sig
493+        d.addCallback(lambda ignored:
494+            self._fn.check(Monitor()))
495         d.addCallback(self.check_bad, "test_check_all_bad_sig")
496         return d
497 
498hunk ./src/allmydata/test/test_mutable.py 1239
499     def test_check_all_bad_blocks(self):
500-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
501+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
502         # the Checker won't notice this.. it doesn't look at actual data
503hunk ./src/allmydata/test/test_mutable.py 1241
504-        d = self._fn.check(Monitor())
505+        d.addCallback(lambda ignored:
506+            self._fn.check(Monitor()))
507         d.addCallback(self.check_good, "test_check_all_bad_blocks")
508         return d
509 
510hunk ./src/allmydata/test/test_mutable.py 1252
511         return d
512 
513     def test_verify_all_bad_sig(self):
514-        corrupt(None, self._storage, 1) # bad sig
515-        d = self._fn.check(Monitor(), verify=True)
516+        d = corrupt(None, self._storage, 1) # bad sig
517+        d.addCallback(lambda ignored:
518+            self._fn.check(Monitor(), verify=True))
519         d.addCallback(self.check_bad, "test_verify_all_bad_sig")
520         return d
521 
522hunk ./src/allmydata/test/test_mutable.py 1259
523     def test_verify_one_bad_sig(self):
524-        corrupt(None, self._storage, 1, [9]) # bad sig
525-        d = self._fn.check(Monitor(), verify=True)
526+        d = corrupt(None, self._storage, 1, [9]) # bad sig
527+        d.addCallback(lambda ignored:
528+            self._fn.check(Monitor(), verify=True))
529         d.addCallback(self.check_bad, "test_verify_one_bad_sig")
530         return d
531 
532hunk ./src/allmydata/test/test_mutable.py 1266
533     def test_verify_one_bad_block(self):
534-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
535+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
536         # the Verifier *will* notice this, since it examines every byte
537hunk ./src/allmydata/test/test_mutable.py 1268
538-        d = self._fn.check(Monitor(), verify=True)
539+        d.addCallback(lambda ignored:
540+            self._fn.check(Monitor(), verify=True))
541         d.addCallback(self.check_bad, "test_verify_one_bad_block")
542         d.addCallback(self.check_expected_failure,
543                       CorruptShareError, "block hash tree failure",
544hunk ./src/allmydata/test/test_mutable.py 1277
545         return d
546 
547     def test_verify_one_bad_sharehash(self):
548-        corrupt(None, self._storage, "share_hash_chain", [9], 5)
549-        d = self._fn.check(Monitor(), verify=True)
550+        d = corrupt(None, self._storage, "share_hash_chain", [9], 5)
551+        d.addCallback(lambda ignored:
552+            self._fn.check(Monitor(), verify=True))
553         d.addCallback(self.check_bad, "test_verify_one_bad_sharehash")
554         d.addCallback(self.check_expected_failure,
555                       CorruptShareError, "corrupt hashes",
556hunk ./src/allmydata/test/test_mutable.py 1287
557         return d
558 
559     def test_verify_one_bad_encprivkey(self):
560-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
561-        d = self._fn.check(Monitor(), verify=True)
562+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
563+        d.addCallback(lambda ignored:
564+            self._fn.check(Monitor(), verify=True))
565         d.addCallback(self.check_bad, "test_verify_one_bad_encprivkey")
566         d.addCallback(self.check_expected_failure,
567                       CorruptShareError, "invalid privkey",
568hunk ./src/allmydata/test/test_mutable.py 1297
569         return d
570 
571     def test_verify_one_bad_encprivkey_uncheckable(self):
572-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
573+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
574         readonly_fn = self._fn.get_readonly()
575         # a read-only node has no way to validate the privkey
576hunk ./src/allmydata/test/test_mutable.py 1300
577-        d = readonly_fn.check(Monitor(), verify=True)
578+        d.addCallback(lambda ignored:
579+            readonly_fn.check(Monitor(), verify=True))
580         d.addCallback(self.check_good,
581                       "test_verify_one_bad_encprivkey_uncheckable")
582         return d
583}
584[Alter the ServermapUpdater to find MDMF files
585Kevan Carstensen <kevan@isnotajoke.com>**20100626234118
586 Ignore-this: 25f6278209c2983ba8f307cfe0fde0
587 
588 The servermapupdater should find MDMF files on a grid in the same way
589 that it finds SDMF files. This patch makes it do that.
590] {
591hunk ./src/allmydata/mutable/servermap.py 7
592 from itertools import count
593 from twisted.internet import defer
594 from twisted.python import failure
595-from foolscap.api import DeadReferenceError, RemoteException, eventually
596+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
597+                         fireEventually
598 from allmydata.util import base32, hashutil, idlib, log
599 from allmydata.storage.server import si_b2a
600 from allmydata.interfaces import IServermapUpdaterStatus
601hunk ./src/allmydata/mutable/servermap.py 17
602 from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \
603      DictOfSets, CorruptShareError, NeedMoreDataError
604 from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
605-     SIGNED_PREFIX_LENGTH
606+     SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
607 
608 class UpdateStatus:
609     implements(IServermapUpdaterStatus)
610hunk ./src/allmydata/mutable/servermap.py 254
611         """Return a set of versionids, one for each version that is currently
612         recoverable."""
613         versionmap = self.make_versionmap()
614-
615         recoverable_versions = set()
616         for (verinfo, shares) in versionmap.items():
617             (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
618hunk ./src/allmydata/mutable/servermap.py 366
619         self._servers_responded = set()
620 
621         # how much data should we read?
622+        # SDMF:
623         #  * if we only need the checkstring, then [0:75]
624         #  * if we need to validate the checkstring sig, then [543ish:799ish]
625         #  * if we need the verification key, then [107:436ish]
626hunk ./src/allmydata/mutable/servermap.py 374
627         #  * if we need the encrypted private key, we want [-1216ish:]
628         #   * but we can't read from negative offsets
629         #   * the offset table tells us the 'ish', also the positive offset
630-        # A future version of the SMDF slot format should consider using
631-        # fixed-size slots so we can retrieve less data. For now, we'll just
632-        # read 2000 bytes, which also happens to read enough actual data to
633-        # pre-fetch a 9-entry dirnode.
634+        # MDMF:
635+        #  * Checkstring? [0:72]
636+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
637+        #    the offset table will tell us for sure.
638+        #  * If we need the verification key, we have to consult the offset
639+        #    table as well.
640+        # At this point, we don't know which we are. Our filenode can
641+        # tell us, but it might be lying -- in some cases, we're
642+        # responsible for telling it which kind of file it is.
643         self._read_size = 4000
644         if mode == MODE_CHECK:
645             # we use unpack_prefix_and_signature, so we need 1k
646hunk ./src/allmydata/mutable/servermap.py 432
647         self._queries_completed = 0
648 
649         sb = self._storage_broker
650+        # All of the peers, permuted by the storage index, as usual.
651         full_peerlist = sb.get_servers_for_index(self._storage_index)
652         self.full_peerlist = full_peerlist # for use later, immutable
653         self.extra_peers = full_peerlist[:] # peers are removed as we use them
654hunk ./src/allmydata/mutable/servermap.py 439
655         self._good_peers = set() # peers who had some shares
656         self._empty_peers = set() # peers who don't have any shares
657         self._bad_peers = set() # peers to whom our queries failed
658+        self._readers = {} # peerid -> dict(sharewriters), filled in
659+                           # after responses come in.
660 
661         k = self._node.get_required_shares()
662hunk ./src/allmydata/mutable/servermap.py 443
663+        # For what cases can these conditions work?
664         if k is None:
665             # make a guess
666             k = 3
667hunk ./src/allmydata/mutable/servermap.py 456
668         self.num_peers_to_query = k + self.EPSILON
669 
670         if self.mode == MODE_CHECK:
671+            # We want to query all of the peers.
672             initial_peers_to_query = dict(full_peerlist)
673             must_query = set(initial_peers_to_query.keys())
674             self.extra_peers = []
675hunk ./src/allmydata/mutable/servermap.py 464
676             # we're planning to replace all the shares, so we want a good
677             # chance of finding them all. We will keep searching until we've
678             # seen epsilon that don't have a share.
679+            # We don't query all of the peers because that could take a while.
680             self.num_peers_to_query = N + self.EPSILON
681             initial_peers_to_query, must_query = self._build_initial_querylist()
682             self.required_num_empty_peers = self.EPSILON
683hunk ./src/allmydata/mutable/servermap.py 474
684             # might also avoid the round trip required to read the encrypted
685             # private key.
686 
687-        else:
688+        else: # MODE_READ, MODE_ANYTHING
689+            # 2k peers is good enough.
690             initial_peers_to_query, must_query = self._build_initial_querylist()
691 
692         # this is a set of peers that we are required to get responses from:
693hunk ./src/allmydata/mutable/servermap.py 490
694         # before we can consider ourselves finished, and self.extra_peers
695         # contains the overflow (peers that we should tap if we don't get
696         # enough responses)
697+        # I guess that self._must_query is a subset of
698+        # initial_peers_to_query?
699+        assert set(must_query).issubset(set(initial_peers_to_query))
700 
701         self._send_initial_requests(initial_peers_to_query)
702         self._status.timings["initial_queries"] = time.time() - self._started
703hunk ./src/allmydata/mutable/servermap.py 549
704         # errors that aren't handled by _query_failed (and errors caused by
705         # _query_failed) get logged, but we still want to check for doneness.
706         d.addErrback(log.err)
707-        d.addBoth(self._check_for_done)
708         d.addErrback(self._fatal_error)
709hunk ./src/allmydata/mutable/servermap.py 550
710+        d.addCallback(self._check_for_done)
711         return d
712 
713     def _do_read(self, ss, peerid, storage_index, shnums, readv):
714hunk ./src/allmydata/mutable/servermap.py 569
715         d = ss.callRemote("slot_readv", storage_index, shnums, readv)
716         return d
717 
718+
719+    def _got_corrupt_share(self, e, shnum, peerid, data, lp):
720+        """
721+        I am called when a remote server returns a corrupt share in
722+        response to one of our queries. By corrupt, I mean a share
723+        without a valid signature. I then record the failure, notify the
724+        server of the corruption, and record the share as bad.
725+        """
726+        f = failure.Failure(e)
727+        self.log(format="bad share: %(f_value)s", f_value=str(f),
728+                 failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
729+        # Notify the server that its share is corrupt.
730+        self.notify_server_corruption(peerid, shnum, str(e))
731+        # By flagging this as a bad peer, we won't count any of
732+        # the other shares on that peer as valid, though if we
733+        # happen to find a valid version string amongst those
734+        # shares, we'll keep track of it so that we don't need
735+        # to validate the signature on those again.
736+        self._bad_peers.add(peerid)
737+        self._last_failure = f
738+        # XXX: Use the reader for this?
739+        checkstring = data[:SIGNED_PREFIX_LENGTH]
740+        self._servermap.mark_bad_share(peerid, shnum, checkstring)
741+        self._servermap.problems.append(f)
742+
743+
744+    def _cache_good_sharedata(self, verinfo, shnum, now, data):
745+        """
746+        If one of my queries returns successfully (which means that we
747+        were able to and successfully did validate the signature), I
748+        cache the data that we initially fetched from the storage
749+        server. This will help reduce the number of roundtrips that need
750+        to occur when the file is downloaded, or when the file is
751+        updated.
752+        """
753+        self._node._add_to_cache(verinfo, shnum, 0, data, now)
754+
755+
756     def _got_results(self, datavs, peerid, readsize, stuff, started):
757         lp = self.log(format="got result from [%(peerid)s], %(numshares)d shares",
758                       peerid=idlib.shortnodeid_b2a(peerid),
759hunk ./src/allmydata/mutable/servermap.py 630
760         else:
761             self._empty_peers.add(peerid)
762 
763-        last_verinfo = None
764-        last_shnum = None
765+        ss, storage_index = stuff
766+        ds = []
767+
768         for shnum,datav in datavs.items():
769             data = datav[0]
770hunk ./src/allmydata/mutable/servermap.py 635
771-            try:
772-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
773-                last_verinfo = verinfo
774-                last_shnum = shnum
775-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
776-            except CorruptShareError, e:
777-                # log it and give the other shares a chance to be processed
778-                f = failure.Failure()
779-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
780-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
781-                self.notify_server_corruption(peerid, shnum, str(e))
782-                self._bad_peers.add(peerid)
783-                self._last_failure = f
784-                checkstring = data[:SIGNED_PREFIX_LENGTH]
785-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
786-                self._servermap.problems.append(f)
787-                pass
788-
789-        self._status.timings["cumulative_verify"] += (time.time() - now)
790+            reader = MDMFSlotReadProxy(ss,
791+                                       storage_index,
792+                                       shnum,
793+                                       data)
794+            self._readers.setdefault(peerid, dict())[shnum] = reader
795+            # our goal, with each response, is to validate the version
796+            # information and share data as best we can at this point --
797+            # we do this by validating the signature. To do this, we
798+            # need to do the following:
799+            #   - If we don't already have the public key, fetch the
800+            #     public key. We use this to validate the signature.
801+            if not self._node.get_pubkey():
802+                # fetch and set the public key.
803+                d = reader.get_verification_key()
804+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
805+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
806+                # XXX: Make self._pubkey_query_failed?
807+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
808+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
809+            else:
810+                # we already have the public key.
811+                d = defer.succeed(None)
812+            # Neither of these two branches return anything of
813+            # consequence, so the first entry in our deferredlist will
814+            # be None.
815 
816hunk ./src/allmydata/mutable/servermap.py 661
817-        if self._need_privkey and last_verinfo:
818-            # send them a request for the privkey. We send one request per
819-            # server.
820-            lp2 = self.log("sending privkey request",
821-                           parent=lp, level=log.NOISY)
822-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
823-             offsets_tuple) = last_verinfo
824-            o = dict(offsets_tuple)
825+            # - Next, we need the version information. We almost
826+            #   certainly got this by reading the first thousand or so
827+            #   bytes of the share on the storage server, so we
828+            #   shouldn't need to fetch anything at this step.
829+            d2 = reader.get_verinfo()
830+            d2.addErrback(lambda error, shnum=shnum, peerid=peerid:
831+                self._got_corrupt_share(error, shnum, peerid, data, lp))
832+            # - Next, we need the signature. For an SDMF share, it is
833+            #   likely that we fetched this when doing our initial fetch
834+            #   to get the version information. In MDMF, this lives at
835+            #   the end of the share, so unless the file is quite small,
836+            #   we'll need to do a remote fetch to get it.
837+            d3 = reader.get_signature()
838+            d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
839+                self._got_corrupt_share(error, shnum, peerid, data, lp))
840+            #  Once we have all three of these responses, we can move on
841+            #  to validating the signature
842 
843hunk ./src/allmydata/mutable/servermap.py 679
844-            self._queries_outstanding.add(peerid)
845-            readv = [ (o['enc_privkey'], (o['EOF'] - o['enc_privkey'])) ]
846-            ss = self._servermap.connections[peerid]
847-            privkey_started = time.time()
848-            d = self._do_read(ss, peerid, self._storage_index,
849-                              [last_shnum], readv)
850-            d.addCallback(self._got_privkey_results, peerid, last_shnum,
851-                          privkey_started, lp2)
852-            d.addErrback(self._privkey_query_failed, peerid, last_shnum, lp2)
853-            d.addErrback(log.err)
854-            d.addCallback(self._check_for_done)
855-            d.addErrback(self._fatal_error)
856+            # Does the node already have a privkey? If not, we'll try to
857+            # fetch it here.
858+            if self._need_privkey:
859+                d4 = reader.get_encprivkey()
860+                d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
861+                    self._try_to_validate_privkey(results, peerid, shnum, lp))
862+                d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
863+                    self._privkey_query_failed(error, shnum, data, lp))
864+            else:
865+                d4 = defer.succeed(None)
866 
867hunk ./src/allmydata/mutable/servermap.py 690
868+            dl = defer.DeferredList([d, d2, d3, d4])
869+            dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
870+                self._got_signature_one_share(results, shnum, peerid, lp))
871+            dl.addErrback(lambda error, shnum=shnum, data=data:
872+               self._got_corrupt_share(error, shnum, peerid, data, lp))
873+            dl.addCallback(lambda verinfo, shnum=shnum, peerid=peerid, data=data:
874+                self._cache_good_sharedata(verinfo, shnum, now, data))
875+            ds.append(dl)
876+        # dl is a deferred list that will fire when all of the shares
877+        # that we found on this peer are done processing. When dl fires,
878+        # we know that processing is done, so we can decrement the
879+        # semaphore-like thing that we incremented earlier.
880+        dl = defer.DeferredList(ds, fireOnOneErrback=True)
881+        # Are we done? Done means that there are no more queries to
882+        # send, that there are no outstanding queries, and that we
883+        # haven't received any queries that are still processing. If we
884+        # are done, self._check_for_done will cause the done deferred
885+        # that we returned to our caller to fire, which tells them that
886+        # they have a complete servermap, and that we won't be touching
887+        # the servermap anymore.
888+        dl.addCallback(self._check_for_done)
889+        dl.addErrback(self._fatal_error)
890         # all done!
891         self.log("_got_results done", parent=lp, level=log.NOISY)
892hunk ./src/allmydata/mutable/servermap.py 714
893+        return dl
894+
895+
896+    def _try_to_set_pubkey(self, pubkey_s, peerid, shnum, lp):
897+        if self._node.get_pubkey():
898+            return # don't go through this again if we don't have to
899+        fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
900+        assert len(fingerprint) == 32
901+        if fingerprint != self._node.get_fingerprint():
902+            raise CorruptShareError(peerid, shnum,
903+                                "pubkey doesn't match fingerprint")
904+        self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
905+        assert self._node.get_pubkey()
906+
907 
908     def notify_server_corruption(self, peerid, shnum, reason):
909         ss = self._servermap.connections[peerid]
910hunk ./src/allmydata/mutable/servermap.py 734
911         ss.callRemoteOnly("advise_corrupt_share",
912                           "mutable", self._storage_index, shnum, reason)
913 
914-    def _got_results_one_share(self, shnum, data, peerid, lp):
915+
916+    def _got_signature_one_share(self, results, shnum, peerid, lp):
917+        # It is our job to give versioninfo to our caller. We need to
918+        # raise CorruptShareError if the share is corrupt for any
919+        # reason, something that our caller will handle.
920         self.log(format="_got_results: got shnum #%(shnum)d from peerid %(peerid)s",
921                  shnum=shnum,
922                  peerid=idlib.shortnodeid_b2a(peerid),
923hunk ./src/allmydata/mutable/servermap.py 744
924                  level=log.NOISY,
925                  parent=lp)
926-
927-        # this might raise NeedMoreDataError, if the pubkey and signature
928-        # live at some weird offset. That shouldn't happen, so I'm going to
929-        # treat it as a bad share.
930-        (seqnum, root_hash, IV, k, N, segsize, datalength,
931-         pubkey_s, signature, prefix) = unpack_prefix_and_signature(data)
932-
933-        if not self._node.get_pubkey():
934-            fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
935-            assert len(fingerprint) == 32
936-            if fingerprint != self._node.get_fingerprint():
937-                raise CorruptShareError(peerid, shnum,
938-                                        "pubkey doesn't match fingerprint")
939-            self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
940-
941-        if self._need_privkey:
942-            self._try_to_extract_privkey(data, peerid, shnum, lp)
943-
944-        (ig_version, ig_seqnum, ig_root_hash, ig_IV, ig_k, ig_N,
945-         ig_segsize, ig_datalen, offsets) = unpack_header(data)
946+        _, verinfo, signature, __ = results
947+        (seqnum,
948+         root_hash,
949+         saltish,
950+         segsize,
951+         datalen,
952+         k,
953+         n,
954+         prefix,
955+         offsets) = verinfo[1]
956         offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
957 
958hunk ./src/allmydata/mutable/servermap.py 756
959-        verinfo = (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
960+        # XXX: This should be done for us in the method, so
961+        # presumably you can go in there and fix it.
962+        verinfo = (seqnum,
963+                   root_hash,
964+                   saltish,
965+                   segsize,
966+                   datalen,
967+                   k,
968+                   n,
969+                   prefix,
970                    offsets_tuple)
971hunk ./src/allmydata/mutable/servermap.py 767
972+        # This tuple uniquely identifies a share on the grid; we use it
973+        # to keep track of the ones that we've already seen.
974 
975         if verinfo not in self._valid_versions:
976hunk ./src/allmydata/mutable/servermap.py 771
977-            # it's a new pair. Verify the signature.
978-            valid = self._node.get_pubkey().verify(prefix, signature)
979+            # This is a new version tuple, and we need to validate it
980+            # against the public key before keeping track of it.
981+            assert self._node.get_pubkey()
982+            valid = self._node.get_pubkey().verify(prefix, signature[1])
983             if not valid:
984hunk ./src/allmydata/mutable/servermap.py 776
985-                raise CorruptShareError(peerid, shnum, "signature is invalid")
986+                raise CorruptShareError(peerid, shnum,
987+                                        "signature is invalid")
988 
989hunk ./src/allmydata/mutable/servermap.py 779
990-            # ok, it's a valid verinfo. Add it to the list of validated
991-            # versions.
992-            self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
993-                     % (seqnum, base32.b2a(root_hash)[:4],
994-                        idlib.shortnodeid_b2a(peerid), shnum,
995-                        k, N, segsize, datalength),
996-                     parent=lp)
997-            self._valid_versions.add(verinfo)
998-        # We now know that this is a valid candidate verinfo.
999+        # ok, it's a valid verinfo. Add it to the list of validated
1000+        # versions.
1001+        self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
1002+                 % (seqnum, base32.b2a(root_hash)[:4],
1003+                    idlib.shortnodeid_b2a(peerid), shnum,
1004+                    k, n, segsize, datalen),
1005+                    parent=lp)
1006+        self._valid_versions.add(verinfo)
1007+        # We now know that this is a valid candidate verinfo. Whether or
1008+        # not this instance of it is valid is a matter for the next
1009+        # statement; at this point, we just know that if we see this
1010+        # version info again, that its signature checks out and that
1011+        # we're okay to skip the signature-checking step.
1012 
1013hunk ./src/allmydata/mutable/servermap.py 793
1014+        # (peerid, shnum) are bound in the method invocation.
1015         if (peerid, shnum) in self._servermap.bad_shares:
1016             # we've been told that the rest of the data in this share is
1017             # unusable, so don't add it to the servermap.
1018hunk ./src/allmydata/mutable/servermap.py 808
1019         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
1020         return verinfo
1021 
1022+
1023     def _deserialize_pubkey(self, pubkey_s):
1024         verifier = rsa.create_verifying_key_from_string(pubkey_s)
1025         return verifier
1026hunk ./src/allmydata/mutable/servermap.py 813
1027 
1028-    def _try_to_extract_privkey(self, data, peerid, shnum, lp):
1029-        try:
1030-            r = unpack_share(data)
1031-        except NeedMoreDataError, e:
1032-            # this share won't help us. oh well.
1033-            offset = e.encprivkey_offset
1034-            length = e.encprivkey_length
1035-            self.log("shnum %d on peerid %s: share was too short (%dB) "
1036-                     "to get the encprivkey; [%d:%d] ought to hold it" %
1037-                     (shnum, idlib.shortnodeid_b2a(peerid), len(data),
1038-                      offset, offset+length),
1039-                     parent=lp)
1040-            # NOTE: if uncoordinated writes are taking place, someone might
1041-            # change the share (and most probably move the encprivkey) before
1042-            # we get a chance to do one of these reads and fetch it. This
1043-            # will cause us to see a NotEnoughSharesError(unable to fetch
1044-            # privkey) instead of an UncoordinatedWriteError . This is a
1045-            # nuisance, but it will go away when we move to DSA-based mutable
1046-            # files (since the privkey will be small enough to fit in the
1047-            # write cap).
1048-
1049-            return
1050-
1051-        (seqnum, root_hash, IV, k, N, segsize, datalen,
1052-         pubkey, signature, share_hash_chain, block_hash_tree,
1053-         share_data, enc_privkey) = r
1054-
1055-        return self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1056 
1057     def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
1058hunk ./src/allmydata/mutable/servermap.py 815
1059-
1060+        """
1061+        Given a writekey from a remote server, I validate it against the
1062+        writekey stored in my node. If it is valid, then I set the
1063+        privkey and encprivkey properties of the node.
1064+        """
1065         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
1066         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
1067         if alleged_writekey != self._node.get_writekey():
1068hunk ./src/allmydata/mutable/servermap.py 892
1069         self._queries_completed += 1
1070         self._last_failure = f
1071 
1072-    def _got_privkey_results(self, datavs, peerid, shnum, started, lp):
1073-        now = time.time()
1074-        elapsed = now - started
1075-        self._status.add_per_server_time(peerid, "privkey", started, elapsed)
1076-        self._queries_outstanding.discard(peerid)
1077-        if not self._need_privkey:
1078-            return
1079-        if shnum not in datavs:
1080-            self.log("privkey wasn't there when we asked it",
1081-                     level=log.WEIRD, umid="VA9uDQ")
1082-            return
1083-        datav = datavs[shnum]
1084-        enc_privkey = datav[0]
1085-        self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1086 
1087     def _privkey_query_failed(self, f, peerid, shnum, lp):
1088         self._queries_outstanding.discard(peerid)
1089hunk ./src/allmydata/mutable/servermap.py 906
1090         self._servermap.problems.append(f)
1091         self._last_failure = f
1092 
1093+
1094     def _check_for_done(self, res):
1095         # exit paths:
1096         #  return self._send_more_queries(outstanding) : send some more queries
1097hunk ./src/allmydata/mutable/servermap.py 912
1098         #  return self._done() : all done
1099         #  return : keep waiting, no new queries
1100-
1101         lp = self.log(format=("_check_for_done, mode is '%(mode)s', "
1102                               "%(outstanding)d queries outstanding, "
1103                               "%(extra)d extra peers available, "
1104hunk ./src/allmydata/mutable/servermap.py 1117
1105         self._servermap.last_update_time = self._started
1106         # the servermap will not be touched after this
1107         self.log("servermap: %s" % self._servermap.summarize_versions())
1108+
1109         eventually(self._done_deferred.callback, self._servermap)
1110 
1111     def _fatal_error(self, f):
1112hunk ./src/allmydata/test/test_mutable.py 637
1113         d.addCallback(_created)
1114         return d
1115 
1116-    def publish_multiple(self):
1117+    def publish_mdmf(self):
1118+        # like publish_one, except that the result is guaranteed to be
1119+        # an MDMF file.
1120+        # self.CONTENTS should have more than one segment.
1121+        self.CONTENTS = "This is an MDMF file" * 100000
1122+        self._storage = FakeStorage()
1123+        self._nodemaker = make_nodemaker(self._storage)
1124+        self._storage_broker = self._nodemaker.storage_broker
1125+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
1126+        def _created(node):
1127+            self._fn = node
1128+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1129+        d.addCallback(_created)
1130+        return d
1131+
1132+
1133+    def publish_sdmf(self):
1134+        # like publish_one, except that the result is guaranteed to be
1135+        # an SDMF file
1136+        self.CONTENTS = "This is an SDMF file" * 1000
1137+        self._storage = FakeStorage()
1138+        self._nodemaker = make_nodemaker(self._storage)
1139+        self._storage_broker = self._nodemaker.storage_broker
1140+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
1141+        def _created(node):
1142+            self._fn = node
1143+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1144+        d.addCallback(_created)
1145+        return d
1146+
1147+
1148+    def publish_multiple(self, version=0):
1149         self.CONTENTS = ["Contents 0",
1150                          "Contents 1",
1151                          "Contents 2",
1152hunk ./src/allmydata/test/test_mutable.py 677
1153         self._copied_shares = {}
1154         self._storage = FakeStorage()
1155         self._nodemaker = make_nodemaker(self._storage)
1156-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0]) # seqnum=1
1157+        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
1158         def _created(node):
1159             self._fn = node
1160             # now create multiple versions of the same file, and accumulate
1161hunk ./src/allmydata/test/test_mutable.py 906
1162         return d
1163 
1164 
1165+    def test_servermapupdater_finds_mdmf_files(self):
1166+        # setUp already published an MDMF file for us. We just need to
1167+        # make sure that when we run the ServermapUpdater, the file is
1168+        # reported to have one recoverable version.
1169+        d = defer.succeed(None)
1170+        d.addCallback(lambda ignored:
1171+            self.publish_mdmf())
1172+        d.addCallback(lambda ignored:
1173+            self.make_servermap(mode=MODE_CHECK))
1174+        # Calling make_servermap also updates the servermap in the mode
1175+        # that we specify, so we just need to see what it says.
1176+        def _check_servermap(sm):
1177+            self.failUnlessEqual(len(sm.recoverable_versions()), 1)
1178+        d.addCallback(_check_servermap)
1179+        return d
1180+
1181+
1182+    def test_servermapupdater_finds_sdmf_files(self):
1183+        d = defer.succeed(None)
1184+        d.addCallback(lambda ignored:
1185+            self.publish_sdmf())
1186+        d.addCallback(lambda ignored:
1187+            self.make_servermap(mode=MODE_CHECK))
1188+        d.addCallback(lambda servermap:
1189+            self.failUnlessEqual(len(servermap.recoverable_versions()), 1))
1190+        return d
1191+
1192 
1193 class Roundtrip(unittest.TestCase, testutil.ShouldFailMixin, PublishMixin):
1194     def setUp(self):
1195hunk ./src/allmydata/test/test_mutable.py 1050
1196         return d
1197     test_no_servers_download.timeout = 15
1198 
1199+
1200     def _test_corrupt_all(self, offset, substring,
1201                           should_succeed=False, corrupt_early=True,
1202                           failure_checker=None):
1203}
1204[Make a segmented mutable uploader
1205Kevan Carstensen <kevan@isnotajoke.com>**20100626234204
1206 Ignore-this: d199af8ab0bc64d8ed2bc19c5437bfba
1207 
1208 The mutable file uploader should be able to publish files with one
1209 segment and files with multiple segments. This patch makes it do that.
1210 This is still incomplete, and rather ugly -- I need to flesh out error
1211 handling, I need to write tests, and I need to remove some of the uglier
1212 kludges in the process before I can call this done.
1213] {
1214hunk ./src/allmydata/mutable/publish.py 8
1215 from zope.interface import implements
1216 from twisted.internet import defer
1217 from twisted.python import failure
1218-from allmydata.interfaces import IPublishStatus
1219+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
1220 from allmydata.util import base32, hashutil, mathutil, idlib, log
1221 from allmydata import hashtree, codec
1222 from allmydata.storage.server import si_b2a
1223hunk ./src/allmydata/mutable/publish.py 19
1224      UncoordinatedWriteError, NotEnoughServersError
1225 from allmydata.mutable.servermap import ServerMap
1226 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
1227-     unpack_checkstring, SIGNED_PREFIX
1228+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
1229+
1230+KiB = 1024
1231+DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
1232 
1233 class PublishStatus:
1234     implements(IPublishStatus)
1235hunk ./src/allmydata/mutable/publish.py 112
1236         self._status.set_helper(False)
1237         self._status.set_progress(0.0)
1238         self._status.set_active(True)
1239+        # We use this to control how the file is written.
1240+        version = self._node.get_version()
1241+        assert version in (SDMF_VERSION, MDMF_VERSION)
1242+        self._version = version
1243 
1244     def get_status(self):
1245         return self._status
1246hunk ./src/allmydata/mutable/publish.py 134
1247         simultaneous write.
1248         """
1249 
1250-        # 1: generate shares (SDMF: files are small, so we can do it in RAM)
1251-        # 2: perform peer selection, get candidate servers
1252-        #  2a: send queries to n+epsilon servers, to determine current shares
1253-        #  2b: based upon responses, create target map
1254-        # 3: send slot_testv_and_readv_and_writev messages
1255-        # 4: as responses return, update share-dispatch table
1256-        # 4a: may need to run recovery algorithm
1257-        # 5: when enough responses are back, we're done
1258+        # 0. Setup encoding parameters, encoder, and other such things.
1259+        # 1. Encrypt, encode, and publish segments.
1260 
1261         self.log("starting publish, datalen is %s" % len(newdata))
1262         self._status.set_size(len(newdata))
1263hunk ./src/allmydata/mutable/publish.py 187
1264         self.bad_peers = set() # peerids who have errbacked/refused requests
1265 
1266         self.newdata = newdata
1267-        self.salt = os.urandom(16)
1268 
1269hunk ./src/allmydata/mutable/publish.py 188
1270+        # This will set self.segment_size, self.num_segments, and
1271+        # self.fec.
1272         self.setup_encoding_parameters()
1273 
1274         # if we experience any surprises (writes which were rejected because
1275hunk ./src/allmydata/mutable/publish.py 238
1276             self.bad_share_checkstrings[key] = old_checkstring
1277             self.connections[peerid] = self._servermap.connections[peerid]
1278 
1279-        # create the shares. We'll discard these as they are delivered. SDMF:
1280-        # we're allowed to hold everything in memory.
1281+        # Now, the process dovetails -- if this is an SDMF file, we need
1282+        # to write an SDMF file. Otherwise, we need to write an MDMF
1283+        # file.
1284+        if self._version == MDMF_VERSION:
1285+            return self._publish_mdmf()
1286+        else:
1287+            return self._publish_sdmf()
1288+        #return self.done_deferred
1289+
1290+    def _publish_mdmf(self):
1291+        # Next, we find homes for all of the shares that we don't have
1292+        # homes for yet.
1293+        # TODO: Make this part do peer selection.
1294+        self.update_goal()
1295+        self.writers = {}
1296+        # For each (peerid, shnum) in self.goal, we make an
1297+        # MDMFSlotWriteProxy for that peer. We'll use this to write
1298+        # shares to the peer.
1299+        for key in self.goal:
1300+            peerid, shnum = key
1301+            write_enabler = self._node.get_write_enabler(peerid)
1302+            renew_secret = self._node.get_renewal_secret(peerid)
1303+            cancel_secret = self._node.get_cancel_secret(peerid)
1304+            secrets = (write_enabler, renew_secret, cancel_secret)
1305+
1306+            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
1307+                                                      self.connections[peerid],
1308+                                                      self._storage_index,
1309+                                                      secrets,
1310+                                                      self._new_seqnum,
1311+                                                      self.required_shares,
1312+                                                      self.total_shares,
1313+                                                      self.segment_size,
1314+                                                      len(self.newdata))
1315+            if (peerid, shnum) in self._servermap.servermap:
1316+                old_versionid, old_timestamp = self._servermap.servermap[key]
1317+                (old_seqnum, old_root_hash, old_salt, old_segsize,
1318+                 old_datalength, old_k, old_N, old_prefix,
1319+                 old_offsets_tuple) = old_versionid
1320+                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
1321+
1322+        # Now, we start pushing shares.
1323+        self._status.timings["setup"] = time.time() - self._started
1324+        def _start_pushing(res):
1325+            self._started_pushing = time.time()
1326+            return res
1327+
1328+        # First, we encrypt, encode, and publish the shares that we need
1329+        # to encrypt, encode, and publish.
1330+
1331+        # This will eventually hold the block hash chain for each share
1332+        # that we publish. We define it this way so that empty publishes
1333+        # will still have something to write to the remote slot.
1334+        self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
1335+        self.sharehash_leaves = None # eventually [sharehashes]
1336+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
1337+                              # validate the share]
1338 
1339hunk ./src/allmydata/mutable/publish.py 296
1340+        d = defer.succeed(None)
1341+        self.log("Starting push")
1342+        for i in xrange(self.num_segments - 1):
1343+            d.addCallback(lambda ignored, i=i:
1344+                self.push_segment(i))
1345+            d.addCallback(self._turn_barrier)
1346+        # We have at least one segment, so we will have a tail segment
1347+        if self.num_segments > 0:
1348+            d.addCallback(lambda ignored:
1349+                self.push_tail_segment())
1350+
1351+        d.addCallback(lambda ignored:
1352+            self.push_encprivkey())
1353+        d.addCallback(lambda ignored:
1354+            self.push_blockhashes())
1355+        d.addCallback(lambda ignored:
1356+            self.push_sharehashes())
1357+        d.addCallback(lambda ignored:
1358+            self.push_toplevel_hashes_and_signature())
1359+        d.addCallback(lambda ignored:
1360+            self.finish_publishing())
1361+        return d
1362+
1363+
1364+    def _publish_sdmf(self):
1365         self._status.timings["setup"] = time.time() - self._started
1366hunk ./src/allmydata/mutable/publish.py 322
1367+        self.salt = os.urandom(16)
1368+
1369         d = self._encrypt_and_encode()
1370         d.addCallback(self._generate_shares)
1371         def _start_pushing(res):
1372hunk ./src/allmydata/mutable/publish.py 335
1373 
1374         return self.done_deferred
1375 
1376+
1377     def setup_encoding_parameters(self):
1378hunk ./src/allmydata/mutable/publish.py 337
1379-        segment_size = len(self.newdata)
1380+        if self._version == MDMF_VERSION:
1381+            segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
1382+        else:
1383+            segment_size = len(self.newdata) # SDMF is only one segment
1384         # this must be a multiple of self.required_shares
1385         segment_size = mathutil.next_multiple(segment_size,
1386                                               self.required_shares)
1387hunk ./src/allmydata/mutable/publish.py 350
1388                                                   segment_size)
1389         else:
1390             self.num_segments = 0
1391-        assert self.num_segments in [0, 1,] # SDMF restrictions
1392+        if self._version == SDMF_VERSION:
1393+            assert self.num_segments in (0, 1) # SDMF
1394+            return
1395+        # calculate the tail segment size.
1396+        self.tail_segment_size = len(self.newdata) % segment_size
1397+
1398+        if self.tail_segment_size == 0:
1399+            # The tail segment is the same size as the other segments.
1400+            self.tail_segment_size = segment_size
1401+
1402+        # We'll make an encoder ahead-of-time for the normal-sized
1403+        # segments (defined as any segment of segment_size size.
1404+        # (the part of the code that puts the tail segment will make its
1405+        #  own encoder for that part)
1406+        fec = codec.CRSEncoder()
1407+        fec.set_params(self.segment_size,
1408+                       self.required_shares, self.total_shares)
1409+        self.piece_size = fec.get_block_size()
1410+        self.fec = fec
1411+
1412+
1413+    def push_segment(self, segnum):
1414+        started = time.time()
1415+        segsize = self.segment_size
1416+        self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
1417+        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
1418+        assert len(data) == segsize
1419+
1420+        salt = os.urandom(16)
1421+
1422+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1423+        enc = AES(key)
1424+        crypttext = enc.process(data)
1425+        assert len(crypttext) == len(data)
1426+
1427+        now = time.time()
1428+        self._status.timings["encrypt"] = now - started
1429+        started = now
1430+
1431+        # now apply FEC
1432+
1433+        self._status.set_status("Encoding")
1434+        crypttext_pieces = [None] * self.required_shares
1435+        piece_size = self.piece_size
1436+        for i in range(len(crypttext_pieces)):
1437+            offset = i * piece_size
1438+            piece = crypttext[offset:offset+piece_size]
1439+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1440+            crypttext_pieces[i] = piece
1441+            assert len(piece) == piece_size
1442+        d = self.fec.encode(crypttext_pieces)
1443+        def _done_encoding(res):
1444+            elapsed = time.time() - started
1445+            self._status.timings["encode"] = elapsed
1446+            return res
1447+        d.addCallback(_done_encoding)
1448+
1449+        def _push_shares_and_salt(results):
1450+            shares, shareids = results
1451+            dl = []
1452+            for i in xrange(len(shares)):
1453+                sharedata = shares[i]
1454+                shareid = shareids[i]
1455+                block_hash = hashutil.block_hash(salt + sharedata)
1456+                self.blockhashes[shareid].append(block_hash)
1457+
1458+                # find the writer for this share
1459+                d = self.writers[shareid].put_block(sharedata, segnum, salt)
1460+                dl.append(d)
1461+            # TODO: Naturally, we need to check on the results of these.
1462+            return defer.DeferredList(dl)
1463+        d.addCallback(_push_shares_and_salt)
1464+        return d
1465+
1466+
1467+    def push_tail_segment(self):
1468+        # This is essentially the same as push_segment, except that we
1469+        # don't use the cached encoder that we use elsewhere.
1470+        self.log("Pushing tail segment")
1471+        started = time.time()
1472+        segsize = self.segment_size
1473+        data = self.newdata[segsize * (self.num_segments-1):]
1474+        assert len(data) == self.tail_segment_size
1475+        salt = os.urandom(16)
1476+
1477+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1478+        enc = AES(key)
1479+        crypttext = enc.process(data)
1480+        assert len(crypttext) == len(data)
1481+
1482+        now = time.time()
1483+        self._status.timings['encrypt'] = now - started
1484+        started = now
1485+
1486+        self._status.set_status("Encoding")
1487+        tail_fec = codec.CRSEncoder()
1488+        tail_fec.set_params(self.tail_segment_size,
1489+                            self.required_shares,
1490+                            self.total_shares)
1491+
1492+        crypttext_pieces = [None] * self.required_shares
1493+        piece_size = tail_fec.get_block_size()
1494+        for i in range(len(crypttext_pieces)):
1495+            offset = i * piece_size
1496+            piece = crypttext[offset:offset+piece_size]
1497+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1498+            crypttext_pieces[i] = piece
1499+            assert len(piece) == piece_size
1500+        d = tail_fec.encode(crypttext_pieces)
1501+        def _push_shares_and_salt(results):
1502+            shares, shareids = results
1503+            dl = []
1504+            for i in xrange(len(shares)):
1505+                sharedata = shares[i]
1506+                shareid = shareids[i]
1507+                block_hash = hashutil.block_hash(salt + sharedata)
1508+                self.blockhashes[shareid].append(block_hash)
1509+                # find the writer for this share
1510+                d = self.writers[shareid].put_block(sharedata,
1511+                                                    self.num_segments - 1,
1512+                                                    salt)
1513+                dl.append(d)
1514+            # TODO: Naturally, we need to check on the results of these.
1515+            return defer.DeferredList(dl)
1516+        d.addCallback(_push_shares_and_salt)
1517+        return d
1518+
1519+
1520+    def push_encprivkey(self):
1521+        started = time.time()
1522+        encprivkey = self._encprivkey
1523+        dl = []
1524+        def _spy_on_writer(results):
1525+            print results
1526+            return results
1527+        for shnum, writer in self.writers.iteritems():
1528+            d = writer.put_encprivkey(encprivkey)
1529+            dl.append(d)
1530+        d = defer.DeferredList(dl)
1531+        return d
1532+
1533+
1534+    def push_blockhashes(self):
1535+        started = time.time()
1536+        dl = []
1537+        def _spy_on_results(results):
1538+            print results
1539+            return results
1540+        self.sharehash_leaves = [None] * len(self.blockhashes)
1541+        for shnum, blockhashes in self.blockhashes.iteritems():
1542+            t = hashtree.HashTree(blockhashes)
1543+            self.blockhashes[shnum] = list(t)
1544+            # set the leaf for future use.
1545+            self.sharehash_leaves[shnum] = t[0]
1546+            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
1547+            dl.append(d)
1548+        d = defer.DeferredList(dl)
1549+        return d
1550+
1551+
1552+    def push_sharehashes(self):
1553+        share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
1554+        share_hash_chain = {}
1555+        ds = []
1556+        def _spy_on_results(results):
1557+            print results
1558+            return results
1559+        for shnum in xrange(len(self.sharehash_leaves)):
1560+            needed_indices = share_hash_tree.needed_hashes(shnum)
1561+            self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
1562+                                             for i in needed_indices] )
1563+            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
1564+            ds.append(d)
1565+        self.root_hash = share_hash_tree[0]
1566+        d = defer.DeferredList(ds)
1567+        return d
1568+
1569+
1570+    def push_toplevel_hashes_and_signature(self):
1571+        # We need to to three things here:
1572+        #   - Push the root hash and salt hash
1573+        #   - Get the checkstring of the resulting layout; sign that.
1574+        #   - Push the signature
1575+        ds = []
1576+        def _spy_on_results(results):
1577+            print results
1578+            return results
1579+        for shnum in xrange(self.total_shares):
1580+            d = self.writers[shnum].put_root_hash(self.root_hash)
1581+            ds.append(d)
1582+        d = defer.DeferredList(ds)
1583+        def _make_and_place_signature(ignored):
1584+            signable = self.writers[0].get_signable()
1585+            self.signature = self._privkey.sign(signable)
1586+
1587+            ds = []
1588+            for (shnum, writer) in self.writers.iteritems():
1589+                d = writer.put_signature(self.signature)
1590+                ds.append(d)
1591+            return defer.DeferredList(ds)
1592+        d.addCallback(_make_and_place_signature)
1593+        return d
1594+
1595+
1596+    def finish_publishing(self):
1597+        # We're almost done -- we just need to put the verification key
1598+        # and the offsets
1599+        ds = []
1600+        verification_key = self._pubkey.serialize()
1601+
1602+        def _spy_on_results(results):
1603+            print results
1604+            return results
1605+        for (shnum, writer) in self.writers.iteritems():
1606+            d = writer.put_verification_key(verification_key)
1607+            d.addCallback(lambda ignored, writer=writer:
1608+                writer.finish_publishing())
1609+            ds.append(d)
1610+        return defer.DeferredList(ds)
1611+
1612+
1613+    def _turn_barrier(self, res):
1614+        # putting this method in a Deferred chain imposes a guaranteed
1615+        # reactor turn between the pre- and post- portions of that chain.
1616+        # This can be useful to limit memory consumption: since Deferreds do
1617+        # not do tail recursion, code which uses defer.succeed(result) for
1618+        # consistency will cause objects to live for longer than you might
1619+        # normally expect.
1620+        return fireEventually(res)
1621+
1622 
1623     def _fatal_error(self, f):
1624         self.log("error during loop", failure=f, level=log.UNUSUAL)
1625hunk ./src/allmydata/mutable/publish.py 716
1626             self.log_goal(self.goal, "after update: ")
1627 
1628 
1629-
1630     def _encrypt_and_encode(self):
1631         # this returns a Deferred that fires with a list of (sharedata,
1632         # sharenum) tuples. TODO: cache the ciphertext, only produce the
1633hunk ./src/allmydata/mutable/publish.py 757
1634         d.addCallback(_done_encoding)
1635         return d
1636 
1637+
1638     def _generate_shares(self, shares_and_shareids):
1639         # this sets self.shares and self.root_hash
1640         self.log("_generate_shares")
1641hunk ./src/allmydata/mutable/publish.py 1145
1642             self._status.set_progress(1.0)
1643         eventually(self.done_deferred.callback, res)
1644 
1645-
1646hunk ./src/allmydata/test/test_mutable.py 248
1647         d.addCallback(_created)
1648         return d
1649 
1650+
1651+    def test_create_mdmf(self):
1652+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
1653+        def _created(n):
1654+            self.failUnless(isinstance(n, MutableFileNode))
1655+            self.failUnlessEqual(n.get_storage_index(), n._storage_index)
1656+            sb = self.nodemaker.storage_broker
1657+            peer0 = sorted(sb.get_all_serverids())[0]
1658+            shnums = self._storage._peers[peer0].keys()
1659+            self.failUnlessEqual(len(shnums), 1)
1660+        d.addCallback(_created)
1661+        return d
1662+
1663+
1664     def test_serialize(self):
1665         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
1666         calls = []
1667hunk ./src/allmydata/test/test_mutable.py 334
1668         d.addCallback(_created)
1669         return d
1670 
1671+
1672+    def test_create_mdmf_with_initial_contents(self):
1673+        initial_contents = "foobarbaz" * 131072 # 900KiB
1674+        d = self.nodemaker.create_mutable_file(initial_contents,
1675+                                               version=MDMF_VERSION)
1676+        def _created(n):
1677+            d = n.download_best_version()
1678+            d.addCallback(lambda data:
1679+                self.failUnlessEqual(data, initial_contents))
1680+            d.addCallback(lambda ignored:
1681+                n.overwrite(initial_contents + "foobarbaz"))
1682+            d.addCallback(lambda ignored:
1683+                n.download_best_version())
1684+            d.addCallback(lambda data:
1685+                self.failUnlessEqual(data, initial_contents +
1686+                                           "foobarbaz"))
1687+            return d
1688+        d.addCallback(_created)
1689+        return d
1690+
1691+
1692     def test_create_with_initial_contents_function(self):
1693         data = "initial contents"
1694         def _make_contents(n):
1695hunk ./src/allmydata/test/test_mutable.py 370
1696         d.addCallback(lambda data2: self.failUnlessEqual(data2, data))
1697         return d
1698 
1699+
1700+    def test_create_mdmf_with_initial_contents_function(self):
1701+        data = "initial contents" * 100000
1702+        def _make_contents(n):
1703+            self.failUnless(isinstance(n, MutableFileNode))
1704+            key = n.get_writekey()
1705+            self.failUnless(isinstance(key, str), key)
1706+            self.failUnlessEqual(len(key), 16)
1707+            return data
1708+        d = self.nodemaker.create_mutable_file(_make_contents,
1709+                                               version=MDMF_VERSION)
1710+        d.addCallback(lambda n:
1711+            n.download_best_version())
1712+        d.addCallback(lambda data2:
1713+            self.failUnlessEqual(data2, data))
1714+        return d
1715+
1716+
1717     def test_create_with_too_large_contents(self):
1718         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
1719         d = self.nodemaker.create_mutable_file(BIG)
1720}
1721[Write a segmented mutable downloader
1722Kevan Carstensen <kevan@isnotajoke.com>**20100626234314
1723 Ignore-this: d2bef531cde1b5c38f2eb28afdd4b17c
1724 
1725 The segmented mutable downloader can deal with MDMF files (files with
1726 one or more segments in MDMF format) and SDMF files (files with one
1727 segment in SDMF format). It is backwards compatible with the old
1728 file format.
1729 
1730 This patch also contains tests for the segmented mutable downloader.
1731] {
1732hunk ./src/allmydata/mutable/retrieve.py 8
1733 from twisted.internet import defer
1734 from twisted.python import failure
1735 from foolscap.api import DeadReferenceError, eventually, fireEventually
1736-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
1737-from allmydata.util import hashutil, idlib, log
1738+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
1739+                                 MDMF_VERSION, SDMF_VERSION
1740+from allmydata.util import hashutil, idlib, log, mathutil
1741 from allmydata import hashtree, codec
1742 from allmydata.storage.server import si_b2a
1743 from pycryptopp.cipher.aes import AES
1744hunk ./src/allmydata/mutable/retrieve.py 17
1745 from pycryptopp.publickey import rsa
1746 
1747 from allmydata.mutable.common import DictOfSets, CorruptShareError, UncoordinatedWriteError
1748-from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data
1749+from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data, \
1750+                                     MDMFSlotReadProxy
1751 
1752 class RetrieveStatus:
1753     implements(IRetrieveStatus)
1754hunk ./src/allmydata/mutable/retrieve.py 104
1755         self.verinfo = verinfo
1756         # during repair, we may be called upon to grab the private key, since
1757         # it wasn't picked up during a verify=False checker run, and we'll
1758-        # need it for repair to generate the a new version.
1759+        # need it for repair to generate a new version.
1760         self._need_privkey = fetch_privkey
1761         if self._node.get_privkey():
1762             self._need_privkey = False
1763hunk ./src/allmydata/mutable/retrieve.py 109
1764 
1765+        if self._need_privkey:
1766+            # TODO: Evaluate the need for this. We'll use it if we want
1767+            # to limit how many queries are on the wire for the privkey
1768+            # at once.
1769+            self._privkey_query_markers = [] # one Marker for each time we've
1770+                                             # tried to get the privkey.
1771+
1772         self._status = RetrieveStatus()
1773         self._status.set_storage_index(self._storage_index)
1774         self._status.set_helper(False)
1775hunk ./src/allmydata/mutable/retrieve.py 125
1776          offsets_tuple) = self.verinfo
1777         self._status.set_size(datalength)
1778         self._status.set_encoding(k, N)
1779+        self.readers = {}
1780 
1781     def get_status(self):
1782         return self._status
1783hunk ./src/allmydata/mutable/retrieve.py 149
1784         self.remaining_sharemap = DictOfSets()
1785         for (shnum, peerid, timestamp) in shares:
1786             self.remaining_sharemap.add(shnum, peerid)
1787+            # If the servermap update fetched anything, it fetched at least 1
1788+            # KiB, so we ask for that much.
1789+            # TODO: Change the cache methods to allow us to fetch all of the
1790+            # data that they have, then change this method to do that.
1791+            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
1792+                                                               shnum,
1793+                                                               0,
1794+                                                               1000)
1795+            ss = self.servermap.connections[peerid]
1796+            reader = MDMFSlotReadProxy(ss,
1797+                                       self._storage_index,
1798+                                       shnum,
1799+                                       any_cache)
1800+            reader.peerid = peerid
1801+            self.readers[shnum] = reader
1802+
1803 
1804         self.shares = {} # maps shnum to validated blocks
1805hunk ./src/allmydata/mutable/retrieve.py 167
1806+        self._active_readers = [] # list of active readers for this dl.
1807+        self._validated_readers = set() # set of readers that we have
1808+                                        # validated the prefix of
1809+        self._block_hash_trees = {} # shnum => hashtree
1810+        # TODO: Make this into a file-backed consumer or something to
1811+        # conserve memory.
1812+        self._plaintext = ""
1813 
1814         # how many shares do we need?
1815hunk ./src/allmydata/mutable/retrieve.py 176
1816-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1817+        (seqnum,
1818+         root_hash,
1819+         IV,
1820+         segsize,
1821+         datalength,
1822+         k,
1823+         N,
1824+         prefix,
1825          offsets_tuple) = self.verinfo
1826hunk ./src/allmydata/mutable/retrieve.py 185
1827-        assert len(self.remaining_sharemap) >= k
1828-        # we start with the lowest shnums we have available, since FEC is
1829-        # faster if we're using "primary shares"
1830-        self.active_shnums = set(sorted(self.remaining_sharemap.keys())[:k])
1831-        for shnum in self.active_shnums:
1832-            # we use an arbitrary peer who has the share. If shares are
1833-            # doubled up (more than one share per peer), we could make this
1834-            # run faster by spreading the load among multiple peers. But the
1835-            # algorithm to do that is more complicated than I want to write
1836-            # right now, and a well-provisioned grid shouldn't have multiple
1837-            # shares per peer.
1838-            peerid = list(self.remaining_sharemap[shnum])[0]
1839-            self.get_data(shnum, peerid)
1840 
1841hunk ./src/allmydata/mutable/retrieve.py 186
1842-        # control flow beyond this point: state machine. Receiving responses
1843-        # from queries is the input. We might send out more queries, or we
1844-        # might produce a result.
1845 
1846hunk ./src/allmydata/mutable/retrieve.py 187
1847+        # We need one share hash tree for the entire file; its leaves
1848+        # are the roots of the block hash trees for the shares that
1849+        # comprise it, and its root is in the verinfo.
1850+        self.share_hash_tree = hashtree.IncompleteHashTree(N)
1851+        self.share_hash_tree.set_hashes({0: root_hash})
1852+
1853+        # This will set up both the segment decoder and the tail segment
1854+        # decoder, as well as a variety of other instance variables that
1855+        # the download process will use.
1856+        self._setup_encoding_parameters()
1857+        assert len(self.remaining_sharemap) >= k
1858+
1859+        self.log("starting download")
1860+        self._add_active_peers()
1861+        # The download process beyond this is a state machine.
1862+        # _add_active_peers will select the peers that we want to use
1863+        # for the download, and then attempt to start downloading. After
1864+        # each segment, it will check for doneness, reacting to broken
1865+        # peers and corrupt shares as necessary. If it runs out of good
1866+        # peers before downloading all of the segments, _done_deferred
1867+        # will errback.  Otherwise, it will eventually callback with the
1868+        # contents of the mutable file.
1869         return self._done_deferred
1870 
1871hunk ./src/allmydata/mutable/retrieve.py 211
1872-    def get_data(self, shnum, peerid):
1873-        self.log(format="sending sh#%(shnum)d request to [%(peerid)s]",
1874-                 shnum=shnum,
1875-                 peerid=idlib.shortnodeid_b2a(peerid),
1876-                 level=log.NOISY)
1877-        ss = self.servermap.connections[peerid]
1878-        started = time.time()
1879-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1880+
1881+    def _setup_encoding_parameters(self):
1882+        """
1883+        I set up the encoding parameters, including k, n, the number
1884+        of segments associated with this file, and the segment decoder.
1885+        """
1886+        (seqnum,
1887+         root_hash,
1888+         IV,
1889+         segsize,
1890+         datalength,
1891+         k,
1892+         n,
1893+         known_prefix,
1894          offsets_tuple) = self.verinfo
1895hunk ./src/allmydata/mutable/retrieve.py 226
1896-        offsets = dict(offsets_tuple)
1897+        self._required_shares = k
1898+        self._total_shares = n
1899+        self._segment_size = segsize
1900+        self._data_length = datalength
1901+
1902+        if not IV:
1903+            self._version = MDMF_VERSION
1904+        else:
1905+            self._version = SDMF_VERSION
1906+
1907+        if datalength and segsize:
1908+            self._num_segments = mathutil.div_ceil(datalength, segsize)
1909+            self._tail_data_size = datalength % segsize
1910+        else:
1911+            self._num_segments = 0
1912+            self._tail_data_size = 0
1913 
1914hunk ./src/allmydata/mutable/retrieve.py 243
1915-        # we read the checkstring, to make sure that the data we grab is from
1916-        # the right version.
1917-        readv = [ (0, struct.calcsize(SIGNED_PREFIX)) ]
1918+        self._segment_decoder = codec.CRSDecoder()
1919+        self._segment_decoder.set_params(segsize, k, n)
1920+        self._current_segment = 0
1921 
1922hunk ./src/allmydata/mutable/retrieve.py 247
1923-        # We also read the data, and the hashes necessary to validate them
1924-        # (share_hash_chain, block_hash_tree, share_data). We don't read the
1925-        # signature or the pubkey, since that was handled during the
1926-        # servermap phase, and we'll be comparing the share hash chain
1927-        # against the roothash that was validated back then.
1928+        if  not self._tail_data_size:
1929+            self._tail_data_size = segsize
1930 
1931hunk ./src/allmydata/mutable/retrieve.py 250
1932-        readv.append( (offsets['share_hash_chain'],
1933-                       offsets['enc_privkey'] - offsets['share_hash_chain'] ) )
1934+        self._tail_segment_size = mathutil.next_multiple(self._tail_data_size,
1935+                                                         self._required_shares)
1936+        if self._tail_segment_size == self._segment_size:
1937+            self._tail_decoder = self._segment_decoder
1938+        else:
1939+            self._tail_decoder = codec.CRSDecoder()
1940+            self._tail_decoder.set_params(self._tail_segment_size,
1941+                                          self._required_shares,
1942+                                          self._total_shares)
1943 
1944hunk ./src/allmydata/mutable/retrieve.py 260
1945-        # if we need the private key (for repair), we also fetch that
1946-        if self._need_privkey:
1947-            readv.append( (offsets['enc_privkey'],
1948-                           offsets['EOF'] - offsets['enc_privkey']) )
1949+        self.log("got encoding parameters: "
1950+                 "k: %d "
1951+                 "n: %d "
1952+                 "%d segments of %d bytes each (%d byte tail segment)" % \
1953+                 (k, n, self._num_segments, self._segment_size,
1954+                  self._tail_segment_size))
1955 
1956hunk ./src/allmydata/mutable/retrieve.py 267
1957-        m = Marker()
1958-        self._outstanding_queries[m] = (peerid, shnum, started)
1959+        for i in xrange(self._total_shares):
1960+            # So we don't have to do this later.
1961+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
1962 
1963hunk ./src/allmydata/mutable/retrieve.py 271
1964-        # ask the cache first
1965-        got_from_cache = False
1966-        datavs = []
1967-        for (offset, length) in readv:
1968-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
1969-                                                            offset, length)
1970-            if data is not None:
1971-                datavs.append(data)
1972-        if len(datavs) == len(readv):
1973-            self.log("got data from cache")
1974-            got_from_cache = True
1975-            d = fireEventually({shnum: datavs})
1976-            # datavs is a dict mapping shnum to a pair of strings
1977-        else:
1978-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
1979-        self.remaining_sharemap.discard(shnum, peerid)
1980+        # If we have more than one segment, we are an SDMF file, which
1981+        # means that we need to validate the salts as we receive them.
1982+        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
1983+        self._salt_hash_tree[0] = IV # from the prefix.
1984 
1985hunk ./src/allmydata/mutable/retrieve.py 276
1986-        d.addCallback(self._got_results, m, peerid, started, got_from_cache)
1987-        d.addErrback(self._query_failed, m, peerid)
1988-        # errors that aren't handled by _query_failed (and errors caused by
1989-        # _query_failed) get logged, but we still want to check for doneness.
1990-        def _oops(f):
1991-            self.log(format="problem in _query_failed for sh#%(shnum)d to %(peerid)s",
1992-                     shnum=shnum,
1993-                     peerid=idlib.shortnodeid_b2a(peerid),
1994-                     failure=f,
1995-                     level=log.WEIRD, umid="W0xnQA")
1996-        d.addErrback(_oops)
1997-        d.addBoth(self._check_for_done)
1998-        # any error during _check_for_done means the download fails. If the
1999-        # download is successful, _check_for_done will fire _done by itself.
2000-        d.addErrback(self._done)
2001-        d.addErrback(log.err)
2002-        return d # purely for testing convenience
2003 
2004hunk ./src/allmydata/mutable/retrieve.py 277
2005-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
2006-        # isolate the callRemote to a separate method, so tests can subclass
2007-        # Publish and override it
2008-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
2009-        return d
2010+    def _add_active_peers(self):
2011+        """
2012+        I populate self._active_readers with enough active readers to
2013+        retrieve the contents of this mutable file. I am called before
2014+        downloading starts, and (eventually) after each validation
2015+        error, connection error, or other problem in the download.
2016+        """
2017+        # TODO: It would be cool to investigate other heuristics for
2018+        # reader selection. For instance, the cost (in time the user
2019+        # spends waiting for their file) of selecting a really slow peer
2020+        # that happens to have a primary share is probably more than
2021+        # selecting a really fast peer that doesn't have a primary
2022+        # share. Maybe the servermap could be extended to provide this
2023+        # information; it could keep track of latency information while
2024+        # it gathers more important data, and then this routine could
2025+        # use that to select active readers.
2026+        #
2027+        # (these and other questions would be easier to answer with a
2028+        #  robust, configurable tahoe-lafs simulator, which modeled node
2029+        #  failures, differences in node speed, and other characteristics
2030+        #  that we expect storage servers to have.  You could have
2031+        #  presets for really stable grids (like allmydata.com),
2032+        #  friendnets, make it easy to configure your own settings, and
2033+        #  then simulate the effect of big changes on these use cases
2034+        #  instead of just reasoning about what the effect might be. Out
2035+        #  of scope for MDMF, though.)
2036 
2037hunk ./src/allmydata/mutable/retrieve.py 304
2038-    def remove_peer(self, peerid):
2039-        for shnum in list(self.remaining_sharemap.keys()):
2040-            self.remaining_sharemap.discard(shnum, peerid)
2041+        # We need at least self._required_shares readers to download a
2042+        # segment.
2043+        needed = self._required_shares - len(self._active_readers)
2044+        # XXX: Why don't format= log messages work here?
2045+        self.log("adding %d peers to the active peers list" % needed)
2046 
2047hunk ./src/allmydata/mutable/retrieve.py 310
2048-    def _got_results(self, datavs, marker, peerid, started, got_from_cache):
2049-        now = time.time()
2050-        elapsed = now - started
2051-        if not got_from_cache:
2052-            self._status.add_fetch_timing(peerid, elapsed)
2053-        self.log(format="got results (%(shares)d shares) from [%(peerid)s]",
2054-                 shares=len(datavs),
2055-                 peerid=idlib.shortnodeid_b2a(peerid),
2056-                 level=log.NOISY)
2057-        self._outstanding_queries.pop(marker, None)
2058-        if not self._running:
2059-            return
2060+        # We favor lower numbered shares, since FEC is faster with
2061+        # primary shares than with other shares, and lower-numbered
2062+        # shares are more likely to be primary than higher numbered
2063+        # shares.
2064+        active_shnums = set(sorted(self.remaining_sharemap.keys()))
2065+        # We shouldn't consider adding shares that we already have; this
2066+        # will cause problems later.
2067+        active_shnums -= set([reader.shnum for reader in self._active_readers])
2068+        active_shnums = list(active_shnums)[:needed]
2069+        if len(active_shnums) < needed:
2070+            # We don't have enough readers to retrieve the file; fail.
2071+            return self._failed()
2072 
2073hunk ./src/allmydata/mutable/retrieve.py 323
2074-        # note that we only ask for a single share per query, so we only
2075-        # expect a single share back. On the other hand, we use the extra
2076-        # shares if we get them.. seems better than an assert().
2077+        for shnum in active_shnums:
2078+            self._active_readers.append(self.readers[shnum])
2079+            self.log("added reader for share %d" % shnum)
2080+        assert len(self._active_readers) == self._required_shares
2081+        # Conceptually, this is part of the _add_active_peers step. It
2082+        # validates the prefixes of newly added readers to make sure
2083+        # that they match what we are expecting for self.verinfo. If
2084+        # validation is successful, _validate_active_prefixes will call
2085+        # _download_current_segment for us. If validation is
2086+        # unsuccessful, then _validate_prefixes will remove the peer and
2087+        # call _add_active_peers again, where we will attempt to rectify
2088+        # the problem by choosing another peer.
2089+        return self._validate_active_prefixes()
2090 
2091hunk ./src/allmydata/mutable/retrieve.py 337
2092-        for shnum,datav in datavs.items():
2093-            (prefix, hash_and_data) = datav[:2]
2094-            try:
2095-                self._got_results_one_share(shnum, peerid,
2096-                                            prefix, hash_and_data)
2097-            except CorruptShareError, e:
2098-                # log it and give the other shares a chance to be processed
2099-                f = failure.Failure()
2100-                self.log(format="bad share: %(f_value)s",
2101-                         f_value=str(f.value), failure=f,
2102-                         level=log.WEIRD, umid="7fzWZw")
2103-                self.notify_server_corruption(peerid, shnum, str(e))
2104-                self.remove_peer(peerid)
2105-                self.servermap.mark_bad_share(peerid, shnum, prefix)
2106-                self._bad_shares.add( (peerid, shnum) )
2107-                self._status.problems[peerid] = f
2108-                self._last_failure = f
2109-                pass
2110-            if self._need_privkey and len(datav) > 2:
2111-                lp = None
2112-                self._try_to_validate_privkey(datav[2], peerid, shnum, lp)
2113-        # all done!
2114 
2115hunk ./src/allmydata/mutable/retrieve.py 338
2116-    def notify_server_corruption(self, peerid, shnum, reason):
2117-        ss = self.servermap.connections[peerid]
2118-        ss.callRemoteOnly("advise_corrupt_share",
2119-                          "mutable", self._storage_index, shnum, reason)
2120+    def _validate_active_prefixes(self):
2121+        """
2122+        I check to make sure that the prefixes on the peers that I am
2123+        currently reading from match the prefix that we want to see, as
2124+        said in self.verinfo.
2125 
2126hunk ./src/allmydata/mutable/retrieve.py 344
2127-    def _got_results_one_share(self, shnum, peerid,
2128-                               got_prefix, got_hash_and_data):
2129-        self.log("_got_results: got shnum #%d from peerid %s"
2130-                 % (shnum, idlib.shortnodeid_b2a(peerid)))
2131-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2132+        If I find that all of the active peers have acceptable prefixes,
2133+        I pass control to _download_current_segment, which will use
2134+        those peers to do cool things. If I find that some of the active
2135+        peers have unacceptable prefixes, I will remove them from active
2136+        peers (and from further consideration) and call
2137+        _add_active_peers to attempt to rectify the situation. I keep
2138+        track of which peers I have already validated so that I don't
2139+        need to do so again.
2140+        """
2141+        assert self._active_readers, "No more active readers"
2142+
2143+        ds = []
2144+        new_readers = set(self._active_readers) - self._validated_readers
2145+        self.log('validating %d newly-added active readers' % len(new_readers))
2146+
2147+        for reader in new_readers:
2148+            # We force a remote read here -- otherwise, we are relying
2149+            # on cached data that we already verified as valid, and we
2150+            # won't detect an uncoordinated write that has occurred
2151+            # since the last servermap update.
2152+            d = reader.get_prefix(force_remote=True)
2153+            d.addCallback(self._try_to_validate_prefix, reader)
2154+            ds.append(d)
2155+        dl = defer.DeferredList(ds, consumeErrors=True)
2156+        def _check_results(results):
2157+            # Each result in results will be of the form (success, msg).
2158+            # We don't care about msg, but success will tell us whether
2159+            # or not the checkstring validated. If it didn't, we need to
2160+            # remove the offending (peer,share) from our active readers,
2161+            # and ensure that active readers is again populated.
2162+            bad_readers = []
2163+            for i, result in enumerate(results):
2164+                if not result[0]:
2165+                    reader = self._active_readers[i]
2166+                    f = result[1]
2167+                    assert isinstance(f, failure.Failure)
2168+
2169+                    self.log("The reader %s failed to "
2170+                             "properly validate: %s" % \
2171+                             (reader, str(f.value)))
2172+                    bad_readers.append((reader, f))
2173+                else:
2174+                    reader = self._active_readers[i]
2175+                    self.log("the reader %s checks out, so we'll use it" % \
2176+                             reader)
2177+                    self._validated_readers.add(reader)
2178+                    # Each time we validate a reader, we check to see if
2179+                    # we need the private key. If we do, we politely ask
2180+                    # for it and then continue computing. If we find
2181+                    # that we haven't gotten it at the end of
2182+                    # segment decoding, then we'll take more drastic
2183+                    # measures.
2184+                    if self._need_privkey:
2185+                        d = reader.get_encprivkey()
2186+                        d.addCallback(self._try_to_validate_privkey, reader)
2187+            if bad_readers:
2188+                # We do them all at once, or else we screw up list indexing.
2189+                for (reader, f) in bad_readers:
2190+                    self._mark_bad_share(reader, f)
2191+                return self._add_active_peers()
2192+            else:
2193+                return self._download_current_segment()
2194+            # The next step will assert that it has enough active
2195+            # readers to fetch shares; we just need to remove it.
2196+        dl.addCallback(_check_results)
2197+        return dl
2198+
2199+
2200+    def _try_to_validate_prefix(self, prefix, reader):
2201+        """
2202+        I check that the prefix returned by a candidate server for
2203+        retrieval matches the prefix that the servermap knows about
2204+        (and, hence, the prefix that was validated earlier). If it does,
2205+        I return True, which means that I approve of the use of the
2206+        candidate server for segment retrieval. If it doesn't, I return
2207+        False, which means that another server must be chosen.
2208+        """
2209+        (seqnum,
2210+         root_hash,
2211+         IV,
2212+         segsize,
2213+         datalength,
2214+         k,
2215+         N,
2216+         known_prefix,
2217          offsets_tuple) = self.verinfo
2218hunk ./src/allmydata/mutable/retrieve.py 430
2219-        assert len(got_prefix) == len(prefix), (len(got_prefix), len(prefix))
2220-        if got_prefix != prefix:
2221-            msg = "someone wrote to the data since we read the servermap: prefix changed"
2222-            raise UncoordinatedWriteError(msg)
2223-        (share_hash_chain, block_hash_tree,
2224-         share_data) = unpack_share_data(self.verinfo, got_hash_and_data)
2225+        if known_prefix != prefix:
2226+            self.log("prefix from share %d doesn't match" % reader.shnum)
2227+            raise UncoordinatedWriteError("Mismatched prefix -- this could "
2228+                                          "indicate an uncoordinated write")
2229+        # Otherwise, we're okay -- no issues.
2230 
2231hunk ./src/allmydata/mutable/retrieve.py 436
2232-        assert isinstance(share_data, str)
2233-        # build the block hash tree. SDMF has only one leaf.
2234-        leaves = [hashutil.block_hash(share_data)]
2235-        t = hashtree.HashTree(leaves)
2236-        if list(t) != block_hash_tree:
2237-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
2238-        share_hash_leaf = t[0]
2239-        t2 = hashtree.IncompleteHashTree(N)
2240-        # root_hash was checked by the signature
2241-        t2.set_hashes({0: root_hash})
2242-        try:
2243-            t2.set_hashes(hashes=share_hash_chain,
2244-                          leaves={shnum: share_hash_leaf})
2245-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
2246-                IndexError), e:
2247-            msg = "corrupt hashes: %s" % (e,)
2248-            raise CorruptShareError(peerid, shnum, msg)
2249-        self.log(" data valid! len=%d" % len(share_data))
2250-        # each query comes down to this: placing validated share data into
2251-        # self.shares
2252-        self.shares[shnum] = share_data
2253 
2254hunk ./src/allmydata/mutable/retrieve.py 437
2255-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
2256+    def _remove_reader(self, reader):
2257+        """
2258+        At various points, we will wish to remove a peer from
2259+        consideration and/or use. These include, but are not necessarily
2260+        limited to:
2261 
2262hunk ./src/allmydata/mutable/retrieve.py 443
2263-        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2264-        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2265-        if alleged_writekey != self._node.get_writekey():
2266-            self.log("invalid privkey from %s shnum %d" %
2267-                     (idlib.nodeid_b2a(peerid)[:8], shnum),
2268-                     parent=lp, level=log.WEIRD, umid="YIw4tA")
2269-            return
2270+            - A connection error.
2271+            - A mismatched prefix (that is, a prefix that does not match
2272+              our conception of the version information string).
2273+            - A failing block hash, salt hash, or share hash, which can
2274+              indicate disk failure/bit flips, or network trouble.
2275 
2276hunk ./src/allmydata/mutable/retrieve.py 449
2277-        # it's good
2278-        self.log("got valid privkey from shnum %d on peerid %s" %
2279-                 (shnum, idlib.shortnodeid_b2a(peerid)),
2280-                 parent=lp)
2281-        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2282-        self._node._populate_encprivkey(enc_privkey)
2283-        self._node._populate_privkey(privkey)
2284-        self._need_privkey = False
2285+        This method will do that. I will make sure that the
2286+        (shnum,reader) combination represented by my reader argument is
2287+        not used for anything else during this download. I will not
2288+        advise the reader of any corruption, something that my callers
2289+        may wish to do on their own.
2290+        """
2291+        # TODO: When you're done writing this, see if this is ever
2292+        # actually used for something that _mark_bad_share isn't. I have
2293+        # a feeling that they will be used for very similar things, and
2294+        # that having them both here is just going to be an epic amount
2295+        # of code duplication.
2296+        #
2297+        # (well, okay, not epic, but meaningful)
2298+        self.log("removing reader %s" % reader)
2299+        # Remove the reader from _active_readers
2300+        self._active_readers.remove(reader)
2301+        # TODO: self.readers.remove(reader)?
2302+        for shnum in list(self.remaining_sharemap.keys()):
2303+            self.remaining_sharemap.discard(shnum, reader.peerid)
2304 
2305hunk ./src/allmydata/mutable/retrieve.py 469
2306-    def _query_failed(self, f, marker, peerid):
2307-        self.log(format="query to [%(peerid)s] failed",
2308-                 peerid=idlib.shortnodeid_b2a(peerid),
2309-                 level=log.NOISY)
2310-        self._status.problems[peerid] = f
2311-        self._outstanding_queries.pop(marker, None)
2312-        if not self._running:
2313-            return
2314-        self._last_failure = f
2315-        self.remove_peer(peerid)
2316-        level = log.WEIRD
2317-        if f.check(DeadReferenceError):
2318-            level = log.UNUSUAL
2319-        self.log(format="error during query: %(f_value)s",
2320-                 f_value=str(f.value), failure=f, level=level, umid="gOJB5g")
2321 
2322hunk ./src/allmydata/mutable/retrieve.py 470
2323-    def _check_for_done(self, res):
2324-        # exit paths:
2325-        #  return : keep waiting, no new queries
2326-        #  return self._send_more_queries(outstanding) : send some more queries
2327-        #  fire self._done(plaintext) : download successful
2328-        #  raise exception : download fails
2329+    def _mark_bad_share(self, reader, f):
2330+        """
2331+        I mark the (peerid, shnum) encapsulated by my reader argument as
2332+        a bad share, which means that it will not be used anywhere else.
2333 
2334hunk ./src/allmydata/mutable/retrieve.py 475
2335-        self.log(format="_check_for_done: running=%(running)s, decoding=%(decoding)s",
2336-                 running=self._running, decoding=self._decoding,
2337-                 level=log.NOISY)
2338-        if not self._running:
2339-            return
2340-        if self._decoding:
2341-            return
2342-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2343-         offsets_tuple) = self.verinfo
2344+        There are several reasons to want to mark something as a bad
2345+        share. These include:
2346 
2347hunk ./src/allmydata/mutable/retrieve.py 478
2348-        if len(self.shares) < k:
2349-            # we don't have enough shares yet
2350-            return self._maybe_send_more_queries(k)
2351-        if self._need_privkey:
2352-            # we got k shares, but none of them had a valid privkey. TODO:
2353-            # look further. Adding code to do this is a bit complicated, and
2354-            # I want to avoid that complication, and this should be pretty
2355-            # rare (k shares with bitflips in the enc_privkey but not in the
2356-            # data blocks). If we actually do get here, the subsequent repair
2357-            # will fail for lack of a privkey.
2358-            self.log("got k shares but still need_privkey, bummer",
2359-                     level=log.WEIRD, umid="MdRHPA")
2360+            - A connection error to the peer.
2361+            - A mismatched prefix (that is, a prefix that does not match
2362+              our local conception of the version information string).
2363+            - A failing block hash, salt hash, share hash, or other
2364+              integrity check.
2365 
2366hunk ./src/allmydata/mutable/retrieve.py 484
2367-        # we have enough to finish. All the shares have had their hashes
2368-        # checked, so if something fails at this point, we don't know how
2369-        # to fix it, so the download will fail.
2370+        This method will ensure that readers that we wish to mark bad
2371+        (for these reasons or other reasons) are not used for the rest
2372+        of the download. Additionally, it will attempt to tell the
2373+        remote peer (with no guarantee of success) that its share is
2374+        corrupt.
2375+        """
2376+        self.log("marking share %d on server %s as bad" % \
2377+                 (reader.shnum, reader))
2378+        self._remove_reader(reader)
2379+        self._bad_shares.add((reader.peerid, reader.shnum))
2380+        self._status.problems[reader.peerid] = f
2381+        self._last_failure = f
2382+        self.notify_server_corruption(reader.peerid, reader.shnum,
2383+                                      str(f.value))
2384 
2385hunk ./src/allmydata/mutable/retrieve.py 499
2386-        self._decoding = True # avoid reentrancy
2387-        self._status.set_status("decoding")
2388-        now = time.time()
2389-        elapsed = now - self._started
2390-        self._status.timings["fetch"] = elapsed
2391 
2392hunk ./src/allmydata/mutable/retrieve.py 500
2393-        d = defer.maybeDeferred(self._decode)
2394-        d.addCallback(self._decrypt, IV, self._node.get_readkey())
2395-        d.addBoth(self._done)
2396-        return d # purely for test convenience
2397+    def _download_current_segment(self):
2398+        """
2399+        I download, validate, decode, decrypt, and assemble the segment
2400+        that this Retrieve is currently responsible for downloading.
2401+        """
2402+        assert len(self._active_readers) >= self._required_shares
2403+        if self._current_segment < self._num_segments:
2404+            d = self._process_segment(self._current_segment)
2405+        else:
2406+            d = defer.succeed(None)
2407+        d.addCallback(self._check_for_done)
2408+        return d
2409 
2410hunk ./src/allmydata/mutable/retrieve.py 513
2411-    def _maybe_send_more_queries(self, k):
2412-        # we don't have enough shares yet. Should we send out more queries?
2413-        # There are some number of queries outstanding, each for a single
2414-        # share. If we can generate 'needed_shares' additional queries, we do
2415-        # so. If we can't, then we know this file is a goner, and we raise
2416-        # NotEnoughSharesError.
2417-        self.log(format=("_maybe_send_more_queries, have=%(have)d, k=%(k)d, "
2418-                         "outstanding=%(outstanding)d"),
2419-                 have=len(self.shares), k=k,
2420-                 outstanding=len(self._outstanding_queries),
2421-                 level=log.NOISY)
2422 
2423hunk ./src/allmydata/mutable/retrieve.py 514
2424-        remaining_shares = k - len(self.shares)
2425-        needed = remaining_shares - len(self._outstanding_queries)
2426-        if not needed:
2427-            # we have enough queries in flight already
2428+    def _process_segment(self, segnum):
2429+        """
2430+        I download, validate, decode, and decrypt one segment of the
2431+        file that this Retrieve is retrieving. This means coordinating
2432+        the process of getting k blocks of that file, validating them,
2433+        assembling them into one segment with the decoder, and then
2434+        decrypting them.
2435+        """
2436+        self.log("processing segment %d" % segnum)
2437 
2438hunk ./src/allmydata/mutable/retrieve.py 524
2439-            # TODO: but if they've been in flight for a long time, and we
2440-            # have reason to believe that new queries might respond faster
2441-            # (i.e. we've seen other queries come back faster, then consider
2442-            # sending out new queries. This could help with peers which have
2443-            # silently gone away since the servermap was updated, for which
2444-            # we're still waiting for the 15-minute TCP disconnect to happen.
2445-            self.log("enough queries are in flight, no more are needed",
2446-                     level=log.NOISY)
2447-            return
2448+        # TODO: The old code uses a marker. Should this code do that
2449+        # too? What did the Marker do?
2450+        assert len(self._active_readers) >= self._required_shares
2451+
2452+        # We need to ask each of our active readers for its block and
2453+        # salt. We will then validate those. If validation is
2454+        # successful, we will assemble the results into plaintext.
2455+        ds = []
2456+        for reader in self._active_readers:
2457+            d = reader.get_block_and_salt(segnum, queue=True)
2458+            d2 = self._get_needed_hashes(reader, segnum)
2459+            dl = defer.DeferredList([d, d2], consumeErrors=True)
2460+            dl.addCallback(self._validate_block, segnum, reader)
2461+            dl.addErrback(self._validation_or_decoding_failed, [reader])
2462+            ds.append(dl)
2463+            reader.flush()
2464+        dl = defer.DeferredList(ds)
2465+        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
2466+        return dl
2467 
2468hunk ./src/allmydata/mutable/retrieve.py 544
2469-        outstanding_shnums = set([shnum
2470-                                  for (peerid, shnum, started)
2471-                                  in self._outstanding_queries.values()])
2472-        # prefer low-numbered shares, they are more likely to be primary
2473-        available_shnums = sorted(self.remaining_sharemap.keys())
2474-        for shnum in available_shnums:
2475-            if shnum in outstanding_shnums:
2476-                # skip ones that are already in transit
2477-                continue
2478-            if shnum not in self.remaining_sharemap:
2479-                # no servers for that shnum. note that DictOfSets removes
2480-                # empty sets from the dict for us.
2481-                continue
2482-            peerid = list(self.remaining_sharemap[shnum])[0]
2483-            # get_data will remove that peerid from the sharemap, and add the
2484-            # query to self._outstanding_queries
2485-            self._status.set_status("Retrieving More Shares")
2486-            self.get_data(shnum, peerid)
2487-            needed -= 1
2488-            if not needed:
2489+
2490+    def _maybe_decode_and_decrypt_segment(self, blocks_and_salts, segnum):
2491+        """
2492+        I take the results of fetching and validating the blocks from a
2493+        callback chain in another method. If the results are such that
2494+        they tell me that validation and fetching succeeded without
2495+        incident, I will proceed with decoding and decryption.
2496+        Otherwise, I will do nothing.
2497+        """
2498+        self.log("trying to decode and decrypt segment %d" % segnum)
2499+        failures = False
2500+        for block_and_salt in blocks_and_salts:
2501+            if not block_and_salt[0] or block_and_salt[1] == None:
2502+                self.log("some validation operations failed; not proceeding")
2503+                failures = True
2504                 break
2505hunk ./src/allmydata/mutable/retrieve.py 560
2506+        if not failures:
2507+            self.log("everything looks ok, building segment %d" % segnum)
2508+            d = self._decode_blocks(blocks_and_salts, segnum)
2509+            d.addCallback(self._decrypt_segment)
2510+            d.addErrback(self._validation_or_decoding_failed,
2511+                         self._active_readers)
2512+            d.addCallback(self._set_segment)
2513+            return d
2514+        else:
2515+            return defer.succeed(None)
2516+
2517+
2518+    def _set_segment(self, segment):
2519+        """
2520+        Given a plaintext segment, I register that segment with the
2521+        target that is handling the file download.
2522+        """
2523+        self.log("got plaintext for segment %d" % self._current_segment)
2524+        self._plaintext += segment
2525+        self._current_segment += 1
2526 
2527hunk ./src/allmydata/mutable/retrieve.py 581
2528-        # at this point, we have as many outstanding queries as we can. If
2529-        # needed!=0 then we might not have enough to recover the file.
2530-        if needed:
2531-            format = ("ran out of peers: "
2532-                      "have %(have)d shares (k=%(k)d), "
2533-                      "%(outstanding)d queries in flight, "
2534-                      "need %(need)d more, "
2535-                      "found %(bad)d bad shares")
2536-            args = {"have": len(self.shares),
2537-                    "k": k,
2538-                    "outstanding": len(self._outstanding_queries),
2539-                    "need": needed,
2540-                    "bad": len(self._bad_shares),
2541-                    }
2542-            self.log(format=format,
2543-                     level=log.WEIRD, umid="ezTfjw", **args)
2544-            err = NotEnoughSharesError("%s, last failure: %s" %
2545-                                      (format % args, self._last_failure))
2546-            if self._bad_shares:
2547-                self.log("We found some bad shares this pass. You should "
2548-                         "update the servermap and try again to check "
2549-                         "more peers",
2550-                         level=log.WEIRD, umid="EFkOlA")
2551-                err.servermap = self.servermap
2552-            raise err
2553 
2554hunk ./src/allmydata/mutable/retrieve.py 582
2555+    def _validation_or_decoding_failed(self, f, readers):
2556+        """
2557+        I am called when a block or a salt fails to correctly validate, or when
2558+        the decryption or decoding operation fails for some reason.  I react to
2559+        this failure by notifying the remote server of corruption, and then
2560+        removing the remote peer from further activity.
2561+        """
2562+        assert isinstance(readers, list)
2563+        bad_shnums = [reader.shnum for reader in readers]
2564+
2565+        self.log("validation or decoding failed on share(s) %s, peer(s) %s "
2566+                 ", segment %d: %s" % \
2567+                 (bad_shnums, readers, self._current_segment, str(f)))
2568+        for reader in readers:
2569+            self._mark_bad_share(reader, f)
2570         return
2571 
2572hunk ./src/allmydata/mutable/retrieve.py 599
2573-    def _decode(self):
2574-        started = time.time()
2575-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2576-         offsets_tuple) = self.verinfo
2577 
2578hunk ./src/allmydata/mutable/retrieve.py 600
2579-        # shares_dict is a dict mapping shnum to share data, but the codec
2580-        # wants two lists.
2581-        shareids = []; shares = []
2582-        for shareid, share in self.shares.items():
2583+    def _validate_block(self, results, segnum, reader):
2584+        """
2585+        I validate a block from one share on a remote server.
2586+        """
2587+        # Grab the part of the block hash tree that is necessary to
2588+        # validate this block, then generate the block hash root.
2589+        self.log("validating share %d for segment %d" % (reader.shnum,
2590+                                                             segnum))
2591+        # Did we fail to fetch either of the things that we were
2592+        # supposed to? Fail if so.
2593+        if not results[0][0] and results[1][0]:
2594+            # handled by the errback handler.
2595+
2596+            # These all get batched into one query, so the resulting
2597+            # failure should be the same for all of them, so we can just
2598+            # use the first one.
2599+            assert isinstance(results[0][1], failure.Failure)
2600+
2601+            f = results[0][1]
2602+            raise CorruptShareError(reader.peerid,
2603+                                    reader.shnum,
2604+                                    "Connection error: %s" % str(f))
2605+
2606+        block_and_salt, block_and_sharehashes = results
2607+        block, salt = block_and_salt[1]
2608+        blockhashes, sharehashes = block_and_sharehashes[1]
2609+
2610+        blockhashes = dict(enumerate(blockhashes[1]))
2611+        self.log("the reader gave me the following blockhashes: %s" % \
2612+                 blockhashes.keys())
2613+        self.log("the reader gave me the following sharehashes: %s" % \
2614+                 sharehashes[1].keys())
2615+        bht = self._block_hash_trees[reader.shnum]
2616+
2617+        if bht.needed_hashes(segnum, include_leaf=True):
2618+            try:
2619+                bht.set_hashes(blockhashes)
2620+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2621+                    IndexError), e:
2622+                raise CorruptShareError(reader.peerid,
2623+                                        reader.shnum,
2624+                                        "block hash tree failure: %s" % e)
2625+
2626+        if self._version == MDMF_VERSION:
2627+            blockhash = hashutil.block_hash(salt + block)
2628+        else:
2629+            blockhash = hashutil.block_hash(block)
2630+        # If this works without an error, then validation is
2631+        # successful.
2632+        try:
2633+           bht.set_hashes(leaves={segnum: blockhash})
2634+        except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2635+                IndexError), e:
2636+            raise CorruptShareError(reader.peerid,
2637+                                    reader.shnum,
2638+                                    "block hash tree failure: %s" % e)
2639+
2640+        # Reaching this point means that we know that this segment
2641+        # is correct. Now we need to check to see whether the share
2642+        # hash chain is also correct.
2643+        # SDMF wrote share hash chains that didn't contain the
2644+        # leaves, which would be produced from the block hash tree.
2645+        # So we need to validate the block hash tree first. If
2646+        # successful, then bht[0] will contain the root for the
2647+        # shnum, which will be a leaf in the share hash tree, which
2648+        # will allow us to validate the rest of the tree.
2649+        if self.share_hash_tree.needed_hashes(reader.shnum,
2650+                                               include_leaf=True):
2651+            try:
2652+                self.share_hash_tree.set_hashes(hashes=sharehashes[1],
2653+                                            leaves={reader.shnum: bht[0]})
2654+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2655+                    IndexError), e:
2656+                raise CorruptShareError(reader.peerid,
2657+                                        reader.shnum,
2658+                                        "corrupt hashes: %s" % e)
2659+
2660+        # TODO: Validate the salt, too.
2661+        self.log('share %d is valid for segment %d' % (reader.shnum,
2662+                                                       segnum))
2663+        return {reader.shnum: (block, salt)}
2664+
2665+
2666+    def _get_needed_hashes(self, reader, segnum):
2667+        """
2668+        I get the hashes needed to validate segnum from the reader, then return
2669+        to my caller when this is done.
2670+        """
2671+        bht = self._block_hash_trees[reader.shnum]
2672+        needed = bht.needed_hashes(segnum, include_leaf=True)
2673+        # The root of the block hash tree is also a leaf in the share
2674+        # hash tree. So we don't need to fetch it from the remote
2675+        # server. In the case of files with one segment, this means that
2676+        # we won't fetch any block hash tree from the remote server,
2677+        # since the hash of each share of the file is the entire block
2678+        # hash tree, and is a leaf in the share hash tree. This is fine,
2679+        # since any share corruption will be detected in the share hash
2680+        # tree.
2681+        #needed.discard(0)
2682+        self.log("getting blockhashes for segment %d, share %d: %s" % \
2683+                 (segnum, reader.shnum, str(needed)))
2684+        d1 = reader.get_blockhashes(needed, queue=True, force_remote=True)
2685+        if self.share_hash_tree.needed_hashes(reader.shnum):
2686+            need = self.share_hash_tree.needed_hashes(reader.shnum)
2687+            self.log("also need sharehashes for share %d: %s" % (reader.shnum,
2688+                                                                 str(need)))
2689+            d2 = reader.get_sharehashes(need, queue=True, force_remote=True)
2690+        else:
2691+            d2 = defer.succeed({}) # the logic in the next method
2692+                                   # expects a dict
2693+        dl = defer.DeferredList([d1, d2], consumeErrors=True)
2694+        return dl
2695+
2696+
2697+    def _decode_blocks(self, blocks_and_salts, segnum):
2698+        """
2699+        I take a list of k blocks and salts, and decode that into a
2700+        single encrypted segment.
2701+        """
2702+        d = {}
2703+        # We want to merge our dictionaries to the form
2704+        # {shnum: blocks_and_salts}
2705+        #
2706+        # The dictionaries come from validate block that way, so we just
2707+        # need to merge them.
2708+        for block_and_salt in blocks_and_salts:
2709+            d.update(block_and_salt[1])
2710+
2711+        # All of these blocks should have the same salt; in SDMF, it is
2712+        # the file-wide IV, while in MDMF it is the per-segment salt. In
2713+        # either case, we just need to get one of them and use it.
2714+        #
2715+        # d.items()[0] is like (shnum, (block, salt))
2716+        # d.items()[0][1] is like (block, salt)
2717+        # d.items()[0][1][1] is the salt.
2718+        salt = d.items()[0][1][1]
2719+        # Next, extract just the blocks from the dict. We'll use the
2720+        # salt in the next step.
2721+        share_and_shareids = [(k, v[0]) for k, v in d.items()]
2722+        d2 = dict(share_and_shareids)
2723+        shareids = []
2724+        shares = []
2725+        for shareid, share in d2.items():
2726             shareids.append(shareid)
2727             shares.append(share)
2728 
2729hunk ./src/allmydata/mutable/retrieve.py 746
2730-        assert len(shareids) >= k, len(shareids)
2731+        assert len(shareids) >= self._required_shares, len(shareids)
2732         # zfec really doesn't want extra shares
2733hunk ./src/allmydata/mutable/retrieve.py 748
2734-        shareids = shareids[:k]
2735-        shares = shares[:k]
2736-
2737-        fec = codec.CRSDecoder()
2738-        fec.set_params(segsize, k, N)
2739-
2740-        self.log("params %s, we have %d shares" % ((segsize, k, N), len(shares)))
2741-        self.log("about to decode, shareids=%s" % (shareids,))
2742-        d = defer.maybeDeferred(fec.decode, shares, shareids)
2743-        def _done(buffers):
2744-            self._status.timings["decode"] = time.time() - started
2745-            self.log(" decode done, %d buffers" % len(buffers))
2746+        shareids = shareids[:self._required_shares]
2747+        shares = shares[:self._required_shares]
2748+        self.log("decoding segment %d" % segnum)
2749+        if segnum == self._num_segments - 1:
2750+            d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids)
2751+        else:
2752+            d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids)
2753+        def _process(buffers):
2754             segment = "".join(buffers)
2755hunk ./src/allmydata/mutable/retrieve.py 757
2756+            self.log(format="now decoding segment %(segnum)s of %(numsegs)s",
2757+                     segnum=segnum,
2758+                     numsegs=self._num_segments,
2759+                     level=log.NOISY)
2760             self.log(" joined length %d, datalength %d" %
2761hunk ./src/allmydata/mutable/retrieve.py 762
2762-                     (len(segment), datalength))
2763-            segment = segment[:datalength]
2764+                     (len(segment), self._data_length))
2765+            if segnum == self._num_segments - 1:
2766+                size_to_use = self._tail_data_size
2767+            else:
2768+                size_to_use = self._segment_size
2769+            segment = segment[:size_to_use]
2770             self.log(" segment len=%d" % len(segment))
2771hunk ./src/allmydata/mutable/retrieve.py 769
2772-            return segment
2773-        def _err(f):
2774-            self.log(" decode failed: %s" % f)
2775-            return f
2776-        d.addCallback(_done)
2777-        d.addErrback(_err)
2778+            return segment, salt
2779+        d.addCallback(_process)
2780         return d
2781 
2782hunk ./src/allmydata/mutable/retrieve.py 773
2783-    def _decrypt(self, crypttext, IV, readkey):
2784+
2785+    def _decrypt_segment(self, segment_and_salt):
2786+        """
2787+        I take a single segment and its salt, and decrypt it. I return
2788+        the plaintext of the segment that is in my argument.
2789+        """
2790+        segment, salt = segment_and_salt
2791         self._status.set_status("decrypting")
2792hunk ./src/allmydata/mutable/retrieve.py 781
2793+        self.log("decrypting segment %d" % self._current_segment)
2794         started = time.time()
2795hunk ./src/allmydata/mutable/retrieve.py 783
2796-        key = hashutil.ssk_readkey_data_hash(IV, readkey)
2797+        key = hashutil.ssk_readkey_data_hash(salt, self._node.get_readkey())
2798         decryptor = AES(key)
2799hunk ./src/allmydata/mutable/retrieve.py 785
2800-        plaintext = decryptor.process(crypttext)
2801+        plaintext = decryptor.process(segment)
2802         self._status.timings["decrypt"] = time.time() - started
2803         return plaintext
2804 
2805hunk ./src/allmydata/mutable/retrieve.py 789
2806-    def _done(self, res):
2807-        if not self._running:
2808+
2809+    def notify_server_corruption(self, peerid, shnum, reason):
2810+        ss = self.servermap.connections[peerid]
2811+        ss.callRemoteOnly("advise_corrupt_share",
2812+                          "mutable", self._storage_index, shnum, reason)
2813+
2814+
2815+    def _try_to_validate_privkey(self, enc_privkey, reader):
2816+
2817+        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2818+        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2819+        if alleged_writekey != self._node.get_writekey():
2820+            self.log("invalid privkey from %s shnum %d" %
2821+                     (reader, reader.shnum),
2822+                     level=log.WEIRD, umid="YIw4tA")
2823             return
2824hunk ./src/allmydata/mutable/retrieve.py 805
2825-        self._running = False
2826-        self._status.set_active(False)
2827-        self._status.timings["total"] = time.time() - self._started
2828-        # res is either the new contents, or a Failure
2829-        if isinstance(res, failure.Failure):
2830-            self.log("Retrieve done, with failure", failure=res,
2831-                     level=log.UNUSUAL)
2832-            self._status.set_status("Failed")
2833-        else:
2834-            self.log("Retrieve done, success!")
2835-            self._status.set_status("Finished")
2836-            self._status.set_progress(1.0)
2837-            # remember the encoding parameters, use them again next time
2838-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2839-             offsets_tuple) = self.verinfo
2840-            self._node._populate_required_shares(k)
2841-            self._node._populate_total_shares(N)
2842-        eventually(self._done_deferred.callback, res)
2843 
2844hunk ./src/allmydata/mutable/retrieve.py 806
2845+        # it's good
2846+        self.log("got valid privkey from shnum %d on reader %s" %
2847+                 (reader.shnum, reader))
2848+        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2849+        self._node._populate_encprivkey(enc_privkey)
2850+        self._node._populate_privkey(privkey)
2851+        self._need_privkey = False
2852+
2853+
2854+    def _check_for_done(self, res):
2855+        """
2856+        I check to see if this Retrieve object has successfully finished
2857+        its work.
2858+
2859+        I can exit in the following ways:
2860+            - If there are no more segments to download, then I exit by
2861+              causing self._done_deferred to fire with the plaintext
2862+              content requested by the caller.
2863+            - If there are still segments to be downloaded, and there
2864+              are enough active readers (readers which have not broken
2865+              and have not given us corrupt data) to continue
2866+              downloading, I send control back to
2867+              _download_current_segment.
2868+            - If there are still segments to be downloaded but there are
2869+              not enough active peers to download them, I ask
2870+              _add_active_peers to add more peers. If it is successful,
2871+              it will call _download_current_segment. If there are not
2872+              enough peers to retrieve the file, then that will cause
2873+              _done_deferred to errback.
2874+        """
2875+        self.log("checking for doneness")
2876+        if self._current_segment == self._num_segments:
2877+            # No more segments to download, we're done.
2878+            self.log("got plaintext, done")
2879+            return self._done()
2880+
2881+        if len(self._active_readers) >= self._required_shares:
2882+            # More segments to download, but we have enough good peers
2883+            # in self._active_readers that we can do that without issue,
2884+            # so go nab the next segment.
2885+            self.log("not done yet: on segment %d of %d" % \
2886+                     (self._current_segment + 1, self._num_segments))
2887+            return self._download_current_segment()
2888+
2889+        self.log("not done yet: on segment %d of %d, need to add peers" % \
2890+                 (self._current_segment + 1, self._num_segments))
2891+        return self._add_active_peers()
2892+
2893+
2894+    def _done(self):
2895+        """
2896+        I am called by _check_for_done when the download process has
2897+        finished successfully. After making some useful logging
2898+        statements, I return the decrypted contents to the owner of this
2899+        Retrieve object through self._done_deferred.
2900+        """
2901+        eventually(self._done_deferred.callback, self._plaintext)
2902+
2903+
2904+    def _failed(self):
2905+        """
2906+        I am called by _add_active_peers when there are not enough
2907+        active peers left to complete the download. After making some
2908+        useful logging statements, I return an exception to that effect
2909+        to the caller of this Retrieve object through
2910+        self._done_deferred.
2911+        """
2912+        format = ("ran out of peers: "
2913+                  "have %(have)d of %(total)d segments "
2914+                  "found %(bad)d bad shares "
2915+                  "encoding %(k)d-of-%(n)d")
2916+        args = {"have": self._current_segment,
2917+                "total": self._num_segments,
2918+                "k": self._required_shares,
2919+                "n": self._total_shares,
2920+                "bad": len(self._bad_shares)}
2921+        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
2922+                                                        str(self._last_failure)))
2923+        f = failure.Failure(e)
2924+        eventually(self._done_deferred.callback, f)
2925hunk ./src/allmydata/test/test_mutable.py 12
2926 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
2927      ssk_pubkey_fingerprint_hash
2928 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
2929-     NotEnoughSharesError
2930+     NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
2931 from allmydata.monitor import Monitor
2932 from allmydata.test.common import ShouldFailMixin
2933 from allmydata.test.no_network import GridTestMixin
2934hunk ./src/allmydata/test/test_mutable.py 28
2935 from allmydata.mutable.retrieve import Retrieve
2936 from allmydata.mutable.publish import Publish
2937 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
2938-from allmydata.mutable.layout import unpack_header, unpack_share
2939+from allmydata.mutable.layout import unpack_header, unpack_share, \
2940+                                     MDMFSlotReadProxy
2941 from allmydata.mutable.repairer import MustForceRepairError
2942 
2943 import allmydata.test.common_util as testutil
2944hunk ./src/allmydata/test/test_mutable.py 104
2945         d = fireEventually()
2946         d.addCallback(lambda res: _call())
2947         return d
2948+
2949     def callRemoteOnly(self, methname, *args, **kwargs):
2950         d = self.callRemote(methname, *args, **kwargs)
2951         d.addBoth(lambda ignore: None)
2952hunk ./src/allmydata/test/test_mutable.py 163
2953 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
2954     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
2955     # list of shnums to corrupt.
2956+    ds = []
2957     for peerid in s._peers:
2958         shares = s._peers[peerid]
2959         for shnum in shares:
2960hunk ./src/allmydata/test/test_mutable.py 190
2961                 else:
2962                     offset1 = offset
2963                     offset2 = 0
2964-                if offset1 == "pubkey":
2965+                if offset1 == "pubkey" and IV:
2966                     real_offset = 107
2967hunk ./src/allmydata/test/test_mutable.py 192
2968+                elif offset1 == "share_data" and not IV:
2969+                    real_offset = 104
2970                 elif offset1 in o:
2971                     real_offset = o[offset1]
2972                 else:
2973hunk ./src/allmydata/test/test_mutable.py 327
2974         d.addCallback(_created)
2975         return d
2976 
2977+
2978+    def test_upload_and_download_mdmf(self):
2979+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
2980+        def _created(n):
2981+            d = defer.succeed(None)
2982+            d.addCallback(lambda ignored:
2983+                n.get_servermap(MODE_READ))
2984+            def _then(servermap):
2985+                dumped = servermap.dump(StringIO())
2986+                self.failUnlessIn("3-of-10", dumped.getvalue())
2987+            d.addCallback(_then)
2988+            # Now overwrite the contents with some new contents. We want
2989+            # to make them big enough to force the file to be uploaded
2990+            # in more than one segment.
2991+            big_contents = "contents1" * 100000 # about 900 KiB
2992+            d.addCallback(lambda ignored:
2993+                n.overwrite(big_contents))
2994+            d.addCallback(lambda ignored:
2995+                n.download_best_version())
2996+            d.addCallback(lambda data:
2997+                self.failUnlessEqual(data, big_contents))
2998+            # Overwrite the contents again with some new contents. As
2999+            # before, they need to be big enough to force multiple
3000+            # segments, so that we make the downloader deal with
3001+            # multiple segments.
3002+            bigger_contents = "contents2" * 1000000 # about 9MiB
3003+            d.addCallback(lambda ignored:
3004+                n.overwrite(bigger_contents))
3005+            d.addCallback(lambda ignored:
3006+                n.download_best_version())
3007+            d.addCallback(lambda data:
3008+                self.failUnlessEqual(data, bigger_contents))
3009+            return d
3010+        d.addCallback(_created)
3011+        return d
3012+
3013+
3014     def test_create_with_initial_contents(self):
3015         d = self.nodemaker.create_mutable_file("contents 1")
3016         def _created(n):
3017hunk ./src/allmydata/test/test_mutable.py 1147
3018 
3019 
3020     def _test_corrupt_all(self, offset, substring,
3021-                          should_succeed=False, corrupt_early=True,
3022-                          failure_checker=None):
3023+                          should_succeed=False,
3024+                          corrupt_early=True,
3025+                          failure_checker=None,
3026+                          fetch_privkey=False):
3027         d = defer.succeed(None)
3028         if corrupt_early:
3029             d.addCallback(corrupt, self._storage, offset)
3030hunk ./src/allmydata/test/test_mutable.py 1167
3031                     self.failUnlessIn(substring, "".join(allproblems))
3032                 return servermap
3033             if should_succeed:
3034-                d1 = self._fn.download_version(servermap, ver)
3035+                d1 = self._fn.download_version(servermap, ver,
3036+                                               fetch_privkey)
3037                 d1.addCallback(lambda new_contents:
3038                                self.failUnlessEqual(new_contents, self.CONTENTS))
3039             else:
3040hunk ./src/allmydata/test/test_mutable.py 1175
3041                 d1 = self.shouldFail(NotEnoughSharesError,
3042                                      "_corrupt_all(offset=%s)" % (offset,),
3043                                      substring,
3044-                                     self._fn.download_version, servermap, ver)
3045+                                     self._fn.download_version, servermap,
3046+                                                                ver,
3047+                                                                fetch_privkey)
3048             if failure_checker:
3049                 d1.addCallback(failure_checker)
3050             d1.addCallback(lambda res: servermap)
3051hunk ./src/allmydata/test/test_mutable.py 1186
3052         return d
3053 
3054     def test_corrupt_all_verbyte(self):
3055-        # when the version byte is not 0, we hit an UnknownVersionError error
3056-        # in unpack_share().
3057+        # when the version byte is not 0 or 1, we hit an UnknownVersionError
3058+        # error in unpack_share().
3059         d = self._test_corrupt_all(0, "UnknownVersionError")
3060         def _check_servermap(servermap):
3061             # and the dump should mention the problems
3062hunk ./src/allmydata/test/test_mutable.py 1193
3063             s = StringIO()
3064             dump = servermap.dump(s).getvalue()
3065-            self.failUnless("10 PROBLEMS" in dump, dump)
3066+            self.failUnless("30 PROBLEMS" in dump, dump)
3067         d.addCallback(_check_servermap)
3068         return d
3069 
3070hunk ./src/allmydata/test/test_mutable.py 1263
3071         return self._test_corrupt_all("enc_privkey", None, should_succeed=True)
3072 
3073 
3074+    def test_corrupt_all_encprivkey_late(self):
3075+        # this should work for the same reason as above, but we corrupt
3076+        # after the servermap update to exercise the error handling
3077+        # code.
3078+        # We need to remove the privkey from the node, or the retrieve
3079+        # process won't know to update it.
3080+        self._fn._privkey = None
3081+        return self._test_corrupt_all("enc_privkey",
3082+                                      None, # this shouldn't fail
3083+                                      should_succeed=True,
3084+                                      corrupt_early=False,
3085+                                      fetch_privkey=True)
3086+
3087+
3088     def test_corrupt_all_seqnum_late(self):
3089         # corrupting the seqnum between mapupdate and retrieve should result
3090         # in NotEnoughSharesError, since each share will look invalid
3091hunk ./src/allmydata/test/test_mutable.py 1283
3092         def _check(res):
3093             f = res[0]
3094             self.failUnless(f.check(NotEnoughSharesError))
3095-            self.failUnless("someone wrote to the data since we read the servermap" in str(f))
3096+            self.failUnless("uncoordinated write" in str(f))
3097         return self._test_corrupt_all(1, "ran out of peers",
3098                                       corrupt_early=False,
3099                                       failure_checker=_check)
3100hunk ./src/allmydata/test/test_mutable.py 1333
3101                       self.failUnlessEqual(new_contents, self.CONTENTS))
3102         return d
3103 
3104-    def test_corrupt_some(self):
3105-        # corrupt the data of first five shares (so the servermap thinks
3106-        # they're good but retrieve marks them as bad), so that the
3107-        # MODE_READ set of 6 will be insufficient, forcing node.download to
3108-        # retry with more servers.
3109-        corrupt(None, self._storage, "share_data", range(5))
3110-        d = self.make_servermap()
3111+
3112+    def _test_corrupt_some(self, offset, mdmf=False):
3113+        if mdmf:
3114+            d = self.publish_mdmf()
3115+        else:
3116+            d = defer.succeed(None)
3117+        d.addCallback(lambda ignored:
3118+            corrupt(None, self._storage, offset, range(5)))
3119+        d.addCallback(lambda ignored:
3120+            self.make_servermap())
3121         def _do_retrieve(servermap):
3122             ver = servermap.best_recoverable_version()
3123             self.failUnless(ver)
3124hunk ./src/allmydata/test/test_mutable.py 1349
3125             return self._fn.download_best_version()
3126         d.addCallback(_do_retrieve)
3127         d.addCallback(lambda new_contents:
3128-                      self.failUnlessEqual(new_contents, self.CONTENTS))
3129+            self.failUnlessEqual(new_contents, self.CONTENTS))
3130         return d
3131 
3132hunk ./src/allmydata/test/test_mutable.py 1352
3133+
3134+    def test_corrupt_some(self):
3135+        # corrupt the data of first five shares (so the servermap thinks
3136+        # they're good but retrieve marks them as bad), so that the
3137+        # MODE_READ set of 6 will be insufficient, forcing node.download to
3138+        # retry with more servers.
3139+        return self._test_corrupt_some("share_data")
3140+
3141+
3142     def test_download_fails(self):
3143         d = corrupt(None, self._storage, "signature")
3144         d.addCallback(lambda ignored:
3145hunk ./src/allmydata/test/test_mutable.py 1366
3146             self.shouldFail(UnrecoverableFileError, "test_download_anyway",
3147                             "no recoverable versions",
3148-                            self._fn.download_best_version)
3149+                            self._fn.download_best_version))
3150         return d
3151 
3152 
3153hunk ./src/allmydata/test/test_mutable.py 1370
3154+
3155+    def test_corrupt_mdmf_block_hash_tree(self):
3156+        d = self.publish_mdmf()
3157+        d.addCallback(lambda ignored:
3158+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3159+                                   "block hash tree failure",
3160+                                   corrupt_early=False,
3161+                                   should_succeed=False))
3162+        return d
3163+
3164+
3165+    def test_corrupt_mdmf_block_hash_tree_late(self):
3166+        d = self.publish_mdmf()
3167+        d.addCallback(lambda ignored:
3168+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3169+                                   "block hash tree failure",
3170+                                   corrupt_early=True,
3171+                                   should_succeed=False))
3172+        return d
3173+
3174+
3175+    def test_corrupt_mdmf_share_data(self):
3176+        d = self.publish_mdmf()
3177+        d.addCallback(lambda ignored:
3178+            # TODO: Find out what the block size is and corrupt a
3179+            # specific block, rather than just guessing.
3180+            self._test_corrupt_all(("share_data", 12 * 40),
3181+                                    "block hash tree failure",
3182+                                    corrupt_early=True,
3183+                                    should_succeed=False))
3184+        return d
3185+
3186+
3187+    def test_corrupt_some_mdmf(self):
3188+        return self._test_corrupt_some(("share_data", 12 * 40),
3189+                                       mdmf=True)
3190+
3191+
3192 class CheckerMixin:
3193     def check_good(self, r, where):
3194         self.failUnless(r.is_healthy(), where)
3195hunk ./src/allmydata/test/test_mutable.py 2116
3196             d.addCallback(lambda res:
3197                           self.shouldFail(NotEnoughSharesError,
3198                                           "test_retrieve_surprise",
3199-                                          "ran out of peers: have 0 shares (k=3)",
3200+                                          "ran out of peers: have 0 of 1",
3201                                           n.download_version,
3202                                           self.old_map,
3203                                           self.old_map.best_recoverable_version(),
3204hunk ./src/allmydata/test/test_mutable.py 2125
3205         d.addCallback(_created)
3206         return d
3207 
3208+
3209     def test_unexpected_shares(self):
3210         # upload the file, take a servermap, shut down one of the servers,
3211         # upload it again (causing shares to appear on a new server), then
3212hunk ./src/allmydata/test/test_mutable.py 2329
3213         self.basedir = "mutable/Problems/test_privkey_query_missing"
3214         self.set_up_grid(num_servers=20)
3215         nm = self.g.clients[0].nodemaker
3216-        LARGE = "These are Larger contents" * 2000 # about 50KB
3217+        LARGE = "These are Larger contents" * 2000 # about 50KiB
3218         nm._node_cache = DevNullDictionary() # disable the nodecache
3219 
3220         d = nm.create_mutable_file(LARGE)
3221hunk ./src/allmydata/test/test_mutable.py 2342
3222         d.addCallback(_created)
3223         d.addCallback(lambda res: self.n2.get_servermap(MODE_WRITE))
3224         return d
3225+
3226+
3227+    def test_block_and_hash_query_error(self):
3228+        # This tests for what happens when a query to a remote server
3229+        # fails in either the hash validation step or the block getting
3230+        # step (because of batching, this is the same actual query).
3231+        # We need to have the storage server persist up until the point
3232+        # that its prefix is validated, then suddenly die. This
3233+        # exercises some exception handling code in Retrieve.
3234+        self.basedir = "mutable/Problems/test_block_and_hash_query_error"
3235+        self.set_up_grid(num_servers=20)
3236+        nm = self.g.clients[0].nodemaker
3237+        CONTENTS = "contents" * 2000
3238+        d = nm.create_mutable_file(CONTENTS)
3239+        def _created(node):
3240+            self._node = node
3241+        d.addCallback(_created)
3242+        d.addCallback(lambda ignored:
3243+            self._node.get_servermap(MODE_READ))
3244+        def _then(servermap):
3245+            # we have our servermap. Now we set up the servers like the
3246+            # tests above -- the first one that gets a read call should
3247+            # start throwing errors, but only after returning its prefix
3248+            # for validation. Since we'll download without fetching the
3249+            # private key, the next query to the remote server will be
3250+            # for either a block and salt or for hashes, either of which
3251+            # will exercise the error handling code.
3252+            killer = FirstServerGetsKilled()
3253+            for (serverid, ss) in nm.storage_broker.get_all_servers():
3254+                ss.post_call_notifier = killer.notify
3255+            ver = servermap.best_recoverable_version()
3256+            assert ver
3257+            return self._node.download_version(servermap, ver)
3258+        d.addCallback(_then)
3259+        d.addCallback(lambda data:
3260+            self.failUnlessEqual(data, CONTENTS))
3261+        return d
3262}
3263[mutable/checker.py: check MDMF files
3264Kevan Carstensen <kevan@isnotajoke.com>**20100628225048
3265 Ignore-this: fb697b36285d60552df6ca5ac6a37629
3266 
3267 This patch adapts the mutable file checker and verifier to check and
3268 verify MDMF files. It does this by using the new segmented downloader,
3269 which is trained to perform verification operations on request. This
3270 removes some code duplication.
3271] {
3272hunk ./src/allmydata/mutable/checker.py 12
3273 from allmydata.mutable.common import MODE_CHECK, CorruptShareError
3274 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
3275 from allmydata.mutable.layout import unpack_share, SIGNED_PREFIX_LENGTH
3276+from allmydata.mutable.retrieve import Retrieve # for verifying
3277 
3278 class MutableChecker:
3279 
3280hunk ./src/allmydata/mutable/checker.py 29
3281 
3282     def check(self, verify=False, add_lease=False):
3283         servermap = ServerMap()
3284+        # Updating the servermap in MODE_CHECK will stand a good chance
3285+        # of finding all of the shares, and getting a good idea of
3286+        # recoverability, etc, without verifying.
3287         u = ServermapUpdater(self._node, self._storage_broker, self._monitor,
3288                              servermap, MODE_CHECK, add_lease=add_lease)
3289         if self._history:
3290hunk ./src/allmydata/mutable/checker.py 55
3291         if num_recoverable:
3292             self.best_version = servermap.best_recoverable_version()
3293 
3294+        # The file is unhealthy and needs to be repaired if:
3295+        # - There are unrecoverable versions.
3296         if servermap.unrecoverable_versions():
3297             self.need_repair = True
3298hunk ./src/allmydata/mutable/checker.py 59
3299+        # - There isn't a recoverable version.
3300         if num_recoverable != 1:
3301             self.need_repair = True
3302hunk ./src/allmydata/mutable/checker.py 62
3303+        # - The best recoverable version is missing some shares.
3304         if self.best_version:
3305             available_shares = servermap.shares_available()
3306             (num_distinct_shares, k, N) = available_shares[self.best_version]
3307hunk ./src/allmydata/mutable/checker.py 73
3308 
3309     def _verify_all_shares(self, servermap):
3310         # read every byte of each share
3311+        #
3312+        # This logic is going to be very nearly the same as the
3313+        # downloader. I bet we could pass the downloader a flag that
3314+        # makes it do this, and piggyback onto that instead of
3315+        # duplicating a bunch of code.
3316+        #
3317+        # Like:
3318+        #  r = Retrieve(blah, blah, blah, verify=True)
3319+        #  d = r.download()
3320+        #  (wait, wait, wait, d.callback)
3321+        # 
3322+        #  Then, when it has finished, we can check the servermap (which
3323+        #  we provided to Retrieve) to figure out which shares are bad,
3324+        #  since the Retrieve process will have updated the servermap as
3325+        #  it went along.
3326+        #
3327+        #  By passing the verify=True flag to the constructor, we are
3328+        #  telling the downloader a few things.
3329+        #
3330+        #  1. It needs to download all N shares, not just K shares.
3331+        #  2. It doesn't need to decrypt or decode the shares, only
3332+        #     verify them.
3333         if not self.best_version:
3334             return
3335hunk ./src/allmydata/mutable/checker.py 97
3336-        versionmap = servermap.make_versionmap()
3337-        shares = versionmap[self.best_version]
3338-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3339-         offsets_tuple) = self.best_version
3340-        offsets = dict(offsets_tuple)
3341-        readv = [ (0, offsets["EOF"]) ]
3342-        dl = []
3343-        for (shnum, peerid, timestamp) in shares:
3344-            ss = servermap.connections[peerid]
3345-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
3346-            d.addCallback(self._got_answer, peerid, servermap)
3347-            dl.append(d)
3348-        return defer.DeferredList(dl, fireOnOneErrback=True, consumeErrors=True)
3349 
3350hunk ./src/allmydata/mutable/checker.py 98
3351-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
3352-        # isolate the callRemote to a separate method, so tests can subclass
3353-        # Publish and override it
3354-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
3355+        r = Retrieve(self._node, servermap, self.best_version, verify=True)
3356+        d = r.download()
3357+        d.addCallback(self._process_bad_shares)
3358         return d
3359 
3360hunk ./src/allmydata/mutable/checker.py 103
3361-    def _got_answer(self, datavs, peerid, servermap):
3362-        for shnum,datav in datavs.items():
3363-            data = datav[0]
3364-            try:
3365-                self._got_results_one_share(shnum, peerid, data)
3366-            except CorruptShareError:
3367-                f = failure.Failure()
3368-                self.need_repair = True
3369-                self.bad_shares.append( (peerid, shnum, f) )
3370-                prefix = data[:SIGNED_PREFIX_LENGTH]
3371-                servermap.mark_bad_share(peerid, shnum, prefix)
3372-                ss = servermap.connections[peerid]
3373-                self.notify_server_corruption(ss, shnum, str(f.value))
3374-
3375-    def check_prefix(self, peerid, shnum, data):
3376-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3377-         offsets_tuple) = self.best_version
3378-        got_prefix = data[:SIGNED_PREFIX_LENGTH]
3379-        if got_prefix != prefix:
3380-            raise CorruptShareError(peerid, shnum,
3381-                                    "prefix mismatch: share changed while we were reading it")
3382-
3383-    def _got_results_one_share(self, shnum, peerid, data):
3384-        self.check_prefix(peerid, shnum, data)
3385-
3386-        # the [seqnum:signature] pieces are validated by _compare_prefix,
3387-        # which checks their signature against the pubkey known to be
3388-        # associated with this file.
3389 
3390hunk ./src/allmydata/mutable/checker.py 104
3391-        (seqnum, root_hash, IV, k, N, segsize, datalen, pubkey, signature,
3392-         share_hash_chain, block_hash_tree, share_data,
3393-         enc_privkey) = unpack_share(data)
3394-
3395-        # validate [share_hash_chain,block_hash_tree,share_data]
3396-
3397-        leaves = [hashutil.block_hash(share_data)]
3398-        t = hashtree.HashTree(leaves)
3399-        if list(t) != block_hash_tree:
3400-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
3401-        share_hash_leaf = t[0]
3402-        t2 = hashtree.IncompleteHashTree(N)
3403-        # root_hash was checked by the signature
3404-        t2.set_hashes({0: root_hash})
3405-        try:
3406-            t2.set_hashes(hashes=share_hash_chain,
3407-                          leaves={shnum: share_hash_leaf})
3408-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
3409-                IndexError), e:
3410-            msg = "corrupt hashes: %s" % (e,)
3411-            raise CorruptShareError(peerid, shnum, msg)
3412-
3413-        # validate enc_privkey: only possible if we have a write-cap
3414-        if not self._node.is_readonly():
3415-            alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3416-            alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3417-            if alleged_writekey != self._node.get_writekey():
3418-                raise CorruptShareError(peerid, shnum, "invalid privkey")
3419+    def _process_bad_shares(self, bad_shares):
3420+        if bad_shares:
3421+            self.need_repair = True
3422+        self.bad_shares = bad_shares
3423 
3424hunk ./src/allmydata/mutable/checker.py 109
3425-    def notify_server_corruption(self, ss, shnum, reason):
3426-        ss.callRemoteOnly("advise_corrupt_share",
3427-                          "mutable", self._storage_index, shnum, reason)
3428 
3429     def _count_shares(self, smap, version):
3430         available_shares = smap.shares_available()
3431hunk ./src/allmydata/test/test_mutable.py 193
3432                 if offset1 == "pubkey" and IV:
3433                     real_offset = 107
3434                 elif offset1 == "share_data" and not IV:
3435-                    real_offset = 104
3436+                    real_offset = 107
3437                 elif offset1 in o:
3438                     real_offset = o[offset1]
3439                 else:
3440hunk ./src/allmydata/test/test_mutable.py 395
3441             return d
3442         d.addCallback(_created)
3443         return d
3444+    test_create_mdmf_with_initial_contents.timeout = 20
3445 
3446 
3447     def test_create_with_initial_contents_function(self):
3448hunk ./src/allmydata/test/test_mutable.py 700
3449                                            k, N, segsize, datalen)
3450                 self.failUnless(p._pubkey.verify(sig_material, signature))
3451                 #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
3452-                self.failUnless(isinstance(share_hash_chain, dict))
3453-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3454+                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3455                 for shnum,share_hash in share_hash_chain.items():
3456                     self.failUnless(isinstance(shnum, int))
3457                     self.failUnless(isinstance(share_hash, str))
3458hunk ./src/allmydata/test/test_mutable.py 820
3459                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
3460 
3461 
3462+
3463+
3464 class Servermap(unittest.TestCase, PublishMixin):
3465     def setUp(self):
3466         return self.publish_one()
3467hunk ./src/allmydata/test/test_mutable.py 951
3468         self._storage._peers = {} # delete all shares
3469         ms = self.make_servermap
3470         d = defer.succeed(None)
3471-
3472+#
3473         d.addCallback(lambda res: ms(mode=MODE_CHECK))
3474         d.addCallback(lambda sm: self.failUnlessNoneRecoverable(sm))
3475 
3476hunk ./src/allmydata/test/test_mutable.py 1440
3477         d.addCallback(self.check_good, "test_check_good")
3478         return d
3479 
3480+    def test_check_mdmf_good(self):
3481+        d = self.publish_mdmf()
3482+        d.addCallback(lambda ignored:
3483+            self._fn.check(Monitor()))
3484+        d.addCallback(self.check_good, "test_check_mdmf_good")
3485+        return d
3486+
3487     def test_check_no_shares(self):
3488         for shares in self._storage._peers.values():
3489             shares.clear()
3490hunk ./src/allmydata/test/test_mutable.py 1454
3491         d.addCallback(self.check_bad, "test_check_no_shares")
3492         return d
3493 
3494+    def test_check_mdmf_no_shares(self):
3495+        d = self.publish_mdmf()
3496+        def _then(ignored):
3497+            for share in self._storage._peers.values():
3498+                share.clear()
3499+        d.addCallback(_then)
3500+        d.addCallback(lambda ignored:
3501+            self._fn.check(Monitor()))
3502+        d.addCallback(self.check_bad, "test_check_mdmf_no_shares")
3503+        return d
3504+
3505     def test_check_not_enough_shares(self):
3506         for shares in self._storage._peers.values():
3507             for shnum in shares.keys():
3508hunk ./src/allmydata/test/test_mutable.py 1474
3509         d.addCallback(self.check_bad, "test_check_not_enough_shares")
3510         return d
3511 
3512+    def test_check_mdmf_not_enough_shares(self):
3513+        d = self.publish_mdmf()
3514+        def _then(ignored):
3515+            for shares in self._storage._peers.values():
3516+                for shnum in shares.keys():
3517+                    if shnum > 0:
3518+                        del shares[shnum]
3519+        d.addCallback(_then)
3520+        d.addCallback(lambda ignored:
3521+            self._fn.check(Monitor()))
3522+        d.addCallback(self.check_bad, "test_check_mdmf_not_enougH_shares")
3523+        return d
3524+
3525+
3526     def test_check_all_bad_sig(self):
3527         d = corrupt(None, self._storage, 1) # bad sig
3528         d.addCallback(lambda ignored:
3529hunk ./src/allmydata/test/test_mutable.py 1495
3530         d.addCallback(self.check_bad, "test_check_all_bad_sig")
3531         return d
3532 
3533+    def test_check_mdmf_all_bad_sig(self):
3534+        d = self.publish_mdmf()
3535+        d.addCallback(lambda ignored:
3536+            corrupt(None, self._storage, 1))
3537+        d.addCallback(lambda ignored:
3538+            self._fn.check(Monitor()))
3539+        d.addCallback(self.check_bad, "test_check_mdmf_all_bad_sig")
3540+        return d
3541+
3542     def test_check_all_bad_blocks(self):
3543         d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
3544         # the Checker won't notice this.. it doesn't look at actual data
3545hunk ./src/allmydata/test/test_mutable.py 1512
3546         d.addCallback(self.check_good, "test_check_all_bad_blocks")
3547         return d
3548 
3549+
3550+    def test_check_mdmf_all_bad_blocks(self):
3551+        d = self.publish_mdmf()
3552+        d.addCallback(lambda ignored:
3553+            corrupt(None, self._storage, "share_data"))
3554+        d.addCallback(lambda ignored:
3555+            self._fn.check(Monitor()))
3556+        d.addCallback(self.check_good, "test_check_mdmf_all_bad_blocks")
3557+        return d
3558+
3559     def test_verify_good(self):
3560         d = self._fn.check(Monitor(), verify=True)
3561         d.addCallback(self.check_good, "test_verify_good")
3562hunk ./src/allmydata/test/test_mutable.py 1582
3563                       "test_verify_one_bad_encprivkey_uncheckable")
3564         return d
3565 
3566+
3567+    def test_verify_mdmf_good(self):
3568+        d = self.publish_mdmf()
3569+        d.addCallback(lambda ignored:
3570+            self._fn.check(Monitor(), verify=True))
3571+        d.addCallback(self.check_good, "test_verify_mdmf_good")
3572+        return d
3573+
3574+
3575+    def test_verify_mdmf_one_bad_block(self):
3576+        d = self.publish_mdmf()
3577+        d.addCallback(lambda ignored:
3578+            corrupt(None, self._storage, "share_data", [1]))
3579+        d.addCallback(lambda ignored:
3580+            self._fn.check(Monitor(), verify=True))
3581+        # We should find one bad block here
3582+        d.addCallback(self.check_bad, "test_verify_mdmf_one_bad_block")
3583+        d.addCallback(self.check_expected_failure,
3584+                      CorruptShareError, "block hash tree failure",
3585+                      "test_verify_mdmf_one_bad_block")
3586+        return d
3587+
3588+
3589+    def test_verify_mdmf_bad_encprivkey(self):
3590+        d = self.publish_mdmf()
3591+        d.addCallback(lambda ignored:
3592+            corrupt(None, self._storage, "enc_privkey", [1]))
3593+        d.addCallback(lambda ignored:
3594+            self._fn.check(Monitor(), verify=True))
3595+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
3596+        d.addCallback(self.check_expected_failure,
3597+                      CorruptShareError, "privkey",
3598+                      "test_verify_mdmf_bad_encprivkey")
3599+        return d
3600+
3601+
3602+    def test_verify_mdmf_bad_sig(self):
3603+        d = self.publish_mdmf()
3604+        d.addCallback(lambda ignored:
3605+            corrupt(None, self._storage, 1, [1]))
3606+        d.addCallback(lambda ignored:
3607+            self._fn.check(Monitor(), verify=True))
3608+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_sig")
3609+        return d
3610+
3611+
3612+    def test_verify_mdmf_bad_encprivkey_uncheckable(self):
3613+        d = self.publish_mdmf()
3614+        d.addCallback(lambda ignored:
3615+            corrupt(None, self._storage, "enc_privkey", [1]))
3616+        d.addCallback(lambda ignored:
3617+            self._fn.get_readonly())
3618+        d.addCallback(lambda fn:
3619+            fn.check(Monitor(), verify=True))
3620+        d.addCallback(self.check_good,
3621+                      "test_verify_mdmf_bad_encprivkey_uncheckable")
3622+        return d
3623+
3624+
3625 class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
3626 
3627     def get_shares(self, s):
3628hunk ./src/allmydata/test/test_mutable.py 1706
3629         current_shares = self.old_shares[-1]
3630         self.failUnlessEqual(old_shares, current_shares)
3631 
3632+
3633     def test_unrepairable_0shares(self):
3634         d = self.publish_one()
3635         def _delete_all_shares(ign):
3636hunk ./src/allmydata/test/test_mutable.py 1721
3637         d.addCallback(_check)
3638         return d
3639 
3640+    def test_mdmf_unrepairable_0shares(self):
3641+        d = self.publish_mdmf()
3642+        def _delete_all_shares(ign):
3643+            shares = self._storage._peers
3644+            for peerid in shares:
3645+                shares[peerid] = {}
3646+        d.addCallback(_delete_all_shares)
3647+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3648+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3649+        d.addCallback(lambda crr: self.failIf(crr.get_successful()))
3650+        return d
3651+
3652+
3653     def test_unrepairable_1share(self):
3654         d = self.publish_one()
3655         def _delete_all_shares(ign):
3656hunk ./src/allmydata/test/test_mutable.py 1750
3657         d.addCallback(_check)
3658         return d
3659 
3660+    def test_mdmf_unrepairable_1share(self):
3661+        d = self.publish_mdmf()
3662+        def _delete_all_shares(ign):
3663+            shares = self._storage._peers
3664+            for peerid in shares:
3665+                for shnum in list(shares[peerid]):
3666+                    if shnum > 0:
3667+                        del shares[peerid][shnum]
3668+        d.addCallback(_delete_all_shares)
3669+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3670+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3671+        def _check(crr):
3672+            self.failUnlessEqual(crr.get_successful(), False)
3673+        d.addCallback(_check)
3674+        return d
3675+
3676+    def test_repairable_5shares(self):
3677+        d = self.publish_mdmf()
3678+        def _delete_all_shares(ign):
3679+            shares = self._storage._peers
3680+            for peerid in shares:
3681+                for shnum in list(shares[peerid]):
3682+                    if shnum > 4:
3683+                        del shares[peerid][shnum]
3684+        d.addCallback(_delete_all_shares)
3685+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3686+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3687+        def _check(crr):
3688+            self.failUnlessEqual(crr.get_successful(), True)
3689+        d.addCallback(_check)
3690+        return d
3691+
3692+    def test_mdmf_repairable_5shares(self):
3693+        d = self.publish_mdmf()
3694+        def _delete_all_shares(ign):
3695+            shares = self._storage._peers
3696+            for peerid in shares:
3697+                for shnum in list(shares[peerid]):
3698+                    if shnum > 5:
3699+                        del shares[peerid][shnum]
3700+        d.addCallback(_delete_all_shares)
3701+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3702+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3703+        def _check(crr):
3704+            self.failUnlessEqual(crr.get_successful(), True)
3705+        d.addCallback(_check)
3706+        return d
3707+
3708+
3709     def test_merge(self):
3710         self.old_shares = []
3711         d = self.publish_multiple()
3712}
3713[mutable/retrieve.py: learn how to verify mutable files
3714Kevan Carstensen <kevan@isnotajoke.com>**20100628225201
3715 Ignore-this: 989af7800c47589620918461ec989483
3716] {
3717hunk ./src/allmydata/mutable/retrieve.py 86
3718     # Retrieve object will remain tied to a specific version of the file, and
3719     # will use a single ServerMap instance.
3720 
3721-    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False):
3722+    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
3723+                 verify=False):
3724         self._node = filenode
3725         assert self._node.get_pubkey()
3726         self._storage_index = filenode.get_storage_index()
3727hunk ./src/allmydata/mutable/retrieve.py 106
3728         # during repair, we may be called upon to grab the private key, since
3729         # it wasn't picked up during a verify=False checker run, and we'll
3730         # need it for repair to generate a new version.
3731-        self._need_privkey = fetch_privkey
3732-        if self._node.get_privkey():
3733+        self._need_privkey = fetch_privkey or verify
3734+        if self._node.get_privkey() and not verify:
3735             self._need_privkey = False
3736 
3737         if self._need_privkey:
3738hunk ./src/allmydata/mutable/retrieve.py 117
3739             self._privkey_query_markers = [] # one Marker for each time we've
3740                                              # tried to get the privkey.
3741 
3742+        # verify means that we are using the downloader logic to verify all
3743+        # of our shares. This tells the downloader a few things.
3744+        #
3745+        # 1. We need to download all of the shares.
3746+        # 2. We don't need to decode or decrypt the shares, since our
3747+        #    caller doesn't care about the plaintext, only the
3748+        #    information about which shares are or are not valid.
3749+        # 3. When we are validating readers, we need to validate the
3750+        #    signature on the prefix. Do we? We already do this in the
3751+        #    servermap update?
3752+        #
3753+        # (just work on 1 and 2 for now, I guess)
3754+        self._verify = False
3755+        if verify:
3756+            self._verify = True
3757+
3758         self._status = RetrieveStatus()
3759         self._status.set_storage_index(self._storage_index)
3760         self._status.set_helper(False)
3761hunk ./src/allmydata/mutable/retrieve.py 323
3762 
3763         # We need at least self._required_shares readers to download a
3764         # segment.
3765-        needed = self._required_shares - len(self._active_readers)
3766+        if self._verify:
3767+            needed = self._total_shares
3768+        else:
3769+            needed = self._required_shares - len(self._active_readers)
3770         # XXX: Why don't format= log messages work here?
3771         self.log("adding %d peers to the active peers list" % needed)
3772 
3773hunk ./src/allmydata/mutable/retrieve.py 339
3774         # will cause problems later.
3775         active_shnums -= set([reader.shnum for reader in self._active_readers])
3776         active_shnums = list(active_shnums)[:needed]
3777-        if len(active_shnums) < needed:
3778+        if len(active_shnums) < needed and not self._verify:
3779             # We don't have enough readers to retrieve the file; fail.
3780             return self._failed()
3781 
3782hunk ./src/allmydata/mutable/retrieve.py 346
3783         for shnum in active_shnums:
3784             self._active_readers.append(self.readers[shnum])
3785             self.log("added reader for share %d" % shnum)
3786-        assert len(self._active_readers) == self._required_shares
3787+        assert len(self._active_readers) >= self._required_shares
3788         # Conceptually, this is part of the _add_active_peers step. It
3789         # validates the prefixes of newly added readers to make sure
3790         # that they match what we are expecting for self.verinfo. If
3791hunk ./src/allmydata/mutable/retrieve.py 416
3792                     # that we haven't gotten it at the end of
3793                     # segment decoding, then we'll take more drastic
3794                     # measures.
3795-                    if self._need_privkey:
3796+                    if self._need_privkey and not self._node.is_readonly():
3797                         d = reader.get_encprivkey()
3798                         d.addCallback(self._try_to_validate_privkey, reader)
3799             if bad_readers:
3800hunk ./src/allmydata/mutable/retrieve.py 423
3801                 # We do them all at once, or else we screw up list indexing.
3802                 for (reader, f) in bad_readers:
3803                     self._mark_bad_share(reader, f)
3804-                return self._add_active_peers()
3805+                if self._verify:
3806+                    if len(self._active_readers) >= self._required_shares:
3807+                        return self._download_current_segment()
3808+                    else:
3809+                        return self._failed()
3810+                else:
3811+                    return self._add_active_peers()
3812             else:
3813                 return self._download_current_segment()
3814             # The next step will assert that it has enough active
3815hunk ./src/allmydata/mutable/retrieve.py 518
3816         """
3817         self.log("marking share %d on server %s as bad" % \
3818                  (reader.shnum, reader))
3819+        prefix = self.verinfo[-2]
3820+        self.servermap.mark_bad_share(reader.peerid,
3821+                                      reader.shnum,
3822+                                      prefix)
3823         self._remove_reader(reader)
3824hunk ./src/allmydata/mutable/retrieve.py 523
3825-        self._bad_shares.add((reader.peerid, reader.shnum))
3826+        self._bad_shares.add((reader.peerid, reader.shnum, f))
3827         self._status.problems[reader.peerid] = f
3828         self._last_failure = f
3829         self.notify_server_corruption(reader.peerid, reader.shnum,
3830hunk ./src/allmydata/mutable/retrieve.py 571
3831             ds.append(dl)
3832             reader.flush()
3833         dl = defer.DeferredList(ds)
3834-        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3835+        if self._verify:
3836+            dl.addCallback(lambda ignored: "")
3837+            dl.addCallback(self._set_segment)
3838+        else:
3839+            dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3840         return dl
3841 
3842 
3843hunk ./src/allmydata/mutable/retrieve.py 701
3844         # shnum, which will be a leaf in the share hash tree, which
3845         # will allow us to validate the rest of the tree.
3846         if self.share_hash_tree.needed_hashes(reader.shnum,
3847-                                               include_leaf=True):
3848+                                              include_leaf=True) or \
3849+                                              self._verify:
3850             try:
3851                 self.share_hash_tree.set_hashes(hashes=sharehashes[1],
3852                                             leaves={reader.shnum: bht[0]})
3853hunk ./src/allmydata/mutable/retrieve.py 832
3854 
3855 
3856     def _try_to_validate_privkey(self, enc_privkey, reader):
3857-
3858         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3859         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3860         if alleged_writekey != self._node.get_writekey():
3861hunk ./src/allmydata/mutable/retrieve.py 838
3862             self.log("invalid privkey from %s shnum %d" %
3863                      (reader, reader.shnum),
3864                      level=log.WEIRD, umid="YIw4tA")
3865+            if self._verify:
3866+                self.servermap.mark_bad_share(reader.peerid, reader.shnum,
3867+                                              self.verinfo[-2])
3868+                e = CorruptShareError(reader.peerid,
3869+                                      reader.shnum,
3870+                                      "invalid privkey")
3871+                f = failure.Failure(e)
3872+                self._bad_shares.add((reader.peerid, reader.shnum, f))
3873             return
3874 
3875         # it's good
3876hunk ./src/allmydata/mutable/retrieve.py 904
3877         statements, I return the decrypted contents to the owner of this
3878         Retrieve object through self._done_deferred.
3879         """
3880-        eventually(self._done_deferred.callback, self._plaintext)
3881+        if self._verify:
3882+            ret = list(self._bad_shares)
3883+            self.log("done verifying, found %d bad shares" % len(ret))
3884+        else:
3885+            ret = self._plaintext
3886+        eventually(self._done_deferred.callback, ret)
3887 
3888 
3889     def _failed(self):
3890hunk ./src/allmydata/mutable/retrieve.py 920
3891         to the caller of this Retrieve object through
3892         self._done_deferred.
3893         """
3894-        format = ("ran out of peers: "
3895-                  "have %(have)d of %(total)d segments "
3896-                  "found %(bad)d bad shares "
3897-                  "encoding %(k)d-of-%(n)d")
3898-        args = {"have": self._current_segment,
3899-                "total": self._num_segments,
3900-                "k": self._required_shares,
3901-                "n": self._total_shares,
3902-                "bad": len(self._bad_shares)}
3903-        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
3904-                                                        str(self._last_failure)))
3905-        f = failure.Failure(e)
3906-        eventually(self._done_deferred.callback, f)
3907+        if self._verify:
3908+            ret = list(self._bad_shares)
3909+        else:
3910+            format = ("ran out of peers: "
3911+                      "have %(have)d of %(total)d segments "
3912+                      "found %(bad)d bad shares "
3913+                      "encoding %(k)d-of-%(n)d")
3914+            args = {"have": self._current_segment,
3915+                    "total": self._num_segments,
3916+                    "k": self._required_shares,
3917+                    "n": self._total_shares,
3918+                    "bad": len(self._bad_shares)}
3919+            e = NotEnoughSharesError("%s, last failure: %s" % \
3920+                                     (format % args, str(self._last_failure)))
3921+            f = failure.Failure(e)
3922+            ret = f
3923+        eventually(self._done_deferred.callback, ret)
3924}
3925[interfaces.py: add IMutableSlotWriter
3926Kevan Carstensen <kevan@isnotajoke.com>**20100630183305
3927 Ignore-this: ff9dca96ef1a009ae85485682f81ea5
3928] hunk ./src/allmydata/interfaces.py 418
3929         """
3930 
3931 
3932+class IMutableSlotWriter(Interface):
3933+    """
3934+    The interface for a writer around a mutable slot on a remote server.
3935+    """
3936+    def set_checkstring(checkstring, *args):
3937+        """
3938+        Set the checkstring that I will pass to the remote server when
3939+        writing.
3940+
3941+            @param checkstring A packed checkstring to use.
3942+
3943+        Note that implementations can differ in which semantics they
3944+        wish to support for set_checkstring -- they can, for example,
3945+        build the checkstring themselves from its constituents, or
3946+        some other thing.
3947+        """
3948+
3949+    def get_checkstring():
3950+        """
3951+        Get the checkstring that I think currently exists on the remote
3952+        server.
3953+        """
3954+
3955+    def put_block(data, segnum, salt):
3956+        """
3957+        Add a block and salt to the share.
3958+        """
3959+
3960+    def put_encprivey(encprivkey):
3961+        """
3962+        Add the encrypted private key to the share.
3963+        """
3964+
3965+    def put_blockhashes(blockhashes=list):
3966+        """
3967+        Add the block hash tree to the share.
3968+        """
3969+
3970+    def put_sharehashes(sharehashes=dict):
3971+        """
3972+        Add the share hash chain to the share.
3973+        """
3974+
3975+    def get_signable():
3976+        """
3977+        Return the part of the share that needs to be signed.
3978+        """
3979+
3980+    def put_signature(signature):
3981+        """
3982+        Add the signature to the share.
3983+        """
3984+
3985+    def put_verification_key(verification_key):
3986+        """
3987+        Add the verification key to the share.
3988+        """
3989+
3990+    def finish_publishing():
3991+        """
3992+        Do anything necessary to finish writing the share to a remote
3993+        server. I require that no further publishing needs to take place
3994+        after this method has been called.
3995+        """
3996+
3997+
3998 class IURI(Interface):
3999     def init_from_string(uri):
4000         """Accept a string (as created by my to_string() method) and populate
4001[test/test_mutable.py: temporarily disable two tests that are now irrelevant
4002Kevan Carstensen <kevan@isnotajoke.com>**20100701232806
4003 Ignore-this: 701e143567f3954812ca6960af1d6ac7
4004] {
4005hunk ./src/allmydata/test/test_mutable.py 651
4006             self.failUnlessEqual(len(share_ids), 10)
4007         d.addCallback(_done)
4008         return d
4009+    test_encrypt.todo = "Write an equivalent of this for the new uploader"
4010 
4011     def test_generate(self):
4012         nm = make_nodemaker()
4013hunk ./src/allmydata/test/test_mutable.py 713
4014                 self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
4015         d.addCallback(_generated)
4016         return d
4017+    test_generate.todo = "Write an equivalent of this for the new uploader"
4018 
4019     # TODO: when we publish to 20 peers, we should get one share per peer on 10
4020     # when we publish to 3 peers, we should get either 3 or 4 shares per peer
4021}
4022[Add MDMF reader and writer, and SDMF writer
4023Kevan Carstensen <kevan@isnotajoke.com>**20100702225531
4024 Ignore-this: bf6276a91d27dcb4e779b0eb82ea1843
4025 
4026 The MDMF/SDMF reader MDMF writer, and SDMF writer are similar to the
4027 object proxies that exist for immutable files. They abstract away
4028 details of connection, state, and caching from their callers (in this
4029 case, the download, servermap updater, and uploader), and expose methods
4030 to get and set information on the remote server.
4031 
4032 MDMFSlotReadProxy reads a mutable file from the server, doing the right
4033 thing (in most cases) regardless of whether the file is MDMF or SDMF. It
4034 allows callers to tell it how to batch and flush reads.
4035 
4036 MDMFSlotWriteProxy writes an MDMF mutable file to a server.
4037 
4038 SDMFSlotWriteProxy writes an SDMF mutable file to a server.
4039 
4040 This patch also includes tests for MDMFSlotReadProxy,
4041 SDMFSlotWriteProxy, and MDMFSlotWriteProxy.
4042] {
4043hunk ./src/allmydata/mutable/layout.py 4
4044 
4045 import struct
4046 from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError
4047+from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \
4048+                                 MDMF_VERSION, IMutableSlotWriter
4049+from allmydata.util import mathutil, observer
4050+from twisted.python import failure
4051+from twisted.internet import defer
4052+from zope.interface import implements
4053+
4054+
4055+# These strings describe the format of the packed structs they help process
4056+# Here's what they mean:
4057+#
4058+#  PREFIX:
4059+#    >: Big-endian byte order; the most significant byte is first (leftmost).
4060+#    B: The version information; an 8 bit version identifier. Stored as
4061+#       an unsigned char. This is currently 00 00 00 00; our modifications
4062+#       will turn it into 00 00 00 01.
4063+#    Q: The sequence number; this is sort of like a revision history for
4064+#       mutable files; they start at 1 and increase as they are changed after
4065+#       being uploaded. Stored as an unsigned long long, which is 8 bytes in
4066+#       length.
4067+#  32s: The root hash of the share hash tree. We use sha-256d, so we use 32
4068+#       characters = 32 bytes to store the value.
4069+#  16s: The salt for the readkey. This is a 16-byte random value, stored as
4070+#       16 characters.
4071+#
4072+#  SIGNED_PREFIX additions, things that are covered by the signature:
4073+#    B: The "k" encoding parameter. We store this as an 8-bit character,
4074+#       which is convenient because our erasure coding scheme cannot
4075+#       encode if you ask for more than 255 pieces.
4076+#    B: The "N" encoding parameter. Stored as an 8-bit character for the
4077+#       same reasons as above.
4078+#    Q: The segment size of the uploaded file. This will essentially be the
4079+#       length of the file in SDMF. An unsigned long long, so we can store
4080+#       files of quite large size.
4081+#    Q: The data length of the uploaded file. Modulo padding, this will be
4082+#       the same of the data length field. Like the data length field, it is
4083+#       an unsigned long long and can be quite large.
4084+#
4085+#   HEADER additions:
4086+#     L: The offset of the signature of this. An unsigned long.
4087+#     L: The offset of the share hash chain. An unsigned long.
4088+#     L: The offset of the block hash tree. An unsigned long.
4089+#     L: The offset of the share data. An unsigned long.
4090+#     Q: The offset of the encrypted private key. An unsigned long long, to
4091+#        account for the possibility of a lot of share data.
4092+#     Q: The offset of the EOF. An unsigned long long, to account for the
4093+#        possibility of a lot of share data.
4094+#
4095+#  After all of these, we have the following:
4096+#    - The verification key: Occupies the space between the end of the header
4097+#      and the start of the signature (i.e.: data[HEADER_LENGTH:o['signature']].
4098+#    - The signature, which goes from the signature offset to the share hash
4099+#      chain offset.
4100+#    - The share hash chain, which goes from the share hash chain offset to
4101+#      the block hash tree offset.
4102+#    - The share data, which goes from the share data offset to the encrypted
4103+#      private key offset.
4104+#    - The encrypted private key offset, which goes until the end of the file.
4105+#
4106+#  The block hash tree in this encoding has only one share, so the offset of
4107+#  the share data will be 32 bits more than the offset of the block hash tree.
4108+#  Given this, we may need to check to see how many bytes a reasonably sized
4109+#  block hash tree will take up.
4110 
4111 PREFIX = ">BQ32s16s" # each version has a different prefix
4112 SIGNED_PREFIX = ">BQ32s16s BBQQ" # this is covered by the signature
4113hunk ./src/allmydata/mutable/layout.py 73
4114 SIGNED_PREFIX_LENGTH = struct.calcsize(SIGNED_PREFIX)
4115 HEADER = ">BQ32s16s BBQQ LLLLQQ" # includes offsets
4116 HEADER_LENGTH = struct.calcsize(HEADER)
4117+OFFSETS = ">LLLLQQ"
4118+OFFSETS_LENGTH = struct.calcsize(OFFSETS)
4119 
4120 def unpack_header(data):
4121     o = {}
4122hunk ./src/allmydata/mutable/layout.py 194
4123     return (share_hash_chain, block_hash_tree, share_data)
4124 
4125 
4126-def pack_checkstring(seqnum, root_hash, IV):
4127+def pack_checkstring(seqnum, root_hash, IV, version=0):
4128     return struct.pack(PREFIX,
4129hunk ./src/allmydata/mutable/layout.py 196
4130-                       0, # version,
4131+                       version,
4132                        seqnum,
4133                        root_hash,
4134                        IV)
4135hunk ./src/allmydata/mutable/layout.py 269
4136                            encprivkey])
4137     return final_share
4138 
4139+def pack_prefix(seqnum, root_hash, IV,
4140+                required_shares, total_shares,
4141+                segment_size, data_length):
4142+    prefix = struct.pack(SIGNED_PREFIX,
4143+                         0, # version,
4144+                         seqnum,
4145+                         root_hash,
4146+                         IV,
4147+                         required_shares,
4148+                         total_shares,
4149+                         segment_size,
4150+                         data_length,
4151+                         )
4152+    return prefix
4153+
4154+
4155+class SDMFSlotWriteProxy:
4156+    implements(IMutableSlotWriter)
4157+    """
4158+    I represent a remote write slot for an SDMF mutable file. I build a
4159+    share in memory, and then write it in one piece to the remote
4160+    server. This mimics how SDMF shares were built before MDMF (and the
4161+    new MDMF uploader), but provides that functionality in a way that
4162+    allows the MDMF uploader to be built without much special-casing for
4163+    file format, which makes the uploader code more readable.
4164+    """
4165+    def __init__(self,
4166+                 shnum,
4167+                 rref, # a remote reference to a storage server
4168+                 storage_index,
4169+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4170+                 seqnum, # the sequence number of the mutable file
4171+                 required_shares,
4172+                 total_shares,
4173+                 segment_size,
4174+                 data_length): # the length of the original file
4175+        self.shnum = shnum
4176+        self._rref = rref
4177+        self._storage_index = storage_index
4178+        self._secrets = secrets
4179+        self._seqnum = seqnum
4180+        self._required_shares = required_shares
4181+        self._total_shares = total_shares
4182+        self._segment_size = segment_size
4183+        self._data_length = data_length
4184+
4185+        # This is an SDMF file, so it should have only one segment, so,
4186+        # modulo padding of the data length, the segment size and the
4187+        # data length should be the same.
4188+        expected_segment_size = mathutil.next_multiple(data_length,
4189+                                                       self._required_shares)
4190+        assert expected_segment_size == segment_size
4191+
4192+        self._block_size = self._segment_size / self._required_shares
4193+
4194+        # This is meant to mimic how SDMF files were built before MDMF
4195+        # entered the picture: we generate each share in its entirety,
4196+        # then push it off to the storage server in one write. When
4197+        # callers call set_*, they are just populating this dict.
4198+        # finish_publishing will stitch these pieces together into a
4199+        # coherent share, and then write the coherent share to the
4200+        # storage server.
4201+        self._share_pieces = {}
4202+
4203+        # This tells the write logic what checkstring to use when
4204+        # writing remote shares.
4205+        self._testvs = []
4206+
4207+        self._readvs = [(0, struct.calcsize(PREFIX))]
4208+
4209+
4210+    def set_checkstring(self, checkstring_or_seqnum,
4211+                              root_hash=None,
4212+                              salt=None):
4213+        """
4214+        Set the checkstring that I will pass to the remote server when
4215+        writing.
4216+
4217+            @param checkstring_or_seqnum: A packed checkstring to use,
4218+                   or a sequence number. I will treat this as a checkstr
4219+
4220+        Note that implementations can differ in which semantics they
4221+        wish to support for set_checkstring -- they can, for example,
4222+        build the checkstring themselves from its constituents, or
4223+        some other thing.
4224+        """
4225+        if root_hash and salt:
4226+            checkstring = struct.pack(PREFIX,
4227+                                      0,
4228+                                      checkstring_or_seqnum,
4229+                                      root_hash,
4230+                                      salt)
4231+        else:
4232+            checkstring = checkstring_or_seqnum
4233+        self._testvs = [(0, len(checkstring), "eq", checkstring)]
4234+
4235+
4236+    def get_checkstring(self):
4237+        """
4238+        Get the checkstring that I think currently exists on the remote
4239+        server.
4240+        """
4241+        if self._testvs:
4242+            return self._testvs[0][3]
4243+        return ""
4244+
4245+
4246+    def put_block(self, data, segnum, salt):
4247+        """
4248+        Add a block and salt to the share.
4249+        """
4250+        # SDMF files have only one segment
4251+        assert segnum == 0
4252+        assert len(data) == self._block_size
4253+        assert len(salt) == SALT_SIZE
4254+
4255+        self._share_pieces['sharedata'] = data
4256+        self._share_pieces['salt'] = salt
4257+
4258+        # TODO: Figure out something intelligent to return.
4259+        return defer.succeed(None)
4260+
4261+
4262+    def put_encprivkey(self, encprivkey):
4263+        """
4264+        Add the encrypted private key to the share.
4265+        """
4266+        self._share_pieces['encprivkey'] = encprivkey
4267+
4268+        return defer.succeed(None)
4269+
4270+
4271+    def put_blockhashes(self, blockhashes):
4272+        """
4273+        Add the block hash tree to the share.
4274+        """
4275+        assert isinstance(blockhashes, list)
4276+        for h in blockhashes:
4277+            assert len(h) == HASH_SIZE
4278+
4279+        # serialize the blockhashes, then set them.
4280+        blockhashes_s = "".join(blockhashes)
4281+        self._share_pieces['block_hash_tree'] = blockhashes_s
4282+
4283+        return defer.succeed(None)
4284+
4285+
4286+    def put_sharehashes(self, sharehashes):
4287+        """
4288+        Add the share hash chain to the share.
4289+        """
4290+        assert isinstance(sharehashes, dict)
4291+        for h in sharehashes.itervalues():
4292+            assert len(h) == HASH_SIZE
4293+
4294+        # serialize the sharehashes, then set them.
4295+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4296+                                 for i in sorted(sharehashes.keys())])
4297+        self._share_pieces['share_hash_chain'] = sharehashes_s
4298+
4299+        return defer.succeed(None)
4300+
4301+
4302+    def put_root_hash(self, root_hash):
4303+        """
4304+        Add the root hash to the share.
4305+        """
4306+        assert len(root_hash) == HASH_SIZE
4307+
4308+        self._share_pieces['root_hash'] = root_hash
4309+
4310+        return defer.succeed(None)
4311+
4312+
4313+    def put_salt(self, salt):
4314+        """
4315+        Add a salt to an empty SDMF file.
4316+        """
4317+        assert len(salt) == SALT_SIZE
4318+
4319+        self._share_pieces['salt'] = salt
4320+        self._share_pieces['sharedata'] = ""
4321+
4322+
4323+    def get_signable(self):
4324+        """
4325+        Return the part of the share that needs to be signed.
4326+
4327+        SDMF writers need to sign the packed representation of the
4328+        first eight fields of the remote share, that is:
4329+            - version number (0)
4330+            - sequence number
4331+            - root of the share hash tree
4332+            - salt
4333+            - k
4334+            - n
4335+            - segsize
4336+            - datalen
4337+
4338+        This method is responsible for returning that to callers.
4339+        """
4340+        return struct.pack(SIGNED_PREFIX,
4341+                           0,
4342+                           self._seqnum,
4343+                           self._share_pieces['root_hash'],
4344+                           self._share_pieces['salt'],
4345+                           self._required_shares,
4346+                           self._total_shares,
4347+                           self._segment_size,
4348+                           self._data_length)
4349+
4350+
4351+    def put_signature(self, signature):
4352+        """
4353+        Add the signature to the share.
4354+        """
4355+        self._share_pieces['signature'] = signature
4356+
4357+        return defer.succeed(None)
4358+
4359+
4360+    def put_verification_key(self, verification_key):
4361+        """
4362+        Add the verification key to the share.
4363+        """
4364+        self._share_pieces['verification_key'] = verification_key
4365+
4366+        return defer.succeed(None)
4367+
4368+
4369+    def get_verinfo(self):
4370+        """
4371+        I return my verinfo tuple. This is used by the ServermapUpdater
4372+        to keep track of versions of mutable files.
4373+
4374+        The verinfo tuple for MDMF files contains:
4375+            - seqnum
4376+            - root hash
4377+            - a blank (nothing)
4378+            - segsize
4379+            - datalen
4380+            - k
4381+            - n
4382+            - prefix (the thing that you sign)
4383+            - a tuple of offsets
4384+
4385+        We include the nonce in MDMF to simplify processing of version
4386+        information tuples.
4387+
4388+        The verinfo tuple for SDMF files is the same, but contains a
4389+        16-byte IV instead of a hash of salts.
4390+        """
4391+        return (self._seqnum,
4392+                self._share_pieces['root_hash'],
4393+                self._share_pieces['salt'],
4394+                self._segment_size,
4395+                self._data_length,
4396+                self._required_shares,
4397+                self._total_shares,
4398+                self.get_signable(),
4399+                self._get_offsets_tuple())
4400+
4401+    def _get_offsets_dict(self):
4402+        post_offset = HEADER_LENGTH
4403+        offsets = {}
4404+
4405+        verification_key_length = len(self._share_pieces['verification_key'])
4406+        o1 = offsets['signature'] = post_offset + verification_key_length
4407+
4408+        signature_length = len(self._share_pieces['signature'])
4409+        o2 = offsets['share_hash_chain'] = o1 + signature_length
4410+
4411+        share_hash_chain_length = len(self._share_pieces['share_hash_chain'])
4412+        o3 = offsets['block_hash_tree'] = o2 + share_hash_chain_length
4413+
4414+        block_hash_tree_length = len(self._share_pieces['block_hash_tree'])
4415+        o4 = offsets['share_data'] = o3 + block_hash_tree_length
4416+
4417+        share_data_length = len(self._share_pieces['sharedata'])
4418+        o5 = offsets['enc_privkey'] = o4 + share_data_length
4419+
4420+        encprivkey_length = len(self._share_pieces['encprivkey'])
4421+        offsets['EOF'] = o5 + encprivkey_length
4422+        return offsets
4423+
4424+
4425+    def _get_offsets_tuple(self):
4426+        offsets = self._get_offsets_dict()
4427+        return tuple([(key, value) for key, value in offsets.items()])
4428+
4429+
4430+    def _pack_offsets(self):
4431+        offsets = self._get_offsets_dict()
4432+        return struct.pack(">LLLLQQ",
4433+                           offsets['signature'],
4434+                           offsets['share_hash_chain'],
4435+                           offsets['block_hash_tree'],
4436+                           offsets['share_data'],
4437+                           offsets['enc_privkey'],
4438+                           offsets['EOF'])
4439+
4440+
4441+    def finish_publishing(self):
4442+        """
4443+        Do anything necessary to finish writing the share to a remote
4444+        server. I require that no further publishing needs to take place
4445+        after this method has been called.
4446+        """
4447+        for k in ["sharedata", "encprivkey", "signature", "verification_key",
4448+                  "share_hash_chain", "block_hash_tree"]:
4449+            assert k in self._share_pieces
4450+        # This is the only method that actually writes something to the
4451+        # remote server.
4452+        # First, we need to pack the share into data that we can write
4453+        # to the remote server in one write.
4454+        offsets = self._pack_offsets()
4455+        prefix = self.get_signable()
4456+        final_share = "".join([prefix,
4457+                               offsets,
4458+                               self._share_pieces['verification_key'],
4459+                               self._share_pieces['signature'],
4460+                               self._share_pieces['share_hash_chain'],
4461+                               self._share_pieces['block_hash_tree'],
4462+                               self._share_pieces['sharedata'],
4463+                               self._share_pieces['encprivkey']])
4464+
4465+        # Our only data vector is going to be writing the final share,
4466+        # in its entirely.
4467+        datavs = [(0, final_share)]
4468+
4469+        if not self._testvs:
4470+            # Our caller has not provided us with another checkstring
4471+            # yet, so we assume that we are writing a new share, and set
4472+            # a test vector that will allow a new share to be written.
4473+            self._testvs = []
4474+            self._testvs.append(tuple([0, 1, "eq", ""]))
4475+            new_share = True
4476+
4477+        tw_vectors = {}
4478+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
4479+        return self._rref.callRemote("slot_testv_and_readv_and_writev",
4480+                                     self._storage_index,
4481+                                     self._secrets,
4482+                                     tw_vectors,
4483+                                     # TODO is it useful to read something?
4484+                                     self._readvs)
4485+
4486+
4487+MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
4488+MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
4489+MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
4490+MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
4491+MDMFCHECKSTRING = ">BQ32s"
4492+MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
4493+MDMFOFFSETS = ">QQQQQQ"
4494+MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
4495+
4496+class MDMFSlotWriteProxy:
4497+    implements(IMutableSlotWriter)
4498+
4499+    """
4500+    I represent a remote write slot for an MDMF mutable file.
4501+
4502+    I abstract away from my caller the details of block and salt
4503+    management, and the implementation of the on-disk format for MDMF
4504+    shares.
4505+    """
4506+    # Expected layout, MDMF:
4507+    # offset:     size:       name:
4508+    #-- signed part --
4509+    # 0           1           version number (01)
4510+    # 1           8           sequence number
4511+    # 9           32          share tree root hash
4512+    # 41          1           The "k" encoding parameter
4513+    # 42          1           The "N" encoding parameter
4514+    # 43          8           The segment size of the uploaded file
4515+    # 51          8           The data length of the original plaintext
4516+    #-- end signed part --
4517+    # 59          8           The offset of the encrypted private key
4518+    # 67          8           The offset of the block hash tree
4519+    # 75          8           The offset of the share hash chain
4520+    # 83          8           The offset of the signature
4521+    # 91          8           The offset of the verification key
4522+    # 99          8           The offset of the EOF
4523+    #
4524+    # followed by salts and share data, the encrypted private key, the
4525+    # block hash tree, the salt hash tree, the share hash chain, a
4526+    # signature over the first eight fields, and a verification key.
4527+    #
4528+    # The checkstring is the first three fields -- the version number,
4529+    # sequence number, root hash and root salt hash. This is consistent
4530+    # in meaning to what we have with SDMF files, except now instead of
4531+    # using the literal salt, we use a value derived from all of the
4532+    # salts -- the share hash root.
4533+    #
4534+    # The salt is stored before the block for each segment. The block
4535+    # hash tree is computed over the combination of block and salt for
4536+    # each segment. In this way, we get integrity checking for both
4537+    # block and salt with the current block hash tree arrangement.
4538+    #
4539+    # The ordering of the offsets is different to reflect the dependencies
4540+    # that we'll run into with an MDMF file. The expected write flow is
4541+    # something like this:
4542+    #
4543+    #   0: Initialize with the sequence number, encoding parameters and
4544+    #      data length. From this, we can deduce the number of segments,
4545+    #      and where they should go.. We can also figure out where the
4546+    #      encrypted private key should go, because we can figure out how
4547+    #      big the share data will be.
4548+    #
4549+    #   1: Encrypt, encode, and upload the file in chunks. Do something
4550+    #      like
4551+    #
4552+    #       put_block(data, segnum, salt)
4553+    #
4554+    #      to write a block and a salt to the disk. We can do both of
4555+    #      these operations now because we have enough of the offsets to
4556+    #      know where to put them.
4557+    #
4558+    #   2: Put the encrypted private key. Use:
4559+    #
4560+    #        put_encprivkey(encprivkey)
4561+    #
4562+    #      Now that we know the length of the private key, we can fill
4563+    #      in the offset for the block hash tree.
4564+    #
4565+    #   3: We're now in a position to upload the block hash tree for
4566+    #      a share. Put that using something like:
4567+    #       
4568+    #        put_blockhashes(block_hash_tree)
4569+    #
4570+    #      Note that block_hash_tree is a list of hashes -- we'll take
4571+    #      care of the details of serializing that appropriately. When
4572+    #      we get the block hash tree, we are also in a position to
4573+    #      calculate the offset for the share hash chain, and fill that
4574+    #      into the offsets table.
4575+    #
4576+    #   4: At the same time, we're in a position to upload the salt hash
4577+    #      tree. This is a Merkle tree over all of the salts. We use a
4578+    #      Merkle tree so that we can validate each block,salt pair as
4579+    #      we download them later. We do this using
4580+    #
4581+    #        put_salthashes(salt_hash_tree)
4582+    #
4583+    #      When you do this, I automatically put the root of the tree
4584+    #      (the hash at index 0 of the list) in its appropriate slot in
4585+    #      the signed prefix of the share.
4586+    #
4587+    #   5: We're now in a position to upload the share hash chain for
4588+    #      a share. Do that with something like:
4589+    #     
4590+    #        put_sharehashes(share_hash_chain)
4591+    #
4592+    #      share_hash_chain should be a dictionary mapping shnums to
4593+    #      32-byte hashes -- the wrapper handles serialization.
4594+    #      We'll know where to put the signature at this point, also.
4595+    #      The root of this tree will be put explicitly in the next
4596+    #      step.
4597+    #
4598+    #      TODO: Why? Why not just include it in the tree here?
4599+    #
4600+    #   6: Before putting the signature, we must first put the
4601+    #      root_hash. Do this with:
4602+    #
4603+    #        put_root_hash(root_hash).
4604+    #     
4605+    #      In terms of knowing where to put this value, it was always
4606+    #      possible to place it, but it makes sense semantically to
4607+    #      place it after the share hash tree, so that's why you do it
4608+    #      in this order.
4609+    #
4610+    #   6: With the root hash put, we can now sign the header. Use:
4611+    #
4612+    #        get_signable()
4613+    #
4614+    #      to get the part of the header that you want to sign, and use:
4615+    #       
4616+    #        put_signature(signature)
4617+    #
4618+    #      to write your signature to the remote server.
4619+    #
4620+    #   6: Add the verification key, and finish. Do:
4621+    #
4622+    #        put_verification_key(key)
4623+    #
4624+    #      and
4625+    #
4626+    #        finish_publish()
4627+    #
4628+    # Checkstring management:
4629+    #
4630+    # To write to a mutable slot, we have to provide test vectors to ensure
4631+    # that we are writing to the same data that we think we are. These
4632+    # vectors allow us to detect uncoordinated writes; that is, writes
4633+    # where both we and some other shareholder are writing to the
4634+    # mutable slot, and to report those back to the parts of the program
4635+    # doing the writing.
4636+    #
4637+    # With SDMF, this was easy -- all of the share data was written in
4638+    # one go, so it was easy to detect uncoordinated writes, and we only
4639+    # had to do it once. With MDMF, not all of the file is written at
4640+    # once.
4641+    #
4642+    # If a share is new, we write out as much of the header as we can
4643+    # before writing out anything else. This gives other writers a
4644+    # canary that they can use to detect uncoordinated writes, and, if
4645+    # they do the same thing, gives us the same canary. We them update
4646+    # the share. We won't be able to write out two fields of the header
4647+    # -- the share tree hash and the salt hash -- until we finish
4648+    # writing out the share. We only require the writer to provide the
4649+    # initial checkstring, and keep track of what it should be after
4650+    # updates ourselves.
4651+    #
4652+    # If we haven't written anything yet, then on the first write (which
4653+    # will probably be a block + salt of a share), we'll also write out
4654+    # the header. On subsequent passes, we'll expect to see the header.
4655+    # This changes in two places:
4656+    #
4657+    #   - When we write out the salt hash
4658+    #   - When we write out the root of the share hash tree
4659+    #
4660+    # since these values will change the header. It is possible that we
4661+    # can just make those be written in one operation to minimize
4662+    # disruption.
4663+    def __init__(self,
4664+                 shnum,
4665+                 rref, # a remote reference to a storage server
4666+                 storage_index,
4667+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4668+                 seqnum, # the sequence number of the mutable file
4669+                 required_shares,
4670+                 total_shares,
4671+                 segment_size,
4672+                 data_length): # the length of the original file
4673+        self.shnum = shnum
4674+        self._rref = rref
4675+        self._storage_index = storage_index
4676+        self._seqnum = seqnum
4677+        self._required_shares = required_shares
4678+        assert self.shnum >= 0 and self.shnum < total_shares
4679+        self._total_shares = total_shares
4680+        # We build up the offset table as we write things. It is the
4681+        # last thing we write to the remote server.
4682+        self._offsets = {}
4683+        self._testvs = []
4684+        self._secrets = secrets
4685+        # The segment size needs to be a multiple of the k parameter --
4686+        # any padding should have been carried out by the publisher
4687+        # already.
4688+        assert segment_size % required_shares == 0
4689+        self._segment_size = segment_size
4690+        self._data_length = data_length
4691+
4692+        # These are set later -- we define them here so that we can
4693+        # check for their existence easily
4694+
4695+        # This is the root of the share hash tree -- the Merkle tree
4696+        # over the roots of the block hash trees computed for shares in
4697+        # this upload.
4698+        self._root_hash = None
4699+
4700+        # We haven't yet written anything to the remote bucket. By
4701+        # setting this, we tell the _write method as much. The write
4702+        # method will then know that it also needs to add a write vector
4703+        # for the checkstring (or what we have of it) to the first write
4704+        # request. We'll then record that value for future use.  If
4705+        # we're expecting something to be there already, we need to call
4706+        # set_checkstring before we write anything to tell the first
4707+        # write about that.
4708+        self._written = False
4709+
4710+        # When writing data to the storage servers, we get a read vector
4711+        # for free. We'll read the checkstring, which will help us
4712+        # figure out what's gone wrong if a write fails.
4713+        self._readv = [(0, struct.calcsize(MDMFCHECKSTRING))]
4714+
4715+        # We calculate the number of segments because it tells us
4716+        # where the salt part of the file ends/share segment begins,
4717+        # and also because it provides a useful amount of bounds checking.
4718+        self._num_segments = mathutil.div_ceil(self._data_length,
4719+                                               self._segment_size)
4720+        self._block_size = self._segment_size / self._required_shares
4721+        # We also calculate the share size, to help us with block
4722+        # constraints later.
4723+        tail_size = self._data_length % self._segment_size
4724+        if not tail_size:
4725+            self._tail_block_size = self._block_size
4726+        else:
4727+            self._tail_block_size = mathutil.next_multiple(tail_size,
4728+                                                           self._required_shares)
4729+            self._tail_block_size /= self._required_shares
4730+
4731+        # We already know where the sharedata starts; right after the end
4732+        # of the header (which is defined as the signable part + the offsets)
4733+        # We can also calculate where the encrypted private key begins
4734+        # from what we know know.
4735+        self._actual_block_size = self._block_size + SALT_SIZE
4736+        data_size = self._actual_block_size * (self._num_segments - 1)
4737+        data_size += self._tail_block_size
4738+        data_size += SALT_SIZE
4739+        self._offsets['enc_privkey'] = MDMFHEADERSIZE
4740+        self._offsets['enc_privkey'] += data_size
4741+        # We'll wait for the rest. Callers can now call my "put_block" and
4742+        # "set_checkstring" methods.
4743+
4744+
4745+    def set_checkstring(self,
4746+                        seqnum_or_checkstring,
4747+                        root_hash=None,
4748+                        salt=None):
4749+        """
4750+        Set checkstring checkstring for the given shnum.
4751+
4752+        This can be invoked in one of two ways.
4753+
4754+        With one argument, I assume that you are giving me a literal
4755+        checkstring -- e.g., the output of get_checkstring. I will then
4756+        set that checkstring as it is. This form is used by unit tests.
4757+
4758+        With two arguments, I assume that you are giving me a sequence
4759+        number and root hash to make a checkstring from. In that case, I
4760+        will build a checkstring and set it for you. This form is used
4761+        by the publisher.
4762+
4763+        By default, I assume that I am writing new shares to the grid.
4764+        If you don't explcitly set your own checkstring, I will use
4765+        one that requires that the remote share not exist. You will want
4766+        to use this method if you are updating a share in-place;
4767+        otherwise, writes will fail.
4768+        """
4769+        # You're allowed to overwrite checkstrings with this method;
4770+        # I assume that users know what they are doing when they call
4771+        # it.
4772+        if root_hash:
4773+            checkstring = struct.pack(MDMFCHECKSTRING,
4774+                                      1,
4775+                                      seqnum_or_checkstring,
4776+                                      root_hash)
4777+        else:
4778+            checkstring = seqnum_or_checkstring
4779+
4780+        if checkstring == "":
4781+            # We special-case this, since len("") = 0, but we need
4782+            # length of 1 for the case of an empty share to work on the
4783+            # storage server, which is what a checkstring that is the
4784+            # empty string means.
4785+            self._testvs = []
4786+        else:
4787+            self._testvs = []
4788+            self._testvs.append((0, len(checkstring), "eq", checkstring))
4789+
4790+
4791+    def __repr__(self):
4792+        return "MDMFSlotWriteProxy for share %d" % self.shnum
4793+
4794+
4795+    def get_checkstring(self):
4796+        """
4797+        Given a share number, I return a representation of what the
4798+        checkstring for that share on the server will look like.
4799+
4800+        I am mostly used for tests.
4801+        """
4802+        if self._root_hash:
4803+            roothash = self._root_hash
4804+        else:
4805+            roothash = "\x00" * 32
4806+        return struct.pack(MDMFCHECKSTRING,
4807+                           1,
4808+                           self._seqnum,
4809+                           roothash)
4810+
4811+
4812+    def put_block(self, data, segnum, salt):
4813+        """
4814+        Put the encrypted-and-encoded data segment in the slot, along
4815+        with the salt.
4816+        """
4817+        if segnum >= self._num_segments:
4818+            raise LayoutInvalid("I won't overwrite the private key")
4819+        if len(salt) != SALT_SIZE:
4820+            raise LayoutInvalid("I was given a salt of size %d, but "
4821+                                "I wanted a salt of size %d")
4822+        if segnum + 1 == self._num_segments:
4823+            if len(data) != self._tail_block_size:
4824+                raise LayoutInvalid("I was given the wrong size block to write")
4825+        elif len(data) != self._block_size:
4826+            raise LayoutInvalid("I was given the wrong size block to write")
4827+
4828+        # We want to write at len(MDMFHEADER) + segnum * block_size.
4829+
4830+        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
4831+        data = salt + data
4832+
4833+        datavs = [tuple([offset, data])]
4834+        return self._write(datavs)
4835+
4836+
4837+    def put_encprivkey(self, encprivkey):
4838+        """
4839+        Put the encrypted private key in the remote slot.
4840+        """
4841+        assert self._offsets
4842+        assert self._offsets['enc_privkey']
4843+        # You shouldn't re-write the encprivkey after the block hash
4844+        # tree is written, since that could cause the private key to run
4845+        # into the block hash tree. Before it writes the block hash
4846+        # tree, the block hash tree writing method writes the offset of
4847+        # the salt hash tree. So that's a good indicator of whether or
4848+        # not the block hash tree has been written.
4849+        if "share_hash_chain" in self._offsets:
4850+            raise LayoutInvalid("You must write this before the block hash tree")
4851+
4852+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
4853+        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
4854+        def _on_failure():
4855+            del(self._offsets['block_hash_tree'])
4856+        return self._write(datavs, on_failure=_on_failure)
4857+
4858+
4859+    def put_blockhashes(self, blockhashes):
4860+        """
4861+        Put the block hash tree in the remote slot.
4862+
4863+        The encrypted private key must be put before the block hash
4864+        tree, since we need to know how large it is to know where the
4865+        block hash tree should go. The block hash tree must be put
4866+        before the salt hash tree, since its size determines the
4867+        offset of the share hash chain.
4868+        """
4869+        assert self._offsets
4870+        assert isinstance(blockhashes, list)
4871+        if "block_hash_tree" not in self._offsets:
4872+            raise LayoutInvalid("You must put the encrypted private key "
4873+                                "before you put the block hash tree")
4874+        # If written, the share hash chain causes the signature offset
4875+        # to be defined.
4876+        if "signature" in self._offsets:
4877+            raise LayoutInvalid("You must put the block hash tree before "
4878+                                "you put the share hash chain")
4879+        blockhashes_s = "".join(blockhashes)
4880+        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
4881+        datavs = []
4882+        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
4883+        def _on_failure():
4884+            del(self._offsets['share_hash_chain'])
4885+        return self._write(datavs, on_failure=_on_failure)
4886+
4887+
4888+    def put_sharehashes(self, sharehashes):
4889+        """
4890+        Put the share hash chain in the remote slot.
4891+
4892+        The salt hash tree must be put before the share hash chain,
4893+        since we need to know where the salt hash tree ends before we
4894+        can know where the share hash chain starts. The share hash chain
4895+        must be put before the signature, since the length of the packed
4896+        share hash chain determines the offset of the signature. Also,
4897+        semantically, you must know what the root of the salt hash tree
4898+        is before you can generate a valid signature.
4899+        """
4900+        assert isinstance(sharehashes, dict)
4901+        if "share_hash_chain" not in self._offsets:
4902+            raise LayoutInvalid("You need to put the salt hash tree before "
4903+                                "you can put the share hash chain")
4904+        # The signature comes after the share hash chain. If the
4905+        # signature has already been written, we must not write another
4906+        # share hash chain. The signature writes the verification key
4907+        # offset when it gets sent to the remote server, so we look for
4908+        # that.
4909+        if "verification_key" in self._offsets:
4910+            raise LayoutInvalid("You must write the share hash chain "
4911+                                "before you write the signature")
4912+        datavs = []
4913+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4914+                                  for i in sorted(sharehashes.keys())])
4915+        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
4916+        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
4917+        def _on_failure():
4918+            del(self._offsets['signature'])
4919+        return self._write(datavs, on_failure=_on_failure)
4920+
4921+
4922+    def put_root_hash(self, roothash):
4923+        """
4924+        Put the root hash (the root of the share hash tree) in the
4925+        remote slot.
4926+        """
4927+        # It does not make sense to be able to put the root
4928+        # hash without first putting the share hashes, since you need
4929+        # the share hashes to generate the root hash.
4930+        #
4931+        # Signature is defined by the routine that places the share hash
4932+        # chain, so it's a good thing to look for in finding out whether
4933+        # or not the share hash chain exists on the remote server.
4934+        if "signature" not in self._offsets:
4935+            raise LayoutInvalid("You need to put the share hash chain "
4936+                                "before you can put the root share hash")
4937+        if len(roothash) != HASH_SIZE:
4938+            raise LayoutInvalid("hashes and salts must be exactly %d bytes"
4939+                                 % HASH_SIZE)
4940+        datavs = []
4941+        self._root_hash = roothash
4942+        # To write both of these values, we update the checkstring on
4943+        # the remote server, which includes them
4944+        checkstring = self.get_checkstring()
4945+        datavs.append(tuple([0, checkstring]))
4946+        # This write, if successful, changes the checkstring, so we need
4947+        # to update our internal checkstring to be consistent with the
4948+        # one on the server.
4949+        def _on_success():
4950+            self._testvs = [(0, len(checkstring), "eq", checkstring)]
4951+        def _on_failure():
4952+            self._root_hash = None
4953+        return self._write(datavs,
4954+                           on_success=_on_success,
4955+                           on_failure=_on_failure)
4956+
4957+
4958+    def get_signable(self):
4959+        """
4960+        Get the first seven fields of the mutable file; the parts that
4961+        are signed.
4962+        """
4963+        if not self._root_hash:
4964+            raise LayoutInvalid("You need to set the root hash "
4965+                                "before getting something to "
4966+                                "sign")
4967+        return struct.pack(MDMFSIGNABLEHEADER,
4968+                           1,
4969+                           self._seqnum,
4970+                           self._root_hash,
4971+                           self._required_shares,
4972+                           self._total_shares,
4973+                           self._segment_size,
4974+                           self._data_length)
4975+
4976+
4977+    def put_signature(self, signature):
4978+        """
4979+        Put the signature field to the remote slot.
4980+
4981+        I require that the root hash and share hash chain have been put
4982+        to the grid before I will write the signature to the grid.
4983+        """
4984+        if "signature" not in self._offsets:
4985+            raise LayoutInvalid("You must put the share hash chain "
4986+        # It does not make sense to put a signature without first
4987+        # putting the root hash and the salt hash (since otherwise
4988+        # the signature would be incomplete), so we don't allow that.
4989+                       "before putting the signature")
4990+        if not self._root_hash:
4991+            raise LayoutInvalid("You must complete the signed prefix "
4992+                                "before computing a signature")
4993+        # If we put the signature after we put the verification key, we
4994+        # could end up running into the verification key, and will
4995+        # probably screw up the offsets as well. So we don't allow that.
4996+        # The method that writes the verification key defines the EOF
4997+        # offset before writing the verification key, so look for that.
4998+        if "EOF" in self._offsets:
4999+            raise LayoutInvalid("You must write the signature before the verification key")
5000+
5001+        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
5002+        datavs = []
5003+        datavs.append(tuple([self._offsets['signature'], signature]))
5004+        def _on_failure():
5005+            del(self._offsets['verification_key'])
5006+        return self._write(datavs, on_failure=_on_failure)
5007+
5008+
5009+    def put_verification_key(self, verification_key):
5010+        """
5011+        Put the verification key into the remote slot.
5012+
5013+        I require that the signature have been written to the storage
5014+        server before I allow the verification key to be written to the
5015+        remote server.
5016+        """
5017+        if "verification_key" not in self._offsets:
5018+            raise LayoutInvalid("You must put the signature before you "
5019+                                "can put the verification key")
5020+        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
5021+        datavs = []
5022+        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
5023+        def _on_failure():
5024+            del(self._offsets['EOF'])
5025+        return self._write(datavs, on_failure=_on_failure)
5026+
5027+    def _get_offsets_tuple(self):
5028+        return tuple([(key, value) for key, value in self._offsets.items()])
5029+
5030+    def get_verinfo(self):
5031+        return (self._seqnum,
5032+                self._root_hash,
5033+                self._required_shares,
5034+                self._total_shares,
5035+                self._segment_size,
5036+                self._data_length,
5037+                self.get_signable(),
5038+                self._get_offsets_tuple())
5039+
5040+
5041+    def finish_publishing(self):
5042+        """
5043+        Write the offset table and encoding parameters to the remote
5044+        slot, since that's the only thing we have yet to publish at this
5045+        point.
5046+        """
5047+        if "EOF" not in self._offsets:
5048+            raise LayoutInvalid("You must put the verification key before "
5049+                                "you can publish the offsets")
5050+        offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
5051+        offsets = struct.pack(MDMFOFFSETS,
5052+                              self._offsets['enc_privkey'],
5053+                              self._offsets['block_hash_tree'],
5054+                              self._offsets['share_hash_chain'],
5055+                              self._offsets['signature'],
5056+                              self._offsets['verification_key'],
5057+                              self._offsets['EOF'])
5058+        datavs = []
5059+        datavs.append(tuple([offsets_offset, offsets]))
5060+        encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
5061+        params = struct.pack(">BBQQ",
5062+                             self._required_shares,
5063+                             self._total_shares,
5064+                             self._segment_size,
5065+                             self._data_length)
5066+        datavs.append(tuple([encoding_parameters_offset, params]))
5067+        return self._write(datavs)
5068+
5069+
5070+    def _write(self, datavs, on_failure=None, on_success=None):
5071+        """I write the data vectors in datavs to the remote slot."""
5072+        tw_vectors = {}
5073+        new_share = False
5074+        if not self._testvs:
5075+            self._testvs = []
5076+            self._testvs.append(tuple([0, 1, "eq", ""]))
5077+            new_share = True
5078+        if not self._written:
5079+            # Write a new checkstring to the share when we write it, so
5080+            # that we have something to check later.
5081+            new_checkstring = self.get_checkstring()
5082+            datavs.append((0, new_checkstring))
5083+            def _first_write():
5084+                self._written = True
5085+                self._testvs = [(0, len(new_checkstring), "eq", new_checkstring)]
5086+            on_success = _first_write
5087+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
5088+        datalength = sum([len(x[1]) for x in datavs])
5089+        d = self._rref.callRemote("slot_testv_and_readv_and_writev",
5090+                                  self._storage_index,
5091+                                  self._secrets,
5092+                                  tw_vectors,
5093+                                  self._readv)
5094+        def _result(results):
5095+            if isinstance(results, failure.Failure) or not results[0]:
5096+                # Do nothing; the write was unsuccessful.
5097+                if on_failure: on_failure()
5098+            else:
5099+                if on_success: on_success()
5100+            return results
5101+        d.addCallback(_result)
5102+        return d
5103+
5104+
5105+class MDMFSlotReadProxy:
5106+    """
5107+    I read from a mutable slot filled with data written in the MDMF data
5108+    format (which is described above).
5109+
5110+    I can be initialized with some amount of data, which I will use (if
5111+    it is valid) to eliminate some of the need to fetch it from servers.
5112+    """
5113+    def __init__(self,
5114+                 rref,
5115+                 storage_index,
5116+                 shnum,
5117+                 data=""):
5118+        # Start the initialization process.
5119+        self._rref = rref
5120+        self._storage_index = storage_index
5121+        self.shnum = shnum
5122+
5123+        # Before doing anything, the reader is probably going to want to
5124+        # verify that the signature is correct. To do that, they'll need
5125+        # the verification key, and the signature. To get those, we'll
5126+        # need the offset table. So fetch the offset table on the
5127+        # assumption that that will be the first thing that a reader is
5128+        # going to do.
5129+
5130+        # The fact that these encoding parameters are None tells us
5131+        # that we haven't yet fetched them from the remote share, so we
5132+        # should. We could just not set them, but the checks will be
5133+        # easier to read if we don't have to use hasattr.
5134+        self._version_number = None
5135+        self._sequence_number = None
5136+        self._root_hash = None
5137+        # Filled in if we're dealing with an SDMF file. Unused
5138+        # otherwise.
5139+        self._salt = None
5140+        self._required_shares = None
5141+        self._total_shares = None
5142+        self._segment_size = None
5143+        self._data_length = None
5144+        self._offsets = None
5145+
5146+        # If the user has chosen to initialize us with some data, we'll
5147+        # try to satisfy subsequent data requests with that data before
5148+        # asking the storage server for it. If
5149+        self._data = data
5150+        # The way callers interact with cache in the filenode returns
5151+        # None if there isn't any cached data, but the way we index the
5152+        # cached data requires a string, so convert None to "".
5153+        if self._data == None:
5154+            self._data = ""
5155+
5156+        self._queue_observers = observer.ObserverList()
5157+        self._queue_errbacks = observer.ObserverList()
5158+        self._readvs = []
5159+
5160+
5161+    def _maybe_fetch_offsets_and_header(self, force_remote=False):
5162+        """
5163+        I fetch the offset table and the header from the remote slot if
5164+        I don't already have them. If I do have them, I do nothing and
5165+        return an empty Deferred.
5166+        """
5167+        if self._offsets:
5168+            return defer.succeed(None)
5169+        # At this point, we may be either SDMF or MDMF. Fetching 107
5170+        # bytes will be enough to get header and offsets for both SDMF and
5171+        # MDMF, though we'll be left with 4 more bytes than we
5172+        # need if this ends up being MDMF. This is probably less
5173+        # expensive than the cost of a second roundtrip.
5174+        readvs = [(0, 107)]
5175+        d = self._read(readvs, force_remote)
5176+        d.addCallback(self._process_encoding_parameters)
5177+        d.addCallback(self._process_offsets)
5178+        return d
5179+
5180+
5181+    def _process_encoding_parameters(self, encoding_parameters):
5182+        assert self.shnum in encoding_parameters
5183+        encoding_parameters = encoding_parameters[self.shnum][0]
5184+        # The first byte is the version number. It will tell us what
5185+        # to do next.
5186+        (verno,) = struct.unpack(">B", encoding_parameters[:1])
5187+        if verno == MDMF_VERSION:
5188+            read_size = MDMFHEADERWITHOUTOFFSETSSIZE
5189+            (verno,
5190+             seqnum,
5191+             root_hash,
5192+             k,
5193+             n,
5194+             segsize,
5195+             datalen) = struct.unpack(MDMFHEADERWITHOUTOFFSETS,
5196+                                      encoding_parameters[:read_size])
5197+            if segsize == 0 and datalen == 0:
5198+                # Empty file, no segments.
5199+                self._num_segments = 0
5200+            else:
5201+                self._num_segments = mathutil.div_ceil(datalen, segsize)
5202+
5203+        elif verno == SDMF_VERSION:
5204+            read_size = SIGNED_PREFIX_LENGTH
5205+            (verno,
5206+             seqnum,
5207+             root_hash,
5208+             salt,
5209+             k,
5210+             n,
5211+             segsize,
5212+             datalen) = struct.unpack(">BQ32s16s BBQQ",
5213+                                encoding_parameters[:SIGNED_PREFIX_LENGTH])
5214+            self._salt = salt
5215+            if segsize == 0 and datalen == 0:
5216+                # empty file
5217+                self._num_segments = 0
5218+            else:
5219+                # non-empty SDMF files have one segment.
5220+                self._num_segments = 1
5221+        else:
5222+            raise UnknownVersionError("You asked me to read mutable file "
5223+                                      "version %d, but I only understand "
5224+                                      "%d and %d" % (verno, SDMF_VERSION,
5225+                                                     MDMF_VERSION))
5226+
5227+        self._version_number = verno
5228+        self._sequence_number = seqnum
5229+        self._root_hash = root_hash
5230+        self._required_shares = k
5231+        self._total_shares = n
5232+        self._segment_size = segsize
5233+        self._data_length = datalen
5234+
5235+        self._block_size = self._segment_size / self._required_shares
5236+        # We can upload empty files, and need to account for this fact
5237+        # so as to avoid zero-division and zero-modulo errors.
5238+        if datalen > 0:
5239+            tail_size = self._data_length % self._segment_size
5240+        else:
5241+            tail_size = 0
5242+        if not tail_size:
5243+            self._tail_block_size = self._block_size
5244+        else:
5245+            self._tail_block_size = mathutil.next_multiple(tail_size,
5246+                                                    self._required_shares)
5247+            self._tail_block_size /= self._required_shares
5248+
5249+        return encoding_parameters
5250+
5251+
5252+    def _process_offsets(self, offsets):
5253+        if self._version_number == 0:
5254+            read_size = OFFSETS_LENGTH
5255+            read_offset = SIGNED_PREFIX_LENGTH
5256+            end = read_size + read_offset
5257+            (signature,
5258+             share_hash_chain,
5259+             block_hash_tree,
5260+             share_data,
5261+             enc_privkey,
5262+             EOF) = struct.unpack(">LLLLQQ",
5263+                                  offsets[read_offset:end])
5264+            self._offsets = {}
5265+            self._offsets['signature'] = signature
5266+            self._offsets['share_data'] = share_data
5267+            self._offsets['block_hash_tree'] = block_hash_tree
5268+            self._offsets['share_hash_chain'] = share_hash_chain
5269+            self._offsets['enc_privkey'] = enc_privkey
5270+            self._offsets['EOF'] = EOF
5271+
5272+        elif self._version_number == 1:
5273+            read_offset = MDMFHEADERWITHOUTOFFSETSSIZE
5274+            read_length = MDMFOFFSETS_LENGTH
5275+            end = read_offset + read_length
5276+            (encprivkey,
5277+             blockhashes,
5278+             sharehashes,
5279+             signature,
5280+             verification_key,
5281+             eof) = struct.unpack(MDMFOFFSETS,
5282+                                  offsets[read_offset:end])
5283+            self._offsets = {}
5284+            self._offsets['enc_privkey'] = encprivkey
5285+            self._offsets['block_hash_tree'] = blockhashes
5286+            self._offsets['share_hash_chain'] = sharehashes
5287+            self._offsets['signature'] = signature
5288+            self._offsets['verification_key'] = verification_key
5289+            self._offsets['EOF'] = eof
5290+
5291+
5292+    def get_block_and_salt(self, segnum, queue=False):
5293+        """
5294+        I return (block, salt), where block is the block data and
5295+        salt is the salt used to encrypt that segment.
5296+        """
5297+        d = self._maybe_fetch_offsets_and_header()
5298+        def _then(ignored):
5299+            if self._version_number == 1:
5300+                base_share_offset = MDMFHEADERSIZE
5301+            else:
5302+                base_share_offset = self._offsets['share_data']
5303+
5304+            if segnum + 1 > self._num_segments:
5305+                raise LayoutInvalid("Not a valid segment number")
5306+
5307+            if self._version_number == 0:
5308+                share_offset = base_share_offset + self._block_size * segnum
5309+            else:
5310+                share_offset = base_share_offset + (self._block_size + \
5311+                                                    SALT_SIZE) * segnum
5312+            if segnum + 1 == self._num_segments:
5313+                data = self._tail_block_size
5314+            else:
5315+                data = self._block_size
5316+
5317+            if self._version_number == 1:
5318+                data += SALT_SIZE
5319+
5320+            readvs = [(share_offset, data)]
5321+            return readvs
5322+        d.addCallback(_then)
5323+        d.addCallback(lambda readvs:
5324+            self._read(readvs, queue=queue))
5325+        def _process_results(results):
5326+            assert self.shnum in results
5327+            if self._version_number == 0:
5328+                # We only read the share data, but we know the salt from
5329+                # when we fetched the header
5330+                data = results[self.shnum]
5331+                if not data:
5332+                    data = ""
5333+                else:
5334+                    assert len(data) == 1
5335+                    data = data[0]
5336+                salt = self._salt
5337+            else:
5338+                data = results[self.shnum]
5339+                if not data:
5340+                    salt = data = ""
5341+                else:
5342+                    salt_and_data = results[self.shnum][0]
5343+                    salt = salt_and_data[:SALT_SIZE]
5344+                    data = salt_and_data[SALT_SIZE:]
5345+            return data, salt
5346+        d.addCallback(_process_results)
5347+        return d
5348+
5349+
5350+    def get_blockhashes(self, needed=None, queue=False, force_remote=False):
5351+        """
5352+        I return the block hash tree
5353+
5354+        I take an optional argument, needed, which is a set of indices
5355+        correspond to hashes that I should fetch. If this argument is
5356+        missing, I will fetch the entire block hash tree; otherwise, I
5357+        may attempt to fetch fewer hashes, based on what needed says
5358+        that I should do. Note that I may fetch as many hashes as I
5359+        want, so long as the set of hashes that I do fetch is a superset
5360+        of the ones that I am asked for, so callers should be prepared
5361+        to tolerate additional hashes.
5362+        """
5363+        # TODO: Return only the parts of the block hash tree necessary
5364+        # to validate the blocknum provided?
5365+        # This is a good idea, but it is hard to implement correctly. It
5366+        # is bad to fetch any one block hash more than once, so we
5367+        # probably just want to fetch the whole thing at once and then
5368+        # serve it.
5369+        if needed == set([]):
5370+            return defer.succeed([])
5371+        d = self._maybe_fetch_offsets_and_header()
5372+        def _then(ignored):
5373+            blockhashes_offset = self._offsets['block_hash_tree']
5374+            if self._version_number == 1:
5375+                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
5376+            else:
5377+                blockhashes_length = self._offsets['share_data'] - blockhashes_offset
5378+            readvs = [(blockhashes_offset, blockhashes_length)]
5379+            return readvs
5380+        d.addCallback(_then)
5381+        d.addCallback(lambda readvs:
5382+            self._read(readvs, queue=queue, force_remote=force_remote))
5383+        def _build_block_hash_tree(results):
5384+            assert self.shnum in results
5385+
5386+            rawhashes = results[self.shnum][0]
5387+            results = [rawhashes[i:i+HASH_SIZE]
5388+                       for i in range(0, len(rawhashes), HASH_SIZE)]
5389+            return results
5390+        d.addCallback(_build_block_hash_tree)
5391+        return d
5392+
5393+
5394+    def get_sharehashes(self, needed=None, queue=False, force_remote=False):
5395+        """
5396+        I return the part of the share hash chain placed to validate
5397+        this share.
5398+
5399+        I take an optional argument, needed. Needed is a set of indices
5400+        that correspond to the hashes that I should fetch. If needed is
5401+        not present, I will fetch and return the entire share hash
5402+        chain. Otherwise, I may fetch and return any part of the share
5403+        hash chain that is a superset of the part that I am asked to
5404+        fetch. Callers should be prepared to deal with more hashes than
5405+        they've asked for.
5406+        """
5407+        if needed == set([]):
5408+            return defer.succeed([])
5409+        d = self._maybe_fetch_offsets_and_header()
5410+
5411+        def _make_readvs(ignored):
5412+            sharehashes_offset = self._offsets['share_hash_chain']
5413+            if self._version_number == 0:
5414+                sharehashes_length = self._offsets['block_hash_tree'] - sharehashes_offset
5415+            else:
5416+                sharehashes_length = self._offsets['signature'] - sharehashes_offset
5417+            readvs = [(sharehashes_offset, sharehashes_length)]
5418+            return readvs
5419+        d.addCallback(_make_readvs)
5420+        d.addCallback(lambda readvs:
5421+            self._read(readvs, queue=queue, force_remote=force_remote))
5422+        def _build_share_hash_chain(results):
5423+            assert self.shnum in results
5424+
5425+            sharehashes = results[self.shnum][0]
5426+            results = [sharehashes[i:i+(HASH_SIZE + 2)]
5427+                       for i in range(0, len(sharehashes), HASH_SIZE + 2)]
5428+            results = dict([struct.unpack(">H32s", data)
5429+                            for data in results])
5430+            return results
5431+        d.addCallback(_build_share_hash_chain)
5432+        return d
5433+
5434+
5435+    def get_encprivkey(self, queue=False):
5436+        """
5437+        I return the encrypted private key.
5438+        """
5439+        d = self._maybe_fetch_offsets_and_header()
5440+
5441+        def _make_readvs(ignored):
5442+            privkey_offset = self._offsets['enc_privkey']
5443+            if self._version_number == 0:
5444+                privkey_length = self._offsets['EOF'] - privkey_offset
5445+            else:
5446+                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
5447+            readvs = [(privkey_offset, privkey_length)]
5448+            return readvs
5449+        d.addCallback(_make_readvs)
5450+        d.addCallback(lambda readvs:
5451+            self._read(readvs, queue=queue))
5452+        def _process_results(results):
5453+            assert self.shnum in results
5454+            privkey = results[self.shnum][0]
5455+            return privkey
5456+        d.addCallback(_process_results)
5457+        return d
5458+
5459+
5460+    def get_signature(self, queue=False):
5461+        """
5462+        I return the signature of my share.
5463+        """
5464+        d = self._maybe_fetch_offsets_and_header()
5465+
5466+        def _make_readvs(ignored):
5467+            signature_offset = self._offsets['signature']
5468+            if self._version_number == 1:
5469+                signature_length = self._offsets['verification_key'] - signature_offset
5470+            else:
5471+                signature_length = self._offsets['share_hash_chain'] - signature_offset
5472+            readvs = [(signature_offset, signature_length)]
5473+            return readvs
5474+        d.addCallback(_make_readvs)
5475+        d.addCallback(lambda readvs:
5476+            self._read(readvs, queue=queue))
5477+        def _process_results(results):
5478+            assert self.shnum in results
5479+            signature = results[self.shnum][0]
5480+            return signature
5481+        d.addCallback(_process_results)
5482+        return d
5483+
5484+
5485+    def get_verification_key(self, queue=False):
5486+        """
5487+        I return the verification key.
5488+        """
5489+        d = self._maybe_fetch_offsets_and_header()
5490+
5491+        def _make_readvs(ignored):
5492+            if self._version_number == 1:
5493+                vk_offset = self._offsets['verification_key']
5494+                vk_length = self._offsets['EOF'] - vk_offset
5495+            else:
5496+                vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5497+                vk_length = self._offsets['signature'] - vk_offset
5498+            readvs = [(vk_offset, vk_length)]
5499+            return readvs
5500+        d.addCallback(_make_readvs)
5501+        d.addCallback(lambda readvs:
5502+            self._read(readvs, queue=queue))
5503+        def _process_results(results):
5504+            assert self.shnum in results
5505+            verification_key = results[self.shnum][0]
5506+            return verification_key
5507+        d.addCallback(_process_results)
5508+        return d
5509+
5510+
5511+    def get_encoding_parameters(self):
5512+        """
5513+        I return (k, n, segsize, datalen)
5514+        """
5515+        d = self._maybe_fetch_offsets_and_header()
5516+        d.addCallback(lambda ignored:
5517+            (self._required_shares,
5518+             self._total_shares,
5519+             self._segment_size,
5520+             self._data_length))
5521+        return d
5522+
5523+
5524+    def get_seqnum(self):
5525+        """
5526+        I return the sequence number for this share.
5527+        """
5528+        d = self._maybe_fetch_offsets_and_header()
5529+        d.addCallback(lambda ignored:
5530+            self._sequence_number)
5531+        return d
5532+
5533+
5534+    def get_root_hash(self):
5535+        """
5536+        I return the root of the block hash tree
5537+        """
5538+        d = self._maybe_fetch_offsets_and_header()
5539+        d.addCallback(lambda ignored: self._root_hash)
5540+        return d
5541+
5542+
5543+    def get_checkstring(self):
5544+        """
5545+        I return the packed representation of the following:
5546+
5547+            - version number
5548+            - sequence number
5549+            - root hash
5550+            - salt hash
5551+
5552+        which my users use as a checkstring to detect other writers.
5553+        """
5554+        d = self._maybe_fetch_offsets_and_header()
5555+        def _build_checkstring(ignored):
5556+            if self._salt:
5557+                checkstring = strut.pack(PREFIX,
5558+                                         self._version_number,
5559+                                         self._sequence_number,
5560+                                         self._root_hash,
5561+                                         self._salt)
5562+            else:
5563+                checkstring = struct.pack(MDMFCHECKSTRING,
5564+                                          self._version_number,
5565+                                          self._sequence_number,
5566+                                          self._root_hash)
5567+
5568+            return checkstring
5569+        d.addCallback(_build_checkstring)
5570+        return d
5571+
5572+
5573+    def get_prefix(self, force_remote):
5574+        d = self._maybe_fetch_offsets_and_header(force_remote)
5575+        d.addCallback(lambda ignored:
5576+            self._build_prefix())
5577+        return d
5578+
5579+
5580+    def _build_prefix(self):
5581+        # The prefix is another name for the part of the remote share
5582+        # that gets signed. It consists of everything up to and
5583+        # including the datalength, packed by struct.
5584+        if self._version_number == SDMF_VERSION:
5585+            return struct.pack(SIGNED_PREFIX,
5586+                           self._version_number,
5587+                           self._sequence_number,
5588+                           self._root_hash,
5589+                           self._salt,
5590+                           self._required_shares,
5591+                           self._total_shares,
5592+                           self._segment_size,
5593+                           self._data_length)
5594+
5595+        else:
5596+            return struct.pack(MDMFSIGNABLEHEADER,
5597+                           self._version_number,
5598+                           self._sequence_number,
5599+                           self._root_hash,
5600+                           self._required_shares,
5601+                           self._total_shares,
5602+                           self._segment_size,
5603+                           self._data_length)
5604+
5605+
5606+    def _get_offsets_tuple(self):
5607+        # The offsets tuple is another component of the version
5608+        # information tuple. It is basically our offsets dictionary,
5609+        # itemized and in a tuple.
5610+        return self._offsets.copy()
5611+
5612+
5613+    def get_verinfo(self):
5614+        """
5615+        I return my verinfo tuple. This is used by the ServermapUpdater
5616+        to keep track of versions of mutable files.
5617+
5618+        The verinfo tuple for MDMF files contains:
5619+            - seqnum
5620+            - root hash
5621+            - a blank (nothing)
5622+            - segsize
5623+            - datalen
5624+            - k
5625+            - n
5626+            - prefix (the thing that you sign)
5627+            - a tuple of offsets
5628+
5629+        We include the nonce in MDMF to simplify processing of version
5630+        information tuples.
5631+
5632+        The verinfo tuple for SDMF files is the same, but contains a
5633+        16-byte IV instead of a hash of salts.
5634+        """
5635+        d = self._maybe_fetch_offsets_and_header()
5636+        def _build_verinfo(ignored):
5637+            if self._version_number == SDMF_VERSION:
5638+                salt_to_use = self._salt
5639+            else:
5640+                salt_to_use = None
5641+            return (self._sequence_number,
5642+                    self._root_hash,
5643+                    salt_to_use,
5644+                    self._segment_size,
5645+                    self._data_length,
5646+                    self._required_shares,
5647+                    self._total_shares,
5648+                    self._build_prefix(),
5649+                    self._get_offsets_tuple())
5650+        d.addCallback(_build_verinfo)
5651+        return d
5652+
5653+
5654+    def flush(self):
5655+        """
5656+        I flush my queue of read vectors.
5657+        """
5658+        d = self._read(self._readvs)
5659+        def _then(results):
5660+            self._readvs = []
5661+            if isinstance(results, failure.Failure):
5662+                self._queue_errbacks.notify(results)
5663+            else:
5664+                self._queue_observers.notify(results)
5665+            self._queue_observers = observer.ObserverList()
5666+            self._queue_errbacks = observer.ObserverList()
5667+        d.addBoth(_then)
5668+
5669+
5670+    def _read(self, readvs, force_remote=False, queue=False):
5671+        unsatisfiable = filter(lambda x: x[0] + x[1] > len(self._data), readvs)
5672+        # TODO: It's entirely possible to tweak this so that it just
5673+        # fulfills the requests that it can, and not demand that all
5674+        # requests are satisfiable before running it.
5675+        if not unsatisfiable and not force_remote:
5676+            results = [self._data[offset:offset+length]
5677+                       for (offset, length) in readvs]
5678+            results = {self.shnum: results}
5679+            return defer.succeed(results)
5680+        else:
5681+            if queue:
5682+                start = len(self._readvs)
5683+                self._readvs += readvs
5684+                end = len(self._readvs)
5685+                def _get_results(results, start, end):
5686+                    if not self.shnum in results:
5687+                        return {self._shnum: [""]}
5688+                    return {self.shnum: results[self.shnum][start:end]}
5689+                d = defer.Deferred()
5690+                d.addCallback(_get_results, start, end)
5691+                self._queue_observers.subscribe(d.callback)
5692+                self._queue_errbacks.subscribe(d.errback)
5693+                return d
5694+            return self._rref.callRemote("slot_readv",
5695+                                         self._storage_index,
5696+                                         [self.shnum],
5697+                                         readvs)
5698+
5699+
5700+    def is_sdmf(self):
5701+        """I tell my caller whether or not my remote file is SDMF or MDMF
5702+        """
5703+        d = self._maybe_fetch_offsets_and_header()
5704+        d.addCallback(lambda ignored:
5705+            self._version_number == 0)
5706+        return d
5707+
5708+
5709+class LayoutInvalid(Exception):
5710+    """
5711+    This isn't a valid MDMF mutable file
5712+    """
5713hunk ./src/allmydata/test/test_storage.py 2
5714 
5715-import time, os.path, stat, re, simplejson, struct
5716+import time, os.path, stat, re, simplejson, struct, shutil
5717 
5718 from twisted.trial import unittest
5719 
5720hunk ./src/allmydata/test/test_storage.py 22
5721 from allmydata.storage.expirer import LeaseCheckingCrawler
5722 from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \
5723      ReadBucketProxy
5724-from allmydata.interfaces import BadWriteEnablerError
5725-from allmydata.test.common import LoggingServiceParent
5726+from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
5727+                                     LayoutInvalid, MDMFSIGNABLEHEADER, \
5728+                                     SIGNED_PREFIX, MDMFHEADER, \
5729+                                     MDMFOFFSETS, SDMFSlotWriteProxy
5730+from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
5731+                                 SDMF_VERSION
5732+from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
5733 from allmydata.test.common_web import WebRenderingMixin
5734 from allmydata.web.storage import StorageStatus, remove_prefix
5735 
5736hunk ./src/allmydata/test/test_storage.py 106
5737 
5738 class RemoteBucket:
5739 
5740+    def __init__(self):
5741+        self.read_count = 0
5742+        self.write_count = 0
5743+
5744     def callRemote(self, methname, *args, **kwargs):
5745         def _call():
5746             meth = getattr(self.target, "remote_" + methname)
5747hunk ./src/allmydata/test/test_storage.py 114
5748             return meth(*args, **kwargs)
5749+
5750+        if methname == "slot_readv":
5751+            self.read_count += 1
5752+        if "writev" in methname:
5753+            self.write_count += 1
5754+
5755         return defer.maybeDeferred(_call)
5756 
5757hunk ./src/allmydata/test/test_storage.py 122
5758+
5759 class BucketProxy(unittest.TestCase):
5760     def make_bucket(self, name, size):
5761         basedir = os.path.join("storage", "BucketProxy", name)
5762hunk ./src/allmydata/test/test_storage.py 1313
5763         self.failUnless(os.path.exists(prefixdir), prefixdir)
5764         self.failIf(os.path.exists(bucketdir), bucketdir)
5765 
5766+
5767+class MDMFProxies(unittest.TestCase, ShouldFailMixin):
5768+    def setUp(self):
5769+        self.sparent = LoggingServiceParent()
5770+        self._lease_secret = itertools.count()
5771+        self.ss = self.create("MDMFProxies storage test server")
5772+        self.rref = RemoteBucket()
5773+        self.rref.target = self.ss
5774+        self.secrets = (self.write_enabler("we_secret"),
5775+                        self.renew_secret("renew_secret"),
5776+                        self.cancel_secret("cancel_secret"))
5777+        self.segment = "aaaaaa"
5778+        self.block = "aa"
5779+        self.salt = "a" * 16
5780+        self.block_hash = "a" * 32
5781+        self.block_hash_tree = [self.block_hash for i in xrange(6)]
5782+        self.share_hash = self.block_hash
5783+        self.share_hash_chain = dict([(i, self.share_hash) for i in xrange(6)])
5784+        self.signature = "foobarbaz"
5785+        self.verification_key = "vvvvvv"
5786+        self.encprivkey = "private"
5787+        self.root_hash = self.block_hash
5788+        self.salt_hash = self.root_hash
5789+        self.salt_hash_tree = [self.salt_hash for i in xrange(6)]
5790+        self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree)
5791+        self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain)
5792+        # blockhashes and salt hashes are serialized in the same way,
5793+        # only we lop off the first element and store that in the
5794+        # header.
5795+        self.salt_hash_tree_s = self.serialize_blockhashes(self.salt_hash_tree[1:])
5796+
5797+
5798+    def tearDown(self):
5799+        self.sparent.stopService()
5800+        shutil.rmtree(self.workdir("MDMFProxies storage test server"))
5801+
5802+
5803+    def write_enabler(self, we_tag):
5804+        return hashutil.tagged_hash("we_blah", we_tag)
5805+
5806+
5807+    def renew_secret(self, tag):
5808+        return hashutil.tagged_hash("renew_blah", str(tag))
5809+
5810+
5811+    def cancel_secret(self, tag):
5812+        return hashutil.tagged_hash("cancel_blah", str(tag))
5813+
5814+
5815+    def workdir(self, name):
5816+        basedir = os.path.join("storage", "MutableServer", name)
5817+        return basedir
5818+
5819+
5820+    def create(self, name):
5821+        workdir = self.workdir(name)
5822+        ss = StorageServer(workdir, "\x00" * 20)
5823+        ss.setServiceParent(self.sparent)
5824+        return ss
5825+
5826+
5827+    def build_test_mdmf_share(self, tail_segment=False, empty=False):
5828+        # Start with the checkstring
5829+        data = struct.pack(">BQ32s",
5830+                           1,
5831+                           0,
5832+                           self.root_hash)
5833+        self.checkstring = data
5834+        # Next, the encoding parameters
5835+        if tail_segment:
5836+            data += struct.pack(">BBQQ",
5837+                                3,
5838+                                10,
5839+                                6,
5840+                                33)
5841+        elif empty:
5842+            data += struct.pack(">BBQQ",
5843+                                3,
5844+                                10,
5845+                                0,
5846+                                0)
5847+        else:
5848+            data += struct.pack(">BBQQ",
5849+                                3,
5850+                                10,
5851+                                6,
5852+                                36)
5853+        # Now we'll build the offsets.
5854+        sharedata = ""
5855+        if not tail_segment and not empty:
5856+            for i in xrange(6):
5857+                sharedata += self.salt + self.block
5858+        elif tail_segment:
5859+            for i in xrange(5):
5860+                sharedata += self.salt + self.block
5861+            sharedata += self.salt + "a"
5862+
5863+        # The encrypted private key comes after the shares + salts
5864+        offset_size = struct.calcsize(MDMFOFFSETS)
5865+        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
5866+        # The blockhashes come after the private key
5867+        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
5868+        # The sharehashes come after the salt hashes
5869+        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
5870+        # The signature comes after the share hash chain
5871+        signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
5872+        # The verification key comes after the signature
5873+        verification_offset = signature_offset + len(self.signature)
5874+        # The EOF comes after the verification key
5875+        eof_offset = verification_offset + len(self.verification_key)
5876+        data += struct.pack(MDMFOFFSETS,
5877+                            encrypted_private_key_offset,
5878+                            blockhashes_offset,
5879+                            sharehashes_offset,
5880+                            signature_offset,
5881+                            verification_offset,
5882+                            eof_offset)
5883+        self.offsets = {}
5884+        self.offsets['enc_privkey'] = encrypted_private_key_offset
5885+        self.offsets['block_hash_tree'] = blockhashes_offset
5886+        self.offsets['share_hash_chain'] = sharehashes_offset
5887+        self.offsets['signature'] = signature_offset
5888+        self.offsets['verification_key'] = verification_offset
5889+        self.offsets['EOF'] = eof_offset
5890+        # Next, we'll add in the salts and share data,
5891+        data += sharedata
5892+        # the private key,
5893+        data += self.encprivkey
5894+        # the block hash tree,
5895+        data += self.block_hash_tree_s
5896+        # the share hash chain,
5897+        data += self.share_hash_chain_s
5898+        # the signature,
5899+        data += self.signature
5900+        # and the verification key
5901+        data += self.verification_key
5902+        return data
5903+
5904+
5905+    def write_test_share_to_server(self,
5906+                                   storage_index,
5907+                                   tail_segment=False,
5908+                                   empty=False):
5909+        """
5910+        I write some data for the read tests to read to self.ss
5911+
5912+        If tail_segment=True, then I will write a share that has a
5913+        smaller tail segment than other segments.
5914+        """
5915+        write = self.ss.remote_slot_testv_and_readv_and_writev
5916+        data = self.build_test_mdmf_share(tail_segment, empty)
5917+        # Finally, we write the whole thing to the storage server in one
5918+        # pass.
5919+        testvs = [(0, 1, "eq", "")]
5920+        tws = {}
5921+        tws[0] = (testvs, [(0, data)], None)
5922+        readv = [(0, 1)]
5923+        results = write(storage_index, self.secrets, tws, readv)
5924+        self.failUnless(results[0])
5925+
5926+
5927+    def build_test_sdmf_share(self, empty=False):
5928+        if empty:
5929+            sharedata = ""
5930+        else:
5931+            sharedata = self.segment * 6
5932+        self.sharedata = sharedata
5933+        blocksize = len(sharedata) / 3
5934+        block = sharedata[:blocksize]
5935+        self.blockdata = block
5936+        prefix = struct.pack(">BQ32s16s BBQQ",
5937+                             0, # version,
5938+                             0,
5939+                             self.root_hash,
5940+                             self.salt,
5941+                             3,
5942+                             10,
5943+                             len(sharedata),
5944+                             len(sharedata),
5945+                            )
5946+        post_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5947+        signature_offset = post_offset + len(self.verification_key)
5948+        sharehashes_offset = signature_offset + len(self.signature)
5949+        blockhashes_offset = sharehashes_offset + len(self.share_hash_chain_s)
5950+        sharedata_offset = blockhashes_offset + len(self.block_hash_tree_s)
5951+        encprivkey_offset = sharedata_offset + len(block)
5952+        eof_offset = encprivkey_offset + len(self.encprivkey)
5953+        offsets = struct.pack(">LLLLQQ",
5954+                              signature_offset,
5955+                              sharehashes_offset,
5956+                              blockhashes_offset,
5957+                              sharedata_offset,
5958+                              encprivkey_offset,
5959+                              eof_offset)
5960+        final_share = "".join([prefix,
5961+                           offsets,
5962+                           self.verification_key,
5963+                           self.signature,
5964+                           self.share_hash_chain_s,
5965+                           self.block_hash_tree_s,
5966+                           block,
5967+                           self.encprivkey])
5968+        self.offsets = {}
5969+        self.offsets['signature'] = signature_offset
5970+        self.offsets['share_hash_chain'] = sharehashes_offset
5971+        self.offsets['block_hash_tree'] = blockhashes_offset
5972+        self.offsets['share_data'] = sharedata_offset
5973+        self.offsets['enc_privkey'] = encprivkey_offset
5974+        self.offsets['EOF'] = eof_offset
5975+        return final_share
5976+
5977+
5978+    def write_sdmf_share_to_server(self,
5979+                                   storage_index,
5980+                                   empty=False):
5981+        # Some tests need SDMF shares to verify that we can still
5982+        # read them. This method writes one, which resembles but is not
5983+        assert self.rref
5984+        write = self.ss.remote_slot_testv_and_readv_and_writev
5985+        share = self.build_test_sdmf_share(empty)
5986+        testvs = [(0, 1, "eq", "")]
5987+        tws = {}
5988+        tws[0] = (testvs, [(0, share)], None)
5989+        readv = []
5990+        results = write(storage_index, self.secrets, tws, readv)
5991+        self.failUnless(results[0])
5992+
5993+
5994+    def test_read(self):
5995+        self.write_test_share_to_server("si1")
5996+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
5997+        # Check that every method equals what we expect it to.
5998+        d = defer.succeed(None)
5999+        def _check_block_and_salt((block, salt)):
6000+            self.failUnlessEqual(block, self.block)
6001+            self.failUnlessEqual(salt, self.salt)
6002+
6003+        for i in xrange(6):
6004+            d.addCallback(lambda ignored, i=i:
6005+                mr.get_block_and_salt(i))
6006+            d.addCallback(_check_block_and_salt)
6007+
6008+        d.addCallback(lambda ignored:
6009+            mr.get_encprivkey())
6010+        d.addCallback(lambda encprivkey:
6011+            self.failUnlessEqual(self.encprivkey, encprivkey))
6012+
6013+        d.addCallback(lambda ignored:
6014+            mr.get_blockhashes())
6015+        d.addCallback(lambda blockhashes:
6016+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6017+
6018+        d.addCallback(lambda ignored:
6019+            mr.get_sharehashes())
6020+        d.addCallback(lambda sharehashes:
6021+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6022+
6023+        d.addCallback(lambda ignored:
6024+            mr.get_signature())
6025+        d.addCallback(lambda signature:
6026+            self.failUnlessEqual(signature, self.signature))
6027+
6028+        d.addCallback(lambda ignored:
6029+            mr.get_verification_key())
6030+        d.addCallback(lambda verification_key:
6031+            self.failUnlessEqual(verification_key, self.verification_key))
6032+
6033+        d.addCallback(lambda ignored:
6034+            mr.get_seqnum())
6035+        d.addCallback(lambda seqnum:
6036+            self.failUnlessEqual(seqnum, 0))
6037+
6038+        d.addCallback(lambda ignored:
6039+            mr.get_root_hash())
6040+        d.addCallback(lambda root_hash:
6041+            self.failUnlessEqual(self.root_hash, root_hash))
6042+
6043+        d.addCallback(lambda ignored:
6044+            mr.get_seqnum())
6045+        d.addCallback(lambda seqnum:
6046+            self.failUnlessEqual(0, seqnum))
6047+
6048+        d.addCallback(lambda ignored:
6049+            mr.get_encoding_parameters())
6050+        def _check_encoding_parameters((k, n, segsize, datalen)):
6051+            self.failUnlessEqual(k, 3)
6052+            self.failUnlessEqual(n, 10)
6053+            self.failUnlessEqual(segsize, 6)
6054+            self.failUnlessEqual(datalen, 36)
6055+        d.addCallback(_check_encoding_parameters)
6056+
6057+        d.addCallback(lambda ignored:
6058+            mr.get_checkstring())
6059+        d.addCallback(lambda checkstring:
6060+            self.failUnlessEqual(checkstring, checkstring))
6061+        return d
6062+
6063+
6064+    def test_read_with_different_tail_segment_size(self):
6065+        self.write_test_share_to_server("si1", tail_segment=True)
6066+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6067+        d = mr.get_block_and_salt(5)
6068+        def _check_tail_segment(results):
6069+            block, salt = results
6070+            self.failUnlessEqual(len(block), 1)
6071+            self.failUnlessEqual(block, "a")
6072+        d.addCallback(_check_tail_segment)
6073+        return d
6074+
6075+
6076+    def test_get_block_with_invalid_segnum(self):
6077+        self.write_test_share_to_server("si1")
6078+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6079+        d = defer.succeed(None)
6080+        d.addCallback(lambda ignored:
6081+            self.shouldFail(LayoutInvalid, "test invalid segnum",
6082+                            None,
6083+                            mr.get_block_and_salt, 7))
6084+        return d
6085+
6086+
6087+    def test_get_encoding_parameters_first(self):
6088+        self.write_test_share_to_server("si1")
6089+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6090+        d = mr.get_encoding_parameters()
6091+        def _check_encoding_parameters((k, n, segment_size, datalen)):
6092+            self.failUnlessEqual(k, 3)
6093+            self.failUnlessEqual(n, 10)
6094+            self.failUnlessEqual(segment_size, 6)
6095+            self.failUnlessEqual(datalen, 36)
6096+        d.addCallback(_check_encoding_parameters)
6097+        return d
6098+
6099+
6100+    def test_get_seqnum_first(self):
6101+        self.write_test_share_to_server("si1")
6102+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6103+        d = mr.get_seqnum()
6104+        d.addCallback(lambda seqnum:
6105+            self.failUnlessEqual(seqnum, 0))
6106+        return d
6107+
6108+
6109+    def test_get_root_hash_first(self):
6110+        self.write_test_share_to_server("si1")
6111+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6112+        d = mr.get_root_hash()
6113+        d.addCallback(lambda root_hash:
6114+            self.failUnlessEqual(root_hash, self.root_hash))
6115+        return d
6116+
6117+
6118+    def test_get_checkstring_first(self):
6119+        self.write_test_share_to_server("si1")
6120+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6121+        d = mr.get_checkstring()
6122+        d.addCallback(lambda checkstring:
6123+            self.failUnlessEqual(checkstring, self.checkstring))
6124+        return d
6125+
6126+
6127+    def test_write_read_vectors(self):
6128+        # When writing for us, the storage server will return to us a
6129+        # read vector, along with its result. If a write fails because
6130+        # the test vectors failed, this read vector can help us to
6131+        # diagnose the problem. This test ensures that the read vector
6132+        # is working appropriately.
6133+        mw = self._make_new_mw("si1", 0)
6134+        d = defer.succeed(None)
6135+
6136+        # Write one share. This should return a checkstring of nothing,
6137+        # since there is no data there.
6138+        d.addCallback(lambda ignored:
6139+            mw.put_block(self.block, 0, self.salt))
6140+        def _check_first_write(results):
6141+            result, readvs = results
6142+            self.failUnless(result)
6143+            self.failIf(readvs)
6144+        d.addCallback(_check_first_write)
6145+        # Now, there should be a different checkstring returned when
6146+        # we write other shares
6147+        d.addCallback(lambda ignored:
6148+            mw.put_block(self.block, 1, self.salt))
6149+        def _check_next_write(results):
6150+            result, readvs = results
6151+            self.failUnless(result)
6152+            self.expected_checkstring = mw.get_checkstring()
6153+            self.failUnlessIn(0, readvs)
6154+            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
6155+        d.addCallback(_check_next_write)
6156+        # Add the other four shares
6157+        for i in xrange(2, 6):
6158+            d.addCallback(lambda ignored, i=i:
6159+                mw.put_block(self.block, i, self.salt))
6160+            d.addCallback(_check_next_write)
6161+        # Add the encrypted private key
6162+        d.addCallback(lambda ignored:
6163+            mw.put_encprivkey(self.encprivkey))
6164+        d.addCallback(_check_next_write)
6165+        # Add the block hash tree and share hash tree
6166+        d.addCallback(lambda ignored:
6167+            mw.put_blockhashes(self.block_hash_tree))
6168+        d.addCallback(_check_next_write)
6169+        d.addCallback(lambda ignored:
6170+            mw.put_sharehashes(self.share_hash_chain))
6171+        d.addCallback(_check_next_write)
6172+        # Add the root hash and the salt hash. This should change the
6173+        # checkstring, but not in a way that we'll be able to see right
6174+        # now, since the read vectors are applied before the write
6175+        # vectors.
6176+        d.addCallback(lambda ignored:
6177+            mw.put_root_hash(self.root_hash))
6178+        def _check_old_testv_after_new_one_is_written(results):
6179+            result, readvs = results
6180+            self.failUnless(result)
6181+            self.failUnlessIn(0, readvs)
6182+            self.failUnlessEqual(self.expected_checkstring,
6183+                                 readvs[0][0])
6184+            new_checkstring = mw.get_checkstring()
6185+            self.failIfEqual(new_checkstring,
6186+                             readvs[0][0])
6187+        d.addCallback(_check_old_testv_after_new_one_is_written)
6188+        # Now add the signature. This should succeed, meaning that the
6189+        # data gets written and the read vector matches what the writer
6190+        # thinks should be there.
6191+        d.addCallback(lambda ignored:
6192+            mw.put_signature(self.signature))
6193+        d.addCallback(_check_next_write)
6194+        # The checkstring remains the same for the rest of the process.
6195+        return d
6196+
6197+
6198+    def test_blockhashes_after_share_hash_chain(self):
6199+        mw = self._make_new_mw("si1", 0)
6200+        d = defer.succeed(None)
6201+        # Put everything up to and including the share hash chain
6202+        for i in xrange(6):
6203+            d.addCallback(lambda ignored, i=i:
6204+                mw.put_block(self.block, i, self.salt))
6205+        d.addCallback(lambda ignored:
6206+            mw.put_encprivkey(self.encprivkey))
6207+        d.addCallback(lambda ignored:
6208+            mw.put_blockhashes(self.block_hash_tree))
6209+        d.addCallback(lambda ignored:
6210+            mw.put_sharehashes(self.share_hash_chain))
6211+
6212+        # Now try to put the block hash tree again.
6213+        d.addCallback(lambda ignored:
6214+            self.shouldFail(LayoutInvalid, "test repeat salthashes",
6215+                            None,
6216+                            mw.put_blockhashes, self.block_hash_tree))
6217+        return d
6218+
6219+
6220+    def test_encprivkey_after_blockhashes(self):
6221+        mw = self._make_new_mw("si1", 0)
6222+        d = defer.succeed(None)
6223+        # Put everything up to and including the block hash tree
6224+        for i in xrange(6):
6225+            d.addCallback(lambda ignored, i=i:
6226+                mw.put_block(self.block, i, self.salt))
6227+        d.addCallback(lambda ignored:
6228+            mw.put_encprivkey(self.encprivkey))
6229+        d.addCallback(lambda ignored:
6230+            mw.put_blockhashes(self.block_hash_tree))
6231+        d.addCallback(lambda ignored:
6232+            self.shouldFail(LayoutInvalid, "out of order private key",
6233+                            None,
6234+                            mw.put_encprivkey, self.encprivkey))
6235+        return d
6236+
6237+
6238+    def test_share_hash_chain_after_signature(self):
6239+        mw = self._make_new_mw("si1", 0)
6240+        d = defer.succeed(None)
6241+        # Put everything up to and including the signature
6242+        for i in xrange(6):
6243+            d.addCallback(lambda ignored, i=i:
6244+                mw.put_block(self.block, i, self.salt))
6245+        d.addCallback(lambda ignored:
6246+            mw.put_encprivkey(self.encprivkey))
6247+        d.addCallback(lambda ignored:
6248+            mw.put_blockhashes(self.block_hash_tree))
6249+        d.addCallback(lambda ignored:
6250+            mw.put_sharehashes(self.share_hash_chain))
6251+        d.addCallback(lambda ignored:
6252+            mw.put_root_hash(self.root_hash))
6253+        d.addCallback(lambda ignored:
6254+            mw.put_signature(self.signature))
6255+        # Now try to put the share hash chain again. This should fail
6256+        d.addCallback(lambda ignored:
6257+            self.shouldFail(LayoutInvalid, "out of order share hash chain",
6258+                            None,
6259+                            mw.put_sharehashes, self.share_hash_chain))
6260+        return d
6261+
6262+
6263+    def test_signature_after_verification_key(self):
6264+        mw = self._make_new_mw("si1", 0)
6265+        d = defer.succeed(None)
6266+        # Put everything up to and including the verification key.
6267+        for i in xrange(6):
6268+            d.addCallback(lambda ignored, i=i:
6269+                mw.put_block(self.block, i, self.salt))
6270+        d.addCallback(lambda ignored:
6271+            mw.put_encprivkey(self.encprivkey))
6272+        d.addCallback(lambda ignored:
6273+            mw.put_blockhashes(self.block_hash_tree))
6274+        d.addCallback(lambda ignored:
6275+            mw.put_sharehashes(self.share_hash_chain))
6276+        d.addCallback(lambda ignored:
6277+            mw.put_root_hash(self.root_hash))
6278+        d.addCallback(lambda ignored:
6279+            mw.put_signature(self.signature))
6280+        d.addCallback(lambda ignored:
6281+            mw.put_verification_key(self.verification_key))
6282+        # Now try to put the signature again. This should fail
6283+        d.addCallback(lambda ignored:
6284+            self.shouldFail(LayoutInvalid, "signature after verification",
6285+                            None,
6286+                            mw.put_signature, self.signature))
6287+        return d
6288+
6289+
6290+    def test_uncoordinated_write(self):
6291+        # Make two mutable writers, both pointing to the same storage
6292+        # server, both at the same storage index, and try writing to the
6293+        # same share.
6294+        mw1 = self._make_new_mw("si1", 0)
6295+        mw2 = self._make_new_mw("si1", 0)
6296+        d = defer.succeed(None)
6297+        def _check_success(results):
6298+            result, readvs = results
6299+            self.failUnless(result)
6300+
6301+        def _check_failure(results):
6302+            result, readvs = results
6303+            self.failIf(result)
6304+
6305+        d.addCallback(lambda ignored:
6306+            mw1.put_block(self.block, 0, self.salt))
6307+        d.addCallback(_check_success)
6308+        d.addCallback(lambda ignored:
6309+            mw2.put_block(self.block, 0, self.salt))
6310+        d.addCallback(_check_failure)
6311+        return d
6312+
6313+
6314+    def test_invalid_salt_size(self):
6315+        # Salts need to be 16 bytes in size. Writes that attempt to
6316+        # write more or less than this should be rejected.
6317+        mw = self._make_new_mw("si1", 0)
6318+        invalid_salt = "a" * 17 # 17 bytes
6319+        another_invalid_salt = "b" * 15 # 15 bytes
6320+        d = defer.succeed(None)
6321+        d.addCallback(lambda ignored:
6322+            self.shouldFail(LayoutInvalid, "salt too big",
6323+                            None,
6324+                            mw.put_block, self.block, 0, invalid_salt))
6325+        d.addCallback(lambda ignored:
6326+            self.shouldFail(LayoutInvalid, "salt too small",
6327+                            None,
6328+                            mw.put_block, self.block, 0,
6329+                            another_invalid_salt))
6330+        return d
6331+
6332+
6333+    def test_write_test_vectors(self):
6334+        # If we give the write proxy a bogus test vector at
6335+        # any point during the process, it should fail to write.
6336+        mw = self._make_new_mw("si1", 0)
6337+        mw.set_checkstring("this is a lie")
6338+        # The initial write should be expecting to find the improbable
6339+        # checkstring above in place; finding nothing, it should fail.
6340+        d = defer.succeed(None)
6341+        d.addCallback(lambda ignored:
6342+            mw.put_block(self.block, 0, self.salt))
6343+        def _check_failure(results):
6344+            result, readv = results
6345+            self.failIf(result)
6346+        d.addCallback(_check_failure)
6347+        # Now set the checkstring to the empty string, which
6348+        # indicates that no share is there.
6349+        d.addCallback(lambda ignored:
6350+            mw.set_checkstring(""))
6351+        d.addCallback(lambda ignored:
6352+            mw.put_block(self.block, 0, self.salt))
6353+        def _check_success(results):
6354+            result, readv = results
6355+            self.failUnless(result)
6356+        d.addCallback(_check_success)
6357+        # Now set the checkstring to something wrong
6358+        d.addCallback(lambda ignored:
6359+            mw.set_checkstring("something wrong"))
6360+        # This should fail to do anything
6361+        d.addCallback(lambda ignored:
6362+            mw.put_block(self.block, 1, self.salt))
6363+        d.addCallback(_check_failure)
6364+        # Now set it back to what it should be.
6365+        d.addCallback(lambda ignored:
6366+            mw.set_checkstring(mw.get_checkstring()))
6367+        for i in xrange(1, 6):
6368+            d.addCallback(lambda ignored, i=i:
6369+                mw.put_block(self.block, i, self.salt))
6370+            d.addCallback(_check_success)
6371+        d.addCallback(lambda ignored:
6372+            mw.put_encprivkey(self.encprivkey))
6373+        d.addCallback(_check_success)
6374+        d.addCallback(lambda ignored:
6375+            mw.put_blockhashes(self.block_hash_tree))
6376+        d.addCallback(_check_success)
6377+        d.addCallback(lambda ignored:
6378+            mw.put_sharehashes(self.share_hash_chain))
6379+        d.addCallback(_check_success)
6380+        def _keep_old_checkstring(ignored):
6381+            self.old_checkstring = mw.get_checkstring()
6382+            mw.set_checkstring("foobarbaz")
6383+        d.addCallback(_keep_old_checkstring)
6384+        d.addCallback(lambda ignored:
6385+            mw.put_root_hash(self.root_hash))
6386+        d.addCallback(_check_failure)
6387+        d.addCallback(lambda ignored:
6388+            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
6389+        def _restore_old_checkstring(ignored):
6390+            mw.set_checkstring(self.old_checkstring)
6391+        d.addCallback(_restore_old_checkstring)
6392+        d.addCallback(lambda ignored:
6393+            mw.put_root_hash(self.root_hash))
6394+        d.addCallback(_check_success)
6395+        # The checkstring should have been set appropriately for us on
6396+        # the last write; if we try to change it to something else,
6397+        # that change should cause the verification key step to fail.
6398+        d.addCallback(lambda ignored:
6399+            mw.set_checkstring("something else"))
6400+        d.addCallback(lambda ignored:
6401+            mw.put_signature(self.signature))
6402+        d.addCallback(_check_failure)
6403+        d.addCallback(lambda ignored:
6404+            mw.set_checkstring(mw.get_checkstring()))
6405+        d.addCallback(lambda ignored:
6406+            mw.put_signature(self.signature))
6407+        d.addCallback(_check_success)
6408+        d.addCallback(lambda ignored:
6409+            mw.put_verification_key(self.verification_key))
6410+        d.addCallback(_check_success)
6411+        return d
6412+
6413+
6414+    def test_offset_only_set_on_success(self):
6415+        # The write proxy should be smart enough to detect when a write
6416+        # has failed, and to temper its definition of progress based on
6417+        # that.
6418+        mw = self._make_new_mw("si1", 0)
6419+        d = defer.succeed(None)
6420+        for i in xrange(1, 6):
6421+            d.addCallback(lambda ignored, i=i:
6422+                mw.put_block(self.block, i, self.salt))
6423+        def _break_checkstring(ignored):
6424+            self._old_checkstring = mw.get_checkstring()
6425+            mw.set_checkstring("foobarbaz")
6426+
6427+        def _fix_checkstring(ignored):
6428+            mw.set_checkstring(self._old_checkstring)
6429+
6430+        d.addCallback(_break_checkstring)
6431+
6432+        # Setting the encrypted private key shouldn't work now, which is
6433+        # to be expected and is tested elsewhere. We also want to make
6434+        # sure that we can't add the block hash tree after a failed
6435+        # write of this sort.
6436+        d.addCallback(lambda ignored:
6437+            mw.put_encprivkey(self.encprivkey))
6438+        d.addCallback(lambda ignored:
6439+            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
6440+                            None,
6441+                            mw.put_blockhashes, self.block_hash_tree))
6442+        d.addCallback(_fix_checkstring)
6443+        d.addCallback(lambda ignored:
6444+            mw.put_encprivkey(self.encprivkey))
6445+        d.addCallback(_break_checkstring)
6446+        d.addCallback(lambda ignored:
6447+            mw.put_blockhashes(self.block_hash_tree))
6448+        d.addCallback(lambda ignored:
6449+            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
6450+                            None,
6451+                            mw.put_sharehashes, self.share_hash_chain))
6452+        d.addCallback(_fix_checkstring)
6453+        d.addCallback(lambda ignored:
6454+            mw.put_blockhashes(self.block_hash_tree))
6455+        d.addCallback(_break_checkstring)
6456+        d.addCallback(lambda ignored:
6457+            mw.put_sharehashes(self.share_hash_chain))
6458+        d.addCallback(lambda ignored:
6459+            self.shouldFail(LayoutInvalid, "out-of-order root hash",
6460+                            None,
6461+                            mw.put_root_hash, self.root_hash))
6462+        d.addCallback(_fix_checkstring)
6463+        d.addCallback(lambda ignored:
6464+            mw.put_sharehashes(self.share_hash_chain))
6465+        d.addCallback(_break_checkstring)
6466+        d.addCallback(lambda ignored:
6467+            mw.put_root_hash(self.root_hash))
6468+        d.addCallback(lambda ignored:
6469+            self.shouldFail(LayoutInvalid, "out-of-order signature",
6470+                            None,
6471+                            mw.put_signature, self.signature))
6472+        d.addCallback(_fix_checkstring)
6473+        d.addCallback(lambda ignored:
6474+            mw.put_root_hash(self.root_hash))
6475+        d.addCallback(_break_checkstring)
6476+        d.addCallback(lambda ignored:
6477+            mw.put_signature(self.signature))
6478+        d.addCallback(lambda ignored:
6479+            self.shouldFail(LayoutInvalid, "out-of-order verification key",
6480+                            None,
6481+                            mw.put_verification_key,
6482+                            self.verification_key))
6483+        d.addCallback(_fix_checkstring)
6484+        d.addCallback(lambda ignored:
6485+            mw.put_signature(self.signature))
6486+        d.addCallback(_break_checkstring)
6487+        d.addCallback(lambda ignored:
6488+            mw.put_verification_key(self.verification_key))
6489+        d.addCallback(lambda ignored:
6490+            self.shouldFail(LayoutInvalid, "out-of-order finish",
6491+                            None,
6492+                            mw.finish_publishing))
6493+        return d
6494+
6495+
6496+    def serialize_blockhashes(self, blockhashes):
6497+        return "".join(blockhashes)
6498+
6499+
6500+    def serialize_sharehashes(self, sharehashes):
6501+        ret = "".join([struct.pack(">H32s", i, sharehashes[i])
6502+                        for i in sorted(sharehashes.keys())])
6503+        return ret
6504+
6505+
6506+    def test_write(self):
6507+        # This translates to a file with 6 6-byte segments, and with 2-byte
6508+        # blocks.
6509+        mw = self._make_new_mw("si1", 0)
6510+        mw2 = self._make_new_mw("si1", 1)
6511+        # Test writing some blocks.
6512+        read = self.ss.remote_slot_readv
6513+        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
6514+        written_block_size = 2 + len(self.salt)
6515+        written_block = self.block + self.salt
6516+        def _check_block_write(i, share):
6517+            self.failUnlessEqual(read("si1", [share], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
6518+                                {share: [written_block]})
6519+        d = defer.succeed(None)
6520+        for i in xrange(6):
6521+            d.addCallback(lambda ignored, i=i:
6522+                mw.put_block(self.block, i, self.salt))
6523+            d.addCallback(lambda ignored, i=i:
6524+                _check_block_write(i, 0))
6525+        # Now try the same thing, but with share 1 instead of share 0.
6526+        for i in xrange(6):
6527+            d.addCallback(lambda ignored, i=i:
6528+                mw2.put_block(self.block, i, self.salt))
6529+            d.addCallback(lambda ignored, i=i:
6530+                _check_block_write(i, 1))
6531+
6532+        # Next, we make a fake encrypted private key, and put it onto the
6533+        # storage server.
6534+        d.addCallback(lambda ignored:
6535+            mw.put_encprivkey(self.encprivkey))
6536+        expected_private_key_offset = expected_sharedata_offset + \
6537+                                      len(written_block) * 6
6538+        self.failUnlessEqual(len(self.encprivkey), 7)
6539+        d.addCallback(lambda ignored:
6540+            self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
6541+                                 {0: [self.encprivkey]}))
6542+
6543+        # Next, we put a fake block hash tree.
6544+        d.addCallback(lambda ignored:
6545+            mw.put_blockhashes(self.block_hash_tree))
6546+        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
6547+        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
6548+        d.addCallback(lambda ignored:
6549+            self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
6550+                                 {0: [self.block_hash_tree_s]}))
6551+
6552+        # Next, put a fake share hash chain
6553+        d.addCallback(lambda ignored:
6554+            mw.put_sharehashes(self.share_hash_chain))
6555+        expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
6556+        d.addCallback(lambda ignored:
6557+            self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
6558+                                 {0: [self.share_hash_chain_s]}))
6559+
6560+        # Next, we put what is supposed to be the root hash of
6561+        # our share hash tree but isn't       
6562+        d.addCallback(lambda ignored:
6563+            mw.put_root_hash(self.root_hash))
6564+        # The root hash gets inserted at byte 9 (its position is in the header,
6565+        # and is fixed).
6566+        def _check(ignored):
6567+            self.failUnlessEqual(read("si1", [0], [(9, 32)]),
6568+                                 {0: [self.root_hash]})
6569+        d.addCallback(_check)
6570+
6571+        # Next, we put a signature of the header block.
6572+        d.addCallback(lambda ignored:
6573+            mw.put_signature(self.signature))
6574+        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
6575+        self.failUnlessEqual(len(self.signature), 9)
6576+        d.addCallback(lambda ignored:
6577+            self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
6578+                                 {0: [self.signature]}))
6579+
6580+        # Next, we put the verification key
6581+        d.addCallback(lambda ignored:
6582+            mw.put_verification_key(self.verification_key))
6583+        expected_verification_key_offset = expected_signature_offset + len(self.signature)
6584+        self.failUnlessEqual(len(self.verification_key), 6)
6585+        d.addCallback(lambda ignored:
6586+            self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
6587+                                 {0: [self.verification_key]}))
6588+
6589+        def _check_signable(ignored):
6590+            # Make sure that the signable is what we think it should be.
6591+            signable = mw.get_signable()
6592+            verno, seq, roothash, k, n, segsize, datalen = \
6593+                                            struct.unpack(">BQ32sBBQQ",
6594+                                                          signable)
6595+            self.failUnlessEqual(verno, 1)
6596+            self.failUnlessEqual(seq, 0)
6597+            self.failUnlessEqual(roothash, self.root_hash)
6598+            self.failUnlessEqual(k, 3)
6599+            self.failUnlessEqual(n, 10)
6600+            self.failUnlessEqual(segsize, 6)
6601+            self.failUnlessEqual(datalen, 36)
6602+        d.addCallback(_check_signable)
6603+        # Next, we cause the offset table to be published.
6604+        d.addCallback(lambda ignored:
6605+            mw.finish_publishing())
6606+        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
6607+
6608+        def _check_offsets(ignored):
6609+            # Check the version number to make sure that it is correct.
6610+            expected_version_number = struct.pack(">B", 1)
6611+            self.failUnlessEqual(read("si1", [0], [(0, 1)]),
6612+                                 {0: [expected_version_number]})
6613+            # Check the sequence number to make sure that it is correct
6614+            expected_sequence_number = struct.pack(">Q", 0)
6615+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
6616+                                 {0: [expected_sequence_number]})
6617+            # Check that the encoding parameters (k, N, segement size, data
6618+            # length) are what they should be. These are  3, 10, 6, 36
6619+            expected_k = struct.pack(">B", 3)
6620+            self.failUnlessEqual(read("si1", [0], [(41, 1)]),
6621+                                 {0: [expected_k]})
6622+            expected_n = struct.pack(">B", 10)
6623+            self.failUnlessEqual(read("si1", [0], [(42, 1)]),
6624+                                 {0: [expected_n]})
6625+            expected_segment_size = struct.pack(">Q", 6)
6626+            self.failUnlessEqual(read("si1", [0], [(43, 8)]),
6627+                                 {0: [expected_segment_size]})
6628+            expected_data_length = struct.pack(">Q", 36)
6629+            self.failUnlessEqual(read("si1", [0], [(51, 8)]),
6630+                                 {0: [expected_data_length]})
6631+            expected_offset = struct.pack(">Q", expected_private_key_offset)
6632+            self.failUnlessEqual(read("si1", [0], [(59, 8)]),
6633+                                 {0: [expected_offset]})
6634+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
6635+            self.failUnlessEqual(read("si1", [0], [(67, 8)]),
6636+                                 {0: [expected_offset]})
6637+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
6638+            self.failUnlessEqual(read("si1", [0], [(75, 8)]),
6639+                                 {0: [expected_offset]})
6640+            expected_offset = struct.pack(">Q", expected_signature_offset)
6641+            self.failUnlessEqual(read("si1", [0], [(83, 8)]),
6642+                                 {0: [expected_offset]})
6643+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
6644+            self.failUnlessEqual(read("si1", [0], [(91, 8)]),
6645+                                 {0: [expected_offset]})
6646+            expected_offset = struct.pack(">Q", expected_eof_offset)
6647+            self.failUnlessEqual(read("si1", [0], [(99, 8)]),
6648+                                 {0: [expected_offset]})
6649+        d.addCallback(_check_offsets)
6650+        return d
6651+
6652+    def _make_new_mw(self, si, share, datalength=36):
6653+        # This is a file of size 36 bytes. Since it has a segment
6654+        # size of 6, we know that it has 6 byte segments, which will
6655+        # be split into blocks of 2 bytes because our FEC k
6656+        # parameter is 3.
6657+        mw = MDMFSlotWriteProxy(share, self.rref, si, self.secrets, 0, 3, 10,
6658+                                6, datalength)
6659+        return mw
6660+
6661+
6662+    def test_write_rejected_with_too_many_blocks(self):
6663+        mw = self._make_new_mw("si0", 0)
6664+
6665+        # Try writing too many blocks. We should not be able to write
6666+        # more than 6
6667+        # blocks into each share.
6668+        d = defer.succeed(None)
6669+        for i in xrange(6):
6670+            d.addCallback(lambda ignored, i=i:
6671+                mw.put_block(self.block, i, self.salt))
6672+        d.addCallback(lambda ignored:
6673+            self.shouldFail(LayoutInvalid, "too many blocks",
6674+                            None,
6675+                            mw.put_block, self.block, 7, self.salt))
6676+        return d
6677+
6678+
6679+    def test_write_rejected_with_invalid_salt(self):
6680+        # Try writing an invalid salt. Salts are 16 bytes -- any more or
6681+        # less should cause an error.
6682+        mw = self._make_new_mw("si1", 0)
6683+        bad_salt = "a" * 17 # 17 bytes
6684+        d = defer.succeed(None)
6685+        d.addCallback(lambda ignored:
6686+            self.shouldFail(LayoutInvalid, "test_invalid_salt",
6687+                            None, mw.put_block, self.block, 7, bad_salt))
6688+        return d
6689+
6690+
6691+    def test_write_rejected_with_invalid_root_hash(self):
6692+        # Try writing an invalid root hash. This should be SHA256d, and
6693+        # 32 bytes long as a result.
6694+        mw = self._make_new_mw("si2", 0)
6695+        # 17 bytes != 32 bytes
6696+        invalid_root_hash = "a" * 17
6697+        d = defer.succeed(None)
6698+        # Before this test can work, we need to put some blocks + salts,
6699+        # a block hash tree, and a share hash tree. Otherwise, we'll see
6700+        # failures that match what we are looking for, but are caused by
6701+        # the constraints imposed on operation ordering.
6702+        for i in xrange(6):
6703+            d.addCallback(lambda ignored, i=i:
6704+                mw.put_block(self.block, i, self.salt))
6705+        d.addCallback(lambda ignored:
6706+            mw.put_encprivkey(self.encprivkey))
6707+        d.addCallback(lambda ignored:
6708+            mw.put_blockhashes(self.block_hash_tree))
6709+        d.addCallback(lambda ignored:
6710+            mw.put_sharehashes(self.share_hash_chain))
6711+        d.addCallback(lambda ignored:
6712+            self.shouldFail(LayoutInvalid, "invalid root hash",
6713+                            None, mw.put_root_hash, invalid_root_hash))
6714+        return d
6715+
6716+
6717+    def test_write_rejected_with_invalid_blocksize(self):
6718+        # The blocksize implied by the writer that we get from
6719+        # _make_new_mw is 2bytes -- any more or any less than this
6720+        # should be cause for failure, unless it is the tail segment, in
6721+        # which case it may not be failure.
6722+        invalid_block = "a"
6723+        mw = self._make_new_mw("si3", 0, 33) # implies a tail segment with
6724+                                             # one byte blocks
6725+        # 1 bytes != 2 bytes
6726+        d = defer.succeed(None)
6727+        d.addCallback(lambda ignored, invalid_block=invalid_block:
6728+            self.shouldFail(LayoutInvalid, "test blocksize too small",
6729+                            None, mw.put_block, invalid_block, 0,
6730+                            self.salt))
6731+        invalid_block = invalid_block * 3
6732+        # 3 bytes != 2 bytes
6733+        d.addCallback(lambda ignored:
6734+            self.shouldFail(LayoutInvalid, "test blocksize too large",
6735+                            None,
6736+                            mw.put_block, invalid_block, 0, self.salt))
6737+        for i in xrange(5):
6738+            d.addCallback(lambda ignored, i=i:
6739+                mw.put_block(self.block, i, self.salt))
6740+        # Try to put an invalid tail segment
6741+        d.addCallback(lambda ignored:
6742+            self.shouldFail(LayoutInvalid, "test invalid tail segment",
6743+                            None,
6744+                            mw.put_block, self.block, 5, self.salt))
6745+        valid_block = "a"
6746+        d.addCallback(lambda ignored:
6747+            mw.put_block(valid_block, 5, self.salt))
6748+        return d
6749+
6750+
6751+    def test_write_enforces_order_constraints(self):
6752+        # We require that the MDMFSlotWriteProxy be interacted with in a
6753+        # specific way.
6754+        # That way is:
6755+        # 0: __init__
6756+        # 1: write blocks and salts
6757+        # 2: Write the encrypted private key
6758+        # 3: Write the block hashes
6759+        # 4: Write the share hashes
6760+        # 5: Write the root hash and salt hash
6761+        # 6: Write the signature and verification key
6762+        # 7: Write the file.
6763+        #
6764+        # Some of these can be performed out-of-order, and some can't.
6765+        # The dependencies that I want to test here are:
6766+        #  - Private key before block hashes
6767+        #  - share hashes and block hashes before root hash
6768+        #  - root hash before signature
6769+        #  - signature before verification key
6770+        mw0 = self._make_new_mw("si0", 0)
6771+        # Write some shares
6772+        d = defer.succeed(None)
6773+        for i in xrange(6):
6774+            d.addCallback(lambda ignored, i=i:
6775+                mw0.put_block(self.block, i, self.salt))
6776+        # Try to write the block hashes before writing the encrypted
6777+        # private key
6778+        d.addCallback(lambda ignored:
6779+            self.shouldFail(LayoutInvalid, "block hashes before key",
6780+                            None, mw0.put_blockhashes,
6781+                            self.block_hash_tree))
6782+
6783+        # Write the private key.
6784+        d.addCallback(lambda ignored:
6785+            mw0.put_encprivkey(self.encprivkey))
6786+
6787+
6788+        # Try to write the share hash chain without writing the block
6789+        # hash tree
6790+        d.addCallback(lambda ignored:
6791+            self.shouldFail(LayoutInvalid, "share hash chain before "
6792+                                           "salt hash tree",
6793+                            None,
6794+                            mw0.put_sharehashes, self.share_hash_chain))
6795+
6796+        # Try to write the root hash and without writing either the
6797+        # block hashes or the or the share hashes
6798+        d.addCallback(lambda ignored:
6799+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6800+                            None,
6801+                            mw0.put_root_hash, self.root_hash))
6802+
6803+        # Now write the block hashes and try again
6804+        d.addCallback(lambda ignored:
6805+            mw0.put_blockhashes(self.block_hash_tree))
6806+
6807+        d.addCallback(lambda ignored:
6808+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6809+                            None, mw0.put_root_hash, self.root_hash))
6810+
6811+        # We haven't yet put the root hash on the share, so we shouldn't
6812+        # be able to sign it.
6813+        d.addCallback(lambda ignored:
6814+            self.shouldFail(LayoutInvalid, "signature before root hash",
6815+                            None, mw0.put_signature, self.signature))
6816+
6817+        d.addCallback(lambda ignored:
6818+            self.failUnlessRaises(LayoutInvalid, mw0.get_signable))
6819+
6820+        # ..and, since that fails, we also shouldn't be able to put the
6821+        # verification key.
6822+        d.addCallback(lambda ignored:
6823+            self.shouldFail(LayoutInvalid, "key before signature",
6824+                            None, mw0.put_verification_key,
6825+                            self.verification_key))
6826+
6827+        # Now write the share hashes.
6828+        d.addCallback(lambda ignored:
6829+            mw0.put_sharehashes(self.share_hash_chain))
6830+        # We should be able to write the root hash now too
6831+        d.addCallback(lambda ignored:
6832+            mw0.put_root_hash(self.root_hash))
6833+
6834+        # We should still be unable to put the verification key
6835+        d.addCallback(lambda ignored:
6836+            self.shouldFail(LayoutInvalid, "key before signature",
6837+                            None, mw0.put_verification_key,
6838+                            self.verification_key))
6839+
6840+        d.addCallback(lambda ignored:
6841+            mw0.put_signature(self.signature))
6842+
6843+        # We shouldn't be able to write the offsets to the remote server
6844+        # until the offset table is finished; IOW, until we have written
6845+        # the verification key.
6846+        d.addCallback(lambda ignored:
6847+            self.shouldFail(LayoutInvalid, "offsets before verification key",
6848+                            None,
6849+                            mw0.finish_publishing))
6850+
6851+        d.addCallback(lambda ignored:
6852+            mw0.put_verification_key(self.verification_key))
6853+        return d
6854+
6855+
6856+    def test_end_to_end(self):
6857+        mw = self._make_new_mw("si1", 0)
6858+        # Write a share using the mutable writer, and make sure that the
6859+        # reader knows how to read everything back to us.
6860+        d = defer.succeed(None)
6861+        for i in xrange(6):
6862+            d.addCallback(lambda ignored, i=i:
6863+                mw.put_block(self.block, i, self.salt))
6864+        d.addCallback(lambda ignored:
6865+            mw.put_encprivkey(self.encprivkey))
6866+        d.addCallback(lambda ignored:
6867+            mw.put_blockhashes(self.block_hash_tree))
6868+        d.addCallback(lambda ignored:
6869+            mw.put_sharehashes(self.share_hash_chain))
6870+        d.addCallback(lambda ignored:
6871+            mw.put_root_hash(self.root_hash))
6872+        d.addCallback(lambda ignored:
6873+            mw.put_signature(self.signature))
6874+        d.addCallback(lambda ignored:
6875+            mw.put_verification_key(self.verification_key))
6876+        d.addCallback(lambda ignored:
6877+            mw.finish_publishing())
6878+
6879+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6880+        def _check_block_and_salt((block, salt)):
6881+            self.failUnlessEqual(block, self.block)
6882+            self.failUnlessEqual(salt, self.salt)
6883+
6884+        for i in xrange(6):
6885+            d.addCallback(lambda ignored, i=i:
6886+                mr.get_block_and_salt(i))
6887+            d.addCallback(_check_block_and_salt)
6888+
6889+        d.addCallback(lambda ignored:
6890+            mr.get_encprivkey())
6891+        d.addCallback(lambda encprivkey:
6892+            self.failUnlessEqual(self.encprivkey, encprivkey))
6893+
6894+        d.addCallback(lambda ignored:
6895+            mr.get_blockhashes())
6896+        d.addCallback(lambda blockhashes:
6897+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6898+
6899+        d.addCallback(lambda ignored:
6900+            mr.get_sharehashes())
6901+        d.addCallback(lambda sharehashes:
6902+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6903+
6904+        d.addCallback(lambda ignored:
6905+            mr.get_signature())
6906+        d.addCallback(lambda signature:
6907+            self.failUnlessEqual(signature, self.signature))
6908+
6909+        d.addCallback(lambda ignored:
6910+            mr.get_verification_key())
6911+        d.addCallback(lambda verification_key:
6912+            self.failUnlessEqual(verification_key, self.verification_key))
6913+
6914+        d.addCallback(lambda ignored:
6915+            mr.get_seqnum())
6916+        d.addCallback(lambda seqnum:
6917+            self.failUnlessEqual(seqnum, 0))
6918+
6919+        d.addCallback(lambda ignored:
6920+            mr.get_root_hash())
6921+        d.addCallback(lambda root_hash:
6922+            self.failUnlessEqual(self.root_hash, root_hash))
6923+
6924+        d.addCallback(lambda ignored:
6925+            mr.get_encoding_parameters())
6926+        def _check_encoding_parameters((k, n, segsize, datalen)):
6927+            self.failUnlessEqual(k, 3)
6928+            self.failUnlessEqual(n, 10)
6929+            self.failUnlessEqual(segsize, 6)
6930+            self.failUnlessEqual(datalen, 36)
6931+        d.addCallback(_check_encoding_parameters)
6932+
6933+        d.addCallback(lambda ignored:
6934+            mr.get_checkstring())
6935+        d.addCallback(lambda checkstring:
6936+            self.failUnlessEqual(checkstring, mw.get_checkstring()))
6937+        return d
6938+
6939+
6940+    def test_is_sdmf(self):
6941+        # The MDMFSlotReadProxy should also know how to read SDMF files,
6942+        # since it will encounter them on the grid. Callers use the
6943+        # is_sdmf method to test this.
6944+        self.write_sdmf_share_to_server("si1")
6945+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6946+        d = mr.is_sdmf()
6947+        d.addCallback(lambda issdmf:
6948+            self.failUnless(issdmf))
6949+        return d
6950+
6951+
6952+    def test_reads_sdmf(self):
6953+        # The slot read proxy should, naturally, know how to tell us
6954+        # about data in the SDMF format
6955+        self.write_sdmf_share_to_server("si1")
6956+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6957+        d = defer.succeed(None)
6958+        d.addCallback(lambda ignored:
6959+            mr.is_sdmf())
6960+        d.addCallback(lambda issdmf:
6961+            self.failUnless(issdmf))
6962+
6963+        # What do we need to read?
6964+        #  - The sharedata
6965+        #  - The salt
6966+        d.addCallback(lambda ignored:
6967+            mr.get_block_and_salt(0))
6968+        def _check_block_and_salt(results):
6969+            block, salt = results
6970+            # Our original file is 36 bytes long. Then each share is 12
6971+            # bytes in size. The share is composed entirely of the
6972+            # letter a. self.block contains 2 as, so 6 * self.block is
6973+            # what we are looking for.
6974+            self.failUnlessEqual(block, self.block * 6)
6975+            self.failUnlessEqual(salt, self.salt)
6976+        d.addCallback(_check_block_and_salt)
6977+
6978+        #  - The blockhashes
6979+        d.addCallback(lambda ignored:
6980+            mr.get_blockhashes())
6981+        d.addCallback(lambda blockhashes:
6982+            self.failUnlessEqual(self.block_hash_tree,
6983+                                 blockhashes,
6984+                                 blockhashes))
6985+        #  - The sharehashes
6986+        d.addCallback(lambda ignored:
6987+            mr.get_sharehashes())
6988+        d.addCallback(lambda sharehashes:
6989+            self.failUnlessEqual(self.share_hash_chain,
6990+                                 sharehashes))
6991+        #  - The keys
6992+        d.addCallback(lambda ignored:
6993+            mr.get_encprivkey())
6994+        d.addCallback(lambda encprivkey:
6995+            self.failUnlessEqual(encprivkey, self.encprivkey, encprivkey))
6996+        d.addCallback(lambda ignored:
6997+            mr.get_verification_key())
6998+        d.addCallback(lambda verification_key:
6999+            self.failUnlessEqual(verification_key,
7000+                                 self.verification_key,
7001+                                 verification_key))
7002+        #  - The signature
7003+        d.addCallback(lambda ignored:
7004+            mr.get_signature())
7005+        d.addCallback(lambda signature:
7006+            self.failUnlessEqual(signature, self.signature, signature))
7007+
7008+        #  - The sequence number
7009+        d.addCallback(lambda ignored:
7010+            mr.get_seqnum())
7011+        d.addCallback(lambda seqnum:
7012+            self.failUnlessEqual(seqnum, 0, seqnum))
7013+
7014+        #  - The root hash
7015+        d.addCallback(lambda ignored:
7016+            mr.get_root_hash())
7017+        d.addCallback(lambda root_hash:
7018+            self.failUnlessEqual(root_hash, self.root_hash, root_hash))
7019+        return d
7020+
7021+
7022+    def test_only_reads_one_segment_sdmf(self):
7023+        # SDMF shares have only one segment, so it doesn't make sense to
7024+        # read more segments than that. The reader should know this and
7025+        # complain if we try to do that.
7026+        self.write_sdmf_share_to_server("si1")
7027+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7028+        d = defer.succeed(None)
7029+        d.addCallback(lambda ignored:
7030+            mr.is_sdmf())
7031+        d.addCallback(lambda issdmf:
7032+            self.failUnless(issdmf))
7033+        d.addCallback(lambda ignored:
7034+            self.shouldFail(LayoutInvalid, "test bad segment",
7035+                            None,
7036+                            mr.get_block_and_salt, 1))
7037+        return d
7038+
7039+
7040+    def test_read_with_prefetched_mdmf_data(self):
7041+        # The MDMFSlotReadProxy will prefill certain fields if you pass
7042+        # it data that you have already fetched. This is useful for
7043+        # cases like the Servermap, which prefetches ~2kb of data while
7044+        # finding out which shares are on the remote peer so that it
7045+        # doesn't waste round trips.
7046+        mdmf_data = self.build_test_mdmf_share()
7047+        self.write_test_share_to_server("si1")
7048+        def _make_mr(ignored, length):
7049+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:length])
7050+            return mr
7051+
7052+        d = defer.succeed(None)
7053+        # This should be enough to fill in both the encoding parameters
7054+        # and the table of offsets, which will complete the version
7055+        # information tuple.
7056+        d.addCallback(_make_mr, 107)
7057+        d.addCallback(lambda mr:
7058+            mr.get_verinfo())
7059+        def _check_verinfo(verinfo):
7060+            self.failUnless(verinfo)
7061+            self.failUnlessEqual(len(verinfo), 9)
7062+            (seqnum,
7063+             root_hash,
7064+             salt_hash,
7065+             segsize,
7066+             datalen,
7067+             k,
7068+             n,
7069+             prefix,
7070+             offsets) = verinfo
7071+            self.failUnlessEqual(seqnum, 0)
7072+            self.failUnlessEqual(root_hash, self.root_hash)
7073+            self.failUnlessEqual(segsize, 6)
7074+            self.failUnlessEqual(datalen, 36)
7075+            self.failUnlessEqual(k, 3)
7076+            self.failUnlessEqual(n, 10)
7077+            expected_prefix = struct.pack(MDMFSIGNABLEHEADER,
7078+                                          1,
7079+                                          seqnum,
7080+                                          root_hash,
7081+                                          k,
7082+                                          n,
7083+                                          segsize,
7084+                                          datalen)
7085+            self.failUnlessEqual(expected_prefix, prefix)
7086+            self.failUnlessEqual(self.rref.read_count, 0)
7087+        d.addCallback(_check_verinfo)
7088+        # This is not enough data to read a block and a share, so the
7089+        # wrapper should attempt to read this from the remote server.
7090+        d.addCallback(_make_mr, 107)
7091+        d.addCallback(lambda mr:
7092+            mr.get_block_and_salt(0))
7093+        def _check_block_and_salt((block, salt)):
7094+            self.failUnlessEqual(block, self.block)
7095+            self.failUnlessEqual(salt, self.salt)
7096+            self.failUnlessEqual(self.rref.read_count, 1)
7097+        # This should be enough data to read one block.
7098+        d.addCallback(_make_mr, 249)
7099+        d.addCallback(lambda mr:
7100+            mr.get_block_and_salt(0))
7101+        d.addCallback(_check_block_and_salt)
7102+        return d
7103+
7104+
7105+    def test_read_with_prefetched_sdmf_data(self):
7106+        sdmf_data = self.build_test_sdmf_share()
7107+        self.write_sdmf_share_to_server("si1")
7108+        def _make_mr(ignored, length):
7109+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:length])
7110+            return mr
7111+
7112+        d = defer.succeed(None)
7113+        # This should be enough to get us the encoding parameters,
7114+        # offset table, and everything else we need to build a verinfo
7115+        # string.
7116+        d.addCallback(_make_mr, 107)
7117+        d.addCallback(lambda mr:
7118+            mr.get_verinfo())
7119+        def _check_verinfo(verinfo):
7120+            self.failUnless(verinfo)
7121+            self.failUnlessEqual(len(verinfo), 9)
7122+            (seqnum,
7123+             root_hash,
7124+             salt,
7125+             segsize,
7126+             datalen,
7127+             k,
7128+             n,
7129+             prefix,
7130+             offsets) = verinfo
7131+            self.failUnlessEqual(seqnum, 0)
7132+            self.failUnlessEqual(root_hash, self.root_hash)
7133+            self.failUnlessEqual(salt, self.salt)
7134+            self.failUnlessEqual(segsize, 36)
7135+            self.failUnlessEqual(datalen, 36)
7136+            self.failUnlessEqual(k, 3)
7137+            self.failUnlessEqual(n, 10)
7138+            expected_prefix = struct.pack(SIGNED_PREFIX,
7139+                                          0,
7140+                                          seqnum,
7141+                                          root_hash,
7142+                                          salt,
7143+                                          k,
7144+                                          n,
7145+                                          segsize,
7146+                                          datalen)
7147+            self.failUnlessEqual(expected_prefix, prefix)
7148+            self.failUnlessEqual(self.rref.read_count, 0)
7149+        d.addCallback(_check_verinfo)
7150+        # This shouldn't be enough to read any share data.
7151+        d.addCallback(_make_mr, 107)
7152+        d.addCallback(lambda mr:
7153+            mr.get_block_and_salt(0))
7154+        def _check_block_and_salt((block, salt)):
7155+            self.failUnlessEqual(block, self.block * 6)
7156+            self.failUnlessEqual(salt, self.salt)
7157+            # TODO: Fix the read routine so that it reads only the data
7158+            #       that it has cached if it can't read all of it.
7159+            self.failUnlessEqual(self.rref.read_count, 2)
7160+
7161+        # This should be enough to read share data.
7162+        d.addCallback(_make_mr, self.offsets['share_data'])
7163+        d.addCallback(lambda mr:
7164+            mr.get_block_and_salt(0))
7165+        d.addCallback(_check_block_and_salt)
7166+        return d
7167+
7168+
7169+    def test_read_with_empty_mdmf_file(self):
7170+        # Some tests upload a file with no contents to test things
7171+        # unrelated to the actual handling of the content of the file.
7172+        # The reader should behave intelligently in these cases.
7173+        self.write_test_share_to_server("si1", empty=True)
7174+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7175+        # We should be able to get the encoding parameters, and they
7176+        # should be correct.
7177+        d = defer.succeed(None)
7178+        d.addCallback(lambda ignored:
7179+            mr.get_encoding_parameters())
7180+        def _check_encoding_parameters(params):
7181+            self.failUnlessEqual(len(params), 4)
7182+            k, n, segsize, datalen = params
7183+            self.failUnlessEqual(k, 3)
7184+            self.failUnlessEqual(n, 10)
7185+            self.failUnlessEqual(segsize, 0)
7186+            self.failUnlessEqual(datalen, 0)
7187+        d.addCallback(_check_encoding_parameters)
7188+
7189+        # We should not be able to fetch a block, since there are no
7190+        # blocks to fetch
7191+        d.addCallback(lambda ignored:
7192+            self.shouldFail(LayoutInvalid, "get block on empty file",
7193+                            None,
7194+                            mr.get_block_and_salt, 0))
7195+        return d
7196+
7197+
7198+    def test_read_with_empty_sdmf_file(self):
7199+        self.write_sdmf_share_to_server("si1", empty=True)
7200+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7201+        # We should be able to get the encoding parameters, and they
7202+        # should be correct
7203+        d = defer.succeed(None)
7204+        d.addCallback(lambda ignored:
7205+            mr.get_encoding_parameters())
7206+        def _check_encoding_parameters(params):
7207+            self.failUnlessEqual(len(params), 4)
7208+            k, n, segsize, datalen = params
7209+            self.failUnlessEqual(k, 3)
7210+            self.failUnlessEqual(n, 10)
7211+            self.failUnlessEqual(segsize, 0)
7212+            self.failUnlessEqual(datalen, 0)
7213+        d.addCallback(_check_encoding_parameters)
7214+
7215+        # It does not make sense to get a block in this format, so we
7216+        # should not be able to.
7217+        d.addCallback(lambda ignored:
7218+            self.shouldFail(LayoutInvalid, "get block on an empty file",
7219+                            None,
7220+                            mr.get_block_and_salt, 0))
7221+        return d
7222+
7223+
7224+    def test_verinfo_with_sdmf_file(self):
7225+        self.write_sdmf_share_to_server("si1")
7226+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7227+        # We should be able to get the version information.
7228+        d = defer.succeed(None)
7229+        d.addCallback(lambda ignored:
7230+            mr.get_verinfo())
7231+        def _check_verinfo(verinfo):
7232+            self.failUnless(verinfo)
7233+            self.failUnlessEqual(len(verinfo), 9)
7234+            (seqnum,
7235+             root_hash,
7236+             salt,
7237+             segsize,
7238+             datalen,
7239+             k,
7240+             n,
7241+             prefix,
7242+             offsets) = verinfo
7243+            self.failUnlessEqual(seqnum, 0)
7244+            self.failUnlessEqual(root_hash, self.root_hash)
7245+            self.failUnlessEqual(salt, self.salt)
7246+            self.failUnlessEqual(segsize, 36)
7247+            self.failUnlessEqual(datalen, 36)
7248+            self.failUnlessEqual(k, 3)
7249+            self.failUnlessEqual(n, 10)
7250+            expected_prefix = struct.pack(">BQ32s16s BBQQ",
7251+                                          0,
7252+                                          seqnum,
7253+                                          root_hash,
7254+                                          salt,
7255+                                          k,
7256+                                          n,
7257+                                          segsize,
7258+                                          datalen)
7259+            self.failUnlessEqual(prefix, expected_prefix)
7260+            self.failUnlessEqual(offsets, self.offsets)
7261+        d.addCallback(_check_verinfo)
7262+        return d
7263+
7264+
7265+    def test_verinfo_with_mdmf_file(self):
7266+        self.write_test_share_to_server("si1")
7267+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7268+        d = defer.succeed(None)
7269+        d.addCallback(lambda ignored:
7270+            mr.get_verinfo())
7271+        def _check_verinfo(verinfo):
7272+            self.failUnless(verinfo)
7273+            self.failUnlessEqual(len(verinfo), 9)
7274+            (seqnum,
7275+             root_hash,
7276+             IV,
7277+             segsize,
7278+             datalen,
7279+             k,
7280+             n,
7281+             prefix,
7282+             offsets) = verinfo
7283+            self.failUnlessEqual(seqnum, 0)
7284+            self.failUnlessEqual(root_hash, self.root_hash)
7285+            self.failIf(IV)
7286+            self.failUnlessEqual(segsize, 6)
7287+            self.failUnlessEqual(datalen, 36)
7288+            self.failUnlessEqual(k, 3)
7289+            self.failUnlessEqual(n, 10)
7290+            expected_prefix = struct.pack(">BQ32s BBQQ",
7291+                                          1,
7292+                                          seqnum,
7293+                                          root_hash,
7294+                                          k,
7295+                                          n,
7296+                                          segsize,
7297+                                          datalen)
7298+            self.failUnlessEqual(prefix, expected_prefix)
7299+            self.failUnlessEqual(offsets, self.offsets)
7300+        d.addCallback(_check_verinfo)
7301+        return d
7302+
7303+
7304+    def test_reader_queue(self):
7305+        self.write_test_share_to_server('si1')
7306+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7307+        d1 = mr.get_block_and_salt(0, queue=True)
7308+        d2 = mr.get_blockhashes(queue=True)
7309+        d3 = mr.get_sharehashes(queue=True)
7310+        d4 = mr.get_signature(queue=True)
7311+        d5 = mr.get_verification_key(queue=True)
7312+        dl = defer.DeferredList([d1, d2, d3, d4, d5])
7313+        mr.flush()
7314+        def _print(results):
7315+            self.failUnlessEqual(len(results), 5)
7316+            # We have one read for version information and offsets, and
7317+            # one for everything else.
7318+            self.failUnlessEqual(self.rref.read_count, 2)
7319+            block, salt = results[0][1] # results[0] is a boolean that says
7320+                                           # whether or not the operation
7321+                                           # worked.
7322+            self.failUnlessEqual(self.block, block)
7323+            self.failUnlessEqual(self.salt, salt)
7324+
7325+            blockhashes = results[1][1]
7326+            self.failUnlessEqual(self.block_hash_tree, blockhashes)
7327+
7328+            sharehashes = results[2][1]
7329+            self.failUnlessEqual(self.share_hash_chain, sharehashes)
7330+
7331+            signature = results[3][1]
7332+            self.failUnlessEqual(self.signature, signature)
7333+
7334+            verification_key = results[4][1]
7335+            self.failUnlessEqual(self.verification_key, verification_key)
7336+        dl.addCallback(_print)
7337+        return dl
7338+
7339+
7340+    def test_sdmf_writer(self):
7341+        # Go through the motions of writing an SDMF share to the storage
7342+        # server. Then read the storage server to see that the share got
7343+        # written in the way that we think it should have.
7344+
7345+        # We do this first so that the necessary instance variables get
7346+        # set the way we want them for the tests below.
7347+        data = self.build_test_sdmf_share()
7348+        sdmfr = SDMFSlotWriteProxy(0,
7349+                                   self.rref,
7350+                                   "si1",
7351+                                   self.secrets,
7352+                                   0, 3, 10, 36, 36)
7353+        # Put the block and salt.
7354+        sdmfr.put_block(self.blockdata, 0, self.salt)
7355+
7356+        # Put the encprivkey
7357+        sdmfr.put_encprivkey(self.encprivkey)
7358+
7359+        # Put the block and share hash chains
7360+        sdmfr.put_blockhashes(self.block_hash_tree)
7361+        sdmfr.put_sharehashes(self.share_hash_chain)
7362+        sdmfr.put_root_hash(self.root_hash)
7363+
7364+        # Put the signature
7365+        sdmfr.put_signature(self.signature)
7366+
7367+        # Put the verification key
7368+        sdmfr.put_verification_key(self.verification_key)
7369+
7370+        # Now check to make sure that nothing has been written yet.
7371+        self.failUnlessEqual(self.rref.write_count, 0)
7372+
7373+        # Now finish publishing
7374+        d = sdmfr.finish_publishing()
7375+        def _then(ignored):
7376+            self.failUnlessEqual(self.rref.write_count, 1)
7377+            read = self.ss.remote_slot_readv
7378+            self.failUnlessEqual(read("si1", [0], [(0, len(data))]),
7379+                                 {0: [data]})
7380+        d.addCallback(_then)
7381+        return d
7382+
7383+
7384+    def test_sdmf_writer_preexisting_share(self):
7385+        data = self.build_test_sdmf_share()
7386+        self.write_sdmf_share_to_server("si1")
7387+
7388+        # Now there is a share on the storage server. To successfully
7389+        # write, we need to set the checkstring correctly. When we
7390+        # don't, no write should occur.
7391+        sdmfw = SDMFSlotWriteProxy(0,
7392+                                   self.rref,
7393+                                   "si1",
7394+                                   self.secrets,
7395+                                   1, 3, 10, 36, 36)
7396+        sdmfw.put_block(self.blockdata, 0, self.salt)
7397+
7398+        # Put the encprivkey
7399+        sdmfw.put_encprivkey(self.encprivkey)
7400+
7401+        # Put the block and share hash chains
7402+        sdmfw.put_blockhashes(self.block_hash_tree)
7403+        sdmfw.put_sharehashes(self.share_hash_chain)
7404+
7405+        # Put the root hash
7406+        sdmfw.put_root_hash(self.root_hash)
7407+
7408+        # Put the signature
7409+        sdmfw.put_signature(self.signature)
7410+
7411+        # Put the verification key
7412+        sdmfw.put_verification_key(self.verification_key)
7413+
7414+        # We shouldn't have a checkstring yet
7415+        self.failUnlessEqual(sdmfw.get_checkstring(), "")
7416+
7417+        d = sdmfw.finish_publishing()
7418+        def _then(results):
7419+            self.failIf(results[0])
7420+            # this is the correct checkstring
7421+            self._expected_checkstring = results[1][0][0]
7422+            return self._expected_checkstring
7423+
7424+        d.addCallback(_then)
7425+        d.addCallback(sdmfw.set_checkstring)
7426+        d.addCallback(lambda ignored:
7427+            sdmfw.get_checkstring())
7428+        d.addCallback(lambda checkstring:
7429+            self.failUnlessEqual(checkstring, self._expected_checkstring))
7430+        d.addCallback(lambda ignored:
7431+            sdmfw.finish_publishing())
7432+        def _then_again(results):
7433+            self.failUnless(results[0])
7434+            read = self.ss.remote_slot_readv
7435+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
7436+                                 {0: [struct.pack(">Q", 1)]})
7437+            self.failUnlessEqual(read("si1", [0], [(9, len(data) - 9)]),
7438+                                 {0: [data[9:]]})
7439+        d.addCallback(_then_again)
7440+        return d
7441+
7442+
7443 class Stats(unittest.TestCase):
7444 
7445     def setUp(self):
7446}
7447[mutable/publish.py: cleanup + simplification
7448Kevan Carstensen <kevan@isnotajoke.com>**20100702225554
7449 Ignore-this: 36a58424ceceffb1ddc55cc5934399e2
7450] {
7451hunk ./src/allmydata/mutable/publish.py 19
7452      UncoordinatedWriteError, NotEnoughServersError
7453 from allmydata.mutable.servermap import ServerMap
7454 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
7455-     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
7456+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy, \
7457+     SDMFSlotWriteProxy
7458 
7459 KiB = 1024
7460 DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
7461hunk ./src/allmydata/mutable/publish.py 24
7462+PUSHING_BLOCKS_STATE = 0
7463+PUSHING_EVERYTHING_ELSE_STATE = 1
7464+DONE_STATE = 2
7465 
7466 class PublishStatus:
7467     implements(IPublishStatus)
7468hunk ./src/allmydata/mutable/publish.py 229
7469 
7470         self.bad_share_checkstrings = {}
7471 
7472+        # This is set at the last step of the publishing process.
7473+        self.versioninfo = ""
7474+
7475         # we use the servermap to populate the initial goal: this way we will
7476         # try to update each existing share in place.
7477         for (peerid, shnum) in self._servermap.servermap:
7478hunk ./src/allmydata/mutable/publish.py 245
7479             self.bad_share_checkstrings[key] = old_checkstring
7480             self.connections[peerid] = self._servermap.connections[peerid]
7481 
7482-        # Now, the process dovetails -- if this is an SDMF file, we need
7483-        # to write an SDMF file. Otherwise, we need to write an MDMF
7484-        # file.
7485-        if self._version == MDMF_VERSION:
7486-            return self._publish_mdmf()
7487-        else:
7488-            return self._publish_sdmf()
7489-        #return self.done_deferred
7490-
7491-    def _publish_mdmf(self):
7492-        # Next, we find homes for all of the shares that we don't have
7493-        # homes for yet.
7494         # TODO: Make this part do peer selection.
7495         self.update_goal()
7496         self.writers = {}
7497hunk ./src/allmydata/mutable/publish.py 248
7498-        # For each (peerid, shnum) in self.goal, we make an
7499-        # MDMFSlotWriteProxy for that peer. We'll use this to write
7500+        if self._version == MDMF_VERSION:
7501+            writer_class = MDMFSlotWriteProxy
7502+        else:
7503+            writer_class = SDMFSlotWriteProxy
7504+
7505+        # For each (peerid, shnum) in self.goal, we make a
7506+        # write proxy for that peer. We'll use this to write
7507         # shares to the peer.
7508         for key in self.goal:
7509             peerid, shnum = key
7510hunk ./src/allmydata/mutable/publish.py 263
7511             cancel_secret = self._node.get_cancel_secret(peerid)
7512             secrets = (write_enabler, renew_secret, cancel_secret)
7513 
7514-            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
7515-                                                      self.connections[peerid],
7516-                                                      self._storage_index,
7517-                                                      secrets,
7518-                                                      self._new_seqnum,
7519-                                                      self.required_shares,
7520-                                                      self.total_shares,
7521-                                                      self.segment_size,
7522-                                                      len(self.newdata))
7523+            self.writers[shnum] =  writer_class(shnum,
7524+                                                self.connections[peerid],
7525+                                                self._storage_index,
7526+                                                secrets,
7527+                                                self._new_seqnum,
7528+                                                self.required_shares,
7529+                                                self.total_shares,
7530+                                                self.segment_size,
7531+                                                len(self.newdata))
7532+            self.writers[shnum].peerid = peerid
7533             if (peerid, shnum) in self._servermap.servermap:
7534                 old_versionid, old_timestamp = self._servermap.servermap[key]
7535                 (old_seqnum, old_root_hash, old_salt, old_segsize,
7536hunk ./src/allmydata/mutable/publish.py 278
7537                  old_datalength, old_k, old_N, old_prefix,
7538                  old_offsets_tuple) = old_versionid
7539-                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
7540+                self.writers[shnum].set_checkstring(old_seqnum,
7541+                                                    old_root_hash,
7542+                                                    old_salt)
7543+            elif (peerid, shnum) in self.bad_share_checkstrings:
7544+                old_checkstring = self.bad_share_checkstrings[(peerid, shnum)]
7545+                self.writers[shnum].set_checkstring(old_checkstring)
7546+
7547+        # Our remote shares will not have a complete checkstring until
7548+        # after we are done writing share data and have started to write
7549+        # blocks. In the meantime, we need to know what to look for when
7550+        # writing, so that we can detect UncoordinatedWriteErrors.
7551+        self._checkstring = self.writers.values()[0].get_checkstring()
7552 
7553         # Now, we start pushing shares.
7554         self._status.timings["setup"] = time.time() - self._started
7555hunk ./src/allmydata/mutable/publish.py 293
7556-        def _start_pushing(res):
7557-            self._started_pushing = time.time()
7558-            return res
7559-
7560         # First, we encrypt, encode, and publish the shares that we need
7561         # to encrypt, encode, and publish.
7562 
7563hunk ./src/allmydata/mutable/publish.py 306
7564 
7565         d = defer.succeed(None)
7566         self.log("Starting push")
7567-        for i in xrange(self.num_segments - 1):
7568-            d.addCallback(lambda ignored, i=i:
7569-                self.push_segment(i))
7570-            d.addCallback(self._turn_barrier)
7571-        # We have at least one segment, so we will have a tail segment
7572-        if self.num_segments > 0:
7573-            d.addCallback(lambda ignored:
7574-                self.push_tail_segment())
7575-
7576-        d.addCallback(lambda ignored:
7577-            self.push_encprivkey())
7578-        d.addCallback(lambda ignored:
7579-            self.push_blockhashes())
7580-        d.addCallback(lambda ignored:
7581-            self.push_sharehashes())
7582-        d.addCallback(lambda ignored:
7583-            self.push_toplevel_hashes_and_signature())
7584-        d.addCallback(lambda ignored:
7585-            self.finish_publishing())
7586-        return d
7587-
7588-
7589-    def _publish_sdmf(self):
7590-        self._status.timings["setup"] = time.time() - self._started
7591-        self.salt = os.urandom(16)
7592 
7593hunk ./src/allmydata/mutable/publish.py 307
7594-        d = self._encrypt_and_encode()
7595-        d.addCallback(self._generate_shares)
7596-        def _start_pushing(res):
7597-            self._started_pushing = time.time()
7598-            return res
7599-        d.addCallback(_start_pushing)
7600-        d.addCallback(self.loop) # trigger delivery
7601-        d.addErrback(self._fatal_error)
7602+        self._state = PUSHING_BLOCKS_STATE
7603+        self._push()
7604 
7605         return self.done_deferred
7606 
7607hunk ./src/allmydata/mutable/publish.py 327
7608                                                   segment_size)
7609         else:
7610             self.num_segments = 0
7611+
7612+        self.log("building encoding parameters for file")
7613+        self.log("got segsize %d" % self.segment_size)
7614+        self.log("got %d segments" % self.num_segments)
7615+
7616         if self._version == SDMF_VERSION:
7617             assert self.num_segments in (0, 1) # SDMF
7618hunk ./src/allmydata/mutable/publish.py 334
7619-            return
7620         # calculate the tail segment size.
7621hunk ./src/allmydata/mutable/publish.py 335
7622-        self.tail_segment_size = len(self.newdata) % segment_size
7623 
7624hunk ./src/allmydata/mutable/publish.py 336
7625-        if self.tail_segment_size == 0:
7626+        if segment_size and self.newdata:
7627+            self.tail_segment_size = len(self.newdata) % segment_size
7628+        else:
7629+            self.tail_segment_size = 0
7630+
7631+        if self.tail_segment_size == 0 and segment_size:
7632             # The tail segment is the same size as the other segments.
7633             self.tail_segment_size = segment_size
7634 
7635hunk ./src/allmydata/mutable/publish.py 345
7636-        # We'll make an encoder ahead-of-time for the normal-sized
7637-        # segments (defined as any segment of segment_size size.
7638-        # (the part of the code that puts the tail segment will make its
7639-        #  own encoder for that part)
7640+        # Make FEC encoders
7641         fec = codec.CRSEncoder()
7642         fec.set_params(self.segment_size,
7643                        self.required_shares, self.total_shares)
7644hunk ./src/allmydata/mutable/publish.py 352
7645         self.piece_size = fec.get_block_size()
7646         self.fec = fec
7647 
7648+        if self.tail_segment_size == self.segment_size:
7649+            self.tail_fec = self.fec
7650+        else:
7651+            tail_fec = codec.CRSEncoder()
7652+            tail_fec.set_params(self.tail_segment_size,
7653+                                self.required_shares,
7654+                                self.total_shares)
7655+            self.tail_fec = tail_fec
7656+
7657+        self._current_segment = 0
7658+
7659+
7660+    def _push(self, ignored=None):
7661+        """
7662+        I manage state transitions. In particular, I see that we still
7663+        have a good enough number of writers to complete the upload
7664+        successfully.
7665+        """
7666+        # Can we still successfully publish this file?
7667+        # TODO: Keep track of outstanding queries before aborting the
7668+        #       process.
7669+        if len(self.writers) <= self.required_shares or self.surprised:
7670+            return self._failure()
7671+
7672+        # Figure out what we need to do next. Each of these needs to
7673+        # return a deferred so that we don't block execution when this
7674+        # is first called in the upload method.
7675+        if self._state == PUSHING_BLOCKS_STATE:
7676+            return self.push_segment(self._current_segment)
7677+
7678+        # XXX: Do we want more granularity in states? Is that useful at
7679+        #      all?
7680+        #      Yes -- quicker reaction to UCW.
7681+        elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
7682+            return self.push_everything_else()
7683+
7684+        # If we make it to this point, we were successful in placing the
7685+        # file.
7686+        return self._done(None)
7687+
7688 
7689     def push_segment(self, segnum):
7690hunk ./src/allmydata/mutable/publish.py 394
7691+        if self.num_segments == 0 and self._version == SDMF_VERSION:
7692+            self._add_dummy_salts()
7693+
7694+        if segnum == self.num_segments:
7695+            # We don't have any more segments to push.
7696+            self._state = PUSHING_EVERYTHING_ELSE_STATE
7697+            return self._push()
7698+
7699+        d = self._encode_segment(segnum)
7700+        d.addCallback(self._push_segment, segnum)
7701+        def _increment_segnum(ign):
7702+            self._current_segment += 1
7703+        # XXX: I don't think we need to do addBoth here -- any errBacks
7704+        # should be handled within push_segment.
7705+        d.addBoth(_increment_segnum)
7706+        d.addBoth(self._push)
7707+
7708+
7709+    def _add_dummy_salts(self):
7710+        """
7711+        SDMF files need a salt even if they're empty, or the signature
7712+        won't make sense. This method adds a dummy salt to each of our
7713+        SDMF writers so that they can write the signature later.
7714+        """
7715+        salt = os.urandom(16)
7716+        assert self._version == SDMF_VERSION
7717+
7718+        for writer in self.writers.itervalues():
7719+            writer.put_salt(salt)
7720+
7721+
7722+    def _encode_segment(self, segnum):
7723+        """
7724+        I encrypt and encode the segment segnum.
7725+        """
7726         started = time.time()
7727hunk ./src/allmydata/mutable/publish.py 430
7728-        segsize = self.segment_size
7729+
7730+        if segnum + 1 == self.num_segments:
7731+            segsize = self.tail_segment_size
7732+        else:
7733+            segsize = self.segment_size
7734+
7735+
7736+        offset = self.segment_size * segnum
7737+        length = segsize + offset
7738         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
7739hunk ./src/allmydata/mutable/publish.py 440
7740-        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
7741+        data = self.newdata[offset:length]
7742         assert len(data) == segsize
7743 
7744         salt = os.urandom(16)
7745hunk ./src/allmydata/mutable/publish.py 455
7746         started = now
7747 
7748         # now apply FEC
7749+        if segnum + 1 == self.num_segments:
7750+            fec = self.tail_fec
7751+        else:
7752+            fec = self.fec
7753 
7754         self._status.set_status("Encoding")
7755         crypttext_pieces = [None] * self.required_shares
7756hunk ./src/allmydata/mutable/publish.py 462
7757-        piece_size = self.piece_size
7758+        piece_size = fec.get_block_size()
7759         for i in range(len(crypttext_pieces)):
7760             offset = i * piece_size
7761             piece = crypttext[offset:offset+piece_size]
7762hunk ./src/allmydata/mutable/publish.py 469
7763             piece = piece + "\x00"*(piece_size - len(piece)) # padding
7764             crypttext_pieces[i] = piece
7765             assert len(piece) == piece_size
7766-        d = self.fec.encode(crypttext_pieces)
7767+        d = fec.encode(crypttext_pieces)
7768         def _done_encoding(res):
7769             elapsed = time.time() - started
7770             self._status.timings["encode"] = elapsed
7771hunk ./src/allmydata/mutable/publish.py 473
7772-            return res
7773+            return (res, salt)
7774         d.addCallback(_done_encoding)
7775hunk ./src/allmydata/mutable/publish.py 475
7776-
7777-        def _push_shares_and_salt(results):
7778-            shares, shareids = results
7779-            dl = []
7780-            for i in xrange(len(shares)):
7781-                sharedata = shares[i]
7782-                shareid = shareids[i]
7783-                block_hash = hashutil.block_hash(salt + sharedata)
7784-                self.blockhashes[shareid].append(block_hash)
7785-
7786-                # find the writer for this share
7787-                d = self.writers[shareid].put_block(sharedata, segnum, salt)
7788-                dl.append(d)
7789-            # TODO: Naturally, we need to check on the results of these.
7790-            return defer.DeferredList(dl)
7791-        d.addCallback(_push_shares_and_salt)
7792         return d
7793 
7794 
7795hunk ./src/allmydata/mutable/publish.py 478
7796-    def push_tail_segment(self):
7797-        # This is essentially the same as push_segment, except that we
7798-        # don't use the cached encoder that we use elsewhere.
7799-        self.log("Pushing tail segment")
7800+    def _push_segment(self, encoded_and_salt, segnum):
7801+        """
7802+        I push (data, salt) as segment number segnum.
7803+        """
7804+        results, salt = encoded_and_salt
7805+        shares, shareids = results
7806         started = time.time()
7807hunk ./src/allmydata/mutable/publish.py 485
7808-        segsize = self.segment_size
7809-        data = self.newdata[segsize * (self.num_segments-1):]
7810-        assert len(data) == self.tail_segment_size
7811-        salt = os.urandom(16)
7812-
7813-        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
7814-        enc = AES(key)
7815-        crypttext = enc.process(data)
7816-        assert len(crypttext) == len(data)
7817+        dl = []
7818+        for i in xrange(len(shares)):
7819+            sharedata = shares[i]
7820+            shareid = shareids[i]
7821+            if self._version == MDMF_VERSION:
7822+                hashed = salt + sharedata
7823+            else:
7824+                hashed = sharedata
7825+            block_hash = hashutil.block_hash(hashed)
7826+            self.blockhashes[shareid].append(block_hash)
7827 
7828hunk ./src/allmydata/mutable/publish.py 496
7829-        now = time.time()
7830-        self._status.timings['encrypt'] = now - started
7831-        started = now
7832+            # find the writer for this share
7833+            writer = self.writers[shareid]
7834+            d = writer.put_block(sharedata, segnum, salt)
7835+            d.addCallback(self._got_write_answer, writer, started)
7836+            d.addErrback(self._connection_problem, writer)
7837+            dl.append(d)
7838+            # TODO: Naturally, we need to check on the results of these.
7839+        return defer.DeferredList(dl)
7840 
7841hunk ./src/allmydata/mutable/publish.py 505
7842-        self._status.set_status("Encoding")
7843-        tail_fec = codec.CRSEncoder()
7844-        tail_fec.set_params(self.tail_segment_size,
7845-                            self.required_shares,
7846-                            self.total_shares)
7847 
7848hunk ./src/allmydata/mutable/publish.py 506
7849-        crypttext_pieces = [None] * self.required_shares
7850-        piece_size = tail_fec.get_block_size()
7851-        for i in range(len(crypttext_pieces)):
7852-            offset = i * piece_size
7853-            piece = crypttext[offset:offset+piece_size]
7854-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
7855-            crypttext_pieces[i] = piece
7856-            assert len(piece) == piece_size
7857-        d = tail_fec.encode(crypttext_pieces)
7858-        def _push_shares_and_salt(results):
7859-            shares, shareids = results
7860-            dl = []
7861-            for i in xrange(len(shares)):
7862-                sharedata = shares[i]
7863-                shareid = shareids[i]
7864-                block_hash = hashutil.block_hash(salt + sharedata)
7865-                self.blockhashes[shareid].append(block_hash)
7866-                # find the writer for this share
7867-                d = self.writers[shareid].put_block(sharedata,
7868-                                                    self.num_segments - 1,
7869-                                                    salt)
7870-                dl.append(d)
7871-            # TODO: Naturally, we need to check on the results of these.
7872-            return defer.DeferredList(dl)
7873-        d.addCallback(_push_shares_and_salt)
7874+    def push_everything_else(self):
7875+        """
7876+        I put everything else associated with a share.
7877+        """
7878+        encprivkey = self._encprivkey
7879+        d = self.push_encprivkey()
7880+        d.addCallback(self.push_blockhashes)
7881+        d.addCallback(self.push_sharehashes)
7882+        d.addCallback(self.push_toplevel_hashes_and_signature)
7883+        d.addCallback(self.finish_publishing)
7884+        def _change_state(ignored):
7885+            self._state = DONE_STATE
7886+        d.addCallback(_change_state)
7887+        d.addCallback(self._push)
7888         return d
7889 
7890 
7891hunk ./src/allmydata/mutable/publish.py 527
7892         started = time.time()
7893         encprivkey = self._encprivkey
7894         dl = []
7895-        def _spy_on_writer(results):
7896-            print results
7897-            return results
7898-        for shnum, writer in self.writers.iteritems():
7899+        for writer in self.writers.itervalues():
7900             d = writer.put_encprivkey(encprivkey)
7901hunk ./src/allmydata/mutable/publish.py 529
7902+            d.addCallback(self._got_write_answer, writer, started)
7903+            d.addErrback(self._connection_problem, writer)
7904             dl.append(d)
7905         d = defer.DeferredList(dl)
7906         return d
7907hunk ./src/allmydata/mutable/publish.py 536
7908 
7909 
7910-    def push_blockhashes(self):
7911+    def push_blockhashes(self, ignored):
7912         started = time.time()
7913         dl = []
7914hunk ./src/allmydata/mutable/publish.py 539
7915-        def _spy_on_results(results):
7916-            print results
7917-            return results
7918         self.sharehash_leaves = [None] * len(self.blockhashes)
7919         for shnum, blockhashes in self.blockhashes.iteritems():
7920             t = hashtree.HashTree(blockhashes)
7921hunk ./src/allmydata/mutable/publish.py 545
7922             self.blockhashes[shnum] = list(t)
7923             # set the leaf for future use.
7924             self.sharehash_leaves[shnum] = t[0]
7925-            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
7926+            writer = self.writers[shnum]
7927+            d = writer.put_blockhashes(self.blockhashes[shnum])
7928+            d.addCallback(self._got_write_answer, writer, started)
7929+            d.addErrback(self._connection_problem, self.writers[shnum])
7930             dl.append(d)
7931         d = defer.DeferredList(dl)
7932         return d
7933hunk ./src/allmydata/mutable/publish.py 554
7934 
7935 
7936-    def push_sharehashes(self):
7937+    def push_sharehashes(self, ignored):
7938+        started = time.time()
7939         share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
7940         share_hash_chain = {}
7941         ds = []
7942hunk ./src/allmydata/mutable/publish.py 559
7943-        def _spy_on_results(results):
7944-            print results
7945-            return results
7946         for shnum in xrange(len(self.sharehash_leaves)):
7947             needed_indices = share_hash_tree.needed_hashes(shnum)
7948             self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
7949hunk ./src/allmydata/mutable/publish.py 563
7950                                              for i in needed_indices] )
7951-            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
7952+            writer = self.writers[shnum]
7953+            d = writer.put_sharehashes(self.sharehashes[shnum])
7954+            d.addCallback(self._got_write_answer, writer, started)
7955+            d.addErrback(self._connection_problem, writer)
7956             ds.append(d)
7957         self.root_hash = share_hash_tree[0]
7958         d = defer.DeferredList(ds)
7959hunk ./src/allmydata/mutable/publish.py 573
7960         return d
7961 
7962 
7963-    def push_toplevel_hashes_and_signature(self):
7964+    def push_toplevel_hashes_and_signature(self, ignored):
7965         # We need to to three things here:
7966         #   - Push the root hash and salt hash
7967         #   - Get the checkstring of the resulting layout; sign that.
7968hunk ./src/allmydata/mutable/publish.py 578
7969         #   - Push the signature
7970+        started = time.time()
7971         ds = []
7972hunk ./src/allmydata/mutable/publish.py 580
7973-        def _spy_on_results(results):
7974-            print results
7975-            return results
7976         for shnum in xrange(self.total_shares):
7977hunk ./src/allmydata/mutable/publish.py 581
7978-            d = self.writers[shnum].put_root_hash(self.root_hash)
7979+            writer = self.writers[shnum]
7980+            d = writer.put_root_hash(self.root_hash)
7981+            d.addCallback(self._got_write_answer, writer, started)
7982             ds.append(d)
7983         d = defer.DeferredList(ds)
7984hunk ./src/allmydata/mutable/publish.py 586
7985-        def _make_and_place_signature(ignored):
7986-            signable = self.writers[0].get_signable()
7987-            self.signature = self._privkey.sign(signable)
7988-
7989-            ds = []
7990-            for (shnum, writer) in self.writers.iteritems():
7991-                d = writer.put_signature(self.signature)
7992-                ds.append(d)
7993-            return defer.DeferredList(ds)
7994-        d.addCallback(_make_and_place_signature)
7995+        d.addCallback(self._update_checkstring)
7996+        d.addCallback(self._make_and_place_signature)
7997         return d
7998 
7999 
8000hunk ./src/allmydata/mutable/publish.py 591
8001-    def finish_publishing(self):
8002+    def _update_checkstring(self, ignored):
8003+        """
8004+        After putting the root hash, MDMF files will have the
8005+        checkstring written to the storage server. This means that we
8006+        can update our copy of the checkstring so we can detect
8007+        uncoordinated writes. SDMF files will have the same checkstring,
8008+        so we need not do anything.
8009+        """
8010+        self._checkstring = self.writers.values()[0].get_checkstring()
8011+
8012+
8013+    def _make_and_place_signature(self, ignored):
8014+        """
8015+        I create and place the signature.
8016+        """
8017+        started = time.time()
8018+        signable = self.writers[0].get_signable()
8019+        self.signature = self._privkey.sign(signable)
8020+
8021+        ds = []
8022+        for (shnum, writer) in self.writers.iteritems():
8023+            d = writer.put_signature(self.signature)
8024+            d.addCallback(self._got_write_answer, writer, started)
8025+            d.addErrback(self._connection_problem, writer)
8026+            ds.append(d)
8027+        return defer.DeferredList(ds)
8028+
8029+
8030+    def finish_publishing(self, ignored):
8031         # We're almost done -- we just need to put the verification key
8032         # and the offsets
8033hunk ./src/allmydata/mutable/publish.py 622
8034+        started = time.time()
8035         ds = []
8036         verification_key = self._pubkey.serialize()
8037 
8038hunk ./src/allmydata/mutable/publish.py 626
8039-        def _spy_on_results(results):
8040-            print results
8041-            return results
8042+
8043+        # TODO: Bad, since we remove from this same dict. We need to
8044+        # make a copy, or just use a non-iterated value.
8045         for (shnum, writer) in self.writers.iteritems():
8046             d = writer.put_verification_key(verification_key)
8047hunk ./src/allmydata/mutable/publish.py 631
8048+            d.addCallback(self._got_write_answer, writer, started)
8049+            d.addCallback(self._record_verinfo)
8050             d.addCallback(lambda ignored, writer=writer:
8051                 writer.finish_publishing())
8052hunk ./src/allmydata/mutable/publish.py 635
8053+            d.addCallback(self._got_write_answer, writer, started)
8054+            d.addErrback(self._connection_problem, writer)
8055             ds.append(d)
8056         return defer.DeferredList(ds)
8057 
8058hunk ./src/allmydata/mutable/publish.py 641
8059 
8060-    def _turn_barrier(self, res):
8061-        # putting this method in a Deferred chain imposes a guaranteed
8062-        # reactor turn between the pre- and post- portions of that chain.
8063-        # This can be useful to limit memory consumption: since Deferreds do
8064-        # not do tail recursion, code which uses defer.succeed(result) for
8065-        # consistency will cause objects to live for longer than you might
8066-        # normally expect.
8067-        return fireEventually(res)
8068+    def _record_verinfo(self, ignored):
8069+        self.versioninfo = self.writers.values()[0].get_verinfo()
8070 
8071 
8072hunk ./src/allmydata/mutable/publish.py 645
8073-    def _fatal_error(self, f):
8074-        self.log("error during loop", failure=f, level=log.UNUSUAL)
8075-        self._done(f)
8076+    def _connection_problem(self, f, writer):
8077+        """
8078+        We ran into a connection problem while working with writer, and
8079+        need to deal with that.
8080+        """
8081+        self.log("found problem: %s" % str(f))
8082+        self._last_failure = f
8083+        del(self.writers[writer.shnum])
8084 
8085hunk ./src/allmydata/mutable/publish.py 654
8086-    def _update_status(self):
8087-        self._status.set_status("Sending Shares: %d placed out of %d, "
8088-                                "%d messages outstanding" %
8089-                                (len(self.placed),
8090-                                 len(self.goal),
8091-                                 len(self.outstanding)))
8092-        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
8093 
8094     def loop(self, ignored=None):
8095         self.log("entering loop", level=log.NOISY)
8096hunk ./src/allmydata/mutable/publish.py 778
8097             self.log_goal(self.goal, "after update: ")
8098 
8099 
8100-    def _encrypt_and_encode(self):
8101-        # this returns a Deferred that fires with a list of (sharedata,
8102-        # sharenum) tuples. TODO: cache the ciphertext, only produce the
8103-        # shares that we care about.
8104-        self.log("_encrypt_and_encode")
8105-
8106-        self._status.set_status("Encrypting")
8107-        started = time.time()
8108+    def _got_write_answer(self, answer, writer, started):
8109+        if not answer:
8110+            # SDMF writers only pretend to write when readers set their
8111+            # blocks, salts, and so on -- they actually just write once,
8112+            # at the end of the upload process. In fake writes, they
8113+            # return defer.succeed(None). If we see that, we shouldn't
8114+            # bother checking it.
8115+            return
8116 
8117hunk ./src/allmydata/mutable/publish.py 787
8118-        key = hashutil.ssk_readkey_data_hash(self.salt, self.readkey)
8119-        enc = AES(key)
8120-        crypttext = enc.process(self.newdata)
8121-        assert len(crypttext) == len(self.newdata)
8122+        peerid = writer.peerid
8123+        lp = self.log("_got_write_answer from %s, share %d" %
8124+                      (idlib.shortnodeid_b2a(peerid), writer.shnum))
8125 
8126         now = time.time()
8127hunk ./src/allmydata/mutable/publish.py 792
8128-        self._status.timings["encrypt"] = now - started
8129-        started = now
8130-
8131-        # now apply FEC
8132-
8133-        self._status.set_status("Encoding")
8134-        fec = codec.CRSEncoder()
8135-        fec.set_params(self.segment_size,
8136-                       self.required_shares, self.total_shares)
8137-        piece_size = fec.get_block_size()
8138-        crypttext_pieces = [None] * self.required_shares
8139-        for i in range(len(crypttext_pieces)):
8140-            offset = i * piece_size
8141-            piece = crypttext[offset:offset+piece_size]
8142-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
8143-            crypttext_pieces[i] = piece
8144-            assert len(piece) == piece_size
8145-
8146-        d = fec.encode(crypttext_pieces)
8147-        def _done_encoding(res):
8148-            elapsed = time.time() - started
8149-            self._status.timings["encode"] = elapsed
8150-            return res
8151-        d.addCallback(_done_encoding)
8152-        return d
8153-
8154-
8155-    def _generate_shares(self, shares_and_shareids):
8156-        # this sets self.shares and self.root_hash
8157-        self.log("_generate_shares")
8158-        self._status.set_status("Generating Shares")
8159-        started = time.time()
8160-
8161-        # we should know these by now
8162-        privkey = self._privkey
8163-        encprivkey = self._encprivkey
8164-        pubkey = self._pubkey
8165-
8166-        (shares, share_ids) = shares_and_shareids
8167-
8168-        assert len(shares) == len(share_ids)
8169-        assert len(shares) == self.total_shares
8170-        all_shares = {}
8171-        block_hash_trees = {}
8172-        share_hash_leaves = [None] * len(shares)
8173-        for i in range(len(shares)):
8174-            share_data = shares[i]
8175-            shnum = share_ids[i]
8176-            all_shares[shnum] = share_data
8177-
8178-            # build the block hash tree. SDMF has only one leaf.
8179-            leaves = [hashutil.block_hash(share_data)]
8180-            t = hashtree.HashTree(leaves)
8181-            block_hash_trees[shnum] = list(t)
8182-            share_hash_leaves[shnum] = t[0]
8183-        for leaf in share_hash_leaves:
8184-            assert leaf is not None
8185-        share_hash_tree = hashtree.HashTree(share_hash_leaves)
8186-        share_hash_chain = {}
8187-        for shnum in range(self.total_shares):
8188-            needed_hashes = share_hash_tree.needed_hashes(shnum)
8189-            share_hash_chain[shnum] = dict( [ (i, share_hash_tree[i])
8190-                                              for i in needed_hashes ] )
8191-        root_hash = share_hash_tree[0]
8192-        assert len(root_hash) == 32
8193-        self.log("my new root_hash is %s" % base32.b2a(root_hash))
8194-        self._new_version_info = (self._new_seqnum, root_hash, self.salt)
8195-
8196-        prefix = pack_prefix(self._new_seqnum, root_hash, self.salt,
8197-                             self.required_shares, self.total_shares,
8198-                             self.segment_size, len(self.newdata))
8199-
8200-        # now pack the beginning of the share. All shares are the same up
8201-        # to the signature, then they have divergent share hash chains,
8202-        # then completely different block hash trees + salt + share data,
8203-        # then they all share the same encprivkey at the end. The sizes
8204-        # of everything are the same for all shares.
8205-
8206-        sign_started = time.time()
8207-        signature = privkey.sign(prefix)
8208-        self._status.timings["sign"] = time.time() - sign_started
8209-
8210-        verification_key = pubkey.serialize()
8211-
8212-        final_shares = {}
8213-        for shnum in range(self.total_shares):
8214-            final_share = pack_share(prefix,
8215-                                     verification_key,
8216-                                     signature,
8217-                                     share_hash_chain[shnum],
8218-                                     block_hash_trees[shnum],
8219-                                     all_shares[shnum],
8220-                                     encprivkey)
8221-            final_shares[shnum] = final_share
8222-        elapsed = time.time() - started
8223-        self._status.timings["pack"] = elapsed
8224-        self.shares = final_shares
8225-        self.root_hash = root_hash
8226-
8227-        # we also need to build up the version identifier for what we're
8228-        # pushing. Extract the offsets from one of our shares.
8229-        assert final_shares
8230-        offsets = unpack_header(final_shares.values()[0])[-1]
8231-        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
8232-        verinfo = (self._new_seqnum, root_hash, self.salt,
8233-                   self.segment_size, len(self.newdata),
8234-                   self.required_shares, self.total_shares,
8235-                   prefix, offsets_tuple)
8236-        self.versioninfo = verinfo
8237-
8238-
8239-
8240-    def _send_shares(self, needed):
8241-        self.log("_send_shares")
8242-
8243-        # we're finally ready to send out our shares. If we encounter any
8244-        # surprises here, it's because somebody else is writing at the same
8245-        # time. (Note: in the future, when we remove the _query_peers() step
8246-        # and instead speculate about [or remember] which shares are where,
8247-        # surprises here are *not* indications of UncoordinatedWriteError,
8248-        # and we'll need to respond to them more gracefully.)
8249-
8250-        # needed is a set of (peerid, shnum) tuples. The first thing we do is
8251-        # organize it by peerid.
8252-
8253-        peermap = DictOfSets()
8254-        for (peerid, shnum) in needed:
8255-            peermap.add(peerid, shnum)
8256-
8257-        # the next thing is to build up a bunch of test vectors. The
8258-        # semantics of Publish are that we perform the operation if the world
8259-        # hasn't changed since the ServerMap was constructed (more or less).
8260-        # For every share we're trying to place, we create a test vector that
8261-        # tests to see if the server*share still corresponds to the
8262-        # map.
8263-
8264-        all_tw_vectors = {} # maps peerid to tw_vectors
8265-        sm = self._servermap.servermap
8266-
8267-        for key in needed:
8268-            (peerid, shnum) = key
8269-
8270-            if key in sm:
8271-                # an old version of that share already exists on the
8272-                # server, according to our servermap. We will create a
8273-                # request that attempts to replace it.
8274-                old_versionid, old_timestamp = sm[key]
8275-                (old_seqnum, old_root_hash, old_salt, old_segsize,
8276-                 old_datalength, old_k, old_N, old_prefix,
8277-                 old_offsets_tuple) = old_versionid
8278-                old_checkstring = pack_checkstring(old_seqnum,
8279-                                                   old_root_hash,
8280-                                                   old_salt)
8281-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8282-
8283-            elif key in self.bad_share_checkstrings:
8284-                old_checkstring = self.bad_share_checkstrings[key]
8285-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8286-
8287-            else:
8288-                # add a testv that requires the share not exist
8289-
8290-                # Unfortunately, foolscap-0.2.5 has a bug in the way inbound
8291-                # constraints are handled. If the same object is referenced
8292-                # multiple times inside the arguments, foolscap emits a
8293-                # 'reference' token instead of a distinct copy of the
8294-                # argument. The bug is that these 'reference' tokens are not
8295-                # accepted by the inbound constraint code. To work around
8296-                # this, we need to prevent python from interning the
8297-                # (constant) tuple, by creating a new copy of this vector
8298-                # each time.
8299-
8300-                # This bug is fixed in foolscap-0.2.6, and even though this
8301-                # version of Tahoe requires foolscap-0.3.1 or newer, we are
8302-                # supposed to be able to interoperate with older versions of
8303-                # Tahoe which are allowed to use older versions of foolscap,
8304-                # including foolscap-0.2.5 . In addition, I've seen other
8305-                # foolscap problems triggered by 'reference' tokens (see #541
8306-                # for details). So we must keep this workaround in place.
8307-
8308-                #testv = (0, 1, 'eq', "")
8309-                testv = tuple([0, 1, 'eq', ""])
8310-
8311-            testvs = [testv]
8312-            # the write vector is simply the share
8313-            writev = [(0, self.shares[shnum])]
8314-
8315-            if peerid not in all_tw_vectors:
8316-                all_tw_vectors[peerid] = {}
8317-                # maps shnum to (testvs, writevs, new_length)
8318-            assert shnum not in all_tw_vectors[peerid]
8319-
8320-            all_tw_vectors[peerid][shnum] = (testvs, writev, None)
8321-
8322-        # we read the checkstring back from each share, however we only use
8323-        # it to detect whether there was a new share that we didn't know
8324-        # about. The success or failure of the write will tell us whether
8325-        # there was a collision or not. If there is a collision, the first
8326-        # thing we'll do is update the servermap, which will find out what
8327-        # happened. We could conceivably reduce a roundtrip by using the
8328-        # readv checkstring to populate the servermap, but really we'd have
8329-        # to read enough data to validate the signatures too, so it wouldn't
8330-        # be an overall win.
8331-        read_vector = [(0, struct.calcsize(SIGNED_PREFIX))]
8332-
8333-        # ok, send the messages!
8334-        self.log("sending %d shares" % len(all_tw_vectors), level=log.NOISY)
8335-        started = time.time()
8336-        for (peerid, tw_vectors) in all_tw_vectors.items():
8337-
8338-            write_enabler = self._node.get_write_enabler(peerid)
8339-            renew_secret = self._node.get_renewal_secret(peerid)
8340-            cancel_secret = self._node.get_cancel_secret(peerid)
8341-            secrets = (write_enabler, renew_secret, cancel_secret)
8342-            shnums = tw_vectors.keys()
8343-
8344-            for shnum in shnums:
8345-                self.outstanding.add( (peerid, shnum) )
8346-
8347-            d = self._do_testreadwrite(peerid, secrets,
8348-                                       tw_vectors, read_vector)
8349-            d.addCallbacks(self._got_write_answer, self._got_write_error,
8350-                           callbackArgs=(peerid, shnums, started),
8351-                           errbackArgs=(peerid, shnums, started))
8352-            # tolerate immediate errback, like with DeadReferenceError
8353-            d.addBoth(fireEventually)
8354-            d.addCallback(self.loop)
8355-            d.addErrback(self._fatal_error)
8356-
8357-        self._update_status()
8358-        self.log("%d shares sent" % len(all_tw_vectors), level=log.NOISY)
8359+        elapsed = now - started
8360 
8361hunk ./src/allmydata/mutable/publish.py 794
8362-    def _do_testreadwrite(self, peerid, secrets,
8363-                          tw_vectors, read_vector):
8364-        storage_index = self._storage_index
8365-        ss = self.connections[peerid]
8366+        self._status.add_per_server_time(peerid, elapsed)
8367 
8368hunk ./src/allmydata/mutable/publish.py 796
8369-        #print "SS[%s] is %s" % (idlib.shortnodeid_b2a(peerid), ss), ss.tracker.interfaceName
8370-        d = ss.callRemote("slot_testv_and_readv_and_writev",
8371-                          storage_index,
8372-                          secrets,
8373-                          tw_vectors,
8374-                          read_vector)
8375-        return d
8376+        wrote, read_data = answer
8377 
8378hunk ./src/allmydata/mutable/publish.py 798
8379-    def _got_write_answer(self, answer, peerid, shnums, started):
8380-        lp = self.log("_got_write_answer from %s" %
8381-                      idlib.shortnodeid_b2a(peerid))
8382-        for shnum in shnums:
8383-            self.outstanding.discard( (peerid, shnum) )
8384+        surprise_shares = set(read_data.keys()) - set([writer.shnum])
8385 
8386hunk ./src/allmydata/mutable/publish.py 800
8387-        now = time.time()
8388-        elapsed = now - started
8389-        self._status.add_per_server_time(peerid, elapsed)
8390+        # We need to remove from surprise_shares any shares that we are
8391+        # knowingly also writing to that peer from other writers.
8392 
8393hunk ./src/allmydata/mutable/publish.py 803
8394-        wrote, read_data = answer
8395+        # TODO: Precompute this.
8396+        known_shnums = [x.shnum for x in self.writers.values()
8397+                        if x.peerid == peerid]
8398+        surprise_shares -= set(known_shnums)
8399+        self.log("found the following surprise shares: %s" %
8400+                 str(surprise_shares))
8401 
8402hunk ./src/allmydata/mutable/publish.py 810
8403-        surprise_shares = set(read_data.keys()) - set(shnums)
8404+        # Now surprise shares contains all of the shares that we did not
8405+        # expect to be there.
8406 
8407         surprised = False
8408         for shnum in surprise_shares:
8409hunk ./src/allmydata/mutable/publish.py 817
8410             # read_data is a dict mapping shnum to checkstring (SIGNED_PREFIX)
8411             checkstring = read_data[shnum][0]
8412-            their_version_info = unpack_checkstring(checkstring)
8413-            if their_version_info == self._new_version_info:
8414+            # What we want to do here is to see if their (seqnum,
8415+            # roothash, salt) is the same as our (seqnum, roothash,
8416+            # salt), or the equivalent for MDMF. The best way to do this
8417+            # is to store a packed representation of our checkstring
8418+            # somewhere, then not bother unpacking the other
8419+            # checkstring.
8420+            if checkstring == self._checkstring:
8421                 # they have the right share, somehow
8422 
8423                 if (peerid,shnum) in self.goal:
8424hunk ./src/allmydata/mutable/publish.py 902
8425             self.log("our testv failed, so the write did not happen",
8426                      parent=lp, level=log.WEIRD, umid="8sc26g")
8427             self.surprised = True
8428-            self.bad_peers.add(peerid) # don't ask them again
8429+            # TODO: This needs to
8430+            self.bad_peers.add(writer) # don't ask them again
8431             # use the checkstring to add information to the log message
8432             for (shnum,readv) in read_data.items():
8433                 checkstring = readv[0]
8434hunk ./src/allmydata/mutable/publish.py 928
8435             # self.loop() will take care of finding new homes
8436             return
8437 
8438-        for shnum in shnums:
8439-            self.placed.add( (peerid, shnum) )
8440-            # and update the servermap
8441-            self._servermap.add_new_share(peerid, shnum,
8442+        # and update the servermap
8443+        # self.versioninfo is set during the last phase of publishing.
8444+        # If we get there, we know that responses correspond to placed
8445+        # shares, and can safely execute these statements.
8446+        if self.versioninfo:
8447+            self.log("wrote successfully: adding new share to servermap")
8448+            self._servermap.add_new_share(peerid, writer.shnum,
8449                                           self.versioninfo, started)
8450hunk ./src/allmydata/mutable/publish.py 936
8451-
8452-        # self.loop() will take care of checking to see if we're done
8453-        return
8454+            self.placed.add( (peerid, writer.shnum) )
8455 
8456hunk ./src/allmydata/mutable/publish.py 938
8457-    def _got_write_error(self, f, peerid, shnums, started):
8458-        for shnum in shnums:
8459-            self.outstanding.discard( (peerid, shnum) )
8460-        self.bad_peers.add(peerid)
8461-        if self._first_write_error is None:
8462-            self._first_write_error = f
8463-        self.log(format="error while writing shares %(shnums)s to peerid %(peerid)s",
8464-                 shnums=list(shnums), peerid=idlib.shortnodeid_b2a(peerid),
8465-                 failure=f,
8466-                 level=log.UNUSUAL)
8467         # self.loop() will take care of checking to see if we're done
8468         return
8469 
8470hunk ./src/allmydata/mutable/publish.py 949
8471         now = time.time()
8472         self._status.timings["total"] = now - self._started
8473         self._status.set_active(False)
8474-        if isinstance(res, failure.Failure):
8475-            self.log("Publish done, with failure", failure=res,
8476-                     level=log.WEIRD, umid="nRsR9Q")
8477-            self._status.set_status("Failed")
8478-        elif self.surprised:
8479-            self.log("Publish done, UncoordinatedWriteError", level=log.UNUSUAL)
8480-            self._status.set_status("UncoordinatedWriteError")
8481-            # deliver a failure
8482-            res = failure.Failure(UncoordinatedWriteError())
8483-            # TODO: recovery
8484-        else:
8485-            self.log("Publish done, success")
8486-            self._status.set_status("Finished")
8487-            self._status.set_progress(1.0)
8488+        self.log("Publish done, success")
8489+        self._status.set_status("Finished")
8490+        self._status.set_progress(1.0)
8491         eventually(self.done_deferred.callback, res)
8492 
8493hunk ./src/allmydata/mutable/publish.py 954
8494+    def _failure(self):
8495+
8496+        if not self.surprised:
8497+            # We ran out of servers
8498+            self.log("Publish ran out of good servers, "
8499+                     "last failure was: %s" % str(self._last_failure))
8500+            e = NotEnoughServersError("Ran out of non-bad servers, "
8501+                                      "last failure was %s" %
8502+                                      str(self._last_failure))
8503+        else:
8504+            # We ran into shares that we didn't recognize, which means
8505+            # that we need to return an UncoordinatedWriteError.
8506+            self.log("Publish failed with UncoordinatedWriteError")
8507+            e = UncoordinatedWriteError()
8508+        f = failure.Failure(e)
8509+        eventually(self.done_deferred.callback, f)
8510}
8511[test/test_mutable.py: remove tests that are no longer relevant
8512Kevan Carstensen <kevan@isnotajoke.com>**20100702225710
8513 Ignore-this: 90a26b4cc4b2e190a635474ba7097e21
8514] hunk ./src/allmydata/test/test_mutable.py 627
8515         return d
8516 
8517 
8518-class MakeShares(unittest.TestCase):
8519-    def test_encrypt(self):
8520-        nm = make_nodemaker()
8521-        CONTENTS = "some initial contents"
8522-        d = nm.create_mutable_file(CONTENTS)
8523-        def _created(fn):
8524-            p = Publish(fn, nm.storage_broker, None)
8525-            p.salt = "SALT" * 4
8526-            p.readkey = "\x00" * 16
8527-            p.newdata = CONTENTS
8528-            p.required_shares = 3
8529-            p.total_shares = 10
8530-            p.setup_encoding_parameters()
8531-            return p._encrypt_and_encode()
8532-        d.addCallback(_created)
8533-        def _done(shares_and_shareids):
8534-            (shares, share_ids) = shares_and_shareids
8535-            self.failUnlessEqual(len(shares), 10)
8536-            for sh in shares:
8537-                self.failUnless(isinstance(sh, str))
8538-                self.failUnlessEqual(len(sh), 7)
8539-            self.failUnlessEqual(len(share_ids), 10)
8540-        d.addCallback(_done)
8541-        return d
8542-    test_encrypt.todo = "Write an equivalent of this for the new uploader"
8543-
8544-    def test_generate(self):
8545-        nm = make_nodemaker()
8546-        CONTENTS = "some initial contents"
8547-        d = nm.create_mutable_file(CONTENTS)
8548-        def _created(fn):
8549-            self._fn = fn
8550-            p = Publish(fn, nm.storage_broker, None)
8551-            self._p = p
8552-            p.newdata = CONTENTS
8553-            p.required_shares = 3
8554-            p.total_shares = 10
8555-            p.setup_encoding_parameters()
8556-            p._new_seqnum = 3
8557-            p.salt = "SALT" * 4
8558-            # make some fake shares
8559-            shares_and_ids = ( ["%07d" % i for i in range(10)], range(10) )
8560-            p._privkey = fn.get_privkey()
8561-            p._encprivkey = fn.get_encprivkey()
8562-            p._pubkey = fn.get_pubkey()
8563-            return p._generate_shares(shares_and_ids)
8564-        d.addCallback(_created)
8565-        def _generated(res):
8566-            p = self._p
8567-            final_shares = p.shares
8568-            root_hash = p.root_hash
8569-            self.failUnlessEqual(len(root_hash), 32)
8570-            self.failUnless(isinstance(final_shares, dict))
8571-            self.failUnlessEqual(len(final_shares), 10)
8572-            self.failUnlessEqual(sorted(final_shares.keys()), range(10))
8573-            for i,sh in final_shares.items():
8574-                self.failUnless(isinstance(sh, str))
8575-                # feed the share through the unpacker as a sanity-check
8576-                pieces = unpack_share(sh)
8577-                (u_seqnum, u_root_hash, IV, k, N, segsize, datalen,
8578-                 pubkey, signature, share_hash_chain, block_hash_tree,
8579-                 share_data, enc_privkey) = pieces
8580-                self.failUnlessEqual(u_seqnum, 3)
8581-                self.failUnlessEqual(u_root_hash, root_hash)
8582-                self.failUnlessEqual(k, 3)
8583-                self.failUnlessEqual(N, 10)
8584-                self.failUnlessEqual(segsize, 21)
8585-                self.failUnlessEqual(datalen, len(CONTENTS))
8586-                self.failUnlessEqual(pubkey, p._pubkey.serialize())
8587-                sig_material = struct.pack(">BQ32s16s BBQQ",
8588-                                           0, p._new_seqnum, root_hash, IV,
8589-                                           k, N, segsize, datalen)
8590-                self.failUnless(p._pubkey.verify(sig_material, signature))
8591-                #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
8592-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
8593-                for shnum,share_hash in share_hash_chain.items():
8594-                    self.failUnless(isinstance(shnum, int))
8595-                    self.failUnless(isinstance(share_hash, str))
8596-                    self.failUnlessEqual(len(share_hash), 32)
8597-                self.failUnless(isinstance(block_hash_tree, list))
8598-                self.failUnlessEqual(len(block_hash_tree), 1) # very small tree
8599-                self.failUnlessEqual(IV, "SALT"*4)
8600-                self.failUnlessEqual(len(share_data), len("%07d" % 1))
8601-                self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
8602-        d.addCallback(_generated)
8603-        return d
8604-    test_generate.todo = "Write an equivalent of this for the new uploader"
8605-
8606-    # TODO: when we publish to 20 peers, we should get one share per peer on 10
8607-    # when we publish to 3 peers, we should get either 3 or 4 shares per peer
8608-    # when we publish to zero peers, we should get a NotEnoughSharesError
8609-
8610 class PublishMixin:
8611     def publish_one(self):
8612         # publish a file and create shares, which can then be manipulated
8613[interfaces.py: create IMutableUploadable
8614Kevan Carstensen <kevan@isnotajoke.com>**20100706215217
8615 Ignore-this: bee202ec2bfbd8e41f2d4019cce176c7
8616] hunk ./src/allmydata/interfaces.py 1693
8617         """The upload is finished, and whatever filehandle was in use may be
8618         closed."""
8619 
8620+
8621+class IMutableUploadable(Interface):
8622+    """
8623+    I represent content that is due to be uploaded to a mutable filecap.
8624+    """
8625+    # This is somewhat simpler than the IUploadable interface above
8626+    # because mutable files do not need to be concerned with possibly
8627+    # generating a CHK, nor with per-file keys. It is a subset of the
8628+    # methods in IUploadable, though, so we could just as well implement
8629+    # the mutable uploadables as IUploadables that don't happen to use
8630+    # those methods (with the understanding that the unused methods will
8631+    # never be called on such objects)
8632+    def get_size():
8633+        """
8634+        Returns a Deferred that fires with the size of the content held
8635+        by the uploadable.
8636+        """
8637+
8638+    def read(length):
8639+        """
8640+        Returns a list of strings which, when concatenated, are the next
8641+        length bytes of the file, or fewer if there are fewer bytes
8642+        between the current location and the end of the file.
8643+        """
8644+
8645+    def close():
8646+        """
8647+        The process that used the Uploadable is finished using it, so
8648+        the uploadable may be closed.
8649+        """
8650+
8651 class IUploadResults(Interface):
8652     """I am returned by upload() methods. I contain a number of public
8653     attributes which can be read to determine the results of the upload. Some
8654[mutable/publish.py: add MutableDataHandle and MutableFileHandle
8655Kevan Carstensen <kevan@isnotajoke.com>**20100706215257
8656 Ignore-this: 295ea3bc2a962fd14fb7877fc76c011c
8657] {
8658hunk ./src/allmydata/mutable/publish.py 8
8659 from zope.interface import implements
8660 from twisted.internet import defer
8661 from twisted.python import failure
8662-from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
8663+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \
8664+                                 IMutableUploadable
8665 from allmydata.util import base32, hashutil, mathutil, idlib, log
8666 from allmydata import hashtree, codec
8667 from allmydata.storage.server import si_b2a
8668hunk ./src/allmydata/mutable/publish.py 971
8669             e = UncoordinatedWriteError()
8670         f = failure.Failure(e)
8671         eventually(self.done_deferred.callback, f)
8672+
8673+
8674+class MutableFileHandle:
8675+    """
8676+    I am a mutable uploadable built around a filehandle-like object,
8677+    usually either a StringIO instance or a handle to an actual file.
8678+    """
8679+    implements(IMutableUploadable)
8680+
8681+    def __init__(self, filehandle):
8682+        # The filehandle is defined as a generally file-like object that
8683+        # has these two methods. We don't care beyond that.
8684+        assert hasattr(filehandle, "read")
8685+        assert hasattr(filehandle, "close")
8686+
8687+        self._filehandle = filehandle
8688+
8689+
8690+    def get_size(self):
8691+        """
8692+        I return the amount of data in my filehandle.
8693+        """
8694+        if not hasattr(self, "_size"):
8695+            old_position = self._filehandle.tell()
8696+            # Seek to the end of the file by seeking 0 bytes from the
8697+            # file's end
8698+            self._filehandle.seek(0, os.SEEK_END)
8699+            self._size = self._filehandle.tell()
8700+            # Restore the previous position, in case this was called
8701+            # after a read.
8702+            self._filehandle.seek(old_position)
8703+            assert self._filehandle.tell() == old_position
8704+
8705+        assert hasattr(self, "_size")
8706+        return self._size
8707+
8708+
8709+    def read(self, length):
8710+        """
8711+        I return some data (up to length bytes) from my filehandle.
8712+
8713+        In most cases, I return length bytes. If I don't, it is because
8714+        length is longer than the distance between my current position
8715+        in the file that I represent and its end. In that case, I return
8716+        as many bytes as I can before going over the EOF.
8717+        """
8718+        return [self._filehandle.read(length)]
8719+
8720+
8721+    def close(self):
8722+        """
8723+        I close the underlying filehandle. Any further operations on the
8724+        filehandle fail at this point.
8725+        """
8726+        self._filehandle.close()
8727+
8728+
8729+class MutableDataHandle(MutableFileHandle):
8730+    """
8731+    I am a mutable uploadable built around a string, which I then cast
8732+    into a StringIO and treat as a filehandle.
8733+    """
8734+
8735+    def __init__(self, s):
8736+        # Take a string and return a file-like uploadable.
8737+        assert isinstance(s, str)
8738+
8739+        MutableFileHandle.__init__(self, StringIO(s))
8740}
8741[mutable/publish.py: reorganize in preparation of file-like uploadables
8742Kevan Carstensen <kevan@isnotajoke.com>**20100706215541
8743 Ignore-this: 5346c9f919ee5b73807c8f287c64e8ce
8744] {
8745hunk ./src/allmydata/mutable/publish.py 4
8746 
8747 
8748 import os, struct, time
8749+from StringIO import StringIO
8750 from itertools import count
8751 from zope.interface import implements
8752 from twisted.internet import defer
8753hunk ./src/allmydata/mutable/publish.py 118
8754         self._status.set_helper(False)
8755         self._status.set_progress(0.0)
8756         self._status.set_active(True)
8757-        # We use this to control how the file is written.
8758-        version = self._node.get_version()
8759-        assert version in (SDMF_VERSION, MDMF_VERSION)
8760-        self._version = version
8761+        self._version = self._node.get_version()
8762+        assert self._version in (SDMF_VERSION, MDMF_VERSION)
8763+
8764 
8765     def get_status(self):
8766         return self._status
8767hunk ./src/allmydata/mutable/publish.py 141
8768 
8769         # 0. Setup encoding parameters, encoder, and other such things.
8770         # 1. Encrypt, encode, and publish segments.
8771+        self.data = StringIO(newdata)
8772+        self.datalength = len(newdata)
8773 
8774hunk ./src/allmydata/mutable/publish.py 144
8775-        self.log("starting publish, datalen is %s" % len(newdata))
8776-        self._status.set_size(len(newdata))
8777+        self.log("starting publish, datalen is %s" % self.datalength)
8778+        self._status.set_size(self.datalength)
8779         self._status.set_status("Started")
8780         self._started = time.time()
8781 
8782hunk ./src/allmydata/mutable/publish.py 193
8783         self.full_peerlist = full_peerlist # for use later, immutable
8784         self.bad_peers = set() # peerids who have errbacked/refused requests
8785 
8786-        self.newdata = newdata
8787-
8788         # This will set self.segment_size, self.num_segments, and
8789         # self.fec.
8790         self.setup_encoding_parameters()
8791hunk ./src/allmydata/mutable/publish.py 272
8792                                                 self.required_shares,
8793                                                 self.total_shares,
8794                                                 self.segment_size,
8795-                                                len(self.newdata))
8796+                                                self.datalength)
8797             self.writers[shnum].peerid = peerid
8798             if (peerid, shnum) in self._servermap.servermap:
8799                 old_versionid, old_timestamp = self._servermap.servermap[key]
8800hunk ./src/allmydata/mutable/publish.py 318
8801         if self._version == MDMF_VERSION:
8802             segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
8803         else:
8804-            segment_size = len(self.newdata) # SDMF is only one segment
8805+            segment_size = self.datalength # SDMF is only one segment
8806         # this must be a multiple of self.required_shares
8807         segment_size = mathutil.next_multiple(segment_size,
8808                                               self.required_shares)
8809hunk ./src/allmydata/mutable/publish.py 324
8810         self.segment_size = segment_size
8811         if segment_size:
8812-            self.num_segments = mathutil.div_ceil(len(self.newdata),
8813+            self.num_segments = mathutil.div_ceil(self.datalength,
8814                                                   segment_size)
8815         else:
8816             self.num_segments = 0
8817hunk ./src/allmydata/mutable/publish.py 337
8818             assert self.num_segments in (0, 1) # SDMF
8819         # calculate the tail segment size.
8820 
8821-        if segment_size and self.newdata:
8822-            self.tail_segment_size = len(self.newdata) % segment_size
8823+        if segment_size and self.datalength:
8824+            self.tail_segment_size = self.datalength % segment_size
8825         else:
8826             self.tail_segment_size = 0
8827 
8828hunk ./src/allmydata/mutable/publish.py 438
8829             segsize = self.segment_size
8830 
8831 
8832-        offset = self.segment_size * segnum
8833-        length = segsize + offset
8834         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
8835hunk ./src/allmydata/mutable/publish.py 439
8836-        data = self.newdata[offset:length]
8837+        data = self.data.read(segsize)
8838+
8839         assert len(data) == segsize
8840 
8841         salt = os.urandom(16)
8842hunk ./src/allmydata/mutable/publish.py 502
8843             d.addCallback(self._got_write_answer, writer, started)
8844             d.addErrback(self._connection_problem, writer)
8845             dl.append(d)
8846-            # TODO: Naturally, we need to check on the results of these.
8847         return defer.DeferredList(dl)
8848 
8849 
8850}
8851[test/test_mutable.py: write tests for MutableFileHandle and MutableDataHandle
8852Kevan Carstensen <kevan@isnotajoke.com>**20100706215649
8853 Ignore-this: df719a0c52b4bbe9be4fae206c7ab3e7
8854] {
8855hunk ./src/allmydata/test/test_mutable.py 2
8856 
8857-import struct
8858+import struct, os
8859 from cStringIO import StringIO
8860 from twisted.trial import unittest
8861 from twisted.internet import defer, reactor
8862hunk ./src/allmydata/test/test_mutable.py 26
8863      NeedMoreDataError, UnrecoverableFileError, UncoordinatedWriteError, \
8864      NotEnoughServersError, CorruptShareError
8865 from allmydata.mutable.retrieve import Retrieve
8866-from allmydata.mutable.publish import Publish
8867+from allmydata.mutable.publish import Publish, MutableFileHandle, \
8868+                                      MutableDataHandle
8869 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
8870 from allmydata.mutable.layout import unpack_header, unpack_share, \
8871                                      MDMFSlotReadProxy
8872hunk ./src/allmydata/test/test_mutable.py 2465
8873         d.addCallback(lambda data:
8874             self.failUnlessEqual(data, CONTENTS))
8875         return d
8876+
8877+
8878+class FileHandle(unittest.TestCase):
8879+    def setUp(self):
8880+        self.test_data = "Test Data" * 50000
8881+        self.sio = StringIO(self.test_data)
8882+        self.uploadable = MutableFileHandle(self.sio)
8883+
8884+
8885+    def test_filehandle_read(self):
8886+        self.basedir = "mutable/FileHandle/test_filehandle_read"
8887+        chunk_size = 10
8888+        for i in xrange(0, len(self.test_data), chunk_size):
8889+            data = self.uploadable.read(chunk_size)
8890+            data = "".join(data)
8891+            start = i
8892+            end = i + chunk_size
8893+            self.failUnlessEqual(data, self.test_data[start:end])
8894+
8895+
8896+    def test_filehandle_get_size(self):
8897+        self.basedir = "mutable/FileHandle/test_filehandle_get_size"
8898+        actual_size = len(self.test_data)
8899+        size = self.uploadable.get_size()
8900+        self.failUnlessEqual(size, actual_size)
8901+
8902+
8903+    def test_filehandle_get_size_out_of_order(self):
8904+        # We should be able to call get_size whenever we want without
8905+        # disturbing the location of the seek pointer.
8906+        chunk_size = 100
8907+        data = self.uploadable.read(chunk_size)
8908+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8909+
8910+        # Now get the size.
8911+        size = self.uploadable.get_size()
8912+        self.failUnlessEqual(size, len(self.test_data))
8913+
8914+        # Now get more data. We should be right where we left off.
8915+        more_data = self.uploadable.read(chunk_size)
8916+        start = chunk_size
8917+        end = chunk_size * 2
8918+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
8919+
8920+
8921+    def test_filehandle_file(self):
8922+        # Make sure that the MutableFileHandle works on a file as well
8923+        # as a StringIO object, since in some cases it will be asked to
8924+        # deal with files.
8925+        self.basedir = self.mktemp()
8926+        # necessary? What am I doing wrong here?
8927+        os.mkdir(self.basedir)
8928+        f_path = os.path.join(self.basedir, "test_file")
8929+        f = open(f_path, "w")
8930+        f.write(self.test_data)
8931+        f.close()
8932+        f = open(f_path, "r")
8933+
8934+        uploadable = MutableFileHandle(f)
8935+
8936+        data = uploadable.read(len(self.test_data))
8937+        self.failUnlessEqual("".join(data), self.test_data)
8938+        size = uploadable.get_size()
8939+        self.failUnlessEqual(size, len(self.test_data))
8940+
8941+
8942+    def test_close(self):
8943+        # Make sure that the MutableFileHandle closes its handle when
8944+        # told to do so.
8945+        self.uploadable.close()
8946+        self.failUnless(self.sio.closed)
8947+
8948+
8949+class DataHandle(unittest.TestCase):
8950+    def setUp(self):
8951+        self.test_data = "Test Data" * 50000
8952+        self.uploadable = MutableDataHandle(self.test_data)
8953+
8954+
8955+    def test_datahandle_read(self):
8956+        chunk_size = 10
8957+        for i in xrange(0, len(self.test_data), chunk_size):
8958+            data = self.uploadable.read(chunk_size)
8959+            data = "".join(data)
8960+            start = i
8961+            end = i + chunk_size
8962+            self.failUnlessEqual(data, self.test_data[start:end])
8963+
8964+
8965+    def test_datahandle_get_size(self):
8966+        actual_size = len(self.test_data)
8967+        size = self.uploadable.get_size()
8968+        self.failUnlessEqual(size, actual_size)
8969+
8970+
8971+    def test_datahandle_get_size_out_of_order(self):
8972+        # We should be able to call get_size whenever we want without
8973+        # disturbing the location of the seek pointer.
8974+        chunk_size = 100
8975+        data = self.uploadable.read(chunk_size)
8976+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8977+
8978+        # Now get the size.
8979+        size = self.uploadable.get_size()
8980+        self.failUnlessEqual(size, len(self.test_data))
8981+
8982+        # Now get more data. We should be right where we left off.
8983+        more_data = self.uploadable.read(chunk_size)
8984+        start = chunk_size
8985+        end = chunk_size * 2
8986+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
8987}
8988[Alter tests to work with the new APIs
8989Kevan Carstensen <kevan@isnotajoke.com>**20100708000031
8990 Ignore-this: 1f377904ac61ce40e9a04716fbd2ad95
8991] {
8992hunk ./src/allmydata/test/common.py 12
8993 from allmydata import uri, dirnode, client
8994 from allmydata.introducer.server import IntroducerNode
8995 from allmydata.interfaces import IMutableFileNode, IImmutableFileNode, \
8996-     FileTooLargeError, NotEnoughSharesError, ICheckable
8997+     FileTooLargeError, NotEnoughSharesError, ICheckable, \
8998+     IMutableUploadable
8999 from allmydata.check_results import CheckResults, CheckAndRepairResults, \
9000      DeepCheckResults, DeepCheckAndRepairResults
9001 from allmydata.mutable.common import CorruptShareError
9002hunk ./src/allmydata/test/common.py 18
9003 from allmydata.mutable.layout import unpack_header
9004+from allmydata.mutable.publish import MutableDataHandle
9005 from allmydata.storage.server import storage_index_to_dir
9006 from allmydata.storage.mutable import MutableShareFile
9007 from allmydata.util import hashutil, log, fileutil, pollmixin
9008hunk ./src/allmydata/test/common.py 182
9009         self.init_from_cap(make_mutable_file_cap())
9010     def create(self, contents, key_generator=None, keysize=None):
9011         initial_contents = self._get_initial_contents(contents)
9012-        if len(initial_contents) > self.MUTABLE_SIZELIMIT:
9013+        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
9014             raise FileTooLargeError("SDMF is limited to one segment, and "
9015hunk ./src/allmydata/test/common.py 184
9016-                                    "%d > %d" % (len(initial_contents),
9017+                                    "%d > %d" % (initial_contents.get_size(),
9018                                                  self.MUTABLE_SIZELIMIT))
9019hunk ./src/allmydata/test/common.py 186
9020-        self.all_contents[self.storage_index] = initial_contents
9021+        data = initial_contents.read(initial_contents.get_size())
9022+        data = "".join(data)
9023+        self.all_contents[self.storage_index] = data
9024         return defer.succeed(self)
9025     def _get_initial_contents(self, contents):
9026hunk ./src/allmydata/test/common.py 191
9027-        if isinstance(contents, str):
9028-            return contents
9029         if contents is None:
9030hunk ./src/allmydata/test/common.py 192
9031-            return ""
9032+            return MutableDataHandle("")
9033+
9034+        if IMutableUploadable.providedBy(contents):
9035+            return contents
9036+
9037         assert callable(contents), "%s should be callable, not %s" % \
9038                (contents, type(contents))
9039         return contents(self)
9040hunk ./src/allmydata/test/common.py 309
9041         return defer.succeed(self.all_contents[self.storage_index])
9042 
9043     def overwrite(self, new_contents):
9044-        if len(new_contents) > self.MUTABLE_SIZELIMIT:
9045+        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
9046             raise FileTooLargeError("SDMF is limited to one segment, and "
9047hunk ./src/allmydata/test/common.py 311
9048-                                    "%d > %d" % (len(new_contents),
9049+                                    "%d > %d" % (new_contents.get_size(),
9050                                                  self.MUTABLE_SIZELIMIT))
9051         assert not self.is_readonly()
9052hunk ./src/allmydata/test/common.py 314
9053-        self.all_contents[self.storage_index] = new_contents
9054+        new_data = new_contents.read(new_contents.get_size())
9055+        new_data = "".join(new_data)
9056+        self.all_contents[self.storage_index] = new_data
9057         return defer.succeed(None)
9058     def modify(self, modifier):
9059         # this does not implement FileTooLargeError, but the real one does
9060hunk ./src/allmydata/test/common.py 324
9061     def _modify(self, modifier):
9062         assert not self.is_readonly()
9063         old_contents = self.all_contents[self.storage_index]
9064-        self.all_contents[self.storage_index] = modifier(old_contents, None, True)
9065+        new_data = modifier(old_contents, None, True)
9066+        if new_data is not None:
9067+            new_data = new_data.read(new_data.get_size())
9068+            new_data = "".join(new_data)
9069+        self.all_contents[self.storage_index] = new_data
9070         return None
9071 
9072 def make_mutable_file_cap():
9073hunk ./src/allmydata/test/test_checker.py 11
9074 from allmydata.test.no_network import GridTestMixin
9075 from allmydata.immutable.upload import Data
9076 from allmydata.test.common_web import WebRenderingMixin
9077+from allmydata.mutable.publish import MutableDataHandle
9078 
9079 class FakeClient:
9080     def get_storage_broker(self):
9081hunk ./src/allmydata/test/test_checker.py 291
9082         def _stash_immutable(ur):
9083             self.imm = c0.create_node_from_uri(ur.uri)
9084         d.addCallback(_stash_immutable)
9085-        d.addCallback(lambda ign: c0.create_mutable_file("contents"))
9086+        d.addCallback(lambda ign:
9087+            c0.create_mutable_file(MutableDataHandle("contents")))
9088         def _stash_mutable(node):
9089             self.mut = node
9090         d.addCallback(_stash_mutable)
9091hunk ./src/allmydata/test/test_cli.py 12
9092 from allmydata.util import fileutil, hashutil, base32
9093 from allmydata import uri
9094 from allmydata.immutable import upload
9095+from allmydata.mutable.publish import MutableDataHandle
9096 from allmydata.dirnode import normalize
9097 
9098 # Test that the scripts can be imported -- although the actual tests of their
9099hunk ./src/allmydata/test/test_cli.py 1975
9100         self.set_up_grid()
9101         c0 = self.g.clients[0]
9102         DATA = "data" * 100
9103-        d = c0.create_mutable_file(DATA)
9104+        DATA_uploadable = MutableDataHandle(DATA)
9105+        d = c0.create_mutable_file(DATA_uploadable)
9106         def _stash_uri(n):
9107             self.uri = n.get_uri()
9108         d.addCallback(_stash_uri)
9109hunk ./src/allmydata/test/test_cli.py 2077
9110                                            upload.Data("literal",
9111                                                         convergence="")))
9112         d.addCallback(_stash_uri, "small")
9113-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"1"))
9114+        d.addCallback(lambda ign:
9115+            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
9116         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9117         d.addCallback(_stash_uri, "mutable")
9118 
9119hunk ./src/allmydata/test/test_cli.py 2096
9120         # root/small
9121         # root/mutable
9122 
9123+        # We haven't broken anything yet, so this should all be healthy.
9124         d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
9125                                               self.rooturi))
9126         def _check2((rc, out, err)):
9127hunk ./src/allmydata/test/test_cli.py 2111
9128                             in lines, out)
9129         d.addCallback(_check2)
9130 
9131+        # Similarly, all of these results should be as we expect them to
9132+        # be for a healthy file layout.
9133         d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
9134         def _check_stats((rc, out, err)):
9135             self.failUnlessReallyEqual(err, "")
9136hunk ./src/allmydata/test/test_cli.py 2128
9137             self.failUnlessIn(" 317-1000 : 1    (1000 B, 1000 B)", lines)
9138         d.addCallback(_check_stats)
9139 
9140+        # Now we break things.
9141         def _clobber_shares(ignored):
9142             shares = self.find_uri_shares(self.uris[u"gööd"])
9143             self.failUnlessReallyEqual(len(shares), 10)
9144hunk ./src/allmydata/test/test_cli.py 2147
9145         d.addCallback(_clobber_shares)
9146 
9147         # root
9148-        # root/gööd  [9 shares]
9149+        # root/gööd  [1 missing share]
9150         # root/small
9151         # root/mutable [1 corrupt share]
9152 
9153hunk ./src/allmydata/test/test_cli.py 2153
9154         d.addCallback(lambda ign:
9155                       self.do_cli("deep-check", "--verbose", self.rooturi))
9156+        # This should reveal the missing share, but not the corrupt
9157+        # share, since we didn't tell the deep check operation to also
9158+        # verify.
9159         def _check3((rc, out, err)):
9160             self.failUnlessReallyEqual(err, "")
9161             self.failUnlessReallyEqual(rc, 0)
9162hunk ./src/allmydata/test/test_cli.py 2204
9163                                   "--verbose", "--verify", "--repair",
9164                                   self.rooturi))
9165         def _check6((rc, out, err)):
9166+            # We've just repaired the directory. There is no reason for
9167+            # that repair to be unsuccessful.
9168             self.failUnlessReallyEqual(err, "")
9169             self.failUnlessReallyEqual(rc, 0)
9170             lines = out.splitlines()
9171hunk ./src/allmydata/test/test_deepcheck.py 9
9172 from twisted.internet import threads # CLI tests use deferToThread
9173 from allmydata.immutable import upload
9174 from allmydata.mutable.common import UnrecoverableFileError
9175+from allmydata.mutable.publish import MutableDataHandle
9176 from allmydata.util import idlib
9177 from allmydata.util import base32
9178 from allmydata.scripts import runner
9179hunk ./src/allmydata/test/test_deepcheck.py 38
9180         self.basedir = "deepcheck/MutableChecker/good"
9181         self.set_up_grid()
9182         CONTENTS = "a little bit of data"
9183-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9184+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9185+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9186         def _created(node):
9187             self.node = node
9188             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9189hunk ./src/allmydata/test/test_deepcheck.py 61
9190         self.basedir = "deepcheck/MutableChecker/corrupt"
9191         self.set_up_grid()
9192         CONTENTS = "a little bit of data"
9193-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9194+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9195+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9196         def _stash_and_corrupt(node):
9197             self.node = node
9198             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9199hunk ./src/allmydata/test/test_deepcheck.py 99
9200         self.basedir = "deepcheck/MutableChecker/delete_share"
9201         self.set_up_grid()
9202         CONTENTS = "a little bit of data"
9203-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9204+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9205+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9206         def _stash_and_delete(node):
9207             self.node = node
9208             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9209hunk ./src/allmydata/test/test_deepcheck.py 223
9210             self.root = n
9211             self.root_uri = n.get_uri()
9212         d.addCallback(_created_root)
9213-        d.addCallback(lambda ign: c0.create_mutable_file("mutable file contents"))
9214+        d.addCallback(lambda ign:
9215+            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
9216         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
9217         def _created_mutable(n):
9218             self.mutable = n
9219hunk ./src/allmydata/test/test_deepcheck.py 965
9220     def create_mangled(self, ignored, name):
9221         nodetype, mangletype = name.split("-", 1)
9222         if nodetype == "mutable":
9223-            d = self.g.clients[0].create_mutable_file("mutable file contents")
9224+            mutable_uploadable = MutableDataHandle("mutable file contents")
9225+            d = self.g.clients[0].create_mutable_file(mutable_uploadable)
9226             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
9227         elif nodetype == "large":
9228             large = upload.Data("Lots of data\n" * 1000 + name + "\n", None)
9229hunk ./src/allmydata/test/test_dirnode.py 1305
9230     implements(IMutableFileNode)
9231     counter = 0
9232     def __init__(self, initial_contents=""):
9233-        self.data = self._get_initial_contents(initial_contents)
9234+        data = self._get_initial_contents(initial_contents)
9235+        self.data = data.read(data.get_size())
9236+        self.data = "".join(self.data)
9237+
9238         counter = FakeMutableFile.counter
9239         FakeMutableFile.counter += 1
9240         writekey = hashutil.ssk_writekey_hash(str(counter))
9241hunk ./src/allmydata/test/test_dirnode.py 1355
9242         pass
9243 
9244     def modify(self, modifier):
9245-        self.data = modifier(self.data, None, True)
9246+        data = modifier(self.data, None, True)
9247+        self.data = data.read(data.get_size())
9248+        self.data = "".join(self.data)
9249         return defer.succeed(None)
9250 
9251 class FakeNodeMaker(NodeMaker):
9252hunk ./src/allmydata/test/test_hung_server.py 10
9253 from allmydata.util.consumer import download_to_data
9254 from allmydata.immutable import upload
9255 from allmydata.mutable.common import UnrecoverableFileError
9256+from allmydata.mutable.publish import MutableDataHandle
9257 from allmydata.storage.common import storage_index_to_dir
9258 from allmydata.test.no_network import GridTestMixin
9259 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
9260hunk ./src/allmydata/test/test_hung_server.py 96
9261         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
9262 
9263         if mutable:
9264-            d = nm.create_mutable_file(mutable_plaintext)
9265+            uploadable = MutableDataHandle(mutable_plaintext)
9266+            d = nm.create_mutable_file(uploadable)
9267             def _uploaded_mutable(node):
9268                 self.uri = node.get_uri()
9269                 self.shares = self.find_uri_shares(self.uri)
9270hunk ./src/allmydata/test/test_mutable.py 297
9271             d.addCallback(lambda smap: smap.dump(StringIO()))
9272             d.addCallback(lambda sio:
9273                           self.failUnless("3-of-10" in sio.getvalue()))
9274-            d.addCallback(lambda res: n.overwrite("contents 1"))
9275+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9276             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9277             d.addCallback(lambda res: n.download_best_version())
9278             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9279hunk ./src/allmydata/test/test_mutable.py 304
9280             d.addCallback(lambda res: n.get_size_of_best_version())
9281             d.addCallback(lambda size:
9282                           self.failUnlessEqual(size, len("contents 1")))
9283-            d.addCallback(lambda res: n.overwrite("contents 2"))
9284+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9285             d.addCallback(lambda res: n.download_best_version())
9286             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9287             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9288hunk ./src/allmydata/test/test_mutable.py 308
9289-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9290+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9291             d.addCallback(lambda res: n.download_best_version())
9292             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9293             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9294hunk ./src/allmydata/test/test_mutable.py 320
9295             # mapupdate-to-retrieve data caching (i.e. make the shares larger
9296             # than the default readsize, which is 2000 bytes). A 15kB file
9297             # will have 5kB shares.
9298-            d.addCallback(lambda res: n.overwrite("large size file" * 1000))
9299+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
9300             d.addCallback(lambda res: n.download_best_version())
9301             d.addCallback(lambda res:
9302                           self.failUnlessEqual(res, "large size file" * 1000))
9303hunk ./src/allmydata/test/test_mutable.py 343
9304             # to make them big enough to force the file to be uploaded
9305             # in more than one segment.
9306             big_contents = "contents1" * 100000 # about 900 KiB
9307+            big_contents_uploadable = MutableDataHandle(big_contents)
9308             d.addCallback(lambda ignored:
9309hunk ./src/allmydata/test/test_mutable.py 345
9310-                n.overwrite(big_contents))
9311+                n.overwrite(big_contents_uploadable))
9312             d.addCallback(lambda ignored:
9313                 n.download_best_version())
9314             d.addCallback(lambda data:
9315hunk ./src/allmydata/test/test_mutable.py 355
9316             # segments, so that we make the downloader deal with
9317             # multiple segments.
9318             bigger_contents = "contents2" * 1000000 # about 9MiB
9319+            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
9320             d.addCallback(lambda ignored:
9321hunk ./src/allmydata/test/test_mutable.py 357
9322-                n.overwrite(bigger_contents))
9323+                n.overwrite(bigger_contents_uploadable))
9324             d.addCallback(lambda ignored:
9325                 n.download_best_version())
9326             d.addCallback(lambda data:
9327hunk ./src/allmydata/test/test_mutable.py 368
9328 
9329 
9330     def test_create_with_initial_contents(self):
9331-        d = self.nodemaker.create_mutable_file("contents 1")
9332+        upload1 = MutableDataHandle("contents 1")
9333+        d = self.nodemaker.create_mutable_file(upload1)
9334         def _created(n):
9335             d = n.download_best_version()
9336             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9337hunk ./src/allmydata/test/test_mutable.py 373
9338-            d.addCallback(lambda res: n.overwrite("contents 2"))
9339+            upload2 = MutableDataHandle("contents 2")
9340+            d.addCallback(lambda res: n.overwrite(upload2))
9341             d.addCallback(lambda res: n.download_best_version())
9342             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9343             return d
9344hunk ./src/allmydata/test/test_mutable.py 380
9345         d.addCallback(_created)
9346         return d
9347+    test_create_with_initial_contents.timeout = 15
9348 
9349 
9350     def test_create_mdmf_with_initial_contents(self):
9351hunk ./src/allmydata/test/test_mutable.py 385
9352         initial_contents = "foobarbaz" * 131072 # 900KiB
9353-        d = self.nodemaker.create_mutable_file(initial_contents,
9354+        initial_contents_uploadable = MutableDataHandle(initial_contents)
9355+        d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
9356                                                version=MDMF_VERSION)
9357         def _created(n):
9358             d = n.download_best_version()
9359hunk ./src/allmydata/test/test_mutable.py 392
9360             d.addCallback(lambda data:
9361                 self.failUnlessEqual(data, initial_contents))
9362+            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
9363             d.addCallback(lambda ignored:
9364hunk ./src/allmydata/test/test_mutable.py 394
9365-                n.overwrite(initial_contents + "foobarbaz"))
9366+                n.overwrite(uploadable2))
9367             d.addCallback(lambda ignored:
9368                 n.download_best_version())
9369             d.addCallback(lambda data:
9370hunk ./src/allmydata/test/test_mutable.py 413
9371             key = n.get_writekey()
9372             self.failUnless(isinstance(key, str), key)
9373             self.failUnlessEqual(len(key), 16) # AES key size
9374-            return data
9375+            return MutableDataHandle(data)
9376         d = self.nodemaker.create_mutable_file(_make_contents)
9377         def _created(n):
9378             return n.download_best_version()
9379hunk ./src/allmydata/test/test_mutable.py 429
9380             key = n.get_writekey()
9381             self.failUnless(isinstance(key, str), key)
9382             self.failUnlessEqual(len(key), 16)
9383-            return data
9384+            return MutableDataHandle(data)
9385         d = self.nodemaker.create_mutable_file(_make_contents,
9386                                                version=MDMF_VERSION)
9387         d.addCallback(lambda n:
9388hunk ./src/allmydata/test/test_mutable.py 441
9389 
9390     def test_create_with_too_large_contents(self):
9391         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9392-        d = self.nodemaker.create_mutable_file(BIG)
9393+        BIG_uploadable = MutableDataHandle(BIG)
9394+        d = self.nodemaker.create_mutable_file(BIG_uploadable)
9395         def _created(n):
9396hunk ./src/allmydata/test/test_mutable.py 444
9397-            d = n.overwrite(BIG)
9398+            other_BIG_uploadable = MutableDataHandle(BIG)
9399+            d = n.overwrite(other_BIG_uploadable)
9400             return d
9401         d.addCallback(_created)
9402         return d
9403hunk ./src/allmydata/test/test_mutable.py 459
9404 
9405     def test_modify(self):
9406         def _modifier(old_contents, servermap, first_time):
9407-            return old_contents + "line2"
9408+            new_contents = old_contents + "line2"
9409+            return MutableDataHandle(new_contents)
9410         def _non_modifier(old_contents, servermap, first_time):
9411hunk ./src/allmydata/test/test_mutable.py 462
9412-            return old_contents
9413+            return MutableDataHandle(old_contents)
9414         def _none_modifier(old_contents, servermap, first_time):
9415             return None
9416         def _error_modifier(old_contents, servermap, first_time):
9417hunk ./src/allmydata/test/test_mutable.py 468
9418             raise ValueError("oops")
9419         def _toobig_modifier(old_contents, servermap, first_time):
9420-            return "b" * (self.OLD_MAX_SEGMENT_SIZE+1)
9421+            new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9422+            return MutableDataHandle(new_content)
9423         calls = []
9424         def _ucw_error_modifier(old_contents, servermap, first_time):
9425             # simulate an UncoordinatedWriteError once
9426hunk ./src/allmydata/test/test_mutable.py 476
9427             calls.append(1)
9428             if len(calls) <= 1:
9429                 raise UncoordinatedWriteError("simulated")
9430-            return old_contents + "line3"
9431+            new_contents = old_contents + "line3"
9432+            return MutableDataHandle(new_contents)
9433         def _ucw_error_non_modifier(old_contents, servermap, first_time):
9434             # simulate an UncoordinatedWriteError once, and don't actually
9435             # modify the contents on subsequent invocations
9436hunk ./src/allmydata/test/test_mutable.py 484
9437             calls.append(1)
9438             if len(calls) <= 1:
9439                 raise UncoordinatedWriteError("simulated")
9440-            return old_contents
9441+            return MutableDataHandle(old_contents)
9442 
9443hunk ./src/allmydata/test/test_mutable.py 486
9444-        d = self.nodemaker.create_mutable_file("line1")
9445+        initial_contents = "line1"
9446+        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
9447         def _created(n):
9448             d = n.modify(_modifier)
9449             d.addCallback(lambda res: n.download_best_version())
9450hunk ./src/allmydata/test/test_mutable.py 548
9451 
9452     def test_modify_backoffer(self):
9453         def _modifier(old_contents, servermap, first_time):
9454-            return old_contents + "line2"
9455+            return MutableDataHandle(old_contents + "line2")
9456         calls = []
9457         def _ucw_error_modifier(old_contents, servermap, first_time):
9458             # simulate an UncoordinatedWriteError once
9459hunk ./src/allmydata/test/test_mutable.py 555
9460             calls.append(1)
9461             if len(calls) <= 1:
9462                 raise UncoordinatedWriteError("simulated")
9463-            return old_contents + "line3"
9464+            return MutableDataHandle(old_contents + "line3")
9465         def _always_ucw_error_modifier(old_contents, servermap, first_time):
9466             raise UncoordinatedWriteError("simulated")
9467         def _backoff_stopper(node, f):
9468hunk ./src/allmydata/test/test_mutable.py 570
9469         giveuper._delay = 0.1
9470         giveuper.factor = 1
9471 
9472-        d = self.nodemaker.create_mutable_file("line1")
9473+        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
9474         def _created(n):
9475             d = n.modify(_modifier)
9476             d.addCallback(lambda res: n.download_best_version())
9477hunk ./src/allmydata/test/test_mutable.py 620
9478             d.addCallback(lambda smap: smap.dump(StringIO()))
9479             d.addCallback(lambda sio:
9480                           self.failUnless("3-of-10" in sio.getvalue()))
9481-            d.addCallback(lambda res: n.overwrite("contents 1"))
9482+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9483             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9484             d.addCallback(lambda res: n.download_best_version())
9485             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9486hunk ./src/allmydata/test/test_mutable.py 624
9487-            d.addCallback(lambda res: n.overwrite("contents 2"))
9488+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9489             d.addCallback(lambda res: n.download_best_version())
9490             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9491             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9492hunk ./src/allmydata/test/test_mutable.py 628
9493-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9494+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9495             d.addCallback(lambda res: n.download_best_version())
9496             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9497             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9498hunk ./src/allmydata/test/test_mutable.py 646
9499         # publish a file and create shares, which can then be manipulated
9500         # later.
9501         self.CONTENTS = "New contents go here" * 1000
9502+        self.uploadable = MutableDataHandle(self.CONTENTS)
9503         self._storage = FakeStorage()
9504         self._nodemaker = make_nodemaker(self._storage)
9505         self._storage_broker = self._nodemaker.storage_broker
9506hunk ./src/allmydata/test/test_mutable.py 650
9507-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9508+        d = self._nodemaker.create_mutable_file(self.uploadable)
9509         def _created(node):
9510             self._fn = node
9511             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9512hunk ./src/allmydata/test/test_mutable.py 662
9513         # an MDMF file.
9514         # self.CONTENTS should have more than one segment.
9515         self.CONTENTS = "This is an MDMF file" * 100000
9516+        self.uploadable = MutableDataHandle(self.CONTENTS)
9517         self._storage = FakeStorage()
9518         self._nodemaker = make_nodemaker(self._storage)
9519         self._storage_broker = self._nodemaker.storage_broker
9520hunk ./src/allmydata/test/test_mutable.py 666
9521-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
9522+        d = self._nodemaker.create_mutable_file(self.uploadable, version=MDMF_VERSION)
9523         def _created(node):
9524             self._fn = node
9525             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9526hunk ./src/allmydata/test/test_mutable.py 678
9527         # like publish_one, except that the result is guaranteed to be
9528         # an SDMF file
9529         self.CONTENTS = "This is an SDMF file" * 1000
9530+        self.uploadable = MutableDataHandle(self.CONTENTS)
9531         self._storage = FakeStorage()
9532         self._nodemaker = make_nodemaker(self._storage)
9533         self._storage_broker = self._nodemaker.storage_broker
9534hunk ./src/allmydata/test/test_mutable.py 682
9535-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
9536+        d = self._nodemaker.create_mutable_file(self.uploadable, version=SDMF_VERSION)
9537         def _created(node):
9538             self._fn = node
9539             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9540hunk ./src/allmydata/test/test_mutable.py 696
9541                          "Contents 2",
9542                          "Contents 3a",
9543                          "Contents 3b"]
9544+        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
9545         self._copied_shares = {}
9546         self._storage = FakeStorage()
9547         self._nodemaker = make_nodemaker(self._storage)
9548hunk ./src/allmydata/test/test_mutable.py 700
9549-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
9550+        d = self._nodemaker.create_mutable_file(self.uploadables[0], version=version) # seqnum=1
9551         def _created(node):
9552             self._fn = node
9553             # now create multiple versions of the same file, and accumulate
9554hunk ./src/allmydata/test/test_mutable.py 707
9555             # their shares, so we can mix and match them later.
9556             d = defer.succeed(None)
9557             d.addCallback(self._copy_shares, 0)
9558-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[1])) #s2
9559+            d.addCallback(lambda res: node.overwrite(self.uploadables[1])) #s2
9560             d.addCallback(self._copy_shares, 1)
9561hunk ./src/allmydata/test/test_mutable.py 709
9562-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[2])) #s3
9563+            d.addCallback(lambda res: node.overwrite(self.uploadables[2])) #s3
9564             d.addCallback(self._copy_shares, 2)
9565hunk ./src/allmydata/test/test_mutable.py 711
9566-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[3])) #s4a
9567+            d.addCallback(lambda res: node.overwrite(self.uploadables[3])) #s4a
9568             d.addCallback(self._copy_shares, 3)
9569             # now we replace all the shares with version s3, and upload a new
9570             # version to get s4b.
9571hunk ./src/allmydata/test/test_mutable.py 717
9572             rollback = dict([(i,2) for i in range(10)])
9573             d.addCallback(lambda res: self._set_versions(rollback))
9574-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[4])) #s4b
9575+            d.addCallback(lambda res: node.overwrite(self.uploadables[4])) #s4b
9576             d.addCallback(self._copy_shares, 4)
9577             # we leave the storage in state 4
9578             return d
9579hunk ./src/allmydata/test/test_mutable.py 826
9580         # create a new file, which is large enough to knock the privkey out
9581         # of the early part of the file
9582         LARGE = "These are Larger contents" * 200 # about 5KB
9583-        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE))
9584+        LARGE_uploadable = MutableDataHandle(LARGE)
9585+        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
9586         def _created(large_fn):
9587             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
9588             return self.make_servermap(MODE_WRITE, large_fn2)
9589hunk ./src/allmydata/test/test_mutable.py 1842
9590 class MultipleEncodings(unittest.TestCase):
9591     def setUp(self):
9592         self.CONTENTS = "New contents go here"
9593+        self.uploadable = MutableDataHandle(self.CONTENTS)
9594         self._storage = FakeStorage()
9595         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
9596         self._storage_broker = self._nodemaker.storage_broker
9597hunk ./src/allmydata/test/test_mutable.py 1846
9598-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9599+        d = self._nodemaker.create_mutable_file(self.uploadable)
9600         def _created(node):
9601             self._fn = node
9602         d.addCallback(_created)
9603hunk ./src/allmydata/test/test_mutable.py 1872
9604         s = self._storage
9605         s._peers = {} # clear existing storage
9606         p2 = Publish(fn2, self._storage_broker, None)
9607-        d = p2.publish(data)
9608+        uploadable = MutableDataHandle(data)
9609+        d = p2.publish(uploadable)
9610         def _published(res):
9611             shares = s._peers
9612             s._peers = {}
9613hunk ./src/allmydata/test/test_mutable.py 2049
9614         self._set_versions(target)
9615 
9616         def _modify(oldversion, servermap, first_time):
9617-            return oldversion + " modified"
9618+            return MutableDataHandle(oldversion + " modified")
9619         d = self._fn.modify(_modify)
9620         d.addCallback(lambda res: self._fn.download_best_version())
9621         expected = self.CONTENTS[2] + " modified"
9622hunk ./src/allmydata/test/test_mutable.py 2175
9623         self.basedir = "mutable/Problems/test_publish_surprise"
9624         self.set_up_grid()
9625         nm = self.g.clients[0].nodemaker
9626-        d = nm.create_mutable_file("contents 1")
9627+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9628         def _created(n):
9629             d = defer.succeed(None)
9630             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9631hunk ./src/allmydata/test/test_mutable.py 2185
9632             d.addCallback(_got_smap1)
9633             # then modify the file, leaving the old map untouched
9634             d.addCallback(lambda res: log.msg("starting winning write"))
9635-            d.addCallback(lambda res: n.overwrite("contents 2"))
9636+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9637             # now attempt to modify the file with the old servermap. This
9638             # will look just like an uncoordinated write, in which every
9639             # single share got updated between our mapupdate and our publish
9640hunk ./src/allmydata/test/test_mutable.py 2194
9641                           self.shouldFail(UncoordinatedWriteError,
9642                                           "test_publish_surprise", None,
9643                                           n.upload,
9644-                                          "contents 2a", self.old_map))
9645+                                          MutableDataHandle("contents 2a"), self.old_map))
9646             return d
9647         d.addCallback(_created)
9648         return d
9649hunk ./src/allmydata/test/test_mutable.py 2203
9650         self.basedir = "mutable/Problems/test_retrieve_surprise"
9651         self.set_up_grid()
9652         nm = self.g.clients[0].nodemaker
9653-        d = nm.create_mutable_file("contents 1")
9654+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9655         def _created(n):
9656             d = defer.succeed(None)
9657             d.addCallback(lambda res: n.get_servermap(MODE_READ))
9658hunk ./src/allmydata/test/test_mutable.py 2213
9659             d.addCallback(_got_smap1)
9660             # then modify the file, leaving the old map untouched
9661             d.addCallback(lambda res: log.msg("starting winning write"))
9662-            d.addCallback(lambda res: n.overwrite("contents 2"))
9663+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9664             # now attempt to retrieve the old version with the old servermap.
9665             # This will look like someone has changed the file since we
9666             # updated the servermap.
9667hunk ./src/allmydata/test/test_mutable.py 2241
9668         self.basedir = "mutable/Problems/test_unexpected_shares"
9669         self.set_up_grid()
9670         nm = self.g.clients[0].nodemaker
9671-        d = nm.create_mutable_file("contents 1")
9672+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9673         def _created(n):
9674             d = defer.succeed(None)
9675             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9676hunk ./src/allmydata/test/test_mutable.py 2253
9677                 self.g.remove_server(peer0)
9678                 # then modify the file, leaving the old map untouched
9679                 log.msg("starting winning write")
9680-                return n.overwrite("contents 2")
9681+                return n.overwrite(MutableDataHandle("contents 2"))
9682             d.addCallback(_got_smap1)
9683             # now attempt to modify the file with the old servermap. This
9684             # will look just like an uncoordinated write, in which every
9685hunk ./src/allmydata/test/test_mutable.py 2263
9686                           self.shouldFail(UncoordinatedWriteError,
9687                                           "test_surprise", None,
9688                                           n.upload,
9689-                                          "contents 2a", self.old_map))
9690+                                          MutableDataHandle("contents 2a"), self.old_map))
9691             return d
9692         d.addCallback(_created)
9693         return d
9694hunk ./src/allmydata/test/test_mutable.py 2267
9695+    test_unexpected_shares.timeout = 15
9696 
9697     def test_bad_server(self):
9698         # Break one server, then create the file: the initial publish should
9699hunk ./src/allmydata/test/test_mutable.py 2303
9700         d.addCallback(_break_peer0)
9701         # now "create" the file, using the pre-established key, and let the
9702         # initial publish finally happen
9703-        d.addCallback(lambda res: nm.create_mutable_file("contents 1"))
9704+        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
9705         # that ought to work
9706         def _got_node(n):
9707             d = n.download_best_version()
9708hunk ./src/allmydata/test/test_mutable.py 2312
9709             def _break_peer1(res):
9710                 self.connection1.broken = True
9711             d.addCallback(_break_peer1)
9712-            d.addCallback(lambda res: n.overwrite("contents 2"))
9713+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9714             # that ought to work too
9715             d.addCallback(lambda res: n.download_best_version())
9716             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9717hunk ./src/allmydata/test/test_mutable.py 2344
9718         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
9719         self.g.break_server(peerids[0])
9720 
9721-        d = nm.create_mutable_file("contents 1")
9722+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9723         def _created(n):
9724             d = n.download_best_version()
9725             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9726hunk ./src/allmydata/test/test_mutable.py 2352
9727             def _break_second_server(res):
9728                 self.g.break_server(peerids[1])
9729             d.addCallback(_break_second_server)
9730-            d.addCallback(lambda res: n.overwrite("contents 2"))
9731+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9732             # that ought to work too
9733             d.addCallback(lambda res: n.download_best_version())
9734             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9735hunk ./src/allmydata/test/test_mutable.py 2371
9736         d = self.shouldFail(NotEnoughServersError,
9737                             "test_publish_all_servers_bad",
9738                             "Ran out of non-bad servers",
9739-                            nm.create_mutable_file, "contents")
9740+                            nm.create_mutable_file, MutableDataHandle("contents"))
9741         return d
9742 
9743     def test_publish_no_servers(self):
9744hunk ./src/allmydata/test/test_mutable.py 2383
9745         d = self.shouldFail(NotEnoughServersError,
9746                             "test_publish_no_servers",
9747                             "Ran out of non-bad servers",
9748-                            nm.create_mutable_file, "contents")
9749+                            nm.create_mutable_file, MutableDataHandle("contents"))
9750         return d
9751     test_publish_no_servers.timeout = 30
9752 
9753hunk ./src/allmydata/test/test_mutable.py 2401
9754         # we need some contents that are large enough to push the privkey out
9755         # of the early part of the file
9756         LARGE = "These are Larger contents" * 2000 # about 50KB
9757-        d = nm.create_mutable_file(LARGE)
9758+        LARGE_uploadable = MutableDataHandle(LARGE)
9759+        d = nm.create_mutable_file(LARGE_uploadable)
9760         def _created(n):
9761             self.uri = n.get_uri()
9762             self.n2 = nm.create_from_cap(self.uri)
9763hunk ./src/allmydata/test/test_mutable.py 2438
9764         self.set_up_grid(num_servers=20)
9765         nm = self.g.clients[0].nodemaker
9766         LARGE = "These are Larger contents" * 2000 # about 50KiB
9767+        LARGE_uploadable = MutableDataHandle(LARGE)
9768         nm._node_cache = DevNullDictionary() # disable the nodecache
9769 
9770hunk ./src/allmydata/test/test_mutable.py 2441
9771-        d = nm.create_mutable_file(LARGE)
9772+        d = nm.create_mutable_file(LARGE_uploadable)
9773         def _created(n):
9774             self.uri = n.get_uri()
9775             self.n2 = nm.create_from_cap(self.uri)
9776hunk ./src/allmydata/test/test_mutable.py 2464
9777         self.set_up_grid(num_servers=20)
9778         nm = self.g.clients[0].nodemaker
9779         CONTENTS = "contents" * 2000
9780-        d = nm.create_mutable_file(CONTENTS)
9781+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9782+        d = nm.create_mutable_file(CONTENTS_uploadable)
9783         def _created(node):
9784             self._node = node
9785         d.addCallback(_created)
9786hunk ./src/allmydata/test/test_system.py 22
9787 from allmydata.monitor import Monitor
9788 from allmydata.mutable.common import NotWriteableError
9789 from allmydata.mutable import layout as mutable_layout
9790+from allmydata.mutable.publish import MutableDataHandle
9791 from foolscap.api import DeadReferenceError
9792 from twisted.python.failure import Failure
9793 from twisted.web.client import getPage
9794hunk ./src/allmydata/test/test_system.py 460
9795     def test_mutable(self):
9796         self.basedir = "system/SystemTest/test_mutable"
9797         DATA = "initial contents go here."  # 25 bytes % 3 != 0
9798+        DATA_uploadable = MutableDataHandle(DATA)
9799         NEWDATA = "new contents yay"
9800hunk ./src/allmydata/test/test_system.py 462
9801+        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
9802         NEWERDATA = "this is getting old"
9803hunk ./src/allmydata/test/test_system.py 464
9804+        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
9805 
9806         d = self.set_up_nodes(use_key_generator=True)
9807 
9808hunk ./src/allmydata/test/test_system.py 471
9809         def _create_mutable(res):
9810             c = self.clients[0]
9811             log.msg("starting create_mutable_file")
9812-            d1 = c.create_mutable_file(DATA)
9813+            d1 = c.create_mutable_file(DATA_uploadable)
9814             def _done(res):
9815                 log.msg("DONE: %s" % (res,))
9816                 self._mutable_node_1 = res
9817hunk ./src/allmydata/test/test_system.py 558
9818             self.failUnlessEqual(res, DATA)
9819             # replace the data
9820             log.msg("starting replace1")
9821-            d1 = newnode.overwrite(NEWDATA)
9822+            d1 = newnode.overwrite(NEWDATA_uploadable)
9823             d1.addCallback(lambda res: newnode.download_best_version())
9824             return d1
9825         d.addCallback(_check_download_3)
9826hunk ./src/allmydata/test/test_system.py 572
9827             newnode2 = self.clients[3].create_node_from_uri(uri)
9828             self._newnode3 = self.clients[3].create_node_from_uri(uri)
9829             log.msg("starting replace2")
9830-            d1 = newnode1.overwrite(NEWERDATA)
9831+            d1 = newnode1.overwrite(NEWERDATA_uploadable)
9832             d1.addCallback(lambda res: newnode2.download_best_version())
9833             return d1
9834         d.addCallback(_check_download_4)
9835hunk ./src/allmydata/test/test_system.py 642
9836         def _check_empty_file(res):
9837             # make sure we can create empty files, this usually screws up the
9838             # segsize math
9839-            d1 = self.clients[2].create_mutable_file("")
9840+            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
9841             d1.addCallback(lambda newnode: newnode.download_best_version())
9842             d1.addCallback(lambda res: self.failUnlessEqual("", res))
9843             return d1
9844hunk ./src/allmydata/test/test_system.py 673
9845                                  self.key_generator_svc.key_generator.pool_size + size_delta)
9846 
9847         d.addCallback(check_kg_poolsize, 0)
9848-        d.addCallback(lambda junk: self.clients[3].create_mutable_file('hello, world'))
9849+        d.addCallback(lambda junk:
9850+            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
9851         d.addCallback(check_kg_poolsize, -1)
9852         d.addCallback(lambda junk: self.clients[3].create_dirnode())
9853         d.addCallback(check_kg_poolsize, -2)
9854hunk ./src/allmydata/test/test_web.py 3183
9855         def _stash_mutable_uri(n, which):
9856             self.uris[which] = n.get_uri()
9857             assert isinstance(self.uris[which], str)
9858-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9859+        d.addCallback(lambda ign:
9860+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9861         d.addCallback(_stash_mutable_uri, "corrupt")
9862         d.addCallback(lambda ign:
9863                       c0.upload(upload.Data("literal", convergence="")))
9864hunk ./src/allmydata/test/test_web.py 3330
9865         def _stash_mutable_uri(n, which):
9866             self.uris[which] = n.get_uri()
9867             assert isinstance(self.uris[which], str)
9868-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9869+        d.addCallback(lambda ign:
9870+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9871         d.addCallback(_stash_mutable_uri, "corrupt")
9872 
9873         def _compute_fileurls(ignored):
9874hunk ./src/allmydata/test/test_web.py 3993
9875         def _stash_mutable_uri(n, which):
9876             self.uris[which] = n.get_uri()
9877             assert isinstance(self.uris[which], str)
9878-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
9879+        d.addCallback(lambda ign:
9880+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
9881         d.addCallback(_stash_mutable_uri, "mutable")
9882 
9883         def _compute_fileurls(ignored):
9884hunk ./src/allmydata/test/test_web.py 4093
9885                                                         convergence="")))
9886         d.addCallback(_stash_uri, "small")
9887 
9888-        d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
9889+        d.addCallback(lambda ign:
9890+            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
9891         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9892         d.addCallback(_stash_uri, "mutable")
9893 
9894}
9895[Alter mutable files to use file-like objects for publishing instead of strings.
9896Kevan Carstensen <kevan@isnotajoke.com>**20100708000732
9897 Ignore-this: 8dd07d95386b6d540bc21289f981ebd0
9898] {
9899hunk ./src/allmydata/dirnode.py 11
9900 from allmydata.mutable.common import NotWriteableError
9901 from allmydata.mutable.filenode import MutableFileNode
9902 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
9903+from allmydata.mutable.publish import MutableDataHandle
9904 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
9905      IImmutableFileNode, IMutableFileNode, \
9906      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
9907hunk ./src/allmydata/dirnode.py 104
9908 
9909         del children[self.name]
9910         new_contents = self.node._pack_contents(children)
9911-        return new_contents
9912+        uploadable = MutableDataHandle(new_contents)
9913+        return uploadable
9914 
9915 
9916 class MetadataSetter:
9917hunk ./src/allmydata/dirnode.py 130
9918 
9919         children[name] = (child, metadata)
9920         new_contents = self.node._pack_contents(children)
9921-        return new_contents
9922+        uploadable = MutableDataHandle(new_contents)
9923+        return uploadable
9924 
9925 
9926 class Adder:
9927hunk ./src/allmydata/dirnode.py 175
9928 
9929             children[name] = (child, metadata)
9930         new_contents = self.node._pack_contents(children)
9931-        return new_contents
9932+        uploadable = MutableDataHandle(new_contents)
9933+        return uploadable
9934 
9935 def _encrypt_rw_uri(writekey, rw_uri):
9936     precondition(isinstance(rw_uri, str), rw_uri)
9937hunk ./src/allmydata/mutable/filenode.py 7
9938 from zope.interface import implements
9939 from twisted.internet import defer, reactor
9940 from foolscap.api import eventually
9941-from allmydata.interfaces import IMutableFileNode, \
9942-     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
9943+from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
9944+                                 NotEnoughSharesError, \
9945+                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
9946 from allmydata.util import hashutil, log
9947 from allmydata.util.assertutil import precondition
9948 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
9949hunk ./src/allmydata/mutable/filenode.py 16
9950 from allmydata.monitor import Monitor
9951 from pycryptopp.cipher.aes import AES
9952 
9953-from allmydata.mutable.publish import Publish
9954+from allmydata.mutable.publish import Publish, MutableFileHandle, \
9955+                                      MutableDataHandle
9956 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
9957      ResponseCache, UncoordinatedWriteError
9958 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
9959hunk ./src/allmydata/mutable/filenode.py 133
9960         return self._upload(initial_contents, None)
9961 
9962     def _get_initial_contents(self, contents):
9963-        if isinstance(contents, str):
9964-            return contents
9965         if contents is None:
9966hunk ./src/allmydata/mutable/filenode.py 134
9967-            return ""
9968+            return MutableDataHandle("")
9969+
9970+        if IMutableUploadable.providedBy(contents):
9971+            return contents
9972+
9973         assert callable(contents), "%s should be callable, not %s" % \
9974                (contents, type(contents))
9975         return contents(self)
9976hunk ./src/allmydata/mutable/filenode.py 353
9977     def overwrite(self, new_contents):
9978         return self._do_serialized(self._overwrite, new_contents)
9979     def _overwrite(self, new_contents):
9980+        assert IMutableUploadable.providedBy(new_contents)
9981+
9982         servermap = ServerMap()
9983         d = self._update_servermap(servermap, mode=MODE_WRITE)
9984         d.addCallback(lambda ignored: self._upload(new_contents, servermap))
9985hunk ./src/allmydata/mutable/filenode.py 431
9986                 # recovery when it observes UCWE, we need to do a second
9987                 # publish. See #551 for details. We'll basically loop until
9988                 # we managed an uncontested publish.
9989-                new_contents = old_contents
9990-            precondition(isinstance(new_contents, str),
9991-                         "Modifier function must return a string or None")
9992+                old_uploadable = MutableDataHandle(old_contents)
9993+                new_contents = old_uploadable
9994+            precondition((IMutableUploadable.providedBy(new_contents) or
9995+                          new_contents is None),
9996+                         "Modifier function must return an IMutableUploadable "
9997+                         "or None")
9998             return self._upload(new_contents, servermap)
9999         d.addCallback(_apply)
10000         return d
10001hunk ./src/allmydata/mutable/filenode.py 472
10002         return self._do_serialized(self._upload, new_contents, servermap)
10003     def _upload(self, new_contents, servermap):
10004         assert self._pubkey, "update_servermap must be called before publish"
10005+        assert IMutableUploadable.providedBy(new_contents)
10006+
10007         p = Publish(self, self._storage_broker, servermap)
10008         if self._history:
10009hunk ./src/allmydata/mutable/filenode.py 476
10010-            self._history.notify_publish(p.get_status(), len(new_contents))
10011+            self._history.notify_publish(p.get_status(), new_contents.get_size())
10012         d = p.publish(new_contents)
10013hunk ./src/allmydata/mutable/filenode.py 478
10014-        d.addCallback(self._did_upload, len(new_contents))
10015+        d.addCallback(self._did_upload, new_contents.get_size())
10016         return d
10017     def _did_upload(self, res, size):
10018         self._most_recent_size = size
10019hunk ./src/allmydata/mutable/publish.py 141
10020 
10021         # 0. Setup encoding parameters, encoder, and other such things.
10022         # 1. Encrypt, encode, and publish segments.
10023-        self.data = StringIO(newdata)
10024-        self.datalength = len(newdata)
10025+        assert IMutableUploadable.providedBy(newdata)
10026+
10027+        self.data = newdata
10028+        self.datalength = newdata.get_size()
10029 
10030         self.log("starting publish, datalen is %s" % self.datalength)
10031         self._status.set_size(self.datalength)
10032hunk ./src/allmydata/mutable/publish.py 442
10033 
10034         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
10035         data = self.data.read(segsize)
10036+        # XXX: This is dumb. Why return a list?
10037+        data = "".join(data)
10038 
10039         assert len(data) == segsize
10040 
10041hunk ./src/allmydata/mutable/repairer.py 5
10042 from zope.interface import implements
10043 from twisted.internet import defer
10044 from allmydata.interfaces import IRepairResults, ICheckResults
10045+from allmydata.mutable.publish import MutableDataHandle
10046 
10047 class RepairResults:
10048     implements(IRepairResults)
10049hunk ./src/allmydata/mutable/repairer.py 108
10050             raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.")
10051 
10052         d = self.node.download_version(smap, best_version, fetch_privkey=True)
10053+        d.addCallback(lambda data:
10054+            MutableDataHandle(data))
10055         d.addCallback(self.node.upload, smap)
10056         d.addCallback(self.get_results, smap)
10057         return d
10058hunk ./src/allmydata/nodemaker.py 9
10059 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
10060 from allmydata.immutable.upload import Data
10061 from allmydata.mutable.filenode import MutableFileNode
10062+from allmydata.mutable.publish import MutableDataHandle
10063 from allmydata.dirnode import DirectoryNode, pack_children
10064 from allmydata.unknown import UnknownNode
10065 from allmydata import uri
10066merger 0.0 (
10067merger 0.0 (
10068hunk ./src/allmydata/nodemaker.py 107
10069-                                     pack_children(n, initial_children))
10070+                                     pack_children(n, initial_children),
10071+                                     version)
10072hunk ./src/allmydata/nodemaker.py 107
10073-                                     pack_children(n, initial_children))
10074+                                     pack_children(initial_children, n.get_writekey()))
10075)
10076hunk ./src/allmydata/nodemaker.py 107
10077-                                     pack_children(n, initial_children),
10078+                                     MutableDataHandle(
10079+                                        pack_children(n, initial_children)),
10080)
10081hunk ./src/allmydata/web/filenode.py 12
10082 from allmydata.interfaces import ExistingChildError
10083 from allmydata.monitor import Monitor
10084 from allmydata.immutable.upload import FileHandle
10085+from allmydata.mutable.publish import MutableFileHandle
10086 from allmydata.util import log, base32
10087 
10088 from allmydata.web.common import text_plain, WebError, RenderMixin, \
10089hunk ./src/allmydata/web/filenode.py 27
10090         # a new file is being uploaded in our place.
10091         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10092         if mutable:
10093-            req.content.seek(0)
10094-            data = req.content.read()
10095+            data = MutableFileHandle(req.content)
10096             d = client.create_mutable_file(data)
10097             def _uploaded(newnode):
10098                 d2 = self.parentnode.set_node(self.name, newnode,
10099hunk ./src/allmydata/web/filenode.py 61
10100         d.addCallback(lambda res: childnode.get_uri())
10101         return d
10102 
10103-    def _read_data_from_formpost(self, req):
10104-        # SDMF: files are small, and we can only upload data, so we read
10105-        # the whole file into memory before uploading.
10106-        contents = req.fields["file"]
10107-        contents.file.seek(0)
10108-        data = contents.file.read()
10109-        return data
10110 
10111     def replace_me_with_a_formpost(self, req, client, replace):
10112         # create a new file, maybe mutable, maybe immutable
10113hunk ./src/allmydata/web/filenode.py 66
10114         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10115 
10116+        # create an immutable file
10117+        contents = req.fields["file"]
10118         if mutable:
10119hunk ./src/allmydata/web/filenode.py 69
10120-            data = self._read_data_from_formpost(req)
10121-            d = client.create_mutable_file(data)
10122+            uploadable = MutableFileHandle(contents.file)
10123+            d = client.create_mutable_file(uploadable)
10124             def _uploaded(newnode):
10125                 d2 = self.parentnode.set_node(self.name, newnode,
10126                                               overwrite=replace)
10127hunk ./src/allmydata/web/filenode.py 78
10128                 return d2
10129             d.addCallback(_uploaded)
10130             return d
10131-        # create an immutable file
10132-        contents = req.fields["file"]
10133+
10134         uploadable = FileHandle(contents.file, convergence=client.convergence)
10135         d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
10136         d.addCallback(lambda newnode: newnode.get_uri())
10137hunk ./src/allmydata/web/filenode.py 84
10138         return d
10139 
10140+
10141 class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
10142     def __init__(self, client, parentnode, name):
10143         rend.Page.__init__(self)
10144hunk ./src/allmydata/web/filenode.py 278
10145 
10146     def replace_my_contents(self, req):
10147         req.content.seek(0)
10148-        new_contents = req.content.read()
10149+        new_contents = MutableFileHandle(req.content)
10150         d = self.node.overwrite(new_contents)
10151         d.addCallback(lambda res: self.node.get_uri())
10152         return d
10153hunk ./src/allmydata/web/filenode.py 286
10154     def replace_my_contents_with_a_formpost(self, req):
10155         # we have a mutable file. Get the data from the formpost, and replace
10156         # the mutable file's contents with it.
10157-        new_contents = self._read_data_from_formpost(req)
10158+        new_contents = req.fields['file']
10159+        new_contents = MutableFileHandle(new_contents.file)
10160+
10161         d = self.node.overwrite(new_contents)
10162         d.addCallback(lambda res: self.node.get_uri())
10163         return d
10164hunk ./src/allmydata/web/unlinked.py 7
10165 from twisted.internet import defer
10166 from nevow import rend, url, tags as T
10167 from allmydata.immutable.upload import FileHandle
10168+from allmydata.mutable.publish import MutableFileHandle
10169 from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
10170      convert_children_json, WebError
10171 from allmydata.web import status
10172hunk ./src/allmydata/web/unlinked.py 23
10173 def PUTUnlinkedSSK(req, client):
10174     # SDMF: files are small, and we can only upload data
10175     req.content.seek(0)
10176-    data = req.content.read()
10177+    data = MutableFileHandle(req.content)
10178     d = client.create_mutable_file(data)
10179     d.addCallback(lambda n: n.get_uri())
10180     return d
10181hunk ./src/allmydata/web/unlinked.py 87
10182     # "POST /uri", to create an unlinked file.
10183     # SDMF: files are small, and we can only upload data
10184     contents = req.fields["file"]
10185-    contents.file.seek(0)
10186-    data = contents.file.read()
10187+    data = MutableFileHandle(contents.file)
10188     d = client.create_mutable_file(data)
10189     d.addCallback(lambda n: n.get_uri())
10190     return d
10191}
10192[test/test_sftp.py: alter a setup routine to work with new mutable file APIs.
10193Kevan Carstensen <kevan@isnotajoke.com>**20100708193522
10194 Ignore-this: 434bbe1347072076c0836d26fca8ac8a
10195] {
10196hunk ./src/allmydata/test/test_sftp.py 32
10197 
10198 from allmydata.util.consumer import download_to_data
10199 from allmydata.immutable import upload
10200+from allmydata.mutable import publish
10201 from allmydata.test.no_network import GridTestMixin
10202 from allmydata.test.common import ShouldFailMixin
10203 from allmydata.test.common_util import ReallyEqualMixin
10204hunk ./src/allmydata/test/test_sftp.py 84
10205         return d
10206 
10207     def _set_up_tree(self):
10208-        d = self.client.create_mutable_file("mutable file contents")
10209+        u = publish.MutableDataHandle("mutable file contents")
10210+        d = self.client.create_mutable_file(u)
10211         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
10212         def _created_mutable(n):
10213             self.mutable = n
10214}
10215[mutable/publish.py: make MutableFileHandle seek to the beginning of its file handle before reading.
10216Kevan Carstensen <kevan@isnotajoke.com>**20100708193600
10217 Ignore-this: 453a737dc62a79c77b3d360fed9000ab
10218] hunk ./src/allmydata/mutable/publish.py 989
10219         assert hasattr(filehandle, "close")
10220 
10221         self._filehandle = filehandle
10222+        # We must start reading at the beginning of the file, or we risk
10223+        # encountering errors when the data read does not match the size
10224+        # reported to the uploader.
10225+        self._filehandle.seek(0)
10226 
10227 
10228     def get_size(self):
10229[Refactor download interfaces to be more uniform, per #993
10230Kevan Carstensen <kevan@isnotajoke.com>**20100709232912
10231 Ignore-this: 277c5699c4a2dd7c03ecfa0a28458f5b
10232] {
10233hunk ./src/allmydata/immutable/filenode.py 10
10234 from foolscap.api import eventually
10235 from allmydata.interfaces import IImmutableFileNode, ICheckable, \
10236      IDownloadTarget, IUploadResults
10237-from allmydata.util import dictutil, log, base32
10238+from allmydata.util import dictutil, log, base32, consumer
10239 from allmydata.uri import CHKFileURI, LiteralFileURI
10240 from allmydata.immutable.checker import Checker
10241 from allmydata.check_results import CheckResults, CheckAndRepairResults
10242hunk ./src/allmydata/immutable/filenode.py 318
10243                       self.download_cache.read(consumer, offset, size))
10244         return d
10245 
10246+    # IReadable, IFileNode
10247+
10248+    def get_best_readable_version(self):
10249+        """
10250+        Return an IReadable of the best version of this file. Since
10251+        immutable files can have only one version, we just return the
10252+        current filenode.
10253+        """
10254+        return self
10255+
10256+
10257+    def download_best_version(self):
10258+        """
10259+        Download the best version of this file, returning its contents
10260+        as a bytestring. Since there is only one version of an immutable
10261+        file, we download and return the contents of this file.
10262+        """
10263+        d = consumer.download_to_data(self)
10264+        return d
10265+
10266+    # for an immutable file, download_to_data (specified in IReadable)
10267+    # is the same as download_best_version (specified in IFileNode). For
10268+    # mutable files, the difference is more meaningful, since they can
10269+    # have multiple versions.
10270+    download_to_data = download_best_version
10271+
10272+
10273+    # get_size() (IReadable), get_current_size() (IFilesystemNode), and
10274+    # get_size_of_best_version(IFileNode) are all the same for immutable
10275+    # files.
10276+    get_size_of_best_version = get_current_size
10277+
10278+
10279 class LiteralProducer:
10280     implements(IPushProducer)
10281     def resumeProducing(self):
10282hunk ./src/allmydata/immutable/filenode.py 409
10283         d = basic.FileSender().beginFileTransfer(StringIO(data), consumer)
10284         d.addCallback(lambda lastSent: consumer)
10285         return d
10286+
10287+    # IReadable, IFileNode, IFilesystemNode
10288+    def get_best_readable_version(self):
10289+        return self
10290+
10291+
10292+    def download_best_version(self):
10293+        return defer.succeed(self.u.data)
10294+
10295+
10296+    download_to_data = download_best_version
10297+    get_size_of_best_version = get_current_size
10298hunk ./src/allmydata/interfaces.py 563
10299 class MustNotBeUnknownRWError(CapConstraintError):
10300     """Cannot add an unknown child cap specified in a rw_uri field."""
10301 
10302+
10303+class IReadable(Interface):
10304+    """I represent a readable object -- either an immutable file, or a
10305+    specific version of a mutable file.
10306+    """
10307+
10308+    def is_readonly():
10309+        """Return True if this reference provides mutable access to the given
10310+        file or directory (i.e. if you can modify it), or False if not. Note
10311+        that even if this reference is read-only, someone else may hold a
10312+        read-write reference to it.
10313+
10314+        For an IReadable returned by get_best_readable_version(), this will
10315+        always return True, but for instances of subinterfaces such as
10316+        IMutableFileVersion, it may return False."""
10317+
10318+    def is_mutable():
10319+        """Return True if this file or directory is mutable (by *somebody*,
10320+        not necessarily you), False if it is is immutable. Note that a file
10321+        might be mutable overall, but your reference to it might be
10322+        read-only. On the other hand, all references to an immutable file
10323+        will be read-only; there are no read-write references to an immutable
10324+        file."""
10325+
10326+    def get_storage_index():
10327+        """Return the storage index of the file."""
10328+
10329+    def get_size():
10330+        """Return the length (in bytes) of this readable object."""
10331+
10332+    def download_to_data():
10333+        """Download all of the file contents. I return a Deferred that fires
10334+        with the contents as a byte string."""
10335+
10336+    def read(consumer, offset=0, size=None):
10337+        """Download a portion (possibly all) of the file's contents, making
10338+        them available to the given IConsumer. Return a Deferred that fires
10339+        (with the consumer) when the consumer is unregistered (either because
10340+        the last byte has been given to it, or because the consumer threw an
10341+        exception during write(), possibly because it no longer wants to
10342+        receive data). The portion downloaded will start at 'offset' and
10343+        contain 'size' bytes (or the remainder of the file if size==None).
10344+
10345+        The consumer will be used in non-streaming mode: an IPullProducer
10346+        will be attached to it.
10347+
10348+        The consumer will not receive data right away: several network trips
10349+        must occur first. The order of events will be::
10350+
10351+         consumer.registerProducer(p, streaming)
10352+          (if streaming == False)::
10353+           consumer does p.resumeProducing()
10354+            consumer.write(data)
10355+           consumer does p.resumeProducing()
10356+            consumer.write(data).. (repeat until all data is written)
10357+         consumer.unregisterProducer()
10358+         deferred.callback(consumer)
10359+
10360+        If a download error occurs, or an exception is raised by
10361+        consumer.registerProducer() or consumer.write(), I will call
10362+        consumer.unregisterProducer() and then deliver the exception via
10363+        deferred.errback(). To cancel the download, the consumer should call
10364+        p.stopProducing(), which will result in an exception being delivered
10365+        via deferred.errback().
10366+
10367+        See src/allmydata/util/consumer.py for an example of a simple
10368+        download-to-memory consumer.
10369+        """
10370+
10371+
10372+class IMutableFileVersion(IReadable):
10373+    """I provide access to a particular version of a mutable file. The
10374+    access is read/write if I was obtained from a filenode derived from
10375+    a write cap, or read-only if the filenode was derived from a read cap.
10376+    """
10377+
10378+    def get_sequence_number():
10379+        """Return the sequence number of this version."""
10380+
10381+    def get_servermap():
10382+        """Return the IMutableFileServerMap instance that was used to create
10383+        this object.
10384+        """
10385+
10386+    def get_writekey():
10387+        """Return this filenode's writekey, or None if the node does not have
10388+        write-capability. This may be used to assist with data structures
10389+        that need to make certain data available only to writers, such as the
10390+        read-write child caps in dirnodes. The recommended process is to have
10391+        reader-visible data be submitted to the filenode in the clear (where
10392+        it will be encrypted by the filenode using the readkey), but encrypt
10393+        writer-visible data using this writekey.
10394+        """
10395+
10396+    # TODO: Can this be overwrite instead of replace?
10397+    def replace(new_contents):
10398+        """Replace the contents of the mutable file, provided that no other
10399+        node has published (or is attempting to publish, concurrently) a
10400+        newer version of the file than this one.
10401+
10402+        I will avoid modifying any share that is different than the version
10403+        given by get_sequence_number(). However, if another node is writing
10404+        to the file at the same time as me, I may manage to update some shares
10405+        while they update others. If I see any evidence of this, I will signal
10406+        UncoordinatedWriteError, and the file will be left in an inconsistent
10407+        state (possibly the version you provided, possibly the old version,
10408+        possibly somebody else's version, and possibly a mix of shares from
10409+        all of these).
10410+
10411+        The recommended response to UncoordinatedWriteError is to either
10412+        return it to the caller (since they failed to coordinate their
10413+        writes), or to attempt some sort of recovery. It may be sufficient to
10414+        wait a random interval (with exponential backoff) and repeat your
10415+        operation. If I do not signal UncoordinatedWriteError, then I was
10416+        able to write the new version without incident.
10417+
10418+        I return a Deferred that fires (with a PublishStatus object) when the
10419+        update has completed.
10420+        """
10421+
10422+    def modify(modifier_cb):
10423+        """Modify the contents of the file, by downloading this version,
10424+        applying the modifier function (or bound method), then uploading
10425+        the new version. This will succeed as long as no other node
10426+        publishes a version between the download and the upload.
10427+        I return a Deferred that fires (with a PublishStatus object) when
10428+        the update is complete.
10429+
10430+        The modifier callable will be given three arguments: a string (with
10431+        the old contents), a 'first_time' boolean, and a servermap. As with
10432+        download_to_data(), the old contents will be from this version,
10433+        but the modifier can use the servermap to make other decisions
10434+        (such as refusing to apply the delta if there are multiple parallel
10435+        versions, or if there is evidence of a newer unrecoverable version).
10436+        'first_time' will be True the first time the modifier is called,
10437+        and False on any subsequent calls.
10438+
10439+        The callable should return a string with the new contents. The
10440+        callable must be prepared to be called multiple times, and must
10441+        examine the input string to see if the change that it wants to make
10442+        is already present in the old version. If it does not need to make
10443+        any changes, it can either return None, or return its input string.
10444+
10445+        If the modifier raises an exception, it will be returned in the
10446+        errback.
10447+        """
10448+
10449+
10450 # The hierarchy looks like this:
10451 #  IFilesystemNode
10452 #   IFileNode
10453hunk ./src/allmydata/interfaces.py 801
10454     def raise_error():
10455         """Raise any error associated with this node."""
10456 
10457+    # XXX: These may not be appropriate outside the context of an IReadable.
10458     def get_size():
10459         """Return the length (in bytes) of the data this node represents. For
10460         directory nodes, I return the size of the backing store. I return
10461hunk ./src/allmydata/interfaces.py 818
10462 class IFileNode(IFilesystemNode):
10463     """I am a node which represents a file: a sequence of bytes. I am not a
10464     container, like IDirectoryNode."""
10465+    def get_best_readable_version():
10466+        """Return a Deferred that fires with an IReadable for the 'best'
10467+        available version of the file. The IReadable provides only read
10468+        access, even if this filenode was derived from a write cap.
10469 
10470hunk ./src/allmydata/interfaces.py 823
10471-class IImmutableFileNode(IFileNode):
10472-    def read(consumer, offset=0, size=None):
10473-        """Download a portion (possibly all) of the file's contents, making
10474-        them available to the given IConsumer. Return a Deferred that fires
10475-        (with the consumer) when the consumer is unregistered (either because
10476-        the last byte has been given to it, or because the consumer threw an
10477-        exception during write(), possibly because it no longer wants to
10478-        receive data). The portion downloaded will start at 'offset' and
10479-        contain 'size' bytes (or the remainder of the file if size==None).
10480-
10481-        The consumer will be used in non-streaming mode: an IPullProducer
10482-        will be attached to it.
10483+        For an immutable file, there is only one version. For a mutable
10484+        file, the 'best' version is the recoverable version with the
10485+        highest sequence number. If no uncoordinated writes have occurred,
10486+        and if enough shares are available, then this will be the most
10487+        recent version that has been uploaded. If no version is recoverable,
10488+        the Deferred will errback with an UnrecoverableFileError.
10489+        """
10490 
10491hunk ./src/allmydata/interfaces.py 831
10492-        The consumer will not receive data right away: several network trips
10493-        must occur first. The order of events will be::
10494+    def download_best_version():
10495+        """Download the contents of the version that would be returned
10496+        by get_best_readable_version(). This is equivalent to calling
10497+        download_to_data() on the IReadable given by that method.
10498 
10499hunk ./src/allmydata/interfaces.py 836
10500-         consumer.registerProducer(p, streaming)
10501-          (if streaming == False)::
10502-           consumer does p.resumeProducing()
10503-            consumer.write(data)
10504-           consumer does p.resumeProducing()
10505-            consumer.write(data).. (repeat until all data is written)
10506-         consumer.unregisterProducer()
10507-         deferred.callback(consumer)
10508+        I return a Deferred that fires with a byte string when the file
10509+        has been fully downloaded. To support streaming download, use
10510+        the 'read' method of IReadable. If no version is recoverable,
10511+        the Deferred will errback with an UnrecoverableFileError.
10512+        """
10513 
10514hunk ./src/allmydata/interfaces.py 842
10515-        If a download error occurs, or an exception is raised by
10516-        consumer.registerProducer() or consumer.write(), I will call
10517-        consumer.unregisterProducer() and then deliver the exception via
10518-        deferred.errback(). To cancel the download, the consumer should call
10519-        p.stopProducing(), which will result in an exception being delivered
10520-        via deferred.errback().
10521+    def get_size_of_best_version():
10522+        """Find the size of the version that would be returned by
10523+        get_best_readable_version().
10524 
10525hunk ./src/allmydata/interfaces.py 846
10526-        See src/allmydata/util/consumer.py for an example of a simple
10527-        download-to-memory consumer.
10528+        I return a Deferred that fires with an integer. If no version
10529+        is recoverable, the Deferred will errback with an
10530+        UnrecoverableFileError.
10531         """
10532 
10533hunk ./src/allmydata/interfaces.py 851
10534+
10535+class IImmutableFileNode(IFileNode, IReadable):
10536+    """I am a node representing an immutable file. Immutable files have
10537+    only one version"""
10538+
10539+
10540 class IMutableFileNode(IFileNode):
10541     """I provide access to a 'mutable file', which retains its identity
10542     regardless of what contents are put in it.
10543hunk ./src/allmydata/interfaces.py 916
10544     only be retrieved and updated all-at-once, as a single big string. Future
10545     versions of our mutable files will remove this restriction.
10546     """
10547-
10548-    def download_best_version():
10549-        """Download the 'best' available version of the file, meaning one of
10550-        the recoverable versions with the highest sequence number. If no
10551+    def get_best_mutable_version():
10552+        """Return a Deferred that fires with an IMutableFileVersion for
10553+        the 'best' available version of the file. The best version is
10554+        the recoverable version with the highest sequence number. If no
10555         uncoordinated writes have occurred, and if enough shares are
10556hunk ./src/allmydata/interfaces.py 921
10557-        available, then this will be the most recent version that has been
10558-        uploaded.
10559-
10560-        I update an internal servermap with MODE_READ, determine which
10561-        version of the file is indicated by
10562-        servermap.best_recoverable_version(), and return a Deferred that
10563-        fires with its contents. If no version is recoverable, the Deferred
10564-        will errback with UnrecoverableFileError.
10565-        """
10566-
10567-    def get_size_of_best_version():
10568-        """Find the size of the version that would be downloaded with
10569-        download_best_version(), without actually downloading the whole file.
10570+        available, then this will be the most recent version that has
10571+        been uploaded.
10572 
10573hunk ./src/allmydata/interfaces.py 924
10574-        I return a Deferred that fires with an integer.
10575+        If no version is recoverable, the Deferred will errback with an
10576+        UnrecoverableFileError.
10577         """
10578 
10579     def overwrite(new_contents):
10580hunk ./src/allmydata/interfaces.py 964
10581         errback.
10582         """
10583 
10584-
10585     def get_servermap(mode):
10586         """Return a Deferred that fires with an IMutableFileServerMap
10587         instance, updated using the given mode.
10588hunk ./src/allmydata/test/test_filenode.py 98
10589         def _check_segment(res):
10590             self.failUnlessEqual(res, DATA[1:1+5])
10591         d.addCallback(_check_segment)
10592+        d.addCallback(lambda ignored:
10593+            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
10594+        d.addCallback(lambda ignored:
10595+            fn1.get_size_of_best_version())
10596+        d.addCallback(lambda size:
10597+            self.failUnlessEqual(size, len(DATA)))
10598+        d.addCallback(lambda ignored:
10599+            fn1.download_to_data())
10600+        d.addCallback(lambda data:
10601+            self.failUnlessEqual(data, DATA))
10602+        d.addCallback(lambda ignored:
10603+            fn1.download_best_version())
10604+        d.addCallback(lambda data:
10605+            self.failUnlessEqual(data, DATA))
10606 
10607         return d
10608 
10609hunk ./src/allmydata/test/test_immutable.py 153
10610         return d
10611 
10612 
10613+    def test_download_to_data(self):
10614+        d = self.n.download_to_data()
10615+        d.addCallback(lambda data:
10616+            self.failUnlessEqual(data, common.TEST_DATA))
10617+        return d
10618+
10619+
10620+    def test_download_best_version(self):
10621+        d = self.n.download_best_version()
10622+        d.addCallback(lambda data:
10623+            self.failUnlessEqual(data, common.TEST_DATA))
10624+        return d
10625+
10626+
10627+    def test_get_best_readable_version(self):
10628+        n = self.n.get_best_readable_version()
10629+        self.failUnlessEqual(n, self.n)
10630+
10631+    def test_get_size_of_best_version(self):
10632+        d = self.n.get_size_of_best_version()
10633+        d.addCallback(lambda size:
10634+            self.failUnlessEqual(size, len(common.TEST_DATA)))
10635+        return d
10636+
10637+
10638 # XXX extend these tests to show bad behavior of various kinds from servers: raising exception from each remove_foo() method, for example
10639 
10640 # XXX test disconnect DeadReferenceError from get_buckets and get_block_whatsit
10641}
10642[frontends/sftpd.py: alter a mutable file overwrite to work with the new API
10643Kevan Carstensen <kevan@isnotajoke.com>**20100717014446
10644 Ignore-this: 2c57bbc8d9ab97a0e9af0634c00efc86
10645] {
10646hunk ./src/allmydata/frontends/sftpd.py 33
10647 from allmydata.interfaces import IFileNode, IDirectoryNode, ExistingChildError, \
10648      NoSuchChildError, ChildOfWrongTypeError
10649 from allmydata.mutable.common import NotWriteableError
10650+from allmydata.mutable.publish import MutableFileHandle
10651 from allmydata.immutable.upload import FileHandle
10652 from allmydata.dirnode import update_metadata
10653 from allmydata.util.fileutil import EncryptedTemporaryFile
10654merger 0.0 (
10655hunk ./src/allmydata/frontends/sftpd.py 664
10656-            # TODO: use download interface described in #993 when implemented.
10657hunk ./src/allmydata/frontends/sftpd.py 664
10658-            # TODO: use download interface described in #993 when implemented.
10659-            if filenode.is_mutable():
10660-                self.async.addCallback(lambda ign: filenode.download_best_version())
10661-                def _downloaded(data):
10662-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
10663-                    self.consumer.write(data)
10664-                    self.consumer.finish()
10665-                    return None
10666-                self.async.addCallback(_downloaded)
10667-            else:
10668-                download_size = filenode.get_size()
10669-                assert download_size is not None, "download_size is None"
10670+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
10671+
10672+            def _read(version):
10673+                download_size = version.get_size()
10674+                assert download_size is not None
10675+
10676)
10677hunk ./src/allmydata/frontends/sftpd.py 677
10678                 download_size = filenode.get_size()
10679                 assert download_size is not None, "download_size is None"
10680                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
10681-                def _read(ign):
10682-                    if noisy: self.log("_read immutable", level=NOISY)
10683-                    filenode.read(self.consumer, 0, None)
10684-                self.async.addCallback(_read)
10685+
10686+                if noisy: self.log("_read", level=NOISY)
10687+                version.read(self.consumer, 0, None)
10688+            self.async.addCallback(_read)
10689 
10690         eventually(self.async.callback, None)
10691 
10692hunk ./src/allmydata/frontends/sftpd.py 824
10693                     assert parent and childname, (parent, childname, self.metadata)
10694                     d2.addCallback(lambda ign: parent.set_metadata_for(childname, self.metadata))
10695 
10696-                d2.addCallback(lambda ign: self.consumer.get_current_size())
10697-                d2.addCallback(lambda size: self.consumer.read(0, size))
10698-                d2.addCallback(lambda new_contents: self.filenode.overwrite(new_contents))
10699+                d2.addCallback(lambda ign: self.filenode.overwrite(MutableFileHandle(self.consumer.get_file())))
10700             else:
10701                 def _add_file(ign):
10702                     self.log("_add_file childname=%r" % (childname,), level=OPERATIONAL)
10703}
10704[mutable/filenode.py: implement most of IVersion, per #993
10705Kevan Carstensen <kevan@isnotajoke.com>**20100717014516
10706 Ignore-this: d4551142b32ea97040ce0e98a394fde5
10707] {
10708hunk ./src/allmydata/mutable/filenode.py 8
10709 from twisted.internet import defer, reactor
10710 from foolscap.api import eventually
10711 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
10712-                                 NotEnoughSharesError, \
10713-                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
10714-from allmydata.util import hashutil, log
10715+     NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
10716+     IMutableFileVersion
10717+from allmydata.util import hashutil, log, consumer
10718 from allmydata.util.assertutil import precondition
10719 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
10720 from allmydata.monitor import Monitor
10721hunk ./src/allmydata/mutable/filenode.py 17
10722 from pycryptopp.cipher.aes import AES
10723 
10724 from allmydata.mutable.publish import Publish, MutableFileHandle, \
10725-                                      MutableDataHandle
10726+                                      MutableData
10727 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
10728      ResponseCache, UncoordinatedWriteError
10729 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
10730hunk ./src/allmydata/mutable/filenode.py 134
10731 
10732     def _get_initial_contents(self, contents):
10733         if contents is None:
10734-            return MutableDataHandle("")
10735+            return MutableData("")
10736 
10737         if IMutableUploadable.providedBy(contents):
10738             return contents
10739hunk ./src/allmydata/mutable/filenode.py 208
10740 
10741     def get_size(self):
10742         return self._most_recent_size
10743+
10744     def get_current_size(self):
10745         d = self.get_size_of_best_version()
10746         d.addCallback(self._stash_size)
10747hunk ./src/allmydata/mutable/filenode.py 213
10748         return d
10749+
10750     def _stash_size(self, size):
10751         self._most_recent_size = size
10752         return size
10753hunk ./src/allmydata/mutable/filenode.py 272
10754             return cmp(self.__class__, them.__class__)
10755         return cmp(self._uri, them._uri)
10756 
10757-    def _do_serialized(self, cb, *args, **kwargs):
10758-        # note: to avoid deadlock, this callable is *not* allowed to invoke
10759-        # other serialized methods within this (or any other)
10760-        # MutableFileNode. The callable should be a bound method of this same
10761-        # MFN instance.
10762-        d = defer.Deferred()
10763-        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
10764-        # we need to put off d.callback until this Deferred is finished being
10765-        # processed. Otherwise the caller's subsequent activities (like,
10766-        # doing other things with this node) can cause reentrancy problems in
10767-        # the Deferred code itself
10768-        self._serializer.addBoth(lambda res: eventually(d.callback, res))
10769-        # add a log.err just in case something really weird happens, because
10770-        # self._serializer stays around forever, therefore we won't see the
10771-        # usual Unhandled Error in Deferred that would give us a hint.
10772-        self._serializer.addErrback(log.err)
10773-        return d
10774 
10775     #################################
10776     # ICheckable
10777hunk ./src/allmydata/mutable/filenode.py 297
10778 
10779 
10780     #################################
10781-    # IMutableFileNode
10782+    # IFileNode
10783+
10784+    def get_best_readable_version(self):
10785+        """
10786+        I return a Deferred that fires with a MutableFileVersion
10787+        representing the best readable version of the file that I
10788+        represent
10789+        """
10790+        return self.get_readable_version()
10791+
10792+
10793+    def get_readable_version(self, servermap=None, version=None):
10794+        """
10795+        I return a Deferred that fires with an MutableFileVersion for my
10796+        version argument, if there is a recoverable file of that version
10797+        on the grid. If there is no recoverable version, I fire with an
10798+        UnrecoverableFileError.
10799+
10800+        If a servermap is provided, I look in there for the requested
10801+        version. If no servermap is provided, I create and update a new
10802+        one.
10803+
10804+        If no version is provided, then I return a MutableFileVersion
10805+        representing the best recoverable version of the file.
10806+        """
10807+        d = self._get_version_from_servermap(MODE_READ, servermap, version)
10808+        def _build_version((servermap, their_version)):
10809+            assert their_version in servermap.recoverable_versions()
10810+            assert their_version in servermap.make_versionmap()
10811+
10812+            mfv = MutableFileVersion(self,
10813+                                     servermap,
10814+                                     their_version,
10815+                                     self._storage_index,
10816+                                     self._storage_broker,
10817+                                     self._readkey,
10818+                                     history=self._history)
10819+            assert mfv.is_readonly()
10820+            # our caller can use this to download the contents of the
10821+            # mutable file.
10822+            return mfv
10823+        return d.addCallback(_build_version)
10824+
10825+
10826+    def _get_version_from_servermap(self,
10827+                                    mode,
10828+                                    servermap=None,
10829+                                    version=None):
10830+        """
10831+        I return a Deferred that fires with (servermap, version).
10832+
10833+        This function performs validation and a servermap update. If it
10834+        returns (servermap, version), the caller can assume that:
10835+            - servermap was last updated in mode.
10836+            - version is recoverable, and corresponds to the servermap.
10837+
10838+        If version and servermap are provided to me, I will validate
10839+        that version exists in the servermap, and that the servermap was
10840+        updated correctly.
10841+
10842+        If version is not provided, but servermap is, I will validate
10843+        the servermap and return the best recoverable version that I can
10844+        find in the servermap.
10845+
10846+        If the version is provided but the servermap isn't, I will
10847+        obtain a servermap that has been updated in the correct mode and
10848+        validate that version is found and recoverable.
10849+
10850+        If neither servermap nor version are provided, I will obtain a
10851+        servermap updated in the correct mode, and return the best
10852+        recoverable version that I can find in there.
10853+        """
10854+        # XXX: wording ^^^^
10855+        if servermap and servermap.last_update_mode == mode:
10856+            d = defer.succeed(servermap)
10857+        else:
10858+            d = self._get_servermap(mode)
10859+
10860+        def _get_version(servermap, version):
10861+            if version and version not in servermap.recoverable_versions():
10862+                version = None
10863+            else:
10864+                version = servermap.best_recoverable_version()
10865+            if not version:
10866+                raise UnrecoverableFileError("no recoverable versions")
10867+            return (servermap, version)
10868+        return d.addCallback(_get_version, version)
10869+
10870 
10871     def download_best_version(self):
10872hunk ./src/allmydata/mutable/filenode.py 387
10873+        """
10874+        I return a Deferred that fires with the contents of the best
10875+        version of this mutable file.
10876+        """
10877         return self._do_serialized(self._download_best_version)
10878hunk ./src/allmydata/mutable/filenode.py 392
10879+
10880+
10881     def _download_best_version(self):
10882hunk ./src/allmydata/mutable/filenode.py 395
10883-        servermap = ServerMap()
10884-        d = self._try_once_to_download_best_version(servermap, MODE_READ)
10885-        def _maybe_retry(f):
10886-            f.trap(NotEnoughSharesError)
10887-            # the download is worth retrying once. Make sure to use the
10888-            # old servermap, since it is what remembers the bad shares,
10889-            # but use MODE_WRITE to make it look for even more shares.
10890-            # TODO: consider allowing this to retry multiple times.. this
10891-            # approach will let us tolerate about 8 bad shares, I think.
10892-            return self._try_once_to_download_best_version(servermap,
10893-                                                           MODE_WRITE)
10894+        """
10895+        I am the serialized sibling of download_best_version.
10896+        """
10897+        d = self.get_best_readable_version()
10898+        d.addCallback(self._record_size)
10899+        d.addCallback(lambda version: version.download_to_data())
10900+
10901+        # It is possible that the download will fail because there
10902+        # aren't enough shares to be had. If so, we will try again after
10903+        # updating the servermap in MODE_WRITE, which may find more
10904+        # shares than updating in MODE_READ, as we just did. We can do
10905+        # this by getting the best mutable version and downloading from
10906+        # that -- the best mutable version will be a MutableFileVersion
10907+        # with a servermap that was last updated in MODE_WRITE, as we
10908+        # want. If this fails, then we give up.
10909+        def _maybe_retry(failure):
10910+            failure.trap(NotEnoughSharesError)
10911+
10912+            d = self.get_best_mutable_version()
10913+            d.addCallback(self._record_size)
10914+            d.addCallback(lambda version: version.download_to_data())
10915+            return d
10916+
10917         d.addErrback(_maybe_retry)
10918         return d
10919hunk ./src/allmydata/mutable/filenode.py 420
10920-    def _try_once_to_download_best_version(self, servermap, mode):
10921-        d = self._update_servermap(servermap, mode)
10922-        d.addCallback(self._once_updated_download_best_version, servermap)
10923-        return d
10924-    def _once_updated_download_best_version(self, ignored, servermap):
10925-        goal = servermap.best_recoverable_version()
10926-        if not goal:
10927-            raise UnrecoverableFileError("no recoverable versions")
10928-        return self._try_once_to_download_version(servermap, goal)
10929+
10930+
10931+    def _record_size(self, mfv):
10932+        """
10933+        I record the size of a mutable file version.
10934+        """
10935+        self._most_recent_size = mfv.get_size()
10936+        return mfv
10937+
10938 
10939     def get_size_of_best_version(self):
10940hunk ./src/allmydata/mutable/filenode.py 431
10941-        d = self.get_servermap(MODE_READ)
10942-        def _got_servermap(smap):
10943-            ver = smap.best_recoverable_version()
10944-            if not ver:
10945-                raise UnrecoverableFileError("no recoverable version")
10946-            return smap.size_of_version(ver)
10947-        d.addCallback(_got_servermap)
10948-        return d
10949+        """
10950+        I return the size of the best version of this mutable file.
10951+
10952+        This is equivalent to calling get_size() on the result of
10953+        get_best_readable_version().
10954+        """
10955+        d = self.get_best_readable_version()
10956+        return d.addCallback(lambda mfv: mfv.get_size())
10957+
10958+
10959+    #################################
10960+    # IMutableFileNode
10961+
10962+    def get_best_mutable_version(self, servermap=None):
10963+        """
10964+        I return a Deferred that fires with a MutableFileVersion
10965+        representing the best readable version of the file that I
10966+        represent. I am like get_best_readable_version, except that I
10967+        will try to make a writable version if I can.
10968+        """
10969+        return self.get_mutable_version(servermap=servermap)
10970+
10971+
10972+    def get_mutable_version(self, servermap=None, version=None):
10973+        """
10974+        I return a version of this mutable file. I return a Deferred
10975+        that fires with a MutableFileVersion
10976+
10977+        If version is provided, the Deferred will fire with a
10978+        MutableFileVersion initailized with that version. Otherwise, it
10979+        will fire with the best version that I can recover.
10980+
10981+        If servermap is provided, I will use that to find versions
10982+        instead of performing my own servermap update.
10983+        """
10984+        if self.is_readonly():
10985+            return self.get_readable_version(servermap=servermap,
10986+                                             version=version)
10987+
10988+        # get_mutable_version => write intent, so we require that the
10989+        # servermap is updated in MODE_WRITE
10990+        d = self._get_version_from_servermap(MODE_WRITE, servermap, version)
10991+        def _build_version((servermap, smap_version)):
10992+            # these should have been set by the servermap update.
10993+            assert self._secret_holder
10994+            assert self._writekey
10995+
10996+            mfv = MutableFileVersion(self,
10997+                                     servermap,
10998+                                     smap_version,
10999+                                     self._storage_index,
11000+                                     self._storage_broker,
11001+                                     self._readkey,
11002+                                     self._writekey,
11003+                                     self._secret_holder,
11004+                                     history=self._history)
11005+            assert not mfv.is_readonly()
11006+            return mfv
11007+
11008+        return d.addCallback(_build_version)
11009+
11010+
11011+    # XXX: I'm uncomfortable with the difference between upload and
11012+    #      overwrite, which, FWICT, is basically that you don't have to
11013+    #      do a servermap update before you overwrite. We split them up
11014+    #      that way anyway, so I guess there's no real difficulty in
11015+    #      offering both ways to callers, but it also makes the
11016+    #      public-facing API cluttery, and makes it hard to discern the
11017+    #      right way of doing things.
11018 
11019hunk ./src/allmydata/mutable/filenode.py 501
11020+    # In general, we leave it to callers to ensure that they aren't
11021+    # going to cause UncoordinatedWriteErrors when working with
11022+    # MutableFileVersions. We know that the next three operations
11023+    # (upload, overwrite, and modify) will all operate on the same
11024+    # version, so we say that only one of them can be going on at once,
11025+    # and serialize them to ensure that that actually happens, since as
11026+    # the caller in this situation it is our job to do that.
11027     def overwrite(self, new_contents):
11028hunk ./src/allmydata/mutable/filenode.py 509
11029+        """
11030+        I overwrite the contents of the best recoverable version of this
11031+        mutable file with new_contents. This is equivalent to calling
11032+        overwrite on the result of get_best_mutable_version with
11033+        new_contents as an argument. I return a Deferred that eventually
11034+        fires with the results of my replacement process.
11035+        """
11036         return self._do_serialized(self._overwrite, new_contents)
11037hunk ./src/allmydata/mutable/filenode.py 517
11038+
11039+
11040     def _overwrite(self, new_contents):
11041hunk ./src/allmydata/mutable/filenode.py 520
11042-        assert IMutableUploadable.providedBy(new_contents)
11043+        """
11044+        I am the serialized sibling of overwrite.
11045+        """
11046+        d = self.get_best_mutable_version()
11047+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
11048+
11049+
11050+
11051+    def upload(self, new_contents, servermap):
11052+        """
11053+        I overwrite the contents of the best recoverable version of this
11054+        mutable file with new_contents, using servermap instead of
11055+        creating/updating our own servermap. I return a Deferred that
11056+        fires with the results of my upload.
11057+        """
11058+        return self._do_serialized(self._upload, new_contents, servermap)
11059+
11060+
11061+    def _upload(self, new_contents, servermap):
11062+        """
11063+        I am the serialized sibling of upload.
11064+        """
11065+        d = self.get_best_mutable_version(servermap)
11066+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
11067+
11068+
11069+    def modify(self, modifier, backoffer=None):
11070+        """
11071+        I modify the contents of the best recoverable version of this
11072+        mutable file with the modifier. This is equivalent to calling
11073+        modify on the result of get_best_mutable_version. I return a
11074+        Deferred that eventually fires with an UploadResults instance
11075+        describing this process.
11076+        """
11077+        return self._do_serialized(self._modify, modifier, backoffer)
11078+
11079+
11080+    def _modify(self, modifier, backoffer):
11081+        """
11082+        I am the serialized sibling of modify.
11083+        """
11084+        d = self.get_best_mutable_version()
11085+        return d.addCallback(lambda mfv: mfv.modify(modifier, backoffer))
11086+
11087+
11088+    def download_version(self, servermap, version, fetch_privkey=False):
11089+        """
11090+        Download the specified version of this mutable file. I return a
11091+        Deferred that fires with the contents of the specified version
11092+        as a bytestring, or errbacks if the file is not recoverable.
11093+        """
11094+        d = self.get_readable_version(servermap, version)
11095+        return d.addCallback(lambda mfv: mfv.download_to_data(fetch_privkey))
11096+
11097+
11098+    def get_servermap(self, mode):
11099+        """
11100+        I return a servermap that has been updated in mode.
11101+
11102+        mode should be one of MODE_READ, MODE_WRITE, MODE_CHECK or
11103+        MODE_ANYTHING. See servermap.py for more on what these mean.
11104+        """
11105+        return self._do_serialized(self._get_servermap, mode)
11106+
11107 
11108hunk ./src/allmydata/mutable/filenode.py 585
11109+    def _get_servermap(self, mode):
11110+        """
11111+        I am a serialized twin to get_servermap.
11112+        """
11113         servermap = ServerMap()
11114hunk ./src/allmydata/mutable/filenode.py 590
11115-        d = self._update_servermap(servermap, mode=MODE_WRITE)
11116-        d.addCallback(lambda ignored: self._upload(new_contents, servermap))
11117+        return self._update_servermap(servermap, mode)
11118+
11119+
11120+    def _update_servermap(self, servermap, mode):
11121+        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11122+                             mode)
11123+        if self._history:
11124+            self._history.notify_mapupdate(u.get_status())
11125+        return u.update()
11126+
11127+
11128+    def set_version(self, version):
11129+        # I can be set in two ways:
11130+        #  1. When the node is created.
11131+        #  2. (for an existing share) when the Servermap is updated
11132+        #     before I am read.
11133+        assert version in (MDMF_VERSION, SDMF_VERSION)
11134+        self._protocol_version = version
11135+
11136+
11137+    def get_version(self):
11138+        return self._protocol_version
11139+
11140+
11141+    def _do_serialized(self, cb, *args, **kwargs):
11142+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11143+        # other serialized methods within this (or any other)
11144+        # MutableFileNode. The callable should be a bound method of this same
11145+        # MFN instance.
11146+        d = defer.Deferred()
11147+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11148+        # we need to put off d.callback until this Deferred is finished being
11149+        # processed. Otherwise the caller's subsequent activities (like,
11150+        # doing other things with this node) can cause reentrancy problems in
11151+        # the Deferred code itself
11152+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11153+        # add a log.err just in case something really weird happens, because
11154+        # self._serializer stays around forever, therefore we won't see the
11155+        # usual Unhandled Error in Deferred that would give us a hint.
11156+        self._serializer.addErrback(log.err)
11157         return d
11158 
11159 
11160hunk ./src/allmydata/mutable/filenode.py 633
11161+    def _upload(self, new_contents, servermap):
11162+        """
11163+        A MutableFileNode still has to have some way of getting
11164+        published initially, which is what I am here for. After that,
11165+        all publishing, updating, modifying and so on happens through
11166+        MutableFileVersions.
11167+        """
11168+        assert self._pubkey, "update_servermap must be called before publish"
11169+
11170+        p = Publish(self, self._storage_broker, servermap)
11171+        if self._history:
11172+            self._history.notify_publish(p.get_status(),
11173+                                         new_contents.get_size())
11174+        d = p.publish(new_contents)
11175+        d.addCallback(self._did_upload, new_contents.get_size())
11176+        return d
11177+
11178+
11179+    def _did_upload(self, res, size):
11180+        self._most_recent_size = size
11181+        return res
11182+
11183+
11184+class MutableFileVersion:
11185+    """
11186+    I represent a specific version (most likely the best version) of a
11187+    mutable file.
11188+
11189+    Since I implement IReadable, instances which hold a
11190+    reference to an instance of me are guaranteed the ability (absent
11191+    connection difficulties or unrecoverable versions) to read the file
11192+    that I represent. Depending on whether I was initialized with a
11193+    write capability or not, I may also provide callers the ability to
11194+    overwrite or modify the contents of the mutable file that I
11195+    reference.
11196+    """
11197+    implements(IMutableFileVersion)
11198+
11199+    def __init__(self,
11200+                 node,
11201+                 servermap,
11202+                 version,
11203+                 storage_index,
11204+                 storage_broker,
11205+                 readcap,
11206+                 writekey=None,
11207+                 write_secrets=None,
11208+                 history=None):
11209+
11210+        self._node = node
11211+        self._servermap = servermap
11212+        self._version = version
11213+        self._storage_index = storage_index
11214+        self._write_secrets = write_secrets
11215+        self._history = history
11216+        self._storage_broker = storage_broker
11217+
11218+        #assert isinstance(readcap, IURI)
11219+        self._readcap = readcap
11220+
11221+        self._writekey = writekey
11222+        self._serializer = defer.succeed(None)
11223+        self._size = None
11224+
11225+
11226+    def get_sequence_number(self):
11227+        """
11228+        Get the sequence number of the mutable version that I represent.
11229+        """
11230+        return 0
11231+
11232+
11233+    # TODO: Terminology?
11234+    def get_writekey(self):
11235+        """
11236+        I return a writekey or None if I don't have a writekey.
11237+        """
11238+        return self._writekey
11239+
11240+
11241+    def overwrite(self, new_contents):
11242+        """
11243+        I overwrite the contents of this mutable file version with the
11244+        data in new_contents.
11245+        """
11246+        assert not self.is_readonly()
11247+
11248+        return self._do_serialized(self._overwrite, new_contents)
11249+
11250+
11251+    def _overwrite(self, new_contents):
11252+        assert IMutableUploadable.providedBy(new_contents)
11253+        assert self._servermap.last_update_mode == MODE_WRITE
11254+
11255+        return self._upload(new_contents)
11256+
11257+
11258     def modify(self, modifier, backoffer=None):
11259         """I use a modifier callback to apply a change to the mutable file.
11260         I implement the following pseudocode::
11261hunk ./src/allmydata/mutable/filenode.py 770
11262         backoffer should not invoke any methods on this MutableFileNode
11263         instance, and it needs to be highly conscious of deadlock issues.
11264         """
11265+        assert not self.is_readonly()
11266+
11267         return self._do_serialized(self._modify, modifier, backoffer)
11268hunk ./src/allmydata/mutable/filenode.py 773
11269+
11270+
11271     def _modify(self, modifier, backoffer):
11272hunk ./src/allmydata/mutable/filenode.py 776
11273-        servermap = ServerMap()
11274         if backoffer is None:
11275             backoffer = BackoffAgent().delay
11276hunk ./src/allmydata/mutable/filenode.py 778
11277-        return self._modify_and_retry(servermap, modifier, backoffer, True)
11278-    def _modify_and_retry(self, servermap, modifier, backoffer, first_time):
11279-        d = self._modify_once(servermap, modifier, first_time)
11280+        return self._modify_and_retry(modifier, backoffer, True)
11281+
11282+
11283+    def _modify_and_retry(self, modifier, backoffer, first_time):
11284+        """
11285+        I try to apply modifier to the contents of this version of the
11286+        mutable file. If I succeed, I return an UploadResults instance
11287+        describing my success. If I fail, I try again after waiting for
11288+        a little bit.
11289+        """
11290+        log.msg("doing modify")
11291+        d = self._modify_once(modifier, first_time)
11292         def _retry(f):
11293             f.trap(UncoordinatedWriteError)
11294             d2 = defer.maybeDeferred(backoffer, self, f)
11295hunk ./src/allmydata/mutable/filenode.py 794
11296             d2.addCallback(lambda ignored:
11297-                           self._modify_and_retry(servermap, modifier,
11298+                           self._modify_and_retry(modifier,
11299                                                   backoffer, False))
11300             return d2
11301         d.addErrback(_retry)
11302hunk ./src/allmydata/mutable/filenode.py 799
11303         return d
11304-    def _modify_once(self, servermap, modifier, first_time):
11305-        d = self._update_servermap(servermap, MODE_WRITE)
11306-        d.addCallback(self._once_updated_download_best_version, servermap)
11307+
11308+
11309+    def _modify_once(self, modifier, first_time):
11310+        """
11311+        I attempt to apply a modifier to the contents of the mutable
11312+        file.
11313+        """
11314+        assert self._servermap.last_update_mode == MODE_WRITE
11315+
11316+        # download_to_data is serialized, so we have to call this to
11317+        # avoid deadlock.
11318+        d = self._try_to_download_data()
11319         def _apply(old_contents):
11320hunk ./src/allmydata/mutable/filenode.py 812
11321-            new_contents = modifier(old_contents, servermap, first_time)
11322+            new_contents = modifier(old_contents, self._servermap, first_time)
11323             if new_contents is None or new_contents == old_contents:
11324hunk ./src/allmydata/mutable/filenode.py 814
11325+                log.msg("no changes")
11326                 # no changes need to be made
11327                 if first_time:
11328                     return
11329hunk ./src/allmydata/mutable/filenode.py 822
11330                 # recovery when it observes UCWE, we need to do a second
11331                 # publish. See #551 for details. We'll basically loop until
11332                 # we managed an uncontested publish.
11333-                old_uploadable = MutableDataHandle(old_contents)
11334+                old_uploadable = MutableData(old_contents)
11335                 new_contents = old_uploadable
11336             precondition((IMutableUploadable.providedBy(new_contents) or
11337                           new_contents is None),
11338hunk ./src/allmydata/mutable/filenode.py 828
11339                          "Modifier function must return an IMutableUploadable "
11340                          "or None")
11341-            return self._upload(new_contents, servermap)
11342+            return self._upload(new_contents)
11343         d.addCallback(_apply)
11344         return d
11345 
11346hunk ./src/allmydata/mutable/filenode.py 832
11347-    def get_servermap(self, mode):
11348-        return self._do_serialized(self._get_servermap, mode)
11349-    def _get_servermap(self, mode):
11350-        servermap = ServerMap()
11351-        return self._update_servermap(servermap, mode)
11352-    def _update_servermap(self, servermap, mode):
11353-        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11354-                             mode)
11355-        if self._history:
11356-            self._history.notify_mapupdate(u.get_status())
11357-        return u.update()
11358 
11359hunk ./src/allmydata/mutable/filenode.py 833
11360-    def download_version(self, servermap, version, fetch_privkey=False):
11361-        return self._do_serialized(self._try_once_to_download_version,
11362-                                   servermap, version, fetch_privkey)
11363-    def _try_once_to_download_version(self, servermap, version,
11364-                                      fetch_privkey=False):
11365-        r = Retrieve(self, servermap, version, fetch_privkey)
11366+    def is_readonly(self):
11367+        """
11368+        I return True if this MutableFileVersion provides no write
11369+        access to the file that it encapsulates, and False if it
11370+        provides the ability to modify the file.
11371+        """
11372+        return self._writekey is None
11373+
11374+
11375+    def is_mutable(self):
11376+        """
11377+        I return True, since mutable files are always mutable by
11378+        somebody.
11379+        """
11380+        return True
11381+
11382+
11383+    def get_storage_index(self):
11384+        """
11385+        I return the storage index of the reference that I encapsulate.
11386+        """
11387+        return self._storage_index
11388+
11389+
11390+    def get_size(self):
11391+        """
11392+        I return the length, in bytes, of this readable object.
11393+        """
11394+        return self._servermap.size_of_version(self._version)
11395+
11396+
11397+    def download_to_data(self, fetch_privkey=False):
11398+        """
11399+        I return a Deferred that fires with the contents of this
11400+        readable object as a byte string.
11401+
11402+        """
11403+        c = consumer.MemoryConsumer()
11404+        d = self.read(c, fetch_privkey=fetch_privkey)
11405+        d.addCallback(lambda mc: "".join(mc.chunks))
11406+        return d
11407+
11408+
11409+    def _try_to_download_data(self):
11410+        """
11411+        I am an unserialized cousin of download_to_data; I am called
11412+        from the children of modify() to download the data associated
11413+        with this mutable version.
11414+        """
11415+        c = consumer.MemoryConsumer()
11416+        # modify will almost certainly write, so we need the privkey.
11417+        d = self._read(c, fetch_privkey=True)
11418+        d.addCallback(lambda mc: "".join(mc.chunks))
11419+        return d
11420+
11421+
11422+    def _update_servermap(self, mode=MODE_READ):
11423+        """
11424+        I update our Servermap according to my mode argument. I return a
11425+        Deferred that fires with None when this has finished. The
11426+        updated Servermap will be at self._servermap in that case.
11427+        """
11428+        d = self._node.get_servermap(mode)
11429+
11430+        def _got_servermap(servermap):
11431+            assert servermap.last_update_mode == mode
11432+
11433+            self._servermap = servermap
11434+        d.addCallback(_got_servermap)
11435+        return d
11436+
11437+
11438+    def read(self, consumer, offset=0, size=None, fetch_privkey=False):
11439+        """
11440+        I read a portion (possibly all) of the mutable file that I
11441+        reference into consumer.
11442+        """
11443+        return self._do_serialized(self._read, consumer, offset, size,
11444+                                   fetch_privkey)
11445+
11446+
11447+    def _read(self, consumer, offset=0, size=None, fetch_privkey=False):
11448+        """
11449+        I am the serialized companion of read.
11450+        """
11451+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
11452         if self._history:
11453             self._history.notify_retrieve(r.get_status())
11454hunk ./src/allmydata/mutable/filenode.py 921
11455-        d = r.download()
11456-        d.addCallback(self._downloaded_version)
11457+        d = r.download(consumer, offset, size)
11458+        return d
11459+
11460+
11461+    def _do_serialized(self, cb, *args, **kwargs):
11462+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11463+        # other serialized methods within this (or any other)
11464+        # MutableFileNode. The callable should be a bound method of this same
11465+        # MFN instance.
11466+        d = defer.Deferred()
11467+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11468+        # we need to put off d.callback until this Deferred is finished being
11469+        # processed. Otherwise the caller's subsequent activities (like,
11470+        # doing other things with this node) can cause reentrancy problems in
11471+        # the Deferred code itself
11472+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11473+        # add a log.err just in case something really weird happens, because
11474+        # self._serializer stays around forever, therefore we won't see the
11475+        # usual Unhandled Error in Deferred that would give us a hint.
11476+        self._serializer.addErrback(log.err)
11477         return d
11478hunk ./src/allmydata/mutable/filenode.py 942
11479-    def _downloaded_version(self, data):
11480-        self._most_recent_size = len(data)
11481-        return data
11482 
11483hunk ./src/allmydata/mutable/filenode.py 943
11484-    def upload(self, new_contents, servermap):
11485-        return self._do_serialized(self._upload, new_contents, servermap)
11486-    def _upload(self, new_contents, servermap):
11487-        assert self._pubkey, "update_servermap must be called before publish"
11488-        assert IMutableUploadable.providedBy(new_contents)
11489 
11490hunk ./src/allmydata/mutable/filenode.py 944
11491-        p = Publish(self, self._storage_broker, servermap)
11492+    def _upload(self, new_contents):
11493+        #assert self._pubkey, "update_servermap must be called before publish"
11494+        p = Publish(self._node, self._storage_broker, self._servermap)
11495         if self._history:
11496hunk ./src/allmydata/mutable/filenode.py 948
11497-            self._history.notify_publish(p.get_status(), new_contents.get_size())
11498+            self._history.notify_publish(p.get_status(),
11499+                                         new_contents.get_size())
11500         d = p.publish(new_contents)
11501         d.addCallback(self._did_upload, new_contents.get_size())
11502         return d
11503hunk ./src/allmydata/mutable/filenode.py 953
11504-    def _did_upload(self, res, size):
11505-        self._most_recent_size = size
11506-        return res
11507-
11508-
11509-    def set_version(self, version):
11510-        # I can be set in two ways:
11511-        #  1. When the node is created.
11512-        #  2. (for an existing share) when the Servermap is updated
11513-        #     before I am read.
11514-        assert version in (MDMF_VERSION, SDMF_VERSION)
11515-        self._protocol_version = version
11516 
11517 
11518hunk ./src/allmydata/mutable/filenode.py 955
11519-    def get_version(self):
11520-        return self._protocol_version
11521+    def _did_upload(self, res, size):
11522+        self._size = size
11523+        return res
11524}
11525[mutable/publish.py: enable segmented uploading for big files, change constants and wording, change MutableDataHandle to MutableData
11526Kevan Carstensen <kevan@isnotajoke.com>**20100717014549
11527 Ignore-this: f736c60c90ff09c98544af17146cf654
11528] {
11529hunk ./src/allmydata/mutable/publish.py 145
11530 
11531         self.data = newdata
11532         self.datalength = newdata.get_size()
11533+        if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
11534+            self._version = MDMF_VERSION
11535+        else:
11536+            self._version = SDMF_VERSION
11537 
11538         self.log("starting publish, datalen is %s" % self.datalength)
11539         self._status.set_size(self.datalength)
11540hunk ./src/allmydata/mutable/publish.py 1007
11541             old_position = self._filehandle.tell()
11542             # Seek to the end of the file by seeking 0 bytes from the
11543             # file's end
11544-            self._filehandle.seek(0, os.SEEK_END)
11545+            self._filehandle.seek(0, 2) # 2 == os.SEEK_END in 2.5+
11546             self._size = self._filehandle.tell()
11547             # Restore the previous position, in case this was called
11548             # after a read.
11549hunk ./src/allmydata/mutable/publish.py 1022
11550         """
11551         I return some data (up to length bytes) from my filehandle.
11552 
11553-        In most cases, I return length bytes. If I don't, it is because
11554-        length is longer than the distance between my current position
11555-        in the file that I represent and its end. In that case, I return
11556-        as many bytes as I can before going over the EOF.
11557+        In most cases, I return length bytes, but sometimes I won't --
11558+        for example, if I am asked to read beyond the end of a file, or
11559+        an error occurs.
11560         """
11561         return [self._filehandle.read(length)]
11562 
11563hunk ./src/allmydata/mutable/publish.py 1037
11564         self._filehandle.close()
11565 
11566 
11567-class MutableDataHandle(MutableFileHandle):
11568+class MutableData(MutableFileHandle):
11569     """
11570     I am a mutable uploadable built around a string, which I then cast
11571     into a StringIO and treat as a filehandle.
11572}
11573[immutable/filenode.py: fix broken implementation of #993 interfaces
11574Kevan Carstensen <kevan@isnotajoke.com>**20100717015049
11575 Ignore-this: 19ac8cf5d31ac88d4a1998ac342db004
11576] {
11577hunk ./src/allmydata/immutable/filenode.py 326
11578         immutable files can have only one version, we just return the
11579         current filenode.
11580         """
11581-        return self
11582+        return defer.succeed(self)
11583 
11584 
11585     def download_best_version(self):
11586hunk ./src/allmydata/immutable/filenode.py 412
11587 
11588     # IReadable, IFileNode, IFilesystemNode
11589     def get_best_readable_version(self):
11590-        return self
11591+        return defer.succeed(self)
11592 
11593 
11594     def download_best_version(self):
11595}
11596[mutable/retrieve.py: alter Retrieve so that it can download parts of mutable files
11597Kevan Carstensen <kevan@isnotajoke.com>**20100717015123
11598 Ignore-this: f49a6d3c05afc784aff0a4c63964a3e5
11599] {
11600hunk ./src/allmydata/mutable/retrieve.py 7
11601 from zope.interface import implements
11602 from twisted.internet import defer
11603 from twisted.python import failure
11604+from twisted.internet.interfaces import IPushProducer, IConsumer
11605 from foolscap.api import DeadReferenceError, eventually, fireEventually
11606 from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
11607                                  MDMF_VERSION, SDMF_VERSION
11608hunk ./src/allmydata/mutable/retrieve.py 86
11609     # times, and each will have a separate response chain. However the
11610     # Retrieve object will remain tied to a specific version of the file, and
11611     # will use a single ServerMap instance.
11612+    implements(IPushProducer)
11613 
11614     def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
11615                  verify=False):
11616hunk ./src/allmydata/mutable/retrieve.py 129
11617         # 3. When we are validating readers, we need to validate the
11618         #    signature on the prefix. Do we? We already do this in the
11619         #    servermap update?
11620-        #
11621-        # (just work on 1 and 2 for now, I guess)
11622         self._verify = False
11623         if verify:
11624             self._verify = True
11625hunk ./src/allmydata/mutable/retrieve.py 143
11626         self._status.set_size(datalength)
11627         self._status.set_encoding(k, N)
11628         self.readers = {}
11629+        self._paused = False
11630+        self._paused_deferred = None
11631+
11632 
11633     def get_status(self):
11634         return self._status
11635hunk ./src/allmydata/mutable/retrieve.py 157
11636             kwargs["facility"] = "tahoe.mutable.retrieve"
11637         return log.msg(*args, **kwargs)
11638 
11639-    def download(self):
11640+
11641+    ###################
11642+    # IPushProducer
11643+
11644+    def pauseProducing(self):
11645+        """
11646+        I am called by my download target if we have produced too much
11647+        data for it to handle. I make the downloader stop producing new
11648+        data until my resumeProducing method is called.
11649+        """
11650+        if self._paused:
11651+            return
11652+
11653+        # fired when the download is unpaused.
11654+        self._pause_deferred = defer.Deferred()
11655+        self._paused = True
11656+
11657+
11658+    def resumeProducing(self):
11659+        """
11660+        I am called by my download target once it is ready to begin
11661+        receiving data again.
11662+        """
11663+        if not self._paused:
11664+            return
11665+
11666+        self._paused = False
11667+        p = self._pause_deferred
11668+        self._pause_deferred = None
11669+        eventually(p.callback, None)
11670+
11671+
11672+    def _check_for_paused(self, res):
11673+        """
11674+        I am called just before a write to the consumer. I return a
11675+        Deferred that eventually fires with the data that is to be
11676+        written to the consumer. If the download has not been paused,
11677+        the Deferred fires immediately. Otherwise, the Deferred fires
11678+        when the downloader is unpaused.
11679+        """
11680+        if self._paused:
11681+            d = defer.Deferred()
11682+            self._pause_defered.addCallback(lambda ignored: d.callback(res))
11683+            return d
11684+        return defer.succeed(res)
11685+
11686+
11687+    def download(self, consumer=None, offset=0, size=None):
11688+        assert IConsumer.providedBy(consumer) or self._verify
11689+
11690+        if consumer:
11691+            self._consumer = consumer
11692+            # we provide IPushProducer, so streaming=True, per
11693+            # IConsumer.
11694+            self._consumer.registerProducer(self, streaming=True)
11695+
11696         self._done_deferred = defer.Deferred()
11697         self._started = time.time()
11698         self._status.set_status("Retrieving Shares")
11699hunk ./src/allmydata/mutable/retrieve.py 217
11700 
11701+        self._offset = offset
11702+        self._read_length = size
11703+
11704         # first, which servers can we use?
11705         versionmap = self.servermap.make_versionmap()
11706         shares = versionmap[self.verinfo]
11707hunk ./src/allmydata/mutable/retrieve.py 278
11708         assert len(self.remaining_sharemap) >= k
11709 
11710         self.log("starting download")
11711+        self._paused = False
11712         self._add_active_peers()
11713         # The download process beyond this is a state machine.
11714         # _add_active_peers will select the peers that we want to use
11715hunk ./src/allmydata/mutable/retrieve.py 324
11716 
11717         self._segment_decoder = codec.CRSDecoder()
11718         self._segment_decoder.set_params(segsize, k, n)
11719-        self._current_segment = 0
11720 
11721         if  not self._tail_data_size:
11722             self._tail_data_size = segsize
11723hunk ./src/allmydata/mutable/retrieve.py 349
11724             # So we don't have to do this later.
11725             self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
11726 
11727-        # If we have more than one segment, we are an SDMF file, which
11728-        # means that we need to validate the salts as we receive them.
11729-        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
11730-        self._salt_hash_tree[0] = IV # from the prefix.
11731+        # Our last task is to tell the downloader where to start and
11732+        # where to stop. We use three parameters for that:
11733+        #   - self._start_segment: the segment that we need to start
11734+        #     downloading from.
11735+        #   - self._current_segment: the next segment that we need to
11736+        #     download.
11737+        #   - self._last_segment: The last segment that we were asked to
11738+        #     download.
11739+        #
11740+        #  We say that the download is complete when
11741+        #  self._current_segment > self._last_segment. We use
11742+        #  self._start_segment and self._last_segment to know when to
11743+        #  strip things off of segments, and how much to strip.
11744+        if self._offset:
11745+            self.log("got offset: %d" % self._offset)
11746+            # our start segment is the first segment containing the
11747+            # offset we were given.
11748+            start = mathutil.div_ceil(self._offset,
11749+                                      self._segment_size)
11750+            # this gets us the first segment after self._offset. Then
11751+            # our start segment is the one before it.
11752+            start -= 1
11753+
11754+            assert start < self._num_segments
11755+            self._start_segment = start
11756+            self.log("got start segment: %d" % self._start_segment)
11757+        else:
11758+            self._start_segment = 0
11759+
11760+
11761+        if self._read_length:
11762+            # our end segment is the last segment containing part of the
11763+            # segment that we were asked to read.
11764+            self.log("got read length %d" % self._read_length)
11765+            end_data = self._offset + self._read_length
11766+            end = mathutil.div_ceil(end_data,
11767+                                    self._segment_size)
11768+            end -= 1
11769+            assert end < self._num_segments
11770+            self._last_segment = end
11771+            self.log("got end segment: %d" % self._last_segment)
11772+        else:
11773+            self._last_segment = self._num_segments - 1
11774 
11775hunk ./src/allmydata/mutable/retrieve.py 393
11776+        self._current_segment = self._start_segment
11777 
11778     def _add_active_peers(self):
11779         """
11780hunk ./src/allmydata/mutable/retrieve.py 637
11781         that this Retrieve is currently responsible for downloading.
11782         """
11783         assert len(self._active_readers) >= self._required_shares
11784-        if self._current_segment < self._num_segments:
11785+        if self._current_segment <= self._last_segment:
11786             d = self._process_segment(self._current_segment)
11787         else:
11788             d = defer.succeed(None)
11789hunk ./src/allmydata/mutable/retrieve.py 701
11790             d.addCallback(self._decrypt_segment)
11791             d.addErrback(self._validation_or_decoding_failed,
11792                          self._active_readers)
11793+            # check to see whether we've been paused before writing
11794+            # anything.
11795+            d.addCallback(self._check_for_paused)
11796             d.addCallback(self._set_segment)
11797             return d
11798         else:
11799hunk ./src/allmydata/mutable/retrieve.py 716
11800         target that is handling the file download.
11801         """
11802         self.log("got plaintext for segment %d" % self._current_segment)
11803-        self._plaintext += segment
11804+        if self._current_segment == self._start_segment:
11805+            # We're on the first segment. It's possible that we want
11806+            # only some part of the end of this segment, and that we
11807+            # just downloaded the whole thing to get that part. If so,
11808+            # we need to account for that and give the reader just the
11809+            # data that they want.
11810+            n = self._offset % self._segment_size
11811+            self.log("stripping %d bytes off of the first segment" % n)
11812+            self.log("original segment length: %d" % len(segment))
11813+            segment = segment[n:]
11814+            self.log("new segment length: %d" % len(segment))
11815+
11816+        if self._current_segment == self._last_segment and self._read_length is not None:
11817+            # We're on the last segment. It's possible that we only want
11818+            # part of the beginning of this segment, and that we
11819+            # downloaded the whole thing anyway. Make sure to give the
11820+            # caller only the portion of the segment that they want to
11821+            # receive.
11822+            extra = self._read_length
11823+            if self._start_segment != self._last_segment:
11824+                extra -= self._segment_size - \
11825+                            (self._offset % self._segment_size)
11826+            extra %= self._segment_size
11827+            self.log("original segment length: %d" % len(segment))
11828+            segment = segment[:extra]
11829+            self.log("new segment length: %d" % len(segment))
11830+            self.log("only taking %d bytes of the last segment" % extra)
11831+
11832+        if not self._verify:
11833+            self._consumer.write(segment)
11834+        else:
11835+            # we don't care about the plaintext if we are doing a verify.
11836+            segment = None
11837         self._current_segment += 1
11838 
11839 
11840hunk ./src/allmydata/mutable/retrieve.py 848
11841                                         reader.shnum,
11842                                         "corrupt hashes: %s" % e)
11843 
11844-        # TODO: Validate the salt, too.
11845         self.log('share %d is valid for segment %d' % (reader.shnum,
11846                                                        segnum))
11847         return {reader.shnum: (block, salt)}
11848hunk ./src/allmydata/mutable/retrieve.py 1014
11849               _done_deferred to errback.
11850         """
11851         self.log("checking for doneness")
11852-        if self._current_segment == self._num_segments:
11853+        if self._current_segment > self._last_segment:
11854             # No more segments to download, we're done.
11855             self.log("got plaintext, done")
11856             return self._done()
11857hunk ./src/allmydata/mutable/retrieve.py 1043
11858             ret = list(self._bad_shares)
11859             self.log("done verifying, found %d bad shares" % len(ret))
11860         else:
11861-            ret = self._plaintext
11862+            # TODO: upload status here?
11863+            ret = self._consumer
11864+            self._consumer.unregisterProducer()
11865         eventually(self._done_deferred.callback, ret)
11866 
11867 
11868hunk ./src/allmydata/mutable/retrieve.py 1066
11869                       "encoding %(k)d-of-%(n)d")
11870             args = {"have": self._current_segment,
11871                     "total": self._num_segments,
11872+                    "need": self._last_segment,
11873                     "k": self._required_shares,
11874                     "n": self._total_shares,
11875                     "bad": len(self._bad_shares)}
11876}
11877[change MutableDataHandle to MutableData in code.
11878Kevan Carstensen <kevan@isnotajoke.com>**20100717015210
11879 Ignore-this: f85ae425eabc21b47ad60bd6bf1f7dec
11880] {
11881hunk ./src/allmydata/dirnode.py 11
11882 from allmydata.mutable.common import NotWriteableError
11883 from allmydata.mutable.filenode import MutableFileNode
11884 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
11885-from allmydata.mutable.publish import MutableDataHandle
11886+from allmydata.mutable.publish import MutableData
11887 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
11888      IImmutableFileNode, IMutableFileNode, \
11889      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
11890hunk ./src/allmydata/dirnode.py 104
11891 
11892         del children[self.name]
11893         new_contents = self.node._pack_contents(children)
11894-        uploadable = MutableDataHandle(new_contents)
11895+        uploadable = MutableData(new_contents)
11896         return uploadable
11897 
11898 
11899hunk ./src/allmydata/dirnode.py 130
11900 
11901         children[name] = (child, metadata)
11902         new_contents = self.node._pack_contents(children)
11903-        uploadable = MutableDataHandle(new_contents)
11904+        uploadable = MutableData(new_contents)
11905         return uploadable
11906 
11907 
11908hunk ./src/allmydata/dirnode.py 175
11909 
11910             children[name] = (child, metadata)
11911         new_contents = self.node._pack_contents(children)
11912-        uploadable = MutableDataHandle(new_contents)
11913+        uploadable = MutableData(new_contents)
11914         return uploadable
11915 
11916 def _encrypt_rw_uri(writekey, rw_uri):
11917hunk ./src/allmydata/mutable/repairer.py 5
11918 from zope.interface import implements
11919 from twisted.internet import defer
11920 from allmydata.interfaces import IRepairResults, ICheckResults
11921-from allmydata.mutable.publish import MutableDataHandle
11922+from allmydata.mutable.publish import MutableData
11923 
11924 class RepairResults:
11925     implements(IRepairResults)
11926hunk ./src/allmydata/mutable/repairer.py 109
11927 
11928         d = self.node.download_version(smap, best_version, fetch_privkey=True)
11929         d.addCallback(lambda data:
11930-            MutableDataHandle(data))
11931+            MutableData(data))
11932         d.addCallback(self.node.upload, smap)
11933         d.addCallback(self.get_results, smap)
11934         return d
11935hunk ./src/allmydata/nodemaker.py 9
11936 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
11937 from allmydata.immutable.upload import Data
11938 from allmydata.mutable.filenode import MutableFileNode
11939-from allmydata.mutable.publish import MutableDataHandle
11940+from allmydata.mutable.publish import MutableData
11941 from allmydata.dirnode import DirectoryNode, pack_children
11942 from allmydata.unknown import UnknownNode
11943 from allmydata import uri
11944merger 0.0 (
11945merger 0.0 (
11946hunk ./src/allmydata/nodemaker.py 107
11947-                                     pack_children(n, initial_children),
11948+                                     MutableDataHandle(
11949+                                        pack_children(n, initial_children)),
11950merger 0.0 (
11951hunk ./src/allmydata/nodemaker.py 107
11952-                                     pack_children(n, initial_children))
11953+                                     pack_children(n, initial_children),
11954+                                     version)
11955hunk ./src/allmydata/nodemaker.py 107
11956-                                     pack_children(n, initial_children))
11957+                                     pack_children(initial_children, n.get_writekey()))
11958)
11959)
11960hunk ./src/allmydata/nodemaker.py 107
11961-                                     MutableDataHandle(
11962+                                     MutableData(
11963)
11964hunk ./src/allmydata/test/common.py 18
11965      DeepCheckResults, DeepCheckAndRepairResults
11966 from allmydata.mutable.common import CorruptShareError
11967 from allmydata.mutable.layout import unpack_header
11968-from allmydata.mutable.publish import MutableDataHandle
11969+from allmydata.mutable.publish import MutableData
11970 from allmydata.storage.server import storage_index_to_dir
11971 from allmydata.storage.mutable import MutableShareFile
11972 from allmydata.util import hashutil, log, fileutil, pollmixin
11973hunk ./src/allmydata/test/common.py 192
11974         return defer.succeed(self)
11975     def _get_initial_contents(self, contents):
11976         if contents is None:
11977-            return MutableDataHandle("")
11978+            return MutableData("")
11979 
11980         if IMutableUploadable.providedBy(contents):
11981             return contents
11982hunk ./src/allmydata/test/test_checker.py 11
11983 from allmydata.test.no_network import GridTestMixin
11984 from allmydata.immutable.upload import Data
11985 from allmydata.test.common_web import WebRenderingMixin
11986-from allmydata.mutable.publish import MutableDataHandle
11987+from allmydata.mutable.publish import MutableData
11988 
11989 class FakeClient:
11990     def get_storage_broker(self):
11991hunk ./src/allmydata/test/test_checker.py 292
11992             self.imm = c0.create_node_from_uri(ur.uri)
11993         d.addCallback(_stash_immutable)
11994         d.addCallback(lambda ign:
11995-            c0.create_mutable_file(MutableDataHandle("contents")))
11996+            c0.create_mutable_file(MutableData("contents")))
11997         def _stash_mutable(node):
11998             self.mut = node
11999         d.addCallback(_stash_mutable)
12000hunk ./src/allmydata/test/test_cli.py 12
12001 from allmydata.util import fileutil, hashutil, base32
12002 from allmydata import uri
12003 from allmydata.immutable import upload
12004-from allmydata.mutable.publish import MutableDataHandle
12005+from allmydata.mutable.publish import MutableData
12006 from allmydata.dirnode import normalize
12007 
12008 # Test that the scripts can be imported -- although the actual tests of their
12009hunk ./src/allmydata/test/test_cli.py 1975
12010         self.set_up_grid()
12011         c0 = self.g.clients[0]
12012         DATA = "data" * 100
12013-        DATA_uploadable = MutableDataHandle(DATA)
12014+        DATA_uploadable = MutableData(DATA)
12015         d = c0.create_mutable_file(DATA_uploadable)
12016         def _stash_uri(n):
12017             self.uri = n.get_uri()
12018hunk ./src/allmydata/test/test_cli.py 2078
12019                                                         convergence="")))
12020         d.addCallback(_stash_uri, "small")
12021         d.addCallback(lambda ign:
12022-            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
12023+            c0.create_mutable_file(MutableData(DATA+"1")))
12024         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
12025         d.addCallback(_stash_uri, "mutable")
12026 
12027hunk ./src/allmydata/test/test_deepcheck.py 9
12028 from twisted.internet import threads # CLI tests use deferToThread
12029 from allmydata.immutable import upload
12030 from allmydata.mutable.common import UnrecoverableFileError
12031-from allmydata.mutable.publish import MutableDataHandle
12032+from allmydata.mutable.publish import MutableData
12033 from allmydata.util import idlib
12034 from allmydata.util import base32
12035 from allmydata.scripts import runner
12036hunk ./src/allmydata/test/test_deepcheck.py 38
12037         self.basedir = "deepcheck/MutableChecker/good"
12038         self.set_up_grid()
12039         CONTENTS = "a little bit of data"
12040-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12041+        CONTENTS_uploadable = MutableData(CONTENTS)
12042         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12043         def _created(node):
12044             self.node = node
12045hunk ./src/allmydata/test/test_deepcheck.py 61
12046         self.basedir = "deepcheck/MutableChecker/corrupt"
12047         self.set_up_grid()
12048         CONTENTS = "a little bit of data"
12049-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12050+        CONTENTS_uploadable = MutableData(CONTENTS)
12051         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12052         def _stash_and_corrupt(node):
12053             self.node = node
12054hunk ./src/allmydata/test/test_deepcheck.py 99
12055         self.basedir = "deepcheck/MutableChecker/delete_share"
12056         self.set_up_grid()
12057         CONTENTS = "a little bit of data"
12058-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12059+        CONTENTS_uploadable = MutableData(CONTENTS)
12060         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12061         def _stash_and_delete(node):
12062             self.node = node
12063hunk ./src/allmydata/test/test_deepcheck.py 224
12064             self.root_uri = n.get_uri()
12065         d.addCallback(_created_root)
12066         d.addCallback(lambda ign:
12067-            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
12068+            c0.create_mutable_file(MutableData("mutable file contents")))
12069         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
12070         def _created_mutable(n):
12071             self.mutable = n
12072hunk ./src/allmydata/test/test_deepcheck.py 965
12073     def create_mangled(self, ignored, name):
12074         nodetype, mangletype = name.split("-", 1)
12075         if nodetype == "mutable":
12076-            mutable_uploadable = MutableDataHandle("mutable file contents")
12077+            mutable_uploadable = MutableData("mutable file contents")
12078             d = self.g.clients[0].create_mutable_file(mutable_uploadable)
12079             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
12080         elif nodetype == "large":
12081hunk ./src/allmydata/test/test_hung_server.py 10
12082 from allmydata.util.consumer import download_to_data
12083 from allmydata.immutable import upload
12084 from allmydata.mutable.common import UnrecoverableFileError
12085-from allmydata.mutable.publish import MutableDataHandle
12086+from allmydata.mutable.publish import MutableData
12087 from allmydata.storage.common import storage_index_to_dir
12088 from allmydata.test.no_network import GridTestMixin
12089 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
12090hunk ./src/allmydata/test/test_hung_server.py 96
12091         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
12092 
12093         if mutable:
12094-            uploadable = MutableDataHandle(mutable_plaintext)
12095+            uploadable = MutableData(mutable_plaintext)
12096             d = nm.create_mutable_file(uploadable)
12097             def _uploaded_mutable(node):
12098                 self.uri = node.get_uri()
12099hunk ./src/allmydata/test/test_mutable.py 27
12100      NotEnoughServersError, CorruptShareError
12101 from allmydata.mutable.retrieve import Retrieve
12102 from allmydata.mutable.publish import Publish, MutableFileHandle, \
12103-                                      MutableDataHandle
12104+                                      MutableData
12105 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
12106 from allmydata.mutable.layout import unpack_header, unpack_share, \
12107                                      MDMFSlotReadProxy
12108hunk ./src/allmydata/test/test_mutable.py 297
12109             d.addCallback(lambda smap: smap.dump(StringIO()))
12110             d.addCallback(lambda sio:
12111                           self.failUnless("3-of-10" in sio.getvalue()))
12112-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
12113+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
12114             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
12115             d.addCallback(lambda res: n.download_best_version())
12116             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12117hunk ./src/allmydata/test/test_mutable.py 304
12118             d.addCallback(lambda res: n.get_size_of_best_version())
12119             d.addCallback(lambda size:
12120                           self.failUnlessEqual(size, len("contents 1")))
12121-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12122+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12123             d.addCallback(lambda res: n.download_best_version())
12124             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12125             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12126hunk ./src/allmydata/test/test_mutable.py 308
12127-            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
12128+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
12129             d.addCallback(lambda res: n.download_best_version())
12130             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
12131             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
12132hunk ./src/allmydata/test/test_mutable.py 320
12133             # mapupdate-to-retrieve data caching (i.e. make the shares larger
12134             # than the default readsize, which is 2000 bytes). A 15kB file
12135             # will have 5kB shares.
12136-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
12137+            d.addCallback(lambda res: n.overwrite(MutableData("large size file" * 1000)))
12138             d.addCallback(lambda res: n.download_best_version())
12139             d.addCallback(lambda res:
12140                           self.failUnlessEqual(res, "large size file" * 1000))
12141hunk ./src/allmydata/test/test_mutable.py 343
12142             # to make them big enough to force the file to be uploaded
12143             # in more than one segment.
12144             big_contents = "contents1" * 100000 # about 900 KiB
12145-            big_contents_uploadable = MutableDataHandle(big_contents)
12146+            big_contents_uploadable = MutableData(big_contents)
12147             d.addCallback(lambda ignored:
12148                 n.overwrite(big_contents_uploadable))
12149             d.addCallback(lambda ignored:
12150hunk ./src/allmydata/test/test_mutable.py 355
12151             # segments, so that we make the downloader deal with
12152             # multiple segments.
12153             bigger_contents = "contents2" * 1000000 # about 9MiB
12154-            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
12155+            bigger_contents_uploadable = MutableData(bigger_contents)
12156             d.addCallback(lambda ignored:
12157                 n.overwrite(bigger_contents_uploadable))
12158             d.addCallback(lambda ignored:
12159hunk ./src/allmydata/test/test_mutable.py 368
12160 
12161 
12162     def test_create_with_initial_contents(self):
12163-        upload1 = MutableDataHandle("contents 1")
12164+        upload1 = MutableData("contents 1")
12165         d = self.nodemaker.create_mutable_file(upload1)
12166         def _created(n):
12167             d = n.download_best_version()
12168hunk ./src/allmydata/test/test_mutable.py 373
12169             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12170-            upload2 = MutableDataHandle("contents 2")
12171+            upload2 = MutableData("contents 2")
12172             d.addCallback(lambda res: n.overwrite(upload2))
12173             d.addCallback(lambda res: n.download_best_version())
12174             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12175hunk ./src/allmydata/test/test_mutable.py 385
12176 
12177     def test_create_mdmf_with_initial_contents(self):
12178         initial_contents = "foobarbaz" * 131072 # 900KiB
12179-        initial_contents_uploadable = MutableDataHandle(initial_contents)
12180+        initial_contents_uploadable = MutableData(initial_contents)
12181         d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
12182                                                version=MDMF_VERSION)
12183         def _created(n):
12184hunk ./src/allmydata/test/test_mutable.py 392
12185             d = n.download_best_version()
12186             d.addCallback(lambda data:
12187                 self.failUnlessEqual(data, initial_contents))
12188-            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
12189+            uploadable2 = MutableData(initial_contents + "foobarbaz")
12190             d.addCallback(lambda ignored:
12191                 n.overwrite(uploadable2))
12192             d.addCallback(lambda ignored:
12193hunk ./src/allmydata/test/test_mutable.py 413
12194             key = n.get_writekey()
12195             self.failUnless(isinstance(key, str), key)
12196             self.failUnlessEqual(len(key), 16) # AES key size
12197-            return MutableDataHandle(data)
12198+            return MutableData(data)
12199         d = self.nodemaker.create_mutable_file(_make_contents)
12200         def _created(n):
12201             return n.download_best_version()
12202hunk ./src/allmydata/test/test_mutable.py 429
12203             key = n.get_writekey()
12204             self.failUnless(isinstance(key, str), key)
12205             self.failUnlessEqual(len(key), 16)
12206-            return MutableDataHandle(data)
12207+            return MutableData(data)
12208         d = self.nodemaker.create_mutable_file(_make_contents,
12209                                                version=MDMF_VERSION)
12210         d.addCallback(lambda n:
12211hunk ./src/allmydata/test/test_mutable.py 441
12212 
12213     def test_create_with_too_large_contents(self):
12214         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
12215-        BIG_uploadable = MutableDataHandle(BIG)
12216+        BIG_uploadable = MutableData(BIG)
12217         d = self.nodemaker.create_mutable_file(BIG_uploadable)
12218         def _created(n):
12219hunk ./src/allmydata/test/test_mutable.py 444
12220-            other_BIG_uploadable = MutableDataHandle(BIG)
12221+            other_BIG_uploadable = MutableData(BIG)
12222             d = n.overwrite(other_BIG_uploadable)
12223             return d
12224         d.addCallback(_created)
12225hunk ./src/allmydata/test/test_mutable.py 460
12226     def test_modify(self):
12227         def _modifier(old_contents, servermap, first_time):
12228             new_contents = old_contents + "line2"
12229-            return MutableDataHandle(new_contents)
12230+            return MutableData(new_contents)
12231         def _non_modifier(old_contents, servermap, first_time):
12232hunk ./src/allmydata/test/test_mutable.py 462
12233-            return MutableDataHandle(old_contents)
12234+            return MutableData(old_contents)
12235         def _none_modifier(old_contents, servermap, first_time):
12236             return None
12237         def _error_modifier(old_contents, servermap, first_time):
12238hunk ./src/allmydata/test/test_mutable.py 469
12239             raise ValueError("oops")
12240         def _toobig_modifier(old_contents, servermap, first_time):
12241             new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
12242-            return MutableDataHandle(new_content)
12243+            return MutableData(new_content)
12244         calls = []
12245         def _ucw_error_modifier(old_contents, servermap, first_time):
12246             # simulate an UncoordinatedWriteError once
12247hunk ./src/allmydata/test/test_mutable.py 477
12248             if len(calls) <= 1:
12249                 raise UncoordinatedWriteError("simulated")
12250             new_contents = old_contents + "line3"
12251-            return MutableDataHandle(new_contents)
12252+            return MutableData(new_contents)
12253         def _ucw_error_non_modifier(old_contents, servermap, first_time):
12254             # simulate an UncoordinatedWriteError once, and don't actually
12255             # modify the contents on subsequent invocations
12256hunk ./src/allmydata/test/test_mutable.py 484
12257             calls.append(1)
12258             if len(calls) <= 1:
12259                 raise UncoordinatedWriteError("simulated")
12260-            return MutableDataHandle(old_contents)
12261+            return MutableData(old_contents)
12262 
12263         initial_contents = "line1"
12264hunk ./src/allmydata/test/test_mutable.py 487
12265-        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
12266+        d = self.nodemaker.create_mutable_file(MutableData(initial_contents))
12267         def _created(n):
12268             d = n.modify(_modifier)
12269             d.addCallback(lambda res: n.download_best_version())
12270hunk ./src/allmydata/test/test_mutable.py 548
12271 
12272     def test_modify_backoffer(self):
12273         def _modifier(old_contents, servermap, first_time):
12274-            return MutableDataHandle(old_contents + "line2")
12275+            return MutableData(old_contents + "line2")
12276         calls = []
12277         def _ucw_error_modifier(old_contents, servermap, first_time):
12278             # simulate an UncoordinatedWriteError once
12279hunk ./src/allmydata/test/test_mutable.py 555
12280             calls.append(1)
12281             if len(calls) <= 1:
12282                 raise UncoordinatedWriteError("simulated")
12283-            return MutableDataHandle(old_contents + "line3")
12284+            return MutableData(old_contents + "line3")
12285         def _always_ucw_error_modifier(old_contents, servermap, first_time):
12286             raise UncoordinatedWriteError("simulated")
12287         def _backoff_stopper(node, f):
12288hunk ./src/allmydata/test/test_mutable.py 570
12289         giveuper._delay = 0.1
12290         giveuper.factor = 1
12291 
12292-        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
12293+        d = self.nodemaker.create_mutable_file(MutableData("line1"))
12294         def _created(n):
12295             d = n.modify(_modifier)
12296             d.addCallback(lambda res: n.download_best_version())
12297hunk ./src/allmydata/test/test_mutable.py 620
12298             d.addCallback(lambda smap: smap.dump(StringIO()))
12299             d.addCallback(lambda sio:
12300                           self.failUnless("3-of-10" in sio.getvalue()))
12301-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
12302+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
12303             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
12304             d.addCallback(lambda res: n.download_best_version())
12305             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12306hunk ./src/allmydata/test/test_mutable.py 624
12307-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12308+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12309             d.addCallback(lambda res: n.download_best_version())
12310             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12311             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12312hunk ./src/allmydata/test/test_mutable.py 628
12313-            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
12314+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
12315             d.addCallback(lambda res: n.download_best_version())
12316             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
12317             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
12318hunk ./src/allmydata/test/test_mutable.py 646
12319         # publish a file and create shares, which can then be manipulated
12320         # later.
12321         self.CONTENTS = "New contents go here" * 1000
12322-        self.uploadable = MutableDataHandle(self.CONTENTS)
12323+        self.uploadable = MutableData(self.CONTENTS)
12324         self._storage = FakeStorage()
12325         self._nodemaker = make_nodemaker(self._storage)
12326         self._storage_broker = self._nodemaker.storage_broker
12327hunk ./src/allmydata/test/test_mutable.py 662
12328         # an MDMF file.
12329         # self.CONTENTS should have more than one segment.
12330         self.CONTENTS = "This is an MDMF file" * 100000
12331-        self.uploadable = MutableDataHandle(self.CONTENTS)
12332+        self.uploadable = MutableData(self.CONTENTS)
12333         self._storage = FakeStorage()
12334         self._nodemaker = make_nodemaker(self._storage)
12335         self._storage_broker = self._nodemaker.storage_broker
12336hunk ./src/allmydata/test/test_mutable.py 678
12337         # like publish_one, except that the result is guaranteed to be
12338         # an SDMF file
12339         self.CONTENTS = "This is an SDMF file" * 1000
12340-        self.uploadable = MutableDataHandle(self.CONTENTS)
12341+        self.uploadable = MutableData(self.CONTENTS)
12342         self._storage = FakeStorage()
12343         self._nodemaker = make_nodemaker(self._storage)
12344         self._storage_broker = self._nodemaker.storage_broker
12345hunk ./src/allmydata/test/test_mutable.py 696
12346                          "Contents 2",
12347                          "Contents 3a",
12348                          "Contents 3b"]
12349-        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
12350+        self.uploadables = [MutableData(d) for d in self.CONTENTS]
12351         self._copied_shares = {}
12352         self._storage = FakeStorage()
12353         self._nodemaker = make_nodemaker(self._storage)
12354hunk ./src/allmydata/test/test_mutable.py 826
12355         # create a new file, which is large enough to knock the privkey out
12356         # of the early part of the file
12357         LARGE = "These are Larger contents" * 200 # about 5KB
12358-        LARGE_uploadable = MutableDataHandle(LARGE)
12359+        LARGE_uploadable = MutableData(LARGE)
12360         d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
12361         def _created(large_fn):
12362             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
12363hunk ./src/allmydata/test/test_mutable.py 1842
12364 class MultipleEncodings(unittest.TestCase):
12365     def setUp(self):
12366         self.CONTENTS = "New contents go here"
12367-        self.uploadable = MutableDataHandle(self.CONTENTS)
12368+        self.uploadable = MutableData(self.CONTENTS)
12369         self._storage = FakeStorage()
12370         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
12371         self._storage_broker = self._nodemaker.storage_broker
12372hunk ./src/allmydata/test/test_mutable.py 1872
12373         s = self._storage
12374         s._peers = {} # clear existing storage
12375         p2 = Publish(fn2, self._storage_broker, None)
12376-        uploadable = MutableDataHandle(data)
12377+        uploadable = MutableData(data)
12378         d = p2.publish(uploadable)
12379         def _published(res):
12380             shares = s._peers
12381hunk ./src/allmydata/test/test_mutable.py 2049
12382         self._set_versions(target)
12383 
12384         def _modify(oldversion, servermap, first_time):
12385-            return MutableDataHandle(oldversion + " modified")
12386+            return MutableData(oldversion + " modified")
12387         d = self._fn.modify(_modify)
12388         d.addCallback(lambda res: self._fn.download_best_version())
12389         expected = self.CONTENTS[2] + " modified"
12390hunk ./src/allmydata/test/test_mutable.py 2175
12391         self.basedir = "mutable/Problems/test_publish_surprise"
12392         self.set_up_grid()
12393         nm = self.g.clients[0].nodemaker
12394-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12395+        d = nm.create_mutable_file(MutableData("contents 1"))
12396         def _created(n):
12397             d = defer.succeed(None)
12398             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12399hunk ./src/allmydata/test/test_mutable.py 2185
12400             d.addCallback(_got_smap1)
12401             # then modify the file, leaving the old map untouched
12402             d.addCallback(lambda res: log.msg("starting winning write"))
12403-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12404+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12405             # now attempt to modify the file with the old servermap. This
12406             # will look just like an uncoordinated write, in which every
12407             # single share got updated between our mapupdate and our publish
12408hunk ./src/allmydata/test/test_mutable.py 2194
12409                           self.shouldFail(UncoordinatedWriteError,
12410                                           "test_publish_surprise", None,
12411                                           n.upload,
12412-                                          MutableDataHandle("contents 2a"), self.old_map))
12413+                                          MutableData("contents 2a"), self.old_map))
12414             return d
12415         d.addCallback(_created)
12416         return d
12417hunk ./src/allmydata/test/test_mutable.py 2203
12418         self.basedir = "mutable/Problems/test_retrieve_surprise"
12419         self.set_up_grid()
12420         nm = self.g.clients[0].nodemaker
12421-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12422+        d = nm.create_mutable_file(MutableData("contents 1"))
12423         def _created(n):
12424             d = defer.succeed(None)
12425             d.addCallback(lambda res: n.get_servermap(MODE_READ))
12426hunk ./src/allmydata/test/test_mutable.py 2213
12427             d.addCallback(_got_smap1)
12428             # then modify the file, leaving the old map untouched
12429             d.addCallback(lambda res: log.msg("starting winning write"))
12430-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12431+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12432             # now attempt to retrieve the old version with the old servermap.
12433             # This will look like someone has changed the file since we
12434             # updated the servermap.
12435hunk ./src/allmydata/test/test_mutable.py 2241
12436         self.basedir = "mutable/Problems/test_unexpected_shares"
12437         self.set_up_grid()
12438         nm = self.g.clients[0].nodemaker
12439-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12440+        d = nm.create_mutable_file(MutableData("contents 1"))
12441         def _created(n):
12442             d = defer.succeed(None)
12443             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12444hunk ./src/allmydata/test/test_mutable.py 2253
12445                 self.g.remove_server(peer0)
12446                 # then modify the file, leaving the old map untouched
12447                 log.msg("starting winning write")
12448-                return n.overwrite(MutableDataHandle("contents 2"))
12449+                return n.overwrite(MutableData("contents 2"))
12450             d.addCallback(_got_smap1)
12451             # now attempt to modify the file with the old servermap. This
12452             # will look just like an uncoordinated write, in which every
12453hunk ./src/allmydata/test/test_mutable.py 2263
12454                           self.shouldFail(UncoordinatedWriteError,
12455                                           "test_surprise", None,
12456                                           n.upload,
12457-                                          MutableDataHandle("contents 2a"), self.old_map))
12458+                                          MutableData("contents 2a"), self.old_map))
12459             return d
12460         d.addCallback(_created)
12461         return d
12462hunk ./src/allmydata/test/test_mutable.py 2303
12463         d.addCallback(_break_peer0)
12464         # now "create" the file, using the pre-established key, and let the
12465         # initial publish finally happen
12466-        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
12467+        d.addCallback(lambda res: nm.create_mutable_file(MutableData("contents 1")))
12468         # that ought to work
12469         def _got_node(n):
12470             d = n.download_best_version()
12471hunk ./src/allmydata/test/test_mutable.py 2312
12472             def _break_peer1(res):
12473                 self.connection1.broken = True
12474             d.addCallback(_break_peer1)
12475-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12476+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12477             # that ought to work too
12478             d.addCallback(lambda res: n.download_best_version())
12479             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12480hunk ./src/allmydata/test/test_mutable.py 2344
12481         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
12482         self.g.break_server(peerids[0])
12483 
12484-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12485+        d = nm.create_mutable_file(MutableData("contents 1"))
12486         def _created(n):
12487             d = n.download_best_version()
12488             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12489hunk ./src/allmydata/test/test_mutable.py 2352
12490             def _break_second_server(res):
12491                 self.g.break_server(peerids[1])
12492             d.addCallback(_break_second_server)
12493-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12494+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12495             # that ought to work too
12496             d.addCallback(lambda res: n.download_best_version())
12497             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12498hunk ./src/allmydata/test/test_mutable.py 2371
12499         d = self.shouldFail(NotEnoughServersError,
12500                             "test_publish_all_servers_bad",
12501                             "Ran out of non-bad servers",
12502-                            nm.create_mutable_file, MutableDataHandle("contents"))
12503+                            nm.create_mutable_file, MutableData("contents"))
12504         return d
12505 
12506     def test_publish_no_servers(self):
12507hunk ./src/allmydata/test/test_mutable.py 2383
12508         d = self.shouldFail(NotEnoughServersError,
12509                             "test_publish_no_servers",
12510                             "Ran out of non-bad servers",
12511-                            nm.create_mutable_file, MutableDataHandle("contents"))
12512+                            nm.create_mutable_file, MutableData("contents"))
12513         return d
12514     test_publish_no_servers.timeout = 30
12515 
12516hunk ./src/allmydata/test/test_mutable.py 2401
12517         # we need some contents that are large enough to push the privkey out
12518         # of the early part of the file
12519         LARGE = "These are Larger contents" * 2000 # about 50KB
12520-        LARGE_uploadable = MutableDataHandle(LARGE)
12521+        LARGE_uploadable = MutableData(LARGE)
12522         d = nm.create_mutable_file(LARGE_uploadable)
12523         def _created(n):
12524             self.uri = n.get_uri()
12525hunk ./src/allmydata/test/test_mutable.py 2438
12526         self.set_up_grid(num_servers=20)
12527         nm = self.g.clients[0].nodemaker
12528         LARGE = "These are Larger contents" * 2000 # about 50KiB
12529-        LARGE_uploadable = MutableDataHandle(LARGE)
12530+        LARGE_uploadable = MutableData(LARGE)
12531         nm._node_cache = DevNullDictionary() # disable the nodecache
12532 
12533         d = nm.create_mutable_file(LARGE_uploadable)
12534hunk ./src/allmydata/test/test_mutable.py 2464
12535         self.set_up_grid(num_servers=20)
12536         nm = self.g.clients[0].nodemaker
12537         CONTENTS = "contents" * 2000
12538-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12539+        CONTENTS_uploadable = MutableData(CONTENTS)
12540         d = nm.create_mutable_file(CONTENTS_uploadable)
12541         def _created(node):
12542             self._node = node
12543hunk ./src/allmydata/test/test_mutable.py 2565
12544 class DataHandle(unittest.TestCase):
12545     def setUp(self):
12546         self.test_data = "Test Data" * 50000
12547-        self.uploadable = MutableDataHandle(self.test_data)
12548+        self.uploadable = MutableData(self.test_data)
12549 
12550 
12551     def test_datahandle_read(self):
12552hunk ./src/allmydata/test/test_sftp.py 84
12553         return d
12554 
12555     def _set_up_tree(self):
12556-        u = publish.MutableDataHandle("mutable file contents")
12557+        u = publish.MutableData("mutable file contents")
12558         d = self.client.create_mutable_file(u)
12559         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
12560         def _created_mutable(n):
12561hunk ./src/allmydata/test/test_system.py 22
12562 from allmydata.monitor import Monitor
12563 from allmydata.mutable.common import NotWriteableError
12564 from allmydata.mutable import layout as mutable_layout
12565-from allmydata.mutable.publish import MutableDataHandle
12566+from allmydata.mutable.publish import MutableData
12567 from foolscap.api import DeadReferenceError
12568 from twisted.python.failure import Failure
12569 from twisted.web.client import getPage
12570hunk ./src/allmydata/test/test_system.py 460
12571     def test_mutable(self):
12572         self.basedir = "system/SystemTest/test_mutable"
12573         DATA = "initial contents go here."  # 25 bytes % 3 != 0
12574-        DATA_uploadable = MutableDataHandle(DATA)
12575+        DATA_uploadable = MutableData(DATA)
12576         NEWDATA = "new contents yay"
12577hunk ./src/allmydata/test/test_system.py 462
12578-        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
12579+        NEWDATA_uploadable = MutableData(NEWDATA)
12580         NEWERDATA = "this is getting old"
12581hunk ./src/allmydata/test/test_system.py 464
12582-        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
12583+        NEWERDATA_uploadable = MutableData(NEWERDATA)
12584 
12585         d = self.set_up_nodes(use_key_generator=True)
12586 
12587hunk ./src/allmydata/test/test_system.py 642
12588         def _check_empty_file(res):
12589             # make sure we can create empty files, this usually screws up the
12590             # segsize math
12591-            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
12592+            d1 = self.clients[2].create_mutable_file(MutableData(""))
12593             d1.addCallback(lambda newnode: newnode.download_best_version())
12594             d1.addCallback(lambda res: self.failUnlessEqual("", res))
12595             return d1
12596hunk ./src/allmydata/test/test_system.py 674
12597 
12598         d.addCallback(check_kg_poolsize, 0)
12599         d.addCallback(lambda junk:
12600-            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
12601+            self.clients[3].create_mutable_file(MutableData('hello, world')))
12602         d.addCallback(check_kg_poolsize, -1)
12603         d.addCallback(lambda junk: self.clients[3].create_dirnode())
12604         d.addCallback(check_kg_poolsize, -2)
12605hunk ./src/allmydata/test/test_web.py 3184
12606             self.uris[which] = n.get_uri()
12607             assert isinstance(self.uris[which], str)
12608         d.addCallback(lambda ign:
12609-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
12610+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12611         d.addCallback(_stash_mutable_uri, "corrupt")
12612         d.addCallback(lambda ign:
12613                       c0.upload(upload.Data("literal", convergence="")))
12614hunk ./src/allmydata/test/test_web.py 3331
12615             self.uris[which] = n.get_uri()
12616             assert isinstance(self.uris[which], str)
12617         d.addCallback(lambda ign:
12618-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
12619+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12620         d.addCallback(_stash_mutable_uri, "corrupt")
12621 
12622         def _compute_fileurls(ignored):
12623hunk ./src/allmydata/test/test_web.py 3994
12624             self.uris[which] = n.get_uri()
12625             assert isinstance(self.uris[which], str)
12626         d.addCallback(lambda ign:
12627-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
12628+            c0.create_mutable_file(publish.MutableData(DATA+"2")))
12629         d.addCallback(_stash_mutable_uri, "mutable")
12630 
12631         def _compute_fileurls(ignored):
12632hunk ./src/allmydata/test/test_web.py 4094
12633         d.addCallback(_stash_uri, "small")
12634 
12635         d.addCallback(lambda ign:
12636-            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
12637+            c0.create_mutable_file(publish.MutableData("mutable")))
12638         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
12639         d.addCallback(_stash_uri, "mutable")
12640 
12641}
12642[tests: fix tests that were broken by #993
12643Kevan Carstensen <kevan@isnotajoke.com>**20100717015230
12644 Ignore-this: f0ace6538c6d824b7fe271c40a7ebf8d
12645] {
12646hunk ./src/allmydata/test/common.py 152
12647         consumer.write(data[start:end])
12648         return consumer
12649 
12650+
12651+    def get_best_readable_version(self):
12652+        return defer.succeed(self)
12653+
12654+
12655+    download_best_version = download_to_data
12656+
12657+
12658+    def download_to_data(self):
12659+        return download_to_data(self)
12660+
12661+
12662+    def get_size_of_best_version(self):
12663+        return defer.succeed(self.get_size)
12664+
12665+
12666 def make_chk_file_cap(size):
12667     return uri.CHKFileURI(key=os.urandom(16),
12668                           uri_extension_hash=os.urandom(32),
12669hunk ./src/allmydata/test/common.py 318
12670         return d
12671 
12672     def download_best_version(self):
12673+        return defer.succeed(self._download_best_version())
12674+
12675+
12676+    def _download_best_version(self, ignored=None):
12677         if isinstance(self.my_uri, uri.LiteralFileURI):
12678hunk ./src/allmydata/test/common.py 323
12679-            return defer.succeed(self.my_uri.data)
12680+            return self.my_uri.data
12681         if self.storage_index not in self.all_contents:
12682hunk ./src/allmydata/test/common.py 325
12683-            return defer.fail(NotEnoughSharesError(None, 0, 3))
12684-        return defer.succeed(self.all_contents[self.storage_index])
12685+            raise NotEnoughSharesError(None, 0, 3)
12686+        return self.all_contents[self.storage_index]
12687+
12688 
12689     def overwrite(self, new_contents):
12690         if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
12691hunk ./src/allmydata/test/common.py 352
12692         self.all_contents[self.storage_index] = new_data
12693         return None
12694 
12695+    # As actually implemented, MutableFilenode and MutableFileVersion
12696+    # are distinct. However, nothing in the webapi uses (yet) that
12697+    # distinction -- it just uses the unified download interface
12698+    # provided by get_best_readable_version and read. When we start
12699+    # doing cooler things like LDMF, we will want to revise this code to
12700+    # be less simplistic.
12701+    def get_best_readable_version(self):
12702+        return defer.succeed(self)
12703+
12704+
12705+    def read(self, consumer, offset=0, size=None):
12706+        data = self._download_best_version()
12707+        if size:
12708+            data = data[offset:offset+size]
12709+        consumer.write(data)
12710+        return defer.succeed(consumer)
12711+
12712+
12713 def make_mutable_file_cap():
12714     return uri.WriteableSSKFileURI(writekey=os.urandom(16),
12715                                    fingerprint=os.urandom(32))
12716hunk ./src/allmydata/test/test_filenode.py 98
12717         def _check_segment(res):
12718             self.failUnlessEqual(res, DATA[1:1+5])
12719         d.addCallback(_check_segment)
12720-        d.addCallback(lambda ignored:
12721-            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
12722+        d.addCallback(lambda ignored: fn1.get_best_readable_version())
12723+        d.addCallback(lambda fn2: self.failUnlessEqual(fn1, fn2))
12724         d.addCallback(lambda ignored:
12725             fn1.get_size_of_best_version())
12726         d.addCallback(lambda size:
12727hunk ./src/allmydata/test/test_immutable.py 168
12728 
12729 
12730     def test_get_best_readable_version(self):
12731-        n = self.n.get_best_readable_version()
12732-        self.failUnlessEqual(n, self.n)
12733+        d = self.n.get_best_readable_version()
12734+        d.addCallback(lambda n2:
12735+            self.failUnlessEqual(n2, self.n))
12736+        return d
12737 
12738     def test_get_size_of_best_version(self):
12739         d = self.n.get_size_of_best_version()
12740hunk ./src/allmydata/test/test_mutable.py 8
12741 from twisted.internet import defer, reactor
12742 from allmydata import uri, client
12743 from allmydata.nodemaker import NodeMaker
12744-from allmydata.util import base32
12745+from allmydata.util import base32, consumer
12746 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
12747      ssk_pubkey_fingerprint_hash
12748hunk ./src/allmydata/test/test_mutable.py 11
12749+from allmydata.util.deferredutil import gatherResults
12750 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
12751      NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
12752 from allmydata.monitor import Monitor
12753hunk ./src/allmydata/test/test_mutable.py 1000
12754         if version is None:
12755             version = servermap.best_recoverable_version()
12756         r = Retrieve(self._fn, servermap, version)
12757-        return r.download()
12758+        c = consumer.MemoryConsumer()
12759+        d = r.download(consumer=c)
12760+        d.addCallback(lambda mc: "".join(mc.chunks))
12761+        return d
12762+
12763 
12764     def test_basic(self):
12765         d = self.make_servermap()
12766hunk ./src/allmydata/test/test_mutable.py 1263
12767                             in str(servermap.problems[0]))
12768             ver = servermap.best_recoverable_version()
12769             r = Retrieve(self._fn, servermap, ver)
12770-            return r.download()
12771+            c = consumer.MemoryConsumer()
12772+            return r.download(c)
12773         d.addCallback(_do_retrieve)
12774hunk ./src/allmydata/test/test_mutable.py 1266
12775+        d.addCallback(lambda mc: "".join(mc.chunks))
12776         d.addCallback(lambda new_contents:
12777                       self.failUnlessEqual(new_contents, self.CONTENTS))
12778         return d
12779}
12780[test/test_immutable.py: add tests for #993-related modifications
12781Kevan Carstensen <kevan@isnotajoke.com>**20100717015402
12782 Ignore-this: d94ad98bd0d322ead85af2e0aa95be38
12783] hunk ./src/allmydata/test/test_mutable.py 2607
12784         start = chunk_size
12785         end = chunk_size * 2
12786         self.failUnlessEqual("".join(more_data), self.test_data[start:end])
12787+
12788+
12789+class Version(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
12790+    def setUp(self):
12791+        GridTestMixin.setUp(self)
12792+        self.basedir = self.mktemp()
12793+        self.set_up_grid()
12794+        self.c = self.g.clients[0]
12795+        self.nm = self.c.nodemaker
12796+        self.data = "test data" * 100000 # about 900 KiB; MDMF
12797+        self.small_data = "test data" * 10 # about 90 B; SDMF
12798+        return self.do_upload()
12799+
12800+
12801+    def do_upload(self):
12802+        d1 = self.nm.create_mutable_file(MutableData(self.data),
12803+                                         version=MDMF_VERSION)
12804+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
12805+        dl = gatherResults([d1, d2])
12806+        def _then((n1, n2)):
12807+            assert isinstance(n1, MutableFileNode)
12808+            assert isinstance(n2, MutableFileNode)
12809+
12810+            self.mdmf_node = n1
12811+            self.sdmf_node = n2
12812+        dl.addCallback(_then)
12813+        return dl
12814+
12815+
12816+    def test_get_readonly_mutable_version(self):
12817+        # Attempting to get a mutable version of a mutable file from a
12818+        # filenode initialized with a readcap should return a readonly
12819+        # version of that same node.
12820+        ro = self.mdmf_node.get_readonly()
12821+        d = ro.get_best_mutable_version()
12822+        d.addCallback(lambda version:
12823+            self.failUnless(version.is_readonly()))
12824+        d.addCallback(lambda ignored:
12825+            self.sdmf_node.get_readonly())
12826+        d.addCallback(lambda version:
12827+            self.failUnless(version.is_readonly()))
12828+        return d
12829+
12830+
12831+    def test_get_sequence_number(self):
12832+        d = self.mdmf_node.get_best_readable_version()
12833+        d.addCallback(lambda bv:
12834+            self.failUnlessEqual(bv.get_sequence_number(), 0))
12835+        d.addCallback(lambda ignored:
12836+            self.sdmf_node.get_best_readable_version())
12837+        d.addCallback(lambda bv:
12838+            self.failUnlessEqual(bv.get_sequence_number(), 0))
12839+        # Now update. The sequence number in both cases should be 1 in
12840+        # both cases.
12841+        def _do_update(ignored):
12842+            new_data = MutableData("foo bar baz" * 100000)
12843+            new_small_data = MutableData("foo bar baz" * 10)
12844+            d1 = self.mdmf_node.overwrite(new_data)
12845+            d2 = self.sdmf_node.overwrite(new_small_data)
12846+            dl = gatherResults([d1, d2])
12847+            return dl
12848+        d.addCallback(_do_update)
12849+        d.addCallback(lambda ignored:
12850+            self.mdmf_node.get_best_readable_version())
12851+        d.addCallback(lambda bv:
12852+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12853+        d.addCallback(lambda ignored:
12854+            self.sdmf_node.get_best_readable_version())
12855+        d.addCallback(lambda bv:
12856+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12857+        return d
12858+
12859+
12860+    def test_get_writekey(self):
12861+        d = self.mdmf_node.get_best_mutable_version()
12862+        d.addCallback(lambda bv:
12863+            self.failUnlessEqual(bv.get_writekey(),
12864+                                 self.mdmf_node.get_writekey()))
12865+        d.addCallback(lambda ignored:
12866+            self.sdmf_node.get_best_mutable_version())
12867+        d.addCallback(lambda bv:
12868+            self.failUnlessEqual(bv.get_writekey(),
12869+                                 self.sdmf_node.get_writekey()))
12870+        return d
12871+
12872+
12873+    def test_get_storage_index(self):
12874+        d = self.mdmf_node.get_best_mutable_version()
12875+        d.addCallback(lambda bv:
12876+            self.failUnlessEqual(bv.get_storage_index(),
12877+                                 self.mdmf_node.get_storage_index()))
12878+        d.addCallback(lambda ignored:
12879+            self.sdmf_node.get_best_mutable_version())
12880+        d.addCallback(lambda bv:
12881+            self.failUnlessEqual(bv.get_storage_index(),
12882+                                 self.sdmf_node.get_storage_index()))
12883+        return d
12884+
12885+
12886+    def test_get_readonly_version(self):
12887+        d = self.mdmf_node.get_best_readable_version()
12888+        d.addCallback(lambda bv:
12889+            self.failUnless(bv.is_readonly()))
12890+        d.addCallback(lambda ignored:
12891+            self.sdmf_node.get_best_readable_version())
12892+        d.addCallback(lambda bv:
12893+            self.failUnless(bv.is_readonly()))
12894+        return d
12895+
12896+
12897+    def test_get_mutable_version(self):
12898+        d = self.mdmf_node.get_best_mutable_version()
12899+        d.addCallback(lambda bv:
12900+            self.failIf(bv.is_readonly()))
12901+        d.addCallback(lambda ignored:
12902+            self.sdmf_node.get_best_mutable_version())
12903+        d.addCallback(lambda bv:
12904+            self.failIf(bv.is_readonly()))
12905+        return d
12906+
12907+
12908+    def test_toplevel_overwrite(self):
12909+        new_data = MutableData("foo bar baz" * 100000)
12910+        new_small_data = MutableData("foo bar baz" * 10)
12911+        d = self.mdmf_node.overwrite(new_data)
12912+        d.addCallback(lambda ignored:
12913+            self.mdmf_node.download_best_version())
12914+        d.addCallback(lambda data:
12915+            self.failUnlessEqual(data, "foo bar baz" * 100000))
12916+        d.addCallback(lambda ignored:
12917+            self.sdmf_node.overwrite(new_small_data))
12918+        d.addCallback(lambda ignored:
12919+            self.sdmf_node.download_best_version())
12920+        d.addCallback(lambda data:
12921+            self.failUnlessEqual(data, "foo bar baz" * 10))
12922+        return d
12923+
12924+
12925+    def test_toplevel_modify(self):
12926+        def modifier(old_contents, servermap, first_time):
12927+            return MutableData(old_contents + "modified")
12928+        d = self.mdmf_node.modify(modifier)
12929+        d.addCallback(lambda ignored:
12930+            self.mdmf_node.download_best_version())
12931+        d.addCallback(lambda data:
12932+            self.failUnlessIn("modified", data))
12933+        d.addCallback(lambda ignored:
12934+            self.sdmf_node.modify(modifier))
12935+        d.addCallback(lambda ignored:
12936+            self.sdmf_node.download_best_version())
12937+        d.addCallback(lambda data:
12938+            self.failUnlessIn("modified", data))
12939+        return d
12940+
12941+
12942+    def test_version_modify(self):
12943+        # TODO: When we can publish multiple versions, alter this test
12944+        # to modify a version other than the best usable version, then
12945+        # test to see that the best recoverable version is that.
12946+        def modifier(old_contents, servermap, first_time):
12947+            return MutableData(old_contents + "modified")
12948+        d = self.mdmf_node.modify(modifier)
12949+        d.addCallback(lambda ignored:
12950+            self.mdmf_node.download_best_version())
12951+        d.addCallback(lambda data:
12952+            self.failUnlessIn("modified", data))
12953+        d.addCallback(lambda ignored:
12954+            self.sdmf_node.modify(modifier))
12955+        d.addCallback(lambda ignored:
12956+            self.sdmf_node.download_best_version())
12957+        d.addCallback(lambda data:
12958+            self.failUnlessIn("modified", data))
12959+        return d
12960+
12961+
12962+    def test_download_version(self):
12963+        # This will only pass once we get the ability to publish
12964+        # multiple recoverable versions.
12965+        self.failUnless(False)
12966+
12967+
12968+    def test_partial_read(self):
12969+        # read only a few bytes at a time, and see that the results are
12970+        # what we expect.
12971+        d = self.mdmf_node.get_best_readable_version()
12972+        def _read_data(version):
12973+            c = consumer.MemoryConsumer()
12974+            d2 = defer.succeed(None)
12975+            for i in xrange(0, len(self.data), 10000):
12976+                d2.addCallback(lambda ignored, i=i: version.read(c, i, 10000))
12977+            d2.addCallback(lambda ignored:
12978+                self.failUnlessEqual(self.data, "".join(c.chunks)))
12979+            return d2
12980+        d.addCallback(_read_data)
12981+        return d
12982+
12983+
12984+    def test_read(self):
12985+        d = self.mdmf_node.get_best_readable_version()
12986+        def _read_data(version):
12987+            c = consumer.MemoryConsumer()
12988+            d2 = defer.succeed(None)
12989+            d2.addCallback(lambda ignored: version.read(c))
12990+            d2.addCallback(lambda ignored:
12991+                self.failUnlessEqual("".join(c.chunks), self.data))
12992+            return d2
12993+        d.addCallback(_read_data)
12994+        return d
12995+
12996+
12997+    def test_download_best_version(self):
12998+        d = self.mdmf_node.download_best_version()
12999+        d.addCallback(lambda data:
13000+            self.failUnlessEqual(data, self.data))
13001+        d.addCallback(lambda ignored:
13002+            self.sdmf_node.download_best_version())
13003+        d.addCallback(lambda data:
13004+            self.failUnlessEqual(data, self.small_data))
13005+        return d
13006[web/filenode.py: alter download code to use the new #993 interface.
13007Kevan Carstensen <kevan@isnotajoke.com>**20100717015426
13008 Ignore-this: 4e8b89f9b616755f1f644ecea4cda0bb
13009] {
13010hunk ./src/allmydata/web/filenode.py 167
13011             # properly. So we assume that at least the browser will agree
13012             # with itself, and echo back the same bytes that we were given.
13013             filename = get_arg(req, "filename", self.name) or "unknown"
13014-            if self.node.is_mutable():
13015-                # some day: d = self.node.get_best_version()
13016-                d = makeMutableDownloadable(self.node)
13017-            else:
13018-                d = defer.succeed(self.node)
13019+            d = self.node.get_best_readable_version()
13020             d.addCallback(lambda dn: FileDownloader(dn, filename))
13021             return d
13022         if t == "json":
13023hunk ./src/allmydata/web/filenode.py 191
13024         if t:
13025             raise WebError("GET file: bad t=%s" % t)
13026         filename = get_arg(req, "filename", self.name) or "unknown"
13027-        if self.node.is_mutable():
13028-            # some day: d = self.node.get_best_version()
13029-            d = makeMutableDownloadable(self.node)
13030-        else:
13031-            d = defer.succeed(self.node)
13032+        d = self.node.get_best_readable_version()
13033         d.addCallback(lambda dn: FileDownloader(dn, filename))
13034         return d
13035 
13036hunk ./src/allmydata/web/filenode.py 285
13037         d.addCallback(lambda res: self.node.get_uri())
13038         return d
13039 
13040-class MutableDownloadable:
13041-    #implements(IDownloadable)
13042-    def __init__(self, size, node):
13043-        self.size = size
13044-        self.node = node
13045-    def get_size(self):
13046-        return self.size
13047-    def is_mutable(self):
13048-        return True
13049-    def read(self, consumer, offset=0, size=None):
13050-        d = self.node.download_best_version()
13051-        d.addCallback(self._got_data, consumer, offset, size)
13052-        return d
13053-    def _got_data(self, contents, consumer, offset, size):
13054-        start = offset
13055-        if size is not None:
13056-            end = offset+size
13057-        else:
13058-            end = self.size
13059-        # SDMF: we can write the whole file in one big chunk
13060-        consumer.write(contents[start:end])
13061-        return consumer
13062-
13063-def makeMutableDownloadable(n):
13064-    d = defer.maybeDeferred(n.get_size_of_best_version)
13065-    d.addCallback(MutableDownloadable, n)
13066-    return d
13067 
13068 class FileDownloader(rend.Page):
13069     # since we override the rendering process (to let the tahoe Downloader
13070}
13071[test/common.py: remove FileTooLargeErrors that tested for an SDMF limitation that no longer exists
13072Kevan Carstensen <kevan@isnotajoke.com>**20100717015501
13073 Ignore-this: 8b17689d9391a4870a327c1d7c0b3225
13074] {
13075hunk ./src/allmydata/test/common.py 198
13076         self.init_from_cap(make_mutable_file_cap())
13077     def create(self, contents, key_generator=None, keysize=None):
13078         initial_contents = self._get_initial_contents(contents)
13079-        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
13080-            raise FileTooLargeError("SDMF is limited to one segment, and "
13081-                                    "%d > %d" % (initial_contents.get_size(),
13082-                                                 self.MUTABLE_SIZELIMIT))
13083         data = initial_contents.read(initial_contents.get_size())
13084         data = "".join(data)
13085         self.all_contents[self.storage_index] = data
13086hunk ./src/allmydata/test/common.py 326
13087 
13088 
13089     def overwrite(self, new_contents):
13090-        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
13091-            raise FileTooLargeError("SDMF is limited to one segment, and "
13092-                                    "%d > %d" % (new_contents.get_size(),
13093-                                                 self.MUTABLE_SIZELIMIT))
13094         assert not self.is_readonly()
13095         new_data = new_contents.read(new_contents.get_size())
13096         new_data = "".join(new_data)
13097}
13098[nodemaker.py: resolve a conflict introduced in one of the 1.7.1 patches
13099Kevan Carstensen <kevan@isnotajoke.com>**20100720213109
13100 Ignore-this: 4e7d4e611f4cdf04824e9040167aa11
13101] hunk ./src/allmydata/nodemaker.py 107
13102                          "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
13103             node.raise_error()
13104         d = self.create_mutable_file(lambda n:
13105-                                     pack_children(n, initial_children))
13106+                                     MutableData(pack_children(initial_children,
13107+                                                    n.get_writekey())),
13108+                                     version)
13109         d.addCallback(self._create_dirnode)
13110         return d
13111 
13112[frontends/sftpd.py: fix conflicts with trunk
13113Kevan Carstensen <kevan@isnotajoke.com>**20100727224651
13114 Ignore-this: 5636e7a27162bf3ca14d6c9dc07a015
13115] {
13116hunk ./src/allmydata/frontends/sftpd.py 664
13117         else:
13118             assert IFileNode.providedBy(filenode), filenode
13119 
13120-            # TODO: use download interface described in #993 when implemented.
13121-            if filenode.is_mutable():
13122-                self.async.addCallback(lambda ign: filenode.download_best_version())
13123-                def _downloaded(data):
13124-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
13125-                    self.consumer.write(data)
13126-                    self.consumer.finish()
13127-                    return None
13128-                self.async.addCallback(_downloaded)
13129-            else:
13130-                download_size = filenode.get_size()
13131-                assert download_size is not None, "download_size is None"
13132+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
13133+
13134+            def _read(version):
13135+                if noisy: self.log("_read", level=NOISY)
13136+                download_size = version.get_size()
13137+                assert download_size is not None
13138+
13139                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
13140 
13141hunk ./src/allmydata/frontends/sftpd.py 673
13142-                if noisy: self.log("_read", level=NOISY)
13143                 version.read(self.consumer, 0, None)
13144             self.async.addCallback(_read)
13145 
13146}
13147[interfaces.py: Create an IWritable interface
13148Kevan Carstensen <kevan@isnotajoke.com>**20100727224703
13149 Ignore-this: 3fd15da701c31c024963d7ee5c896124
13150] hunk ./src/allmydata/interfaces.py 633
13151         """
13152 
13153 
13154+class IWritable(Interface):
13155+    """
13156+    I define methods that callers can use to update SDMF and MDMF
13157+    mutable files on a Tahoe-LAFS grid.
13158+    """
13159+    # XXX: For the moment, we have only this. It is possible that we
13160+    #      want to move overwrite() and modify() in here too.
13161+    def update(data, offset):
13162+        """
13163+        I write the data from my data argument to the MDMF file,
13164+        starting at offset. I continue writing data until my data
13165+        argument is exhausted, appending data to the file as necessary.
13166+        """
13167+        # assert IMutableUploadable.providedBy(data)
13168+        # to append data: offset=node.get_size_of_best_version()
13169+        # do we want to support compacting MDMF?
13170+        # for an MDMF file, this can be done with O(data.get_size())
13171+        # memory. For an SDMF file, any modification takes
13172+        # O(node.get_size_of_best_version()).
13173+
13174+
13175 class IMutableFileVersion(IReadable):
13176     """I provide access to a particular version of a mutable file. The
13177     access is read/write if I was obtained from a filenode derived from
13178[mutable/layout.py: Alter MDMFSlotWriteProxy to perform all write operations in one actual write operation
13179Kevan Carstensen <kevan@isnotajoke.com>**20100727224725
13180 Ignore-this: 41d577e9d65eba9a38a4051c2a05d4be
13181] {
13182hunk ./src/allmydata/mutable/layout.py 814
13183         # last thing we write to the remote server.
13184         self._offsets = {}
13185         self._testvs = []
13186+        # This is a list of write vectors that will be sent to our
13187+        # remote server once we are directed to write things there.
13188+        self._writevs = []
13189         self._secrets = secrets
13190         # The segment size needs to be a multiple of the k parameter --
13191         # any padding should have been carried out by the publisher
13192hunk ./src/allmydata/mutable/layout.py 947
13193 
13194     def put_block(self, data, segnum, salt):
13195         """
13196-        Put the encrypted-and-encoded data segment in the slot, along
13197-        with the salt.
13198+        I queue a write vector for the data, salt, and segment number
13199+        provided to me. I return None, as I do not actually cause
13200+        anything to be written yet.
13201         """
13202         if segnum >= self._num_segments:
13203             raise LayoutInvalid("I won't overwrite the private key")
13204hunk ./src/allmydata/mutable/layout.py 967
13205         offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
13206         data = salt + data
13207 
13208-        datavs = [tuple([offset, data])]
13209-        return self._write(datavs)
13210+        self._writevs.append(tuple([offset, data]))
13211 
13212 
13213     def put_encprivkey(self, encprivkey):
13214hunk ./src/allmydata/mutable/layout.py 972
13215         """
13216-        Put the encrypted private key in the remote slot.
13217+        I queue a write vector for the encrypted private key provided to
13218+        me.
13219         """
13220         assert self._offsets
13221         assert self._offsets['enc_privkey']
13222hunk ./src/allmydata/mutable/layout.py 986
13223         if "share_hash_chain" in self._offsets:
13224             raise LayoutInvalid("You must write this before the block hash tree")
13225 
13226-        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
13227-        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
13228-        def _on_failure():
13229-            del(self._offsets['block_hash_tree'])
13230-        return self._write(datavs, on_failure=_on_failure)
13231+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + \
13232+            len(encprivkey)
13233+        self._writevs.append(tuple([self._offsets['enc_privkey'], encprivkey]))
13234 
13235 
13236     def put_blockhashes(self, blockhashes):
13237hunk ./src/allmydata/mutable/layout.py 993
13238         """
13239-        Put the block hash tree in the remote slot.
13240+        I queue a write vector to put the block hash tree in blockhashes
13241+        onto the remote server.
13242 
13243hunk ./src/allmydata/mutable/layout.py 996
13244-        The encrypted private key must be put before the block hash
13245+        The encrypted private key must be queued before the block hash
13246         tree, since we need to know how large it is to know where the
13247         block hash tree should go. The block hash tree must be put
13248         before the salt hash tree, since its size determines the
13249hunk ./src/allmydata/mutable/layout.py 1014
13250                                 "you put the share hash chain")
13251         blockhashes_s = "".join(blockhashes)
13252         self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
13253-        datavs = []
13254-        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
13255-        def _on_failure():
13256-            del(self._offsets['share_hash_chain'])
13257-        return self._write(datavs, on_failure=_on_failure)
13258+
13259+        self._writevs.append(tuple([self._offsets['block_hash_tree'],
13260+                                  blockhashes_s]))
13261 
13262 
13263     def put_sharehashes(self, sharehashes):
13264hunk ./src/allmydata/mutable/layout.py 1021
13265         """
13266-        Put the share hash chain in the remote slot.
13267+        I queue a write vector to put the share hash chain in my
13268+        argument onto the remote server.
13269 
13270hunk ./src/allmydata/mutable/layout.py 1024
13271-        The salt hash tree must be put before the share hash chain,
13272+        The salt hash tree must be queued before the share hash chain,
13273         since we need to know where the salt hash tree ends before we
13274         can know where the share hash chain starts. The share hash chain
13275         must be put before the signature, since the length of the packed
13276hunk ./src/allmydata/mutable/layout.py 1044
13277         if "verification_key" in self._offsets:
13278             raise LayoutInvalid("You must write the share hash chain "
13279                                 "before you write the signature")
13280-        datavs = []
13281         sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
13282                                   for i in sorted(sharehashes.keys())])
13283         self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
13284hunk ./src/allmydata/mutable/layout.py 1047
13285-        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
13286-        def _on_failure():
13287-            del(self._offsets['signature'])
13288-        return self._write(datavs, on_failure=_on_failure)
13289+        self._writevs.append(tuple([self._offsets['share_hash_chain'],
13290+                            sharehashes_s]))
13291 
13292 
13293     def put_root_hash(self, roothash):
13294hunk ./src/allmydata/mutable/layout.py 1069
13295         if len(roothash) != HASH_SIZE:
13296             raise LayoutInvalid("hashes and salts must be exactly %d bytes"
13297                                  % HASH_SIZE)
13298-        datavs = []
13299         self._root_hash = roothash
13300         # To write both of these values, we update the checkstring on
13301         # the remote server, which includes them
13302hunk ./src/allmydata/mutable/layout.py 1073
13303         checkstring = self.get_checkstring()
13304-        datavs.append(tuple([0, checkstring]))
13305+        self._writevs.append(tuple([0, checkstring]))
13306         # This write, if successful, changes the checkstring, so we need
13307         # to update our internal checkstring to be consistent with the
13308         # one on the server.
13309hunk ./src/allmydata/mutable/layout.py 1077
13310-        def _on_success():
13311-            self._testvs = [(0, len(checkstring), "eq", checkstring)]
13312-        def _on_failure():
13313-            self._root_hash = None
13314-        return self._write(datavs,
13315-                           on_success=_on_success,
13316-                           on_failure=_on_failure)
13317 
13318 
13319     def get_signable(self):
13320hunk ./src/allmydata/mutable/layout.py 1100
13321 
13322     def put_signature(self, signature):
13323         """
13324-        Put the signature field to the remote slot.
13325+        I queue a write vector for the signature of the MDMF share.
13326 
13327         I require that the root hash and share hash chain have been put
13328         to the grid before I will write the signature to the grid.
13329hunk ./src/allmydata/mutable/layout.py 1123
13330             raise LayoutInvalid("You must write the signature before the verification key")
13331 
13332         self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
13333-        datavs = []
13334-        datavs.append(tuple([self._offsets['signature'], signature]))
13335-        def _on_failure():
13336-            del(self._offsets['verification_key'])
13337-        return self._write(datavs, on_failure=_on_failure)
13338+        self._writevs.append(tuple([self._offsets['signature'], signature]))
13339 
13340 
13341     def put_verification_key(self, verification_key):
13342hunk ./src/allmydata/mutable/layout.py 1128
13343         """
13344-        Put the verification key into the remote slot.
13345+        I queue a write vector for the verification key.
13346 
13347         I require that the signature have been written to the storage
13348         server before I allow the verification key to be written to the
13349hunk ./src/allmydata/mutable/layout.py 1138
13350             raise LayoutInvalid("You must put the signature before you "
13351                                 "can put the verification key")
13352         self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
13353-        datavs = []
13354-        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
13355-        def _on_failure():
13356-            del(self._offsets['EOF'])
13357-        return self._write(datavs, on_failure=_on_failure)
13358+        self._writevs.append(tuple([self._offsets['verification_key'],
13359+                            verification_key]))
13360+
13361 
13362     def _get_offsets_tuple(self):
13363         return tuple([(key, value) for key, value in self._offsets.items()])
13364hunk ./src/allmydata/mutable/layout.py 1145
13365 
13366+
13367     def get_verinfo(self):
13368         return (self._seqnum,
13369                 self._root_hash,
13370hunk ./src/allmydata/mutable/layout.py 1159
13371 
13372     def finish_publishing(self):
13373         """
13374-        Write the offset table and encoding parameters to the remote
13375-        slot, since that's the only thing we have yet to publish at this
13376-        point.
13377+        I add a write vector for the offsets table, and then cause all
13378+        of the write vectors that I've dealt with so far to be published
13379+        to the remote server, ending the write process.
13380         """
13381         if "EOF" not in self._offsets:
13382             raise LayoutInvalid("You must put the verification key before "
13383hunk ./src/allmydata/mutable/layout.py 1174
13384                               self._offsets['signature'],
13385                               self._offsets['verification_key'],
13386                               self._offsets['EOF'])
13387-        datavs = []
13388-        datavs.append(tuple([offsets_offset, offsets]))
13389+        self._writevs.append(tuple([offsets_offset, offsets]))
13390         encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
13391         params = struct.pack(">BBQQ",
13392                              self._required_shares,
13393hunk ./src/allmydata/mutable/layout.py 1181
13394                              self._total_shares,
13395                              self._segment_size,
13396                              self._data_length)
13397-        datavs.append(tuple([encoding_parameters_offset, params]))
13398-        return self._write(datavs)
13399+        self._writevs.append(tuple([encoding_parameters_offset, params]))
13400+        return self._write(self._writevs)
13401 
13402 
13403     def _write(self, datavs, on_failure=None, on_success=None):
13404}
13405[test/test_mutable.py: test that write operations occur all at once
13406Kevan Carstensen <kevan@isnotajoke.com>**20100727224817
13407 Ignore-this: 44cb37c6887ee9baa3e67645ece9555d
13408] {
13409hunk ./src/allmydata/test/test_mutable.py 100
13410         self.storage = storage
13411         self.queries = 0
13412     def callRemote(self, methname, *args, **kwargs):
13413+        self.queries += 1
13414         def _call():
13415             meth = getattr(self, methname)
13416             return meth(*args, **kwargs)
13417hunk ./src/allmydata/test/test_mutable.py 109
13418         return d
13419 
13420     def callRemoteOnly(self, methname, *args, **kwargs):
13421+        self.queries += 1
13422         d = self.callRemote(methname, *args, **kwargs)
13423         d.addBoth(lambda ignore: None)
13424         pass
13425hunk ./src/allmydata/test/test_mutable.py 370
13426         return d
13427 
13428 
13429+    def test_mdmf_write_count(self):
13430+        # Publishing an MDMF file should only cause one write for each
13431+        # share that is to be published. Otherwise, we introduce
13432+        # undesirable semantics that are a regression from SDMF
13433+        upload = MutableData("MDMF" * 100000) # about 400 KiB
13434+        d = self.nodemaker.create_mutable_file(upload,
13435+                                               version=MDMF_VERSION)
13436+        def _check_server_write_counts(ignored):
13437+            sb = self.nodemaker.storage_broker
13438+            peers = sb.test_servers.values()
13439+            for peer in peers:
13440+                self.failUnlessEqual(peer.queries, 1)
13441+        d.addCallback(_check_server_write_counts)
13442+        return d
13443+
13444+
13445     def test_create_with_initial_contents(self):
13446         upload1 = MutableData("contents 1")
13447         d = self.nodemaker.create_mutable_file(upload1)
13448}
13449[test/test_storage.py: modify proxy tests to work with the new writing semantics
13450Kevan Carstensen <kevan@isnotajoke.com>**20100727224853
13451 Ignore-this: 2b6bdde6dc9d8e4e7f096cdb725b40cf
13452] {
13453hunk ./src/allmydata/test/test_storage.py 1681
13454         # diagnose the problem. This test ensures that the read vector
13455         # is working appropriately.
13456         mw = self._make_new_mw("si1", 0)
13457-        d = defer.succeed(None)
13458 
13459hunk ./src/allmydata/test/test_storage.py 1682
13460-        # Write one share. This should return a checkstring of nothing,
13461-        # since there is no data there.
13462-        d.addCallback(lambda ignored:
13463-            mw.put_block(self.block, 0, self.salt))
13464-        def _check_first_write(results):
13465-            result, readvs = results
13466-            self.failUnless(result)
13467-            self.failIf(readvs)
13468-        d.addCallback(_check_first_write)
13469-        # Now, there should be a different checkstring returned when
13470-        # we write other shares
13471-        d.addCallback(lambda ignored:
13472-            mw.put_block(self.block, 1, self.salt))
13473-        def _check_next_write(results):
13474-            result, readvs = results
13475+        for i in xrange(6):
13476+            mw.put_block(self.block, i, self.salt)
13477+        mw.put_encprivkey(self.encprivkey)
13478+        mw.put_blockhashes(self.block_hash_tree)
13479+        mw.put_sharehashes(self.share_hash_chain)
13480+        mw.put_root_hash(self.root_hash)
13481+        mw.put_signature(self.signature)
13482+        mw.put_verification_key(self.verification_key)
13483+        d = mw.finish_publishing()
13484+        def _then(results):
13485+            self.failUnless(len(results), 2)
13486+            result, readv = results
13487             self.failUnless(result)
13488hunk ./src/allmydata/test/test_storage.py 1695
13489-            self.expected_checkstring = mw.get_checkstring()
13490-            self.failUnlessIn(0, readvs)
13491-            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
13492-        d.addCallback(_check_next_write)
13493-        # Add the other four shares
13494-        for i in xrange(2, 6):
13495-            d.addCallback(lambda ignored, i=i:
13496-                mw.put_block(self.block, i, self.salt))
13497-            d.addCallback(_check_next_write)
13498-        # Add the encrypted private key
13499-        d.addCallback(lambda ignored:
13500-            mw.put_encprivkey(self.encprivkey))
13501-        d.addCallback(_check_next_write)
13502-        # Add the block hash tree and share hash tree
13503-        d.addCallback(lambda ignored:
13504-            mw.put_blockhashes(self.block_hash_tree))
13505-        d.addCallback(_check_next_write)
13506-        d.addCallback(lambda ignored:
13507-            mw.put_sharehashes(self.share_hash_chain))
13508-        d.addCallback(_check_next_write)
13509-        # Add the root hash and the salt hash. This should change the
13510-        # checkstring, but not in a way that we'll be able to see right
13511-        # now, since the read vectors are applied before the write
13512-        # vectors.
13513+            self.failIf(readv)
13514+            self.old_checkstring = mw.get_checkstring()
13515+            mw.set_checkstring("")
13516+        d.addCallback(_then)
13517         d.addCallback(lambda ignored:
13518hunk ./src/allmydata/test/test_storage.py 1700
13519-            mw.put_root_hash(self.root_hash))
13520-        def _check_old_testv_after_new_one_is_written(results):
13521+            mw.finish_publishing())
13522+        def _then_again(results):
13523+            self.failUnlessEqual(len(results), 2)
13524             result, readvs = results
13525hunk ./src/allmydata/test/test_storage.py 1704
13526-            self.failUnless(result)
13527+            self.failIf(result)
13528             self.failUnlessIn(0, readvs)
13529hunk ./src/allmydata/test/test_storage.py 1706
13530-            self.failUnlessEqual(self.expected_checkstring,
13531-                                 readvs[0][0])
13532-            new_checkstring = mw.get_checkstring()
13533-            self.failIfEqual(new_checkstring,
13534-                             readvs[0][0])
13535-        d.addCallback(_check_old_testv_after_new_one_is_written)
13536-        # Now add the signature. This should succeed, meaning that the
13537-        # data gets written and the read vector matches what the writer
13538-        # thinks should be there.
13539-        d.addCallback(lambda ignored:
13540-            mw.put_signature(self.signature))
13541-        d.addCallback(_check_next_write)
13542+            readv = readvs[0][0]
13543+            self.failUnlessEqual(readv, self.old_checkstring)
13544+        d.addCallback(_then_again)
13545         # The checkstring remains the same for the rest of the process.
13546         return d
13547 
13548hunk ./src/allmydata/test/test_storage.py 1811
13549         # same share.
13550         mw1 = self._make_new_mw("si1", 0)
13551         mw2 = self._make_new_mw("si1", 0)
13552-        d = defer.succeed(None)
13553+
13554         def _check_success(results):
13555             result, readvs = results
13556             self.failUnless(result)
13557hunk ./src/allmydata/test/test_storage.py 1820
13558             result, readvs = results
13559             self.failIf(result)
13560 
13561-        d.addCallback(lambda ignored:
13562-            mw1.put_block(self.block, 0, self.salt))
13563+        def _write_share(mw):
13564+            for i in xrange(6):
13565+                mw.put_block(self.block, i, self.salt)
13566+            mw.put_encprivkey(self.encprivkey)
13567+            mw.put_blockhashes(self.block_hash_tree)
13568+            mw.put_sharehashes(self.share_hash_chain)
13569+            mw.put_root_hash(self.root_hash)
13570+            mw.put_signature(self.signature)
13571+            mw.put_verification_key(self.verification_key)
13572+            return mw.finish_publishing()
13573+        d = _write_share(mw1)
13574         d.addCallback(_check_success)
13575         d.addCallback(lambda ignored:
13576hunk ./src/allmydata/test/test_storage.py 1833
13577-            mw2.put_block(self.block, 0, self.salt))
13578+            _write_share(mw2))
13579         d.addCallback(_check_failure)
13580         return d
13581 
13582hunk ./src/allmydata/test/test_storage.py 1859
13583 
13584     def test_write_test_vectors(self):
13585         # If we give the write proxy a bogus test vector at
13586-        # any point during the process, it should fail to write.
13587+        # any point during the process, it should fail to write when we
13588+        # tell it to write.
13589+        def _check_failure(results):
13590+            self.failUnlessEqual(len(results), 2)
13591+            res, d = results
13592+            self.failIf(res)
13593+
13594+        def _check_success(results):
13595+            self.failUnlessEqual(len(results), 2)
13596+            res, d = results
13597+            self.failUnless(results)
13598+
13599         mw = self._make_new_mw("si1", 0)
13600         mw.set_checkstring("this is a lie")
13601hunk ./src/allmydata/test/test_storage.py 1873
13602-        # The initial write should be expecting to find the improbable
13603-        # checkstring above in place; finding nothing, it should fail.
13604-        d = defer.succeed(None)
13605-        d.addCallback(lambda ignored:
13606-            mw.put_block(self.block, 0, self.salt))
13607-        def _check_failure(results):
13608-            result, readv = results
13609-            self.failIf(result)
13610+        for i in xrange(6):
13611+            mw.put_block(self.block, i, self.salt)
13612+        mw.put_encprivkey(self.encprivkey)
13613+        mw.put_blockhashes(self.block_hash_tree)
13614+        mw.put_sharehashes(self.share_hash_chain)
13615+        mw.put_root_hash(self.root_hash)
13616+        mw.put_signature(self.signature)
13617+        mw.put_verification_key(self.verification_key)
13618+        d = mw.finish_publishing()
13619         d.addCallback(_check_failure)
13620hunk ./src/allmydata/test/test_storage.py 1883
13621-        # Now set the checkstring to the empty string, which
13622-        # indicates that no share is there.
13623         d.addCallback(lambda ignored:
13624             mw.set_checkstring(""))
13625         d.addCallback(lambda ignored:
13626hunk ./src/allmydata/test/test_storage.py 1886
13627-            mw.put_block(self.block, 0, self.salt))
13628-        def _check_success(results):
13629-            result, readv = results
13630-            self.failUnless(result)
13631-        d.addCallback(_check_success)
13632-        # Now set the checkstring to something wrong
13633-        d.addCallback(lambda ignored:
13634-            mw.set_checkstring("something wrong"))
13635-        # This should fail to do anything
13636-        d.addCallback(lambda ignored:
13637-            mw.put_block(self.block, 1, self.salt))
13638-        d.addCallback(_check_failure)
13639-        # Now set it back to what it should be.
13640-        d.addCallback(lambda ignored:
13641-            mw.set_checkstring(mw.get_checkstring()))
13642-        for i in xrange(1, 6):
13643-            d.addCallback(lambda ignored, i=i:
13644-                mw.put_block(self.block, i, self.salt))
13645-            d.addCallback(_check_success)
13646-        d.addCallback(lambda ignored:
13647-            mw.put_encprivkey(self.encprivkey))
13648-        d.addCallback(_check_success)
13649-        d.addCallback(lambda ignored:
13650-            mw.put_blockhashes(self.block_hash_tree))
13651-        d.addCallback(_check_success)
13652-        d.addCallback(lambda ignored:
13653-            mw.put_sharehashes(self.share_hash_chain))
13654-        d.addCallback(_check_success)
13655-        def _keep_old_checkstring(ignored):
13656-            self.old_checkstring = mw.get_checkstring()
13657-            mw.set_checkstring("foobarbaz")
13658-        d.addCallback(_keep_old_checkstring)
13659-        d.addCallback(lambda ignored:
13660-            mw.put_root_hash(self.root_hash))
13661-        d.addCallback(_check_failure)
13662-        d.addCallback(lambda ignored:
13663-            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
13664-        def _restore_old_checkstring(ignored):
13665-            mw.set_checkstring(self.old_checkstring)
13666-        d.addCallback(_restore_old_checkstring)
13667-        d.addCallback(lambda ignored:
13668-            mw.put_root_hash(self.root_hash))
13669-        d.addCallback(_check_success)
13670-        # The checkstring should have been set appropriately for us on
13671-        # the last write; if we try to change it to something else,
13672-        # that change should cause the verification key step to fail.
13673-        d.addCallback(lambda ignored:
13674-            mw.set_checkstring("something else"))
13675-        d.addCallback(lambda ignored:
13676-            mw.put_signature(self.signature))
13677-        d.addCallback(_check_failure)
13678-        d.addCallback(lambda ignored:
13679-            mw.set_checkstring(mw.get_checkstring()))
13680-        d.addCallback(lambda ignored:
13681-            mw.put_signature(self.signature))
13682-        d.addCallback(_check_success)
13683-        d.addCallback(lambda ignored:
13684-            mw.put_verification_key(self.verification_key))
13685+            mw.finish_publishing())
13686         d.addCallback(_check_success)
13687         return d
13688 
13689hunk ./src/allmydata/test/test_storage.py 1891
13690 
13691-    def test_offset_only_set_on_success(self):
13692-        # The write proxy should be smart enough to detect when a write
13693-        # has failed, and to temper its definition of progress based on
13694-        # that.
13695-        mw = self._make_new_mw("si1", 0)
13696-        d = defer.succeed(None)
13697-        for i in xrange(1, 6):
13698-            d.addCallback(lambda ignored, i=i:
13699-                mw.put_block(self.block, i, self.salt))
13700-        def _break_checkstring(ignored):
13701-            self._old_checkstring = mw.get_checkstring()
13702-            mw.set_checkstring("foobarbaz")
13703-
13704-        def _fix_checkstring(ignored):
13705-            mw.set_checkstring(self._old_checkstring)
13706-
13707-        d.addCallback(_break_checkstring)
13708-
13709-        # Setting the encrypted private key shouldn't work now, which is
13710-        # to be expected and is tested elsewhere. We also want to make
13711-        # sure that we can't add the block hash tree after a failed
13712-        # write of this sort.
13713-        d.addCallback(lambda ignored:
13714-            mw.put_encprivkey(self.encprivkey))
13715-        d.addCallback(lambda ignored:
13716-            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
13717-                            None,
13718-                            mw.put_blockhashes, self.block_hash_tree))
13719-        d.addCallback(_fix_checkstring)
13720-        d.addCallback(lambda ignored:
13721-            mw.put_encprivkey(self.encprivkey))
13722-        d.addCallback(_break_checkstring)
13723-        d.addCallback(lambda ignored:
13724-            mw.put_blockhashes(self.block_hash_tree))
13725-        d.addCallback(lambda ignored:
13726-            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
13727-                            None,
13728-                            mw.put_sharehashes, self.share_hash_chain))
13729-        d.addCallback(_fix_checkstring)
13730-        d.addCallback(lambda ignored:
13731-            mw.put_blockhashes(self.block_hash_tree))
13732-        d.addCallback(_break_checkstring)
13733-        d.addCallback(lambda ignored:
13734-            mw.put_sharehashes(self.share_hash_chain))
13735-        d.addCallback(lambda ignored:
13736-            self.shouldFail(LayoutInvalid, "out-of-order root hash",
13737-                            None,
13738-                            mw.put_root_hash, self.root_hash))
13739-        d.addCallback(_fix_checkstring)
13740-        d.addCallback(lambda ignored:
13741-            mw.put_sharehashes(self.share_hash_chain))
13742-        d.addCallback(_break_checkstring)
13743-        d.addCallback(lambda ignored:
13744-            mw.put_root_hash(self.root_hash))
13745-        d.addCallback(lambda ignored:
13746-            self.shouldFail(LayoutInvalid, "out-of-order signature",
13747-                            None,
13748-                            mw.put_signature, self.signature))
13749-        d.addCallback(_fix_checkstring)
13750-        d.addCallback(lambda ignored:
13751-            mw.put_root_hash(self.root_hash))
13752-        d.addCallback(_break_checkstring)
13753-        d.addCallback(lambda ignored:
13754-            mw.put_signature(self.signature))
13755-        d.addCallback(lambda ignored:
13756-            self.shouldFail(LayoutInvalid, "out-of-order verification key",
13757-                            None,
13758-                            mw.put_verification_key,
13759-                            self.verification_key))
13760-        d.addCallback(_fix_checkstring)
13761-        d.addCallback(lambda ignored:
13762-            mw.put_signature(self.signature))
13763-        d.addCallback(_break_checkstring)
13764-        d.addCallback(lambda ignored:
13765-            mw.put_verification_key(self.verification_key))
13766-        d.addCallback(lambda ignored:
13767-            self.shouldFail(LayoutInvalid, "out-of-order finish",
13768-                            None,
13769-                            mw.finish_publishing))
13770-        return d
13771-
13772-
13773     def serialize_blockhashes(self, blockhashes):
13774         return "".join(blockhashes)
13775 
13776hunk ./src/allmydata/test/test_storage.py 1905
13777         # This translates to a file with 6 6-byte segments, and with 2-byte
13778         # blocks.
13779         mw = self._make_new_mw("si1", 0)
13780-        mw2 = self._make_new_mw("si1", 1)
13781         # Test writing some blocks.
13782         read = self.ss.remote_slot_readv
13783         expected_sharedata_offset = struct.calcsize(MDMFHEADER)
13784hunk ./src/allmydata/test/test_storage.py 1910
13785         written_block_size = 2 + len(self.salt)
13786         written_block = self.block + self.salt
13787-        def _check_block_write(i, share):
13788-            self.failUnlessEqual(read("si1", [share], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
13789-                                {share: [written_block]})
13790-        d = defer.succeed(None)
13791         for i in xrange(6):
13792hunk ./src/allmydata/test/test_storage.py 1911
13793-            d.addCallback(lambda ignored, i=i:
13794-                mw.put_block(self.block, i, self.salt))
13795-            d.addCallback(lambda ignored, i=i:
13796-                _check_block_write(i, 0))
13797-        # Now try the same thing, but with share 1 instead of share 0.
13798-        for i in xrange(6):
13799-            d.addCallback(lambda ignored, i=i:
13800-                mw2.put_block(self.block, i, self.salt))
13801-            d.addCallback(lambda ignored, i=i:
13802-                _check_block_write(i, 1))
13803+            mw.put_block(self.block, i, self.salt)
13804 
13805hunk ./src/allmydata/test/test_storage.py 1913
13806-        # Next, we make a fake encrypted private key, and put it onto the
13807-        # storage server.
13808-        d.addCallback(lambda ignored:
13809-            mw.put_encprivkey(self.encprivkey))
13810-        expected_private_key_offset = expected_sharedata_offset + \
13811+        mw.put_encprivkey(self.encprivkey)
13812+        mw.put_blockhashes(self.block_hash_tree)
13813+        mw.put_sharehashes(self.share_hash_chain)
13814+        mw.put_root_hash(self.root_hash)
13815+        mw.put_signature(self.signature)
13816+        mw.put_verification_key(self.verification_key)
13817+        d = mw.finish_publishing()
13818+        def _check_publish(results):
13819+            self.failUnlessEqual(len(results), 2)
13820+            result, ign = results
13821+            self.failUnless(result, "publish failed")
13822+            for i in xrange(6):
13823+                self.failUnlessEqual(read("si1", [0], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
13824+                                {0: [written_block]})
13825+
13826+            expected_private_key_offset = expected_sharedata_offset + \
13827                                       len(written_block) * 6
13828hunk ./src/allmydata/test/test_storage.py 1930
13829-        self.failUnlessEqual(len(self.encprivkey), 7)
13830-        d.addCallback(lambda ignored:
13831+            self.failUnlessEqual(len(self.encprivkey), 7)
13832             self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
13833hunk ./src/allmydata/test/test_storage.py 1932
13834-                                 {0: [self.encprivkey]}))
13835+                                 {0: [self.encprivkey]})
13836 
13837hunk ./src/allmydata/test/test_storage.py 1934
13838-        # Next, we put a fake block hash tree.
13839-        d.addCallback(lambda ignored:
13840-            mw.put_blockhashes(self.block_hash_tree))
13841-        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
13842-        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
13843-        d.addCallback(lambda ignored:
13844+            expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
13845+            self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
13846             self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
13847hunk ./src/allmydata/test/test_storage.py 1937
13848-                                 {0: [self.block_hash_tree_s]}))
13849+                                 {0: [self.block_hash_tree_s]})
13850 
13851hunk ./src/allmydata/test/test_storage.py 1939
13852-        # Next, put a fake share hash chain
13853-        d.addCallback(lambda ignored:
13854-            mw.put_sharehashes(self.share_hash_chain))
13855-        expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
13856-        d.addCallback(lambda ignored:
13857+            expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
13858             self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
13859hunk ./src/allmydata/test/test_storage.py 1941
13860-                                 {0: [self.share_hash_chain_s]}))
13861+                                 {0: [self.share_hash_chain_s]})
13862 
13863hunk ./src/allmydata/test/test_storage.py 1943
13864-        # Next, we put what is supposed to be the root hash of
13865-        # our share hash tree but isn't       
13866-        d.addCallback(lambda ignored:
13867-            mw.put_root_hash(self.root_hash))
13868-        # The root hash gets inserted at byte 9 (its position is in the header,
13869-        # and is fixed).
13870-        def _check(ignored):
13871             self.failUnlessEqual(read("si1", [0], [(9, 32)]),
13872                                  {0: [self.root_hash]})
13873hunk ./src/allmydata/test/test_storage.py 1945
13874-        d.addCallback(_check)
13875-
13876-        # Next, we put a signature of the header block.
13877-        d.addCallback(lambda ignored:
13878-            mw.put_signature(self.signature))
13879-        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
13880-        self.failUnlessEqual(len(self.signature), 9)
13881-        d.addCallback(lambda ignored:
13882+            expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
13883+            self.failUnlessEqual(len(self.signature), 9)
13884             self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
13885hunk ./src/allmydata/test/test_storage.py 1948
13886-                                 {0: [self.signature]}))
13887+                                 {0: [self.signature]})
13888 
13889hunk ./src/allmydata/test/test_storage.py 1950
13890-        # Next, we put the verification key
13891-        d.addCallback(lambda ignored:
13892-            mw.put_verification_key(self.verification_key))
13893-        expected_verification_key_offset = expected_signature_offset + len(self.signature)
13894-        self.failUnlessEqual(len(self.verification_key), 6)
13895-        d.addCallback(lambda ignored:
13896+            expected_verification_key_offset = expected_signature_offset + len(self.signature)
13897+            self.failUnlessEqual(len(self.verification_key), 6)
13898             self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
13899hunk ./src/allmydata/test/test_storage.py 1953
13900-                                 {0: [self.verification_key]}))
13901+                                 {0: [self.verification_key]})
13902 
13903hunk ./src/allmydata/test/test_storage.py 1955
13904-        def _check_signable(ignored):
13905-            # Make sure that the signable is what we think it should be.
13906             signable = mw.get_signable()
13907             verno, seq, roothash, k, n, segsize, datalen = \
13908                                             struct.unpack(">BQ32sBBQQ",
13909hunk ./src/allmydata/test/test_storage.py 1966
13910             self.failUnlessEqual(n, 10)
13911             self.failUnlessEqual(segsize, 6)
13912             self.failUnlessEqual(datalen, 36)
13913-        d.addCallback(_check_signable)
13914-        # Next, we cause the offset table to be published.
13915-        d.addCallback(lambda ignored:
13916-            mw.finish_publishing())
13917-        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
13918+            expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
13919 
13920hunk ./src/allmydata/test/test_storage.py 1968
13921-        def _check_offsets(ignored):
13922             # Check the version number to make sure that it is correct.
13923             expected_version_number = struct.pack(">B", 1)
13924             self.failUnlessEqual(read("si1", [0], [(0, 1)]),
13925hunk ./src/allmydata/test/test_storage.py 2008
13926             expected_offset = struct.pack(">Q", expected_eof_offset)
13927             self.failUnlessEqual(read("si1", [0], [(99, 8)]),
13928                                  {0: [expected_offset]})
13929-        d.addCallback(_check_offsets)
13930+        d.addCallback(_check_publish)
13931         return d
13932 
13933     def _make_new_mw(self, si, share, datalength=36):
13934}
13935[mutable/publish.py: alter mutable publisher to work with new writing semantics
13936Kevan Carstensen <kevan@isnotajoke.com>**20100727225001
13937 Ignore-this: a6b4628e749e09bfcddf3309271b5831
13938] {
13939hunk ./src/allmydata/mutable/publish.py 389
13940         if self._state == PUSHING_BLOCKS_STATE:
13941             return self.push_segment(self._current_segment)
13942 
13943-        # XXX: Do we want more granularity in states? Is that useful at
13944-        #      all?
13945-        #      Yes -- quicker reaction to UCW.
13946         elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
13947             return self.push_everything_else()
13948 
13949hunk ./src/allmydata/mutable/publish.py 490
13950         results, salt = encoded_and_salt
13951         shares, shareids = results
13952         started = time.time()
13953-        dl = []
13954         for i in xrange(len(shares)):
13955             sharedata = shares[i]
13956             shareid = shareids[i]
13957hunk ./src/allmydata/mutable/publish.py 502
13958 
13959             # find the writer for this share
13960             writer = self.writers[shareid]
13961-            d = writer.put_block(sharedata, segnum, salt)
13962-            d.addCallback(self._got_write_answer, writer, started)
13963-            d.addErrback(self._connection_problem, writer)
13964-            dl.append(d)
13965-        return defer.DeferredList(dl)
13966+            writer.put_block(sharedata, segnum, salt)
13967 
13968 
13969     def push_everything_else(self):
13970hunk ./src/allmydata/mutable/publish.py 510
13971         I put everything else associated with a share.
13972         """
13973         encprivkey = self._encprivkey
13974-        d = self.push_encprivkey()
13975-        d.addCallback(self.push_blockhashes)
13976-        d.addCallback(self.push_sharehashes)
13977-        d.addCallback(self.push_toplevel_hashes_and_signature)
13978-        d.addCallback(self.finish_publishing)
13979+        self.push_encprivkey()
13980+        self.push_blockhashes()
13981+        self.push_sharehashes()
13982+        self.push_toplevel_hashes_and_signature()
13983+        d = self.finish_publishing()
13984         def _change_state(ignored):
13985             self._state = DONE_STATE
13986         d.addCallback(_change_state)
13987hunk ./src/allmydata/mutable/publish.py 525
13988     def push_encprivkey(self):
13989         started = time.time()
13990         encprivkey = self._encprivkey
13991-        dl = []
13992         for writer in self.writers.itervalues():
13993hunk ./src/allmydata/mutable/publish.py 526
13994-            d = writer.put_encprivkey(encprivkey)
13995-            d.addCallback(self._got_write_answer, writer, started)
13996-            d.addErrback(self._connection_problem, writer)
13997-            dl.append(d)
13998-        d = defer.DeferredList(dl)
13999-        return d
14000+            writer.put_encprivkey(encprivkey)
14001 
14002 
14003hunk ./src/allmydata/mutable/publish.py 529
14004-    def push_blockhashes(self, ignored):
14005+    def push_blockhashes(self):
14006         started = time.time()
14007hunk ./src/allmydata/mutable/publish.py 531
14008-        dl = []
14009         self.sharehash_leaves = [None] * len(self.blockhashes)
14010         for shnum, blockhashes in self.blockhashes.iteritems():
14011             t = hashtree.HashTree(blockhashes)
14012hunk ./src/allmydata/mutable/publish.py 538
14013             # set the leaf for future use.
14014             self.sharehash_leaves[shnum] = t[0]
14015             writer = self.writers[shnum]
14016-            d = writer.put_blockhashes(self.blockhashes[shnum])
14017-            d.addCallback(self._got_write_answer, writer, started)
14018-            d.addErrback(self._connection_problem, self.writers[shnum])
14019-            dl.append(d)
14020-        d = defer.DeferredList(dl)
14021-        return d
14022+            writer.put_blockhashes(self.blockhashes[shnum])
14023 
14024 
14025hunk ./src/allmydata/mutable/publish.py 541
14026-    def push_sharehashes(self, ignored):
14027+    def push_sharehashes(self):
14028         started = time.time()
14029         share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
14030         share_hash_chain = {}
14031hunk ./src/allmydata/mutable/publish.py 545
14032-        ds = []
14033         for shnum in xrange(len(self.sharehash_leaves)):
14034             needed_indices = share_hash_tree.needed_hashes(shnum)
14035             self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
14036hunk ./src/allmydata/mutable/publish.py 550
14037                                              for i in needed_indices] )
14038             writer = self.writers[shnum]
14039-            d = writer.put_sharehashes(self.sharehashes[shnum])
14040-            d.addCallback(self._got_write_answer, writer, started)
14041-            d.addErrback(self._connection_problem, writer)
14042-            ds.append(d)
14043+            writer.put_sharehashes(self.sharehashes[shnum])
14044         self.root_hash = share_hash_tree[0]
14045hunk ./src/allmydata/mutable/publish.py 552
14046-        d = defer.DeferredList(ds)
14047-        return d
14048 
14049 
14050hunk ./src/allmydata/mutable/publish.py 554
14051-    def push_toplevel_hashes_and_signature(self, ignored):
14052+    def push_toplevel_hashes_and_signature(self):
14053         # We need to to three things here:
14054         #   - Push the root hash and salt hash
14055         #   - Get the checkstring of the resulting layout; sign that.
14056hunk ./src/allmydata/mutable/publish.py 560
14057         #   - Push the signature
14058         started = time.time()
14059-        ds = []
14060         for shnum in xrange(self.total_shares):
14061             writer = self.writers[shnum]
14062hunk ./src/allmydata/mutable/publish.py 562
14063-            d = writer.put_root_hash(self.root_hash)
14064-            d.addCallback(self._got_write_answer, writer, started)
14065-            ds.append(d)
14066-        d = defer.DeferredList(ds)
14067-        d.addCallback(self._update_checkstring)
14068-        d.addCallback(self._make_and_place_signature)
14069-        return d
14070+            writer.put_root_hash(self.root_hash)
14071+        self._update_checkstring()
14072+        self._make_and_place_signature()
14073 
14074 
14075hunk ./src/allmydata/mutable/publish.py 567
14076-    def _update_checkstring(self, ignored):
14077+    def _update_checkstring(self):
14078         """
14079         After putting the root hash, MDMF files will have the
14080         checkstring written to the storage server. This means that we
14081hunk ./src/allmydata/mutable/publish.py 578
14082         self._checkstring = self.writers.values()[0].get_checkstring()
14083 
14084 
14085-    def _make_and_place_signature(self, ignored):
14086+    def _make_and_place_signature(self):
14087         """
14088         I create and place the signature.
14089         """
14090hunk ./src/allmydata/mutable/publish.py 586
14091         signable = self.writers[0].get_signable()
14092         self.signature = self._privkey.sign(signable)
14093 
14094-        ds = []
14095         for (shnum, writer) in self.writers.iteritems():
14096hunk ./src/allmydata/mutable/publish.py 587
14097-            d = writer.put_signature(self.signature)
14098-            d.addCallback(self._got_write_answer, writer, started)
14099-            d.addErrback(self._connection_problem, writer)
14100-            ds.append(d)
14101-        return defer.DeferredList(ds)
14102+            writer.put_signature(self.signature)
14103 
14104 
14105hunk ./src/allmydata/mutable/publish.py 590
14106-    def finish_publishing(self, ignored):
14107+    def finish_publishing(self):
14108         # We're almost done -- we just need to put the verification key
14109         # and the offsets
14110         started = time.time()
14111hunk ./src/allmydata/mutable/publish.py 601
14112         # TODO: Bad, since we remove from this same dict. We need to
14113         # make a copy, or just use a non-iterated value.
14114         for (shnum, writer) in self.writers.iteritems():
14115-            d = writer.put_verification_key(verification_key)
14116-            d.addCallback(self._got_write_answer, writer, started)
14117-            d.addCallback(self._record_verinfo)
14118-            d.addCallback(lambda ignored, writer=writer:
14119-                writer.finish_publishing())
14120+            writer.put_verification_key(verification_key)
14121+            d = writer.finish_publishing()
14122             d.addCallback(self._got_write_answer, writer, started)
14123             d.addErrback(self._connection_problem, writer)
14124             ds.append(d)
14125hunk ./src/allmydata/mutable/publish.py 606
14126+        self._record_verinfo()
14127         return defer.DeferredList(ds)
14128 
14129 
14130hunk ./src/allmydata/mutable/publish.py 610
14131-    def _record_verinfo(self, ignored):
14132+    def _record_verinfo(self):
14133         self.versioninfo = self.writers.values()[0].get_verinfo()
14134 
14135 
14136}
14137[mutable/servermap.py: lay some groundwork for IWritable
14138Kevan Carstensen <kevan@isnotajoke.com>**20100728232345
14139 Ignore-this: 13346540af31ccd94f490b3457521164
14140] {
14141hunk ./src/allmydata/mutable/servermap.py 9
14142 from twisted.python import failure
14143 from foolscap.api import DeadReferenceError, RemoteException, eventually, \
14144                          fireEventually
14145-from allmydata.util import base32, hashutil, idlib, log
14146+from allmydata.util import base32, hashutil, idlib, log, deferredutil
14147 from allmydata.storage.server import si_b2a
14148 from allmydata.interfaces import IServermapUpdaterStatus
14149 from pycryptopp.publickey import rsa
14150hunk ./src/allmydata/mutable/servermap.py 124
14151         self.bad_shares = {} # maps (peerid,shnum) to old checkstring
14152         self.last_update_mode = None
14153         self.last_update_time = 0
14154+        self.update_data = {} # (verinfo,shnum) => data
14155 
14156     def copy(self):
14157         s = ServerMap()
14158hunk ./src/allmydata/mutable/servermap.py 340
14159         return False
14160 
14161 
14162+    def get_update_data_for_share_and_verinfo(self, shnum, verinfo):
14163+        """
14164+        I return the update data for the given shnum
14165+        """
14166+        update_data = self.update_data[shnum]
14167+        update_datum = [i[1] for i in update_data if i[1] == verinfo][0]
14168+        return update_datum
14169+
14170+
14171+    def set_update_data_for_share_and_verinfo(self, shnum, verinfo, data):
14172+        """
14173+        I record the block hash tree for the given shnum.
14174+        """
14175+        self.update_data.setdefault(shnum , []).append((verinfo, data))
14176+
14177+
14178 class ServermapUpdater:
14179     def __init__(self, filenode, storage_broker, monitor, servermap,
14180hunk ./src/allmydata/mutable/servermap.py 358
14181-                 mode=MODE_READ, add_lease=False):
14182+                 mode=MODE_READ, add_lease=False, update_range=None):
14183         """I update a servermap, locating a sufficient number of useful
14184         shares and remembering where they are located.
14185 
14186hunk ./src/allmydata/mutable/servermap.py 405
14187             # we use unpack_prefix_and_signature, so we need 1k
14188             self._read_size = 1000
14189         self._need_privkey = False
14190+
14191         if mode == MODE_WRITE and not self._node.get_privkey():
14192             self._need_privkey = True
14193         # check+repair: repair requires the privkey, so if we didn't happen
14194hunk ./src/allmydata/mutable/servermap.py 412
14195         # to ask for it during the check, we'll have problems doing the
14196         # publish.
14197 
14198+        self.fetch_update_data = False
14199+        if mode == MODE_WRITE and update_range:
14200+            # We're updating the servermap in preparation for an
14201+            # in-place file update, so we need to fetch some additional
14202+            # data from each share that we find.
14203+            assert len(update_range) == 2
14204+
14205+            self.start_segment = update_range[0]
14206+            self.end_segment = update_range[1]
14207+            self.fetch_update_data = True
14208+
14209         prefix = si_b2a(self._storage_index)[:5]
14210         self._log_number = log.msg(format="SharemapUpdater(%(si)s): starting (%(mode)s)",
14211                                    si=prefix, mode=mode)
14212hunk ./src/allmydata/mutable/servermap.py 643
14213                       level=log.NOISY)
14214         now = time.time()
14215         elapsed = now - started
14216-        self._queries_outstanding.discard(peerid)
14217-        self._servermap.reachable_peers.add(peerid)
14218-        self._must_query.discard(peerid)
14219-        self._queries_completed += 1
14220+        def _done_processing(ignored=None):
14221+            self._queries_outstanding.discard(peerid)
14222+            self._servermap.reachable_peers.add(peerid)
14223+            self._must_query.discard(peerid)
14224+            self._queries_completed += 1
14225         if not self._running:
14226             self.log("but we're not running, so we'll ignore it", parent=lp,
14227                      level=log.NOISY)
14228hunk ./src/allmydata/mutable/servermap.py 651
14229+            _done_processing()
14230             self._status.add_per_server_time(peerid, "late", started, elapsed)
14231             return
14232         self._status.add_per_server_time(peerid, "query", started, elapsed)
14233hunk ./src/allmydata/mutable/servermap.py 679
14234             #     public key. We use this to validate the signature.
14235             if not self._node.get_pubkey():
14236                 # fetch and set the public key.
14237-                d = reader.get_verification_key()
14238+                d = reader.get_verification_key(queue=True)
14239                 d.addCallback(lambda results, shnum=shnum, peerid=peerid:
14240                     self._try_to_set_pubkey(results, peerid, shnum, lp))
14241                 # XXX: Make self._pubkey_query_failed?
14242hunk ./src/allmydata/mutable/servermap.py 688
14243             else:
14244                 # we already have the public key.
14245                 d = defer.succeed(None)
14246+
14247             # Neither of these two branches return anything of
14248             # consequence, so the first entry in our deferredlist will
14249             # be None.
14250hunk ./src/allmydata/mutable/servermap.py 705
14251             #   to get the version information. In MDMF, this lives at
14252             #   the end of the share, so unless the file is quite small,
14253             #   we'll need to do a remote fetch to get it.
14254-            d3 = reader.get_signature()
14255+            d3 = reader.get_signature(queue=True)
14256             d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
14257                 self._got_corrupt_share(error, shnum, peerid, data, lp))
14258             #  Once we have all three of these responses, we can move on
14259hunk ./src/allmydata/mutable/servermap.py 714
14260             # Does the node already have a privkey? If not, we'll try to
14261             # fetch it here.
14262             if self._need_privkey:
14263-                d4 = reader.get_encprivkey()
14264+                d4 = reader.get_encprivkey(queue=True)
14265                 d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
14266                     self._try_to_validate_privkey(results, peerid, shnum, lp))
14267                 d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
14268hunk ./src/allmydata/mutable/servermap.py 722
14269             else:
14270                 d4 = defer.succeed(None)
14271 
14272-            dl = defer.DeferredList([d, d2, d3, d4])
14273+
14274+            if self.fetch_update_data:
14275+                # fetch the block hash tree and first + last segment, as
14276+                # configured earlier.
14277+                # Then set them in wherever we happen to want to set
14278+                # them.
14279+                ds = []
14280+                # XXX: We do this above, too. Is there a good way to
14281+                # make the two routines share the value without
14282+                # introducing more roundtrips?
14283+                ds.append(reader.get_verinfo())
14284+                ds.append(reader.get_blockhashes(queue=True))
14285+                ds.append(reader.get_block_and_salt(self.start_segment,
14286+                                                    queue=True))
14287+                ds.append(reader.get_block_and_salt(self.end_segment,
14288+                                                    queue=True))
14289+                d5 = deferredutil.gatherResults(ds)
14290+                d5.addCallback(self._got_update_results_one_share, shnum)
14291+            else:
14292+                d5 = defer.succeed(None)
14293+
14294+            dl = defer.DeferredList([d, d2, d3, d4, d5])
14295+            reader.flush()
14296             dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
14297                 self._got_signature_one_share(results, shnum, peerid, lp))
14298             dl.addErrback(lambda error, shnum=shnum, data=data:
14299hunk ./src/allmydata/mutable/servermap.py 764
14300         # that we returned to our caller to fire, which tells them that
14301         # they have a complete servermap, and that we won't be touching
14302         # the servermap anymore.
14303+        dl.addCallback(_done_processing)
14304         dl.addCallback(self._check_for_done)
14305         dl.addErrback(self._fatal_error)
14306         # all done!
14307hunk ./src/allmydata/mutable/servermap.py 799
14308                  peerid=idlib.shortnodeid_b2a(peerid),
14309                  level=log.NOISY,
14310                  parent=lp)
14311-        _, verinfo, signature, __ = results
14312+        _, verinfo, signature, __, ___ = results
14313         (seqnum,
14314          root_hash,
14315          saltish,
14316hunk ./src/allmydata/mutable/servermap.py 864
14317         return verinfo
14318 
14319 
14320+    def _got_update_results_one_share(self, results, share):
14321+        """
14322+        I record the update results in results.
14323+        """
14324+        assert len(results) == 4
14325+        verinfo, blockhashes, start, end = results
14326+        update_data = (blockhashes, start, end)
14327+        self._servermap.set_update_data_for_share_and_verinfo(share,
14328+                                                              verinfo,
14329+                                                              update_data)
14330+
14331+
14332     def _deserialize_pubkey(self, pubkey_s):
14333         verifier = rsa.create_verifying_key_from_string(pubkey_s)
14334         return verifier
14335}
14336[test/test_mutable.py: Add tests for new servermap behavior
14337Kevan Carstensen <kevan@isnotajoke.com>**20100728232434
14338 Ignore-this: aa6da7dbc9f86eb8840c8f0e779e644d
14339] {
14340hunk ./src/allmydata/test/test_mutable.py 773
14341     def setUp(self):
14342         return self.publish_one()
14343 
14344-    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None):
14345+    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None,
14346+                       update_range=None):
14347         if fn is None:
14348             fn = self._fn
14349         if sb is None:
14350hunk ./src/allmydata/test/test_mutable.py 780
14351             sb = self._storage_broker
14352         smu = ServermapUpdater(fn, sb, Monitor(),
14353-                               ServerMap(), mode)
14354+                               ServerMap(), mode, update_range=update_range)
14355         d = smu.update()
14356         return d
14357 
14358hunk ./src/allmydata/test/test_mutable.py 855
14359         d.addCallback(lambda sm: self.failUnlessOneRecoverable(sm, 10))
14360         return d
14361 
14362+
14363     def test_mark_bad(self):
14364         d = defer.succeed(None)
14365         ms = self.make_servermap
14366hunk ./src/allmydata/test/test_mutable.py 970
14367         return d
14368 
14369 
14370+    def test_fetch_update(self):
14371+        d = defer.succeed(None)
14372+        d.addCallback(lambda ignored:
14373+            self.publish_mdmf())
14374+        d.addCallback(lambda ignored:
14375+            self.make_servermap(mode=MODE_WRITE, update_range=(1, 2)))
14376+        def _check_servermap(sm):
14377+            # 10 shares
14378+            self.failUnlessEqual(len(sm.update_data), 10)
14379+            # one version
14380+            for data in sm.update_data.itervalues():
14381+                self.failUnlessEqual(len(data), 1)
14382+        d.addCallback(_check_servermap)
14383+        return d
14384+
14385+
14386     def test_servermapupdater_finds_sdmf_files(self):
14387         d = defer.succeed(None)
14388         d.addCallback(lambda ignored:
14389hunk ./src/allmydata/test/test_mutable.py 1756
14390 
14391     def test_mdmf_repairable_5shares(self):
14392         d = self.publish_mdmf()
14393-        def _delete_all_shares(ign):
14394+        def _delete_some_shares(ign):
14395             shares = self._storage._peers
14396             for peerid in shares:
14397                 for shnum in list(shares[peerid]):
14398hunk ./src/allmydata/test/test_mutable.py 1762
14399                     if shnum > 5:
14400                         del shares[peerid][shnum]
14401-        d.addCallback(_delete_all_shares)
14402+        d.addCallback(_delete_some_shares)
14403         d.addCallback(lambda ign: self._fn.check(Monitor()))
14404hunk ./src/allmydata/test/test_mutable.py 1764
14405+        def _check(cr):
14406+            self.failIf(cr.is_healthy())
14407+            self.failUnless(cr.is_recoverable())
14408+            return cr
14409+        d.addCallback(_check)
14410         d.addCallback(lambda check_results: self._fn.repair(check_results))
14411hunk ./src/allmydata/test/test_mutable.py 1770
14412-        def _check(crr):
14413+        def _check1(crr):
14414             self.failUnlessEqual(crr.get_successful(), True)
14415hunk ./src/allmydata/test/test_mutable.py 1772
14416-        d.addCallback(_check)
14417+        d.addCallback(_check1)
14418         return d
14419 
14420 
14421}
14422
14423Context:
14424
14425[misc/build_helpers/run-with-pythonpath.py: fix stale comment, and remove 'trial' example that is not the right way to run trial.
14426david-sarah@jacaranda.org**20100726225729
14427 Ignore-this: a61f55557ad69a1633bfb2b8172cce97
14428] 
14429[docs/specifications/dirnodes.txt: 'mesh'->'grid'.
14430david-sarah@jacaranda.org**20100723061616
14431 Ignore-this: 887bcf921ef00afba8e05e9239035bca
14432] 
14433[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'.
14434david-sarah@jacaranda.org**20100723054703
14435 Ignore-this: f3b98183e7d0a0f391225b8b93ac6c37
14436] 
14437[docs: use current cap to Zooko's wiki page in example text
14438zooko@zooko.com**20100721010543
14439 Ignore-this: 4f36f36758f9fdbaf9eb73eac23b6652
14440 fixes #1134
14441] 
14442[__init__.py: silence DeprecationWarning about BaseException.message globally. fixes #1129
14443david-sarah@jacaranda.org**20100720011939
14444 Ignore-this: 38808986ba79cb2786b010504a22f89
14445] 
14446[test_runner: test that 'tahoe --version' outputs no noise (e.g. DeprecationWarnings).
14447david-sarah@jacaranda.org**20100720011345
14448 Ignore-this: dd358b7b2e5d57282cbe133e8069702e
14449] 
14450[TAG allmydata-tahoe-1.7.1
14451zooko@zooko.com**20100719131352
14452 Ignore-this: 6942056548433dc653a746703819ad8c
14453] 
14454[relnotes.txt: updated for v1.7.1 release!
14455zooko@zooko.com**20100719083059
14456 Ignore-this: 9f10eb19b65a39d652b546c57481da45
14457] 
14458[immutable: add test case of #1128, fix test case of #1118
14459zooko@zooko.com**20100719081612
14460 Ignore-this: 8f9f742e7dac2bd9b49c19bd10f1c204
14461] 
14462[NEWS: add #1118 and reflow
14463zooko@zooko.com**20100719081248
14464 Ignore-this: 37a2e39d58c7b584b3c7f193bc1b30df
14465] 
14466[immutable: fix bug in which preexisting_shares and merged were shallowly referencing the same sets
14467zooko@zooko.com**20100719075426
14468 Ignore-this: 90827f8ce7ff0fc0c3c7f819399b8cf0
14469 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.
14470] 
14471[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.
14472david-sarah@jacaranda.org**20100719044655
14473 Ignore-this: 142d182c0739986812140bb8387077d5
14474] 
14475[docs/known_issues.txt: update release version and date.
14476david-sarah@jacaranda.org**20100718235940
14477 Ignore-this: dbbb42dbfa6c0d205a0b8e6e58eee9c7
14478] 
14479[relnotes.txt, docs/quickstart.html: prepare for 1.7.1 release. Don't claim to work on Cygwin (this might work but is untested).
14480david-sarah@jacaranda.org**20100718235437
14481 Ignore-this: dfc7334ee4bb76c04ee19304a7f1024b
14482] 
14483[immutable: extend the tests to check that the shares that got uploaded really do make a sufficiently Happy distribution
14484zooko@zooko.com**20100719045047
14485 Ignore-this: 89c33a7b795e23018667351045a8d5d0
14486 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.
14487] 
14488[immutable: test for #1118
14489zooko@zooko.com**20100718221537
14490 Ignore-this: 8882aabe2aaec6a0148c87e735d817ad
14491] 
14492[immutable: test for #1124
14493zooko@zooko.com**20100718222907
14494 Ignore-this: 1766e3cbab92ea2a9e246f40eb6e770b
14495] 
14496[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>.
14497david-sarah@jacaranda.org**20100718230420
14498 Ignore-this: aef40f2e74ddeabee5e122e8d80893a1
14499] 
14500[trivial: fix unused import (sorry about that, pyflakes)
14501zooko@zooko.com**20100718215133
14502 Ignore-this: c2414e443405072b51d552295f2c0e8c
14503] 
14504[tests, NEWS, CREDITS re: #1117
14505zooko@zooko.com**20100718203225
14506 Ignore-this: 1f08be2c692fb72cc0dd023259f11354
14507 Give Brian and Kevan promotions, move release date in NEWS to the 18th, commit Brian's test for #1117.
14508 fixes #1117
14509] 
14510[test/test_upload.py: test to see that aborted buckets are ignored by the storage server
14511Kevan Carstensen <kevan@isnotajoke.com>**20100716001046
14512 Ignore-this: cc075c24b1c86d737f3199af894cc780
14513] 
14514[test/test_storage.py: test for the new remote_abort semantics.
14515Kevan Carstensen <kevan@isnotajoke.com>**20100715232148
14516 Ignore-this: d3d6491f17bf670e770ca4b385007515
14517] 
14518[storage/immutable.py: make remote_abort btell the storage server about aborted buckets.
14519Kevan Carstensen <kevan@isnotajoke.com>**20100715232105
14520 Ignore-this: 16ab0090676355abdd5600ed44ff19c9
14521] 
14522[test/test_upload.py: changes to test plumbing for #1117 tests
14523Kevan Carstensen <kevan@isnotajoke.com>**20100715231820
14524 Ignore-this: 78a6d359d7bf8529d283e2815bf1e2de
14525 
14526     - Add a callRemoteOnly method to FakeBucketWriter.
14527     - Change the abort method in FakeBucketWriter to not return a
14528       RuntimeError.
14529] 
14530[immutable/upload.py: abort buckets if peer selection fails
14531Kevan Carstensen <kevan@isnotajoke.com>**20100715231714
14532 Ignore-this: 2a0b643a22284df292d8ed9d91b1fd37
14533] 
14534[test_encodingutil: correct an error in the previous patch to StdlibUnicode.test_open_representable.
14535david-sarah@jacaranda.org**20100718151420
14536 Ignore-this: af050955f623fbc0e4d78e15a0a8a144
14537] 
14538[NEWS: Forward-compatibility improvements for non-ASCII caps (#1051).
14539david-sarah@jacaranda.org**20100718143622
14540 Ignore-this: 1edfebc4bd38a3b5c35e75c99588153f
14541] 
14542[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.
14543david-sarah@jacaranda.org**20100718142915
14544 Ignore-this: c4e78ef4b1478dd400da71cf077ffa4a
14545] 
14546[test_encodingutil: StdlibUnicode.test_open_representable no longer uses a mock.
14547david-sarah@jacaranda.org**20100718125412
14548 Ignore-this: 4bf373a5e2dfe4209e5e364124af29a3
14549] 
14550[docs: add comment clarifying #1051
14551zooko@zooko.com**20100718053250
14552 Ignore-this: 6cfc0930434cbdbbc262dabb58f1505d
14553] 
14554[docs: update NEWS
14555zooko@zooko.com**20100718053225
14556 Ignore-this: 63d5c782ef84812e6d010f0590866831
14557] 
14558[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.
14559david-sarah@jacaranda.org**20100711200252
14560 Ignore-this: c2f193352369d32e06865f8f3e951894
14561] 
14562[Debian documentation update
14563jacob@appelbaum.net**20100305003004] 
14564[debian-docs-patch-final
14565jacob@appelbaum.net**20100304085955] 
14566[M-x whitespace-cleanup
14567zooko@zooko.com**20100718032739
14568 Ignore-this: babfd4af6ad2fc885c957fd5c8b10c3f
14569] 
14570[docs: tidy up NEWS a little
14571zooko@zooko.com**20100718032434
14572 Ignore-this: 54f2820fd1a37c8967609f6bfc4e5e18
14573] 
14574[benchmarking: update bench_dirnode.py to reflect the new directory interfaces
14575zooko@zooko.com**20100718031710
14576 Ignore-this: 368ba523dd3de80d9da29cd58afbe827
14577] 
14578[test_encodingutil: fix test_open_representable, which is only valid when run on a platform for which we know an unrepresentable filename.
14579david-sarah@jacaranda.org**20100718030333
14580 Ignore-this: c114d92c17714a5d4ae005c15267d60c
14581] 
14582[iputil.py: Add support for FreeBSD 7,8 and 9
14583francois@ctrlaltdel.ch**20100718022832
14584 Ignore-this: 1829b4cf4b91107f4cf87841e6167e99
14585 committed by: zooko@zooko.com
14586 date: 2010-07-17
14587 and I also patched: NEWS and CREDITS
14588] 
14589[NEWS: add snippet about #1083
14590zooko@zooko.com**20100718020653
14591 Ignore-this: d353a9d93cbc5a5e6ba4671f78d1e22b
14592] 
14593[fileutil: docstrings for non-obvious usage restrictions on methods of EncryptedTemporaryFile.
14594david-sarah@jacaranda.org**20100717054647
14595 Ignore-this: 46d8fc10782fa8ec2b6c5b168c841943
14596] 
14597[Move EncryptedTemporaryFile from SFTP frontend to allmydata.util.fileutil, and make the FTP frontend also use it (fixing #1083).
14598david-sarah@jacaranda.org**20100711213721
14599 Ignore-this: e452e8ca66391aa2a1a49afe0114f317
14600] 
14601[NEWS: reorder NEWS snippets to be in descending order of interestingness
14602zooko@zooko.com**20100718015929
14603 Ignore-this: 146c42e88a9555a868a04a69dd0e5326
14604] 
14605[Correct stringutils->encodingutil patch to be the newer version, rather than the old version that was committed in error.
14606david-sarah@jacaranda.org**20100718013435
14607 Ignore-this: c8940c4e1aa2e9acc80cd4fe54753cd8
14608] 
14609[test_cli.py: fix error that crept in when rebasing the patch for #1072.
14610david-sarah@jacaranda.org**20100718000123
14611 Ignore-this: 3e8f6cc3a27b747c708221dd581934f4
14612] 
14613[stringutils: add test for when sys.stdout has no encoding attribute (fixes #1099).
14614david-sarah@jacaranda.org**20100717045816
14615 Ignore-this: f28dce6940e909f12f354086d17db54f
14616] 
14617[CLI: add 'tahoe unlink' as an alias to 'tahoe rm', for forward-compatibility.
14618david-sarah@jacaranda.org**20100717220411
14619 Ignore-this: 3ecdde7f2d0498514cef32e118e0b855
14620] 
14621[minor code clean-up in dirnode.py
14622zooko@zooko.com**20100714060255
14623 Ignore-this: bb0ab2783203e605024b3e2f798256a1
14624 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.
14625 fixes #967
14626] 
14627[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.
14628david-sarah@jacaranda.org**20100712003015
14629 Ignore-this: 103b809d180df17a7283077c3104c7be
14630] 
14631[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.
14632david-sarah@jacaranda.org**20100711195525
14633 Ignore-this: deac32d8b91ba26ede18905d3f7d2b93
14634] 
14635[docs: CREDITS and NEWS
14636zooko@zooko.com**20100714060150
14637 Ignore-this: dc83e612f77d69e50ee975f07f6b16fe
14638] 
14639[CREDITS: more creds for Kevan, plus utf-8 BOM
14640zooko@zooko.com**20100619045503
14641 Ignore-this: 72d02bdd7a0f324f1cee8cd399c7c6de
14642] 
14643[cli.py: make command descriptions consistently end with a full stop.
14644david-sarah@jacaranda.org**20100714014538
14645 Ignore-this: 9ee7fa29ca2d1631db4049c2a389a97a
14646] 
14647[SFTP: address some of the comments in zooko's review (#1106).
14648david-sarah@jacaranda.org**20100712025537
14649 Ignore-this: c3921638a2d4f1de2a776ae78e4dc37e
14650] 
14651[docs/logging.txt: note that setting flogging vars might affect tests with race conditions.
14652david-sarah@jacaranda.org**20100712050721
14653 Ignore-this: fc1609d215fcd5561a57fd1226206f27
14654] 
14655[test_storage.py: potential fix for failures when logging is enabled.
14656david-sarah@jacaranda.org**19700713040546
14657 Ignore-this: 5815693a0df3e64c52c3c6b7be2846c7
14658] 
14659[upcase_since_on_welcome
14660terrellrussell@gmail.com**20100708193903] 
14661[server_version_on_welcome_page.dpatch.txt
14662freestorm77@gmail.com**20100605191721
14663 Ignore-this: b450c76dc875f5ac8cca229a666cbd0a
14664 
14665 
14666 - The storage server version is 0 for all storage nodes in the Welcome Page
14667 
14668 
14669] 
14670[NEWS: add NEWS snippets about two recent patches
14671zooko@zooko.com**20100708162058
14672 Ignore-this: 6c9da6a0ad7351a960bdd60f81532899
14673] 
14674[directory_html_top_banner.dpatch
14675freestorm77@gmail.com**20100622205301
14676 Ignore-this: 1d770d975e0c414c996564774f049bca
14677 
14678 The div tag with the link "Return to Welcome page" on the directory.xhtml page is not correct
14679 
14680] 
14681[tahoe_css_toolbar.dpatch
14682freestorm77@gmail.com**20100622210046
14683 Ignore-this: 5b3ebb2e0f52bbba718a932f80c246c0
14684 
14685 CSS modification to be correctly diplayed with Internet Explorer 8
14686 
14687 The links on the top of page directory.xhtml are not diplayed in the same line as display with Firefox.
14688 
14689] 
14690[runnin_test_tahoe_css.dpatch
14691freestorm77@gmail.com**20100622214714
14692 Ignore-this: e0db73d68740aad09a7b9ae60a08c05c
14693 
14694 Runnin test for changes in tahoe.css file
14695 
14696] 
14697[runnin_test_directory_xhtml.dpatch
14698freestorm77@gmail.com**20100622201403
14699 Ignore-this: f8962463fce50b9466405cb59fe11d43
14700 
14701 Runnin test for diretory.xhtml top banner
14702 
14703] 
14704[stringutils.py: tolerate sys.stdout having no 'encoding' attribute.
14705david-sarah@jacaranda.org**20100626040817
14706 Ignore-this: f42cad81cef645ee38ac1df4660cc850
14707] 
14708[quickstart.html: python 2.5 -> 2.6 as recommended version
14709david-sarah@jacaranda.org**20100705175858
14710 Ignore-this: bc3a14645ea1d5435002966ae903199f
14711] 
14712[SFTP: don't call .stopProducing on the producer registered with OverwriteableFileConsumer (which breaks with warner's new downloader).
14713david-sarah@jacaranda.org**20100628231926
14714 Ignore-this: 131b7a5787bc85a9a356b5740d9d996f
14715] 
14716[docs/how_to_make_a_tahoe-lafs_release.txt: trivial correction, install.html should now be quickstart.html.
14717david-sarah@jacaranda.org**20100625223929
14718 Ignore-this: 99a5459cac51bd867cc11ad06927ff30
14719] 
14720[setup: in the Makefile, refuse to upload tarballs unless someone has passed the environment variable "BB_BRANCH" with value "trunk"
14721zooko@zooko.com**20100619034928
14722 Ignore-this: 276ddf9b6ad7ec79e27474862e0f7d6
14723] 
14724[trivial: tiny update to in-line comment
14725zooko@zooko.com**20100614045715
14726 Ignore-this: 10851b0ed2abfed542c97749e5d280bc
14727 (I'm actually committing this patch as a test of the new eager-annotation-computation of trac-darcs.)
14728] 
14729[docs: about.html link to home page early on, and be decentralized storage instead of cloud storage this time around
14730zooko@zooko.com**20100619065318
14731 Ignore-this: dc6db03f696e5b6d2848699e754d8053
14732] 
14733[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"
14734zooko@zooko.com**20100619065124
14735 Ignore-this: e292c7f51c337a84ebfeb366fbd24d6c
14736] 
14737[TAG allmydata-tahoe-1.7.0
14738zooko@zooko.com**20100619052631
14739 Ignore-this: d21e27afe6d85e2e3ba6a3292ba2be1
14740] 
14741Patch bundle hash:
147423cdc02df66839833841b4195bc6014512c86f875