Ticket #393: 393status46.dpatch

File 393status46.dpatch, 958.5 KB (added by kevan, at 2011-07-31T22:51:09Z)

add tighter bounds in MDMF shares, resolve bugs

Line 
1Mon Aug  9 16:32:44 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
2  * interfaces.py: Add #993 interfaces
3
4Mon Aug  9 16:35:35 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
5  * frontends/sftpd.py: Modify the sftp frontend to work with the MDMF changes
6
7Mon Aug  9 17:06:19 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
8  * immutable/filenode.py: Make the immutable file node implement the same interfaces as the mutable one
9
10Mon Aug  9 17:06:33 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
11  * immutable/literal.py: implement the same interfaces as other filenodes
12
13Fri Aug 13 16:49:57 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
14  * scripts: tell 'tahoe put' about MDMF
15
16Sat Aug 14 01:10:12 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
17  * web: Alter the webapi to get along with and take advantage of the MDMF changes
18 
19  The main benefit that the webapi gets from MDMF, at least initially, is
20  the ability to do a streaming download of an MDMF mutable file. It also
21  exposes a way (through the PUT verb) to append to or otherwise modify
22  (in-place) an MDMF mutable file.
23
24Sat Aug 14 15:57:11 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
25  * client.py: learn how to create different kinds of mutable files
26
27Wed Aug 18 17:32:16 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
28  * mutable/checker.py and mutable/repair.py: Modify checker and repairer to work with MDMF
29 
30  The checker and repairer required minimal changes to work with the MDMF
31  modifications made elsewhere. The checker duplicated a lot of the code
32  that was already in the downloader, so I modified the downloader
33  slightly to expose this functionality to the checker and removed the
34  duplicated code. The repairer only required a minor change to deal with
35  data representation.
36
37Wed Aug 18 17:32:31 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
38  * mutable/filenode.py: add versions and partial-file updates to the mutable file node
39 
40  One of the goals of MDMF as a GSoC project is to lay the groundwork for
41  LDMF, a format that will allow Tahoe-LAFS to deal with and encourage
42  multiple versions of a single cap on the grid. In line with this, there
43  is a now a distinction between an overriding mutable file (which can be
44  thought to correspond to the cap/unique identifier for that mutable
45  file) and versions of the mutable file (which we can download, update,
46  and so on). All download, upload, and modification operations end up
47  happening on a particular version of a mutable file, but there are
48  shortcut methods on the object representing the overriding mutable file
49  that perform these operations on the best version of the mutable file
50  (which is what code should be doing until we have LDMF and better
51  support for other paradigms).
52 
53  Another goal of MDMF was to take advantage of segmentation to give
54  callers more efficient partial file updates or appends. This patch
55  implements methods that do that, too.
56 
57
58Wed Aug 18 17:33:42 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
59  * mutable/publish.py: Modify the publish process to support MDMF
60 
61  The inner workings of the publishing process needed to be reworked to a
62  large extend to cope with segmented mutable files, and to cope with
63  partial-file updates of mutable files. This patch does that. It also
64  introduces wrappers for uploadable data, allowing the use of
65  filehandle-like objects as data sources, in addition to strings. This
66  reduces memory inefficiency when dealing with large files through the
67  webapi, and clarifies update code there.
68
69Wed Aug 18 17:35:09 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
70  * nodemaker.py: Make nodemaker expose a way to create MDMF files
71
72Sat Aug 14 15:56:44 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
73  * docs: update docs to mention MDMF
74
75Wed Aug 18 17:33:04 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
76  * mutable/layout.py and interfaces.py: add MDMF writer and reader
77 
78  The MDMF writer is responsible for keeping state as plaintext is
79  gradually processed into share data by the upload process. When the
80  upload finishes, it will write all of its share data to a remote server,
81  reporting its status back to the publisher.
82 
83  The MDMF reader is responsible for abstracting an MDMF file as it sits
84  on the grid from the downloader; specifically, by receiving and
85  responding to requests for arbitrary data within the MDMF file.
86 
87  The interfaces.py file has also been modified to contain an interface
88  for the writer.
89
90Wed Aug 18 17:34:09 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
91  * mutable/retrieve.py: Modify the retrieval process to support MDMF
92 
93  The logic behind a mutable file download had to be adapted to work with
94  segmented mutable files; this patch performs those adaptations. It also
95  exposes some decoding and decrypting functionality to make partial-file
96  updates a little easier, and supports efficient random-access downloads
97  of parts of an MDMF file.
98
99Wed Aug 18 17:34:39 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
100  * mutable/servermap.py: Alter the servermap updater to work with MDMF files
101 
102  These modifications were basically all to the end of having the
103  servermap updater use the unified MDMF + SDMF read interface whenever
104  possible -- this reduces the complexity of the code, making it easier to
105  read and maintain. To do this, I needed to modify the process of
106  updating the servermap a little bit.
107 
108  To support partial-file updates, I also modified the servermap updater
109  to fetch the block hash trees and certain segments of files while it
110  performed a servermap update (this can be done without adding any new
111  roundtrips because of batch-read functionality that the read proxy has).
112 
113
114Wed Aug 18 17:35:31 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
115  * tests:
116 
117      - A lot of existing tests relied on aspects of the mutable file
118        implementation that were changed. This patch updates those tests
119        to work with the changes.
120      - This patch also adds tests for new features.
121
122Sun Feb 20 15:02:01 PST 2011  "Brian Warner <warner@lothar.com>"
123  * resolve conflicts between 393-MDMF patches and trunk as of 1.8.2
124
125Sun Feb 20 17:46:59 PST 2011  "Brian Warner <warner@lothar.com>"
126  * mutable/filenode.py: fix create_mutable_file('string')
127
128Sun Feb 20 21:56:00 PST 2011  "Brian Warner <warner@lothar.com>"
129  * resolve more conflicts with current trunk
130
131Sun Feb 20 22:10:04 PST 2011  "Brian Warner <warner@lothar.com>"
132  * update MDMF code with StorageFarmBroker changes
133
134Fri Feb 25 17:04:33 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
135  * mutable/filenode: Clean up servermap handling in MutableFileVersion
136 
137  We want to update the servermap before attempting to modify a file,
138  which we now do. This introduced code duplication, which was addressed
139  by refactoring the servermap update into its own method, and then
140  eliminating duplicate servermap updates throughout the
141  MutableFileVersion.
142
143Sun Feb 27 15:16:43 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
144  * web: Use the string "replace" to trigger whole-file replacement when processing an offset parameter.
145
146Sun Feb 27 16:34:26 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
147  * docs/configuration.rst: fix more conflicts between #393 and trunk
148
149Sun Feb 27 17:06:37 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
150  * mutable/layout: remove references to the salt hash tree.
151
152Sun Feb 27 18:10:56 PST 2011  warner@lothar.com
153  * test_mutable.py: add test to exercise fencepost bug
154
155Mon Feb 28 00:33:27 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
156  * mutable/publish: account for offsets on segment boundaries.
157
158Mon Feb 28 19:08:07 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
159  * tahoe-put: raise UsageError when given a nonsensical mutable type, move option validation code to the option parser.
160
161Fri Mar  4 17:08:58 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
162  * web: use None instead of False in the case of no offset, use object identity comparison to check whether or not an offset was specified.
163
164Mon Mar  7 00:17:13 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
165  * mutable/filenode: remove incorrect comments about segment boundaries
166
167Mon Mar  7 00:22:29 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
168  * mutable: use integer division where appropriate
169
170Sun May  1 15:41:25 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
171  * mutable/layout.py: reorder on-disk format to aput variable-length fields at the end of the share, after a predictably long preamble
172
173Sun May  1 15:42:49 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
174  * uri.py: Add MDMF cap
175
176Sun May  1 15:45:23 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
177  * nodemaker, mutable/filenode: train nodemaker and filenode to handle MDMF caps
178
179Sun May 15 15:59:46 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
180  * mutable/retrieve: fix typo in paused check
181
182Sun May 15 16:00:08 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
183  * scripts/tahoe_put.py: teach tahoe put about MDMF caps
184
185Sun May 15 16:00:38 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
186  * test/common.py: fix some MDMF-related bugs in common test fixtures
187
188Sun May 15 16:00:54 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
189  * test/test_cli: Alter existing MDMF tests to test for MDMF caps
190
191Sun May 15 16:02:07 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
192  * test/test_mutable.py: write a test for pausing during retrieval, write support structure for that test
193
194Sun May 15 16:03:26 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
195  * test/test_mutable.py: implement cap type checking
196
197Sun May 15 16:03:58 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
198  * test/test_web: add MDMF cap tests
199
200Sun May 15 16:04:21 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
201  * web/filenode.py: complain if a PUT is requested with a readonly cap
202
203Sun May 15 16:04:44 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
204  * web/info.py: Display mutable type information when describing a mutable file
205
206Mon May 30 18:20:36 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
207  * uri: teach mutable URI objects how to allow other objects to give them extension parameters
208
209Mon May 30 18:22:01 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
210  * interfaces: working update to interfaces.py for extension handling
211
212Mon May 30 18:24:47 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
213  * mutable/publish: tell filenodes about encoding parameters so they can be put in the cap
214
215Mon May 30 18:25:57 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
216  * mutable/servermap: caps imply protocol version, so the servermap doesn't need to tell the filenode what it is anymore.
217
218Mon May 30 18:26:41 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
219  * mutable/filenode: pass downloader hints between publisher, MutableFileNode, and MutableFileVersion as convenient
220 
221  We still need to work on making this more thorough; i.e., passing hints
222  when other operations change encoding parameters.
223
224Mon May 30 18:27:39 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
225  * test: change test fixtures to work with our new extension passing API; add, change, and delete tests as appropriate to reflect the fact that caps without hints are now the exception rather than the norm
226
227Fri Jun 17 10:58:08 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
228  * Add MDMF dirnodes
229
230Fri Jun 17 10:59:50 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
231  * Add tests for MDMF directories
232
233Fri Jun 17 11:00:19 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
234  * web: teach WUI and webapi to create MDMF directories
235
236Fri Jun 17 11:01:00 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
237  * test/test_web: test webapi and WUI for MDMF directory handling
238
239Fri Jun 17 11:01:37 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
240  * scripts: teach CLI to make MDMF directories
241
242Fri Jun 17 11:02:09 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
243  * test/test_cli: test CLI's MDMF creation powers
244
245Wed Jul 27 09:28:55 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
246  * mutable/layout: Add tighter bounds on the sizes of certain share components
247
248Wed Jul 27 09:29:55 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
249  * test/test_mutable: write tests that see what happens when there are 255 shares, amend existing tests to also test for this condition.
250
251Thu Jul 28 10:16:01 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
252  * test/test_mutable: add interoperability tests
253
254Thu Jul 28 16:40:15 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
255  * scripts/cli: resolve merge conflicts
256
257Sat Jul 30 14:41:13 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
258  * mutable/publish: clean up error handling.
259
260Sat Jul 30 15:02:08 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
261  * mutable: address failure to publish when there are as  many writers as k, add/fix tests for this
262
263Sat Jul 30 15:03:00 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
264  * test/test_cli: rework a tahoe cp test that relied on an old webapi error message
265
266New patches:
267
268[interfaces.py: Add #993 interfaces
269Kevan Carstensen <kevan@isnotajoke.com>**20100809233244
270 Ignore-this: b58621ac5cc86f1b4b4149f9e6c6a1ce
271] {
272hunk ./src/allmydata/interfaces.py 499
273 class MustNotBeUnknownRWError(CapConstraintError):
274     """Cannot add an unknown child cap specified in a rw_uri field."""
275 
276+
277+class IReadable(Interface):
278+    """I represent a readable object -- either an immutable file, or a
279+    specific version of a mutable file.
280+    """
281+
282+    def is_readonly():
283+        """Return True if this reference provides mutable access to the given
284+        file or directory (i.e. if you can modify it), or False if not. Note
285+        that even if this reference is read-only, someone else may hold a
286+        read-write reference to it.
287+
288+        For an IReadable returned by get_best_readable_version(), this will
289+        always return True, but for instances of subinterfaces such as
290+        IMutableFileVersion, it may return False."""
291+
292+    def is_mutable():
293+        """Return True if this file or directory is mutable (by *somebody*,
294+        not necessarily you), False if it is is immutable. Note that a file
295+        might be mutable overall, but your reference to it might be
296+        read-only. On the other hand, all references to an immutable file
297+        will be read-only; there are no read-write references to an immutable
298+        file."""
299+
300+    def get_storage_index():
301+        """Return the storage index of the file."""
302+
303+    def get_size():
304+        """Return the length (in bytes) of this readable object."""
305+
306+    def download_to_data():
307+        """Download all of the file contents. I return a Deferred that fires
308+        with the contents as a byte string."""
309+
310+    def read(consumer, offset=0, size=None):
311+        """Download a portion (possibly all) of the file's contents, making
312+        them available to the given IConsumer. Return a Deferred that fires
313+        (with the consumer) when the consumer is unregistered (either because
314+        the last byte has been given to it, or because the consumer threw an
315+        exception during write(), possibly because it no longer wants to
316+        receive data). The portion downloaded will start at 'offset' and
317+        contain 'size' bytes (or the remainder of the file if size==None).
318+
319+        The consumer will be used in non-streaming mode: an IPullProducer
320+        will be attached to it.
321+
322+        The consumer will not receive data right away: several network trips
323+        must occur first. The order of events will be::
324+
325+         consumer.registerProducer(p, streaming)
326+          (if streaming == False)::
327+           consumer does p.resumeProducing()
328+            consumer.write(data)
329+           consumer does p.resumeProducing()
330+            consumer.write(data).. (repeat until all data is written)
331+         consumer.unregisterProducer()
332+         deferred.callback(consumer)
333+
334+        If a download error occurs, or an exception is raised by
335+        consumer.registerProducer() or consumer.write(), I will call
336+        consumer.unregisterProducer() and then deliver the exception via
337+        deferred.errback(). To cancel the download, the consumer should call
338+        p.stopProducing(), which will result in an exception being delivered
339+        via deferred.errback().
340+
341+        See src/allmydata/util/consumer.py for an example of a simple
342+        download-to-memory consumer.
343+        """
344+
345+
346+class IWritable(Interface):
347+    """
348+    I define methods that callers can use to update SDMF and MDMF
349+    mutable files on a Tahoe-LAFS grid.
350+    """
351+    # XXX: For the moment, we have only this. It is possible that we
352+    #      want to move overwrite() and modify() in here too.
353+    def update(data, offset):
354+        """
355+        I write the data from my data argument to the MDMF file,
356+        starting at offset. I continue writing data until my data
357+        argument is exhausted, appending data to the file as necessary.
358+        """
359+        # assert IMutableUploadable.providedBy(data)
360+        # to append data: offset=node.get_size_of_best_version()
361+        # do we want to support compacting MDMF?
362+        # for an MDMF file, this can be done with O(data.get_size())
363+        # memory. For an SDMF file, any modification takes
364+        # O(node.get_size_of_best_version()).
365+
366+
367+class IMutableFileVersion(IReadable):
368+    """I provide access to a particular version of a mutable file. The
369+    access is read/write if I was obtained from a filenode derived from
370+    a write cap, or read-only if the filenode was derived from a read cap.
371+    """
372+
373+    def get_sequence_number():
374+        """Return the sequence number of this version."""
375+
376+    def get_servermap():
377+        """Return the IMutableFileServerMap instance that was used to create
378+        this object.
379+        """
380+
381+    def get_writekey():
382+        """Return this filenode's writekey, or None if the node does not have
383+        write-capability. This may be used to assist with data structures
384+        that need to make certain data available only to writers, such as the
385+        read-write child caps in dirnodes. The recommended process is to have
386+        reader-visible data be submitted to the filenode in the clear (where
387+        it will be encrypted by the filenode using the readkey), but encrypt
388+        writer-visible data using this writekey.
389+        """
390+
391+    # TODO: Can this be overwrite instead of replace?
392+    def replace(new_contents):
393+        """Replace the contents of the mutable file, provided that no other
394+        node has published (or is attempting to publish, concurrently) a
395+        newer version of the file than this one.
396+
397+        I will avoid modifying any share that is different than the version
398+        given by get_sequence_number(). However, if another node is writing
399+        to the file at the same time as me, I may manage to update some shares
400+        while they update others. If I see any evidence of this, I will signal
401+        UncoordinatedWriteError, and the file will be left in an inconsistent
402+        state (possibly the version you provided, possibly the old version,
403+        possibly somebody else's version, and possibly a mix of shares from
404+        all of these).
405+
406+        The recommended response to UncoordinatedWriteError is to either
407+        return it to the caller (since they failed to coordinate their
408+        writes), or to attempt some sort of recovery. It may be sufficient to
409+        wait a random interval (with exponential backoff) and repeat your
410+        operation. If I do not signal UncoordinatedWriteError, then I was
411+        able to write the new version without incident.
412+
413+        I return a Deferred that fires (with a PublishStatus object) when the
414+        update has completed.
415+        """
416+
417+    def modify(modifier_cb):
418+        """Modify the contents of the file, by downloading this version,
419+        applying the modifier function (or bound method), then uploading
420+        the new version. This will succeed as long as no other node
421+        publishes a version between the download and the upload.
422+        I return a Deferred that fires (with a PublishStatus object) when
423+        the update is complete.
424+
425+        The modifier callable will be given three arguments: a string (with
426+        the old contents), a 'first_time' boolean, and a servermap. As with
427+        download_to_data(), the old contents will be from this version,
428+        but the modifier can use the servermap to make other decisions
429+        (such as refusing to apply the delta if there are multiple parallel
430+        versions, or if there is evidence of a newer unrecoverable version).
431+        'first_time' will be True the first time the modifier is called,
432+        and False on any subsequent calls.
433+
434+        The callable should return a string with the new contents. The
435+        callable must be prepared to be called multiple times, and must
436+        examine the input string to see if the change that it wants to make
437+        is already present in the old version. If it does not need to make
438+        any changes, it can either return None, or return its input string.
439+
440+        If the modifier raises an exception, it will be returned in the
441+        errback.
442+        """
443+
444+
445 # The hierarchy looks like this:
446 #  IFilesystemNode
447 #   IFileNode
448hunk ./src/allmydata/interfaces.py 758
449     def raise_error():
450         """Raise any error associated with this node."""
451 
452+    # XXX: These may not be appropriate outside the context of an IReadable.
453     def get_size():
454         """Return the length (in bytes) of the data this node represents. For
455         directory nodes, I return the size of the backing store. I return
456hunk ./src/allmydata/interfaces.py 775
457 class IFileNode(IFilesystemNode):
458     """I am a node which represents a file: a sequence of bytes. I am not a
459     container, like IDirectoryNode."""
460+    def get_best_readable_version():
461+        """Return a Deferred that fires with an IReadable for the 'best'
462+        available version of the file. The IReadable provides only read
463+        access, even if this filenode was derived from a write cap.
464 
465hunk ./src/allmydata/interfaces.py 780
466-class IImmutableFileNode(IFileNode):
467-    def read(consumer, offset=0, size=None):
468-        """Download a portion (possibly all) of the file's contents, making
469-        them available to the given IConsumer. Return a Deferred that fires
470-        (with the consumer) when the consumer is unregistered (either because
471-        the last byte has been given to it, or because the consumer threw an
472-        exception during write(), possibly because it no longer wants to
473-        receive data). The portion downloaded will start at 'offset' and
474-        contain 'size' bytes (or the remainder of the file if size==None).
475-
476-        The consumer will be used in non-streaming mode: an IPullProducer
477-        will be attached to it.
478+        For an immutable file, there is only one version. For a mutable
479+        file, the 'best' version is the recoverable version with the
480+        highest sequence number. If no uncoordinated writes have occurred,
481+        and if enough shares are available, then this will be the most
482+        recent version that has been uploaded. If no version is recoverable,
483+        the Deferred will errback with an UnrecoverableFileError.
484+        """
485 
486hunk ./src/allmydata/interfaces.py 788
487-        The consumer will not receive data right away: several network trips
488-        must occur first. The order of events will be::
489+    def download_best_version():
490+        """Download the contents of the version that would be returned
491+        by get_best_readable_version(). This is equivalent to calling
492+        download_to_data() on the IReadable given by that method.
493 
494hunk ./src/allmydata/interfaces.py 793
495-         consumer.registerProducer(p, streaming)
496-          (if streaming == False)::
497-           consumer does p.resumeProducing()
498-            consumer.write(data)
499-           consumer does p.resumeProducing()
500-            consumer.write(data).. (repeat until all data is written)
501-         consumer.unregisterProducer()
502-         deferred.callback(consumer)
503+        I return a Deferred that fires with a byte string when the file
504+        has been fully downloaded. To support streaming download, use
505+        the 'read' method of IReadable. If no version is recoverable,
506+        the Deferred will errback with an UnrecoverableFileError.
507+        """
508 
509hunk ./src/allmydata/interfaces.py 799
510-        If a download error occurs, or an exception is raised by
511-        consumer.registerProducer() or consumer.write(), I will call
512-        consumer.unregisterProducer() and then deliver the exception via
513-        deferred.errback(). To cancel the download, the consumer should call
514-        p.stopProducing(), which will result in an exception being delivered
515-        via deferred.errback().
516+    def get_size_of_best_version():
517+        """Find the size of the version that would be returned by
518+        get_best_readable_version().
519 
520hunk ./src/allmydata/interfaces.py 803
521-        See src/allmydata/util/consumer.py for an example of a simple
522-        download-to-memory consumer.
523+        I return a Deferred that fires with an integer. If no version
524+        is recoverable, the Deferred will errback with an
525+        UnrecoverableFileError.
526         """
527 
528hunk ./src/allmydata/interfaces.py 808
529+
530+class IImmutableFileNode(IFileNode, IReadable):
531+    """I am a node representing an immutable file. Immutable files have
532+    only one version"""
533+
534+
535 class IMutableFileNode(IFileNode):
536     """I provide access to a 'mutable file', which retains its identity
537     regardless of what contents are put in it.
538hunk ./src/allmydata/interfaces.py 873
539     only be retrieved and updated all-at-once, as a single big string. Future
540     versions of our mutable files will remove this restriction.
541     """
542-
543-    def download_best_version():
544-        """Download the 'best' available version of the file, meaning one of
545-        the recoverable versions with the highest sequence number. If no
546+    def get_best_mutable_version():
547+        """Return a Deferred that fires with an IMutableFileVersion for
548+        the 'best' available version of the file. The best version is
549+        the recoverable version with the highest sequence number. If no
550         uncoordinated writes have occurred, and if enough shares are
551hunk ./src/allmydata/interfaces.py 878
552-        available, then this will be the most recent version that has been
553-        uploaded.
554+        available, then this will be the most recent version that has
555+        been uploaded.
556 
557hunk ./src/allmydata/interfaces.py 881
558-        I update an internal servermap with MODE_READ, determine which
559-        version of the file is indicated by
560-        servermap.best_recoverable_version(), and return a Deferred that
561-        fires with its contents. If no version is recoverable, the Deferred
562-        will errback with UnrecoverableFileError.
563-        """
564-
565-    def get_size_of_best_version():
566-        """Find the size of the version that would be downloaded with
567-        download_best_version(), without actually downloading the whole file.
568-
569-        I return a Deferred that fires with an integer.
570+        If no version is recoverable, the Deferred will errback with an
571+        UnrecoverableFileError.
572         """
573 
574     def overwrite(new_contents):
575hunk ./src/allmydata/interfaces.py 921
576         errback.
577         """
578 
579-
580     def get_servermap(mode):
581         """Return a Deferred that fires with an IMutableFileServerMap
582         instance, updated using the given mode.
583hunk ./src/allmydata/interfaces.py 974
584         writer-visible data using this writekey.
585         """
586 
587+    def set_version(version):
588+        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
589+        we upload in SDMF for reasons of compatibility. If you want to
590+        change this, set_version will let you do that.
591+
592+        To say that this file should be uploaded in SDMF, pass in a 0. To
593+        say that the file should be uploaded as MDMF, pass in a 1.
594+        """
595+
596+    def get_version():
597+        """Returns the mutable file protocol version."""
598+
599 class NotEnoughSharesError(Exception):
600     """Download was unable to get enough shares"""
601 
602hunk ./src/allmydata/interfaces.py 1822
603         """The upload is finished, and whatever filehandle was in use may be
604         closed."""
605 
606+
607+class IMutableUploadable(Interface):
608+    """
609+    I represent content that is due to be uploaded to a mutable filecap.
610+    """
611+    # This is somewhat simpler than the IUploadable interface above
612+    # because mutable files do not need to be concerned with possibly
613+    # generating a CHK, nor with per-file keys. It is a subset of the
614+    # methods in IUploadable, though, so we could just as well implement
615+    # the mutable uploadables as IUploadables that don't happen to use
616+    # those methods (with the understanding that the unused methods will
617+    # never be called on such objects)
618+    def get_size():
619+        """
620+        Returns a Deferred that fires with the size of the content held
621+        by the uploadable.
622+        """
623+
624+    def read(length):
625+        """
626+        Returns a list of strings which, when concatenated, are the next
627+        length bytes of the file, or fewer if there are fewer bytes
628+        between the current location and the end of the file.
629+        """
630+
631+    def close():
632+        """
633+        The process that used the Uploadable is finished using it, so
634+        the uploadable may be closed.
635+        """
636+
637 class IUploadResults(Interface):
638     """I am returned by upload() methods. I contain a number of public
639     attributes which can be read to determine the results of the upload. Some
640}
641[frontends/sftpd.py: Modify the sftp frontend to work with the MDMF changes
642Kevan Carstensen <kevan@isnotajoke.com>**20100809233535
643 Ignore-this: 2d25e2cfcd0d7bbcbba660c7e1da12f
644] {
645hunk ./src/allmydata/frontends/sftpd.py 33
646 from allmydata.interfaces import IFileNode, IDirectoryNode, ExistingChildError, \
647      NoSuchChildError, ChildOfWrongTypeError
648 from allmydata.mutable.common import NotWriteableError
649+from allmydata.mutable.publish import MutableFileHandle
650 from allmydata.immutable.upload import FileHandle
651 from allmydata.dirnode import update_metadata
652 from allmydata.util.fileutil import EncryptedTemporaryFile
653hunk ./src/allmydata/frontends/sftpd.py 667
654         else:
655             assert IFileNode.providedBy(filenode), filenode
656 
657-            if filenode.is_mutable():
658-                self.async.addCallback(lambda ign: filenode.download_best_version())
659-                def _downloaded(data):
660-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
661-                    self.consumer.write(data)
662-                    self.consumer.finish()
663-                    return None
664-                self.async.addCallback(_downloaded)
665-            else:
666-                download_size = filenode.get_size()
667-                assert download_size is not None, "download_size is None"
668+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
669+
670+            def _read(version):
671+                if noisy: self.log("_read", level=NOISY)
672+                download_size = version.get_size()
673+                assert download_size is not None
674+
675                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
676hunk ./src/allmydata/frontends/sftpd.py 675
677-                def _read(ign):
678-                    if noisy: self.log("_read immutable", level=NOISY)
679-                    filenode.read(self.consumer, 0, None)
680-                self.async.addCallback(_read)
681+
682+                version.read(self.consumer, 0, None)
683+            self.async.addCallback(_read)
684 
685         eventually(self.async.callback, None)
686 
687hunk ./src/allmydata/frontends/sftpd.py 821
688                     assert parent and childname, (parent, childname, self.metadata)
689                     d2.addCallback(lambda ign: parent.set_metadata_for(childname, self.metadata))
690 
691-                d2.addCallback(lambda ign: self.consumer.get_current_size())
692-                d2.addCallback(lambda size: self.consumer.read(0, size))
693-                d2.addCallback(lambda new_contents: self.filenode.overwrite(new_contents))
694+                d2.addCallback(lambda ign: self.filenode.overwrite(MutableFileHandle(self.consumer.get_file())))
695             else:
696                 def _add_file(ign):
697                     self.log("_add_file childname=%r" % (childname,), level=OPERATIONAL)
698}
699[immutable/filenode.py: Make the immutable file node implement the same interfaces as the mutable one
700Kevan Carstensen <kevan@isnotajoke.com>**20100810000619
701 Ignore-this: 93e536c0f8efb705310f13ff64621527
702] {
703hunk ./src/allmydata/immutable/filenode.py 8
704 now = time.time
705 from zope.interface import implements
706 from twisted.internet import defer
707-from twisted.internet.interfaces import IConsumer
708 
709hunk ./src/allmydata/immutable/filenode.py 9
710-from allmydata.interfaces import IImmutableFileNode, IUploadResults
711 from allmydata import uri
712hunk ./src/allmydata/immutable/filenode.py 10
713+from twisted.internet.interfaces import IConsumer
714+from twisted.protocols import basic
715+from foolscap.api import eventually
716+from allmydata.interfaces import IImmutableFileNode, ICheckable, \
717+     IDownloadTarget, IUploadResults
718+from allmydata.util import dictutil, log, base32, consumer
719+from allmydata.immutable.checker import Checker
720 from allmydata.check_results import CheckResults, CheckAndRepairResults
721 from allmydata.util.dictutil import DictOfSets
722 from pycryptopp.cipher.aes import AES
723hunk ./src/allmydata/immutable/filenode.py 285
724         return self._cnode.check_and_repair(monitor, verify, add_lease)
725     def check(self, monitor, verify=False, add_lease=False):
726         return self._cnode.check(monitor, verify, add_lease)
727+
728+    def get_best_readable_version(self):
729+        """
730+        Return an IReadable of the best version of this file. Since
731+        immutable files can have only one version, we just return the
732+        current filenode.
733+        """
734+        return defer.succeed(self)
735+
736+
737+    def download_best_version(self):
738+        """
739+        Download the best version of this file, returning its contents
740+        as a bytestring. Since there is only one version of an immutable
741+        file, we download and return the contents of this file.
742+        """
743+        d = consumer.download_to_data(self)
744+        return d
745+
746+    # for an immutable file, download_to_data (specified in IReadable)
747+    # is the same as download_best_version (specified in IFileNode). For
748+    # mutable files, the difference is more meaningful, since they can
749+    # have multiple versions.
750+    download_to_data = download_best_version
751+
752+
753+    # get_size() (IReadable), get_current_size() (IFilesystemNode), and
754+    # get_size_of_best_version(IFileNode) are all the same for immutable
755+    # files.
756+    get_size_of_best_version = get_current_size
757}
758[immutable/literal.py: implement the same interfaces as other filenodes
759Kevan Carstensen <kevan@isnotajoke.com>**20100810000633
760 Ignore-this: b50dd5df2d34ecd6477b8499a27aef13
761] hunk ./src/allmydata/immutable/literal.py 106
762         d.addCallback(lambda lastSent: consumer)
763         return d
764 
765+    # IReadable, IFileNode, IFilesystemNode
766+    def get_best_readable_version(self):
767+        return defer.succeed(self)
768+
769+
770+    def download_best_version(self):
771+        return defer.succeed(self.u.data)
772+
773+
774+    download_to_data = download_best_version
775+    get_size_of_best_version = get_current_size
776+
777[scripts: tell 'tahoe put' about MDMF
778Kevan Carstensen <kevan@isnotajoke.com>**20100813234957
779 Ignore-this: c106b3384fc676bd3c0fb466d2a52b1b
780] {
781hunk ./src/allmydata/scripts/cli.py 167
782     optFlags = [
783         ("mutable", "m", "Create a mutable file instead of an immutable one."),
784         ]
785+    optParameters = [
786+        ("mutable-type", None, False, "Create a mutable file in the given format. Valid formats are 'sdmf' for SDMF and 'mdmf' for MDMF"),
787+        ]
788 
789     def parseArgs(self, arg1=None, arg2=None):
790         # see Examples below
791hunk ./src/allmydata/scripts/tahoe_put.py 21
792     from_file = options.from_file
793     to_file = options.to_file
794     mutable = options['mutable']
795+    mutable_type = False
796+
797+    if mutable:
798+        mutable_type = options['mutable-type']
799     if options['quiet']:
800         verbosity = 0
801     else:
802hunk ./src/allmydata/scripts/tahoe_put.py 33
803     stdout = options.stdout
804     stderr = options.stderr
805 
806+    if mutable_type and mutable_type not in ('sdmf', 'mdmf'):
807+        # Don't try to pass unsupported types to the webapi
808+        print >>stderr, "error: %s is an invalid format" % mutable_type
809+        return 1
810+
811     if nodeurl[-1] != "/":
812         nodeurl += "/"
813     if to_file:
814hunk ./src/allmydata/scripts/tahoe_put.py 76
815         url = nodeurl + "uri"
816     if mutable:
817         url += "?mutable=true"
818+    if mutable_type:
819+        assert mutable
820+        url += "&mutable-type=%s" % mutable_type
821+
822     if from_file:
823         infileobj = open(os.path.expanduser(from_file), "rb")
824     else:
825}
826[web: Alter the webapi to get along with and take advantage of the MDMF changes
827Kevan Carstensen <kevan@isnotajoke.com>**20100814081012
828 Ignore-this: 96c2ed4e4a9f450fb84db5d711d10bd6
829 
830 The main benefit that the webapi gets from MDMF, at least initially, is
831 the ability to do a streaming download of an MDMF mutable file. It also
832 exposes a way (through the PUT verb) to append to or otherwise modify
833 (in-place) an MDMF mutable file.
834] {
835hunk ./src/allmydata/web/common.py 12
836 from allmydata.interfaces import ExistingChildError, NoSuchChildError, \
837      FileTooLargeError, NotEnoughSharesError, NoSharesError, \
838      EmptyPathnameComponentError, MustBeDeepImmutableError, \
839-     MustBeReadonlyError, MustNotBeUnknownRWError
840+     MustBeReadonlyError, MustNotBeUnknownRWError, SDMF_VERSION, MDMF_VERSION
841 from allmydata.mutable.common import UnrecoverableFileError
842 from allmydata.util import abbreviate
843 from allmydata.util.encodingutil import to_str, quote_output
844hunk ./src/allmydata/web/common.py 35
845     else:
846         return boolean_of_arg(replace)
847 
848+
849+def parse_mutable_type_arg(arg):
850+    if not arg:
851+        return None # interpreted by the caller as "let the nodemaker decide"
852+
853+    arg = arg.lower()
854+    assert arg in ("mdmf", "sdmf")
855+
856+    if arg == "mdmf":
857+        return MDMF_VERSION
858+
859+    return SDMF_VERSION
860+
861+
862+def parse_offset_arg(offset):
863+    # XXX: This will raise a ValueError when invoked on something that
864+    # is not an integer. Is that okay? Or do we want a better error
865+    # message? Since this call is going to be used by programmers and
866+    # their tools rather than users (through the wui), it is not
867+    # inconsistent to return that, I guess.
868+    offset = int(offset)
869+    return offset
870+
871+
872 def get_root(ctx_or_req):
873     req = IRequest(ctx_or_req)
874     # the addSlash=True gives us one extra (empty) segment
875hunk ./src/allmydata/web/directory.py 19
876 from allmydata.uri import from_string_dirnode
877 from allmydata.interfaces import IDirectoryNode, IFileNode, IFilesystemNode, \
878      IImmutableFileNode, IMutableFileNode, ExistingChildError, \
879-     NoSuchChildError, EmptyPathnameComponentError
880+     NoSuchChildError, EmptyPathnameComponentError, SDMF_VERSION, MDMF_VERSION
881 from allmydata.monitor import Monitor, OperationCancelledError
882 from allmydata import dirnode
883 from allmydata.web.common import text_plain, WebError, \
884hunk ./src/allmydata/web/directory.py 153
885         if not t:
886             # render the directory as HTML, using the docFactory and Nevow's
887             # whole templating thing.
888-            return DirectoryAsHTML(self.node)
889+            return DirectoryAsHTML(self.node,
890+                                   self.client.mutable_file_default)
891 
892         if t == "json":
893             return DirectoryJSONMetadata(ctx, self.node)
894hunk ./src/allmydata/web/directory.py 556
895     docFactory = getxmlfile("directory.xhtml")
896     addSlash = True
897 
898-    def __init__(self, node):
899+    def __init__(self, node, default_mutable_format):
900         rend.Page.__init__(self)
901         self.node = node
902 
903hunk ./src/allmydata/web/directory.py 560
904+        assert default_mutable_format in (MDMF_VERSION, SDMF_VERSION)
905+        self.default_mutable_format = default_mutable_format
906+
907     def beforeRender(self, ctx):
908         # attempt to get the dirnode's children, stashing them (or the
909         # failure that results) for later use
910hunk ./src/allmydata/web/directory.py 780
911             ]]
912         forms.append(T.div(class_="freeform-form")[mkdir])
913 
914+        # Build input elements for mutable file type. We do this outside
915+        # of the list so we can check the appropriate format, based on
916+        # the default configured in the client (which reflects the
917+        # default configured in tahoe.cfg)
918+        if self.default_mutable_format == MDMF_VERSION:
919+            mdmf_input = T.input(type='radio', name='mutable-type',
920+                                 id='mutable-type-mdmf', value='mdmf',
921+                                 checked='checked')
922+        else:
923+            mdmf_input = T.input(type='radio', name='mutable-type',
924+                                 id='mutable-type-mdmf', value='mdmf')
925+
926+        if self.default_mutable_format == SDMF_VERSION:
927+            sdmf_input = T.input(type='radio', name='mutable-type',
928+                                 id='mutable-type-sdmf', value='sdmf',
929+                                 checked="checked")
930+        else:
931+            sdmf_input = T.input(type='radio', name='mutable-type',
932+                                 id='mutable-type-sdmf', value='sdmf')
933+
934         upload = T.form(action=".", method="post",
935                         enctype="multipart/form-data")[
936             T.fieldset[
937hunk ./src/allmydata/web/directory.py 812
938             T.input(type="submit", value="Upload"),
939             " Mutable?:",
940             T.input(type="checkbox", name="mutable"),
941+            sdmf_input, T.label(for_="mutable-type-sdmf")["SDMF"],
942+            mdmf_input,
943+            T.label(for_="mutable-type-mdmf")["MDMF (experimental)"],
944             ]]
945         forms.append(T.div(class_="freeform-form")[upload])
946 
947hunk ./src/allmydata/web/directory.py 850
948                 kiddata = ("filenode", {'size': childnode.get_size(),
949                                         'mutable': childnode.is_mutable(),
950                                         })
951+                if childnode.is_mutable() and \
952+                    childnode.get_version() is not None:
953+                    mutable_type = childnode.get_version()
954+                    assert mutable_type in (SDMF_VERSION, MDMF_VERSION)
955+
956+                    if mutable_type == MDMF_VERSION:
957+                        mutable_type = "mdmf"
958+                    else:
959+                        mutable_type = "sdmf"
960+                    kiddata[1]['mutable-type'] = mutable_type
961+
962             elif IDirectoryNode.providedBy(childnode):
963                 kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
964             else:
965hunk ./src/allmydata/web/filenode.py 9
966 from nevow import url, rend
967 from nevow.inevow import IRequest
968 
969-from allmydata.interfaces import ExistingChildError
970+from allmydata.interfaces import ExistingChildError, SDMF_VERSION, MDMF_VERSION
971 from allmydata.monitor import Monitor
972 from allmydata.immutable.upload import FileHandle
973hunk ./src/allmydata/web/filenode.py 12
974+from allmydata.mutable.publish import MutableFileHandle
975+from allmydata.mutable.common import MODE_READ
976 from allmydata.util import log, base32
977 
978 from allmydata.web.common import text_plain, WebError, RenderMixin, \
979hunk ./src/allmydata/web/filenode.py 18
980      boolean_of_arg, get_arg, should_create_intermediate_directories, \
981-     MyExceptionHandler, parse_replace_arg
982+     MyExceptionHandler, parse_replace_arg, parse_offset_arg, \
983+     parse_mutable_type_arg
984 from allmydata.web.check_results import CheckResults, \
985      CheckAndRepairResults, LiteralCheckResults
986 from allmydata.web.info import MoreInfo
987hunk ./src/allmydata/web/filenode.py 29
988         # a new file is being uploaded in our place.
989         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
990         if mutable:
991-            req.content.seek(0)
992-            data = req.content.read()
993-            d = client.create_mutable_file(data)
994+            mutable_type = parse_mutable_type_arg(get_arg(req,
995+                                                          "mutable-type",
996+                                                          None))
997+            data = MutableFileHandle(req.content)
998+            d = client.create_mutable_file(data, version=mutable_type)
999             def _uploaded(newnode):
1000                 d2 = self.parentnode.set_node(self.name, newnode,
1001                                               overwrite=replace)
1002hunk ./src/allmydata/web/filenode.py 66
1003         d.addCallback(lambda res: childnode.get_uri())
1004         return d
1005 
1006-    def _read_data_from_formpost(self, req):
1007-        # SDMF: files are small, and we can only upload data, so we read
1008-        # the whole file into memory before uploading.
1009-        contents = req.fields["file"]
1010-        contents.file.seek(0)
1011-        data = contents.file.read()
1012-        return data
1013 
1014     def replace_me_with_a_formpost(self, req, client, replace):
1015         # create a new file, maybe mutable, maybe immutable
1016hunk ./src/allmydata/web/filenode.py 71
1017         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
1018 
1019+        # create an immutable file
1020+        contents = req.fields["file"]
1021         if mutable:
1022hunk ./src/allmydata/web/filenode.py 74
1023-            data = self._read_data_from_formpost(req)
1024-            d = client.create_mutable_file(data)
1025+            mutable_type = parse_mutable_type_arg(get_arg(req, "mutable-type",
1026+                                                          None))
1027+            uploadable = MutableFileHandle(contents.file)
1028+            d = client.create_mutable_file(uploadable, version=mutable_type)
1029             def _uploaded(newnode):
1030                 d2 = self.parentnode.set_node(self.name, newnode,
1031                                               overwrite=replace)
1032hunk ./src/allmydata/web/filenode.py 85
1033                 return d2
1034             d.addCallback(_uploaded)
1035             return d
1036-        # create an immutable file
1037-        contents = req.fields["file"]
1038+
1039         uploadable = FileHandle(contents.file, convergence=client.convergence)
1040         d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
1041         d.addCallback(lambda newnode: newnode.get_uri())
1042hunk ./src/allmydata/web/filenode.py 91
1043         return d
1044 
1045+
1046 class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
1047     def __init__(self, client, parentnode, name):
1048         rend.Page.__init__(self)
1049hunk ./src/allmydata/web/filenode.py 174
1050             # properly. So we assume that at least the browser will agree
1051             # with itself, and echo back the same bytes that we were given.
1052             filename = get_arg(req, "filename", self.name) or "unknown"
1053-            if self.node.is_mutable():
1054-                # some day: d = self.node.get_best_version()
1055-                d = makeMutableDownloadable(self.node)
1056-            else:
1057-                d = defer.succeed(self.node)
1058+            d = self.node.get_best_readable_version()
1059             d.addCallback(lambda dn: FileDownloader(dn, filename))
1060             return d
1061         if t == "json":
1062hunk ./src/allmydata/web/filenode.py 178
1063-            if self.parentnode and self.name:
1064-                d = self.parentnode.get_metadata_for(self.name)
1065+            # We do this to make sure that fields like size and
1066+            # mutable-type (which depend on the file on the grid and not
1067+            # just on the cap) are filled in. The latter gets used in
1068+            # tests, in particular.
1069+            #
1070+            # TODO: Make it so that the servermap knows how to update in
1071+            # a mode specifically designed to fill in these fields, and
1072+            # then update it in that mode.
1073+            if self.node.is_mutable():
1074+                d = self.node.get_servermap(MODE_READ)
1075             else:
1076                 d = defer.succeed(None)
1077hunk ./src/allmydata/web/filenode.py 190
1078+            if self.parentnode and self.name:
1079+                d.addCallback(lambda ignored:
1080+                    self.parentnode.get_metadata_for(self.name))
1081+            else:
1082+                d.addCallback(lambda ignored: None)
1083             d.addCallback(lambda md: FileJSONMetadata(ctx, self.node, md))
1084             return d
1085         if t == "info":
1086hunk ./src/allmydata/web/filenode.py 211
1087         if t:
1088             raise WebError("GET file: bad t=%s" % t)
1089         filename = get_arg(req, "filename", self.name) or "unknown"
1090-        if self.node.is_mutable():
1091-            # some day: d = self.node.get_best_version()
1092-            d = makeMutableDownloadable(self.node)
1093-        else:
1094-            d = defer.succeed(self.node)
1095+        d = self.node.get_best_readable_version()
1096         d.addCallback(lambda dn: FileDownloader(dn, filename))
1097         return d
1098 
1099hunk ./src/allmydata/web/filenode.py 219
1100         req = IRequest(ctx)
1101         t = get_arg(req, "t", "").strip()
1102         replace = parse_replace_arg(get_arg(req, "replace", "true"))
1103+        offset = parse_offset_arg(get_arg(req, "offset", -1))
1104 
1105         if not t:
1106hunk ./src/allmydata/web/filenode.py 222
1107-            if self.node.is_mutable():
1108+            if self.node.is_mutable() and offset >= 0:
1109+                return self.update_my_contents(req, offset)
1110+
1111+            elif self.node.is_mutable():
1112                 return self.replace_my_contents(req)
1113             if not replace:
1114                 # this is the early trap: if someone else modifies the
1115hunk ./src/allmydata/web/filenode.py 232
1116                 # directory while we're uploading, the add_file(overwrite=)
1117                 # call in replace_me_with_a_child will do the late trap.
1118                 raise ExistingChildError()
1119+            if offset >= 0:
1120+                raise WebError("PUT to a file: append operation invoked "
1121+                               "on an immutable cap")
1122+
1123+
1124             assert self.parentnode and self.name
1125             return self.replace_me_with_a_child(req, self.client, replace)
1126         if t == "uri":
1127hunk ./src/allmydata/web/filenode.py 299
1128 
1129     def replace_my_contents(self, req):
1130         req.content.seek(0)
1131-        new_contents = req.content.read()
1132+        new_contents = MutableFileHandle(req.content)
1133         d = self.node.overwrite(new_contents)
1134         d.addCallback(lambda res: self.node.get_uri())
1135         return d
1136hunk ./src/allmydata/web/filenode.py 304
1137 
1138+
1139+    def update_my_contents(self, req, offset):
1140+        req.content.seek(0)
1141+        added_contents = MutableFileHandle(req.content)
1142+
1143+        d = self.node.get_best_mutable_version()
1144+        d.addCallback(lambda mv:
1145+            mv.update(added_contents, offset))
1146+        d.addCallback(lambda ignored:
1147+            self.node.get_uri())
1148+        return d
1149+
1150+
1151     def replace_my_contents_with_a_formpost(self, req):
1152         # we have a mutable file. Get the data from the formpost, and replace
1153         # the mutable file's contents with it.
1154hunk ./src/allmydata/web/filenode.py 320
1155-        new_contents = self._read_data_from_formpost(req)
1156+        new_contents = req.fields['file']
1157+        new_contents = MutableFileHandle(new_contents.file)
1158+
1159         d = self.node.overwrite(new_contents)
1160         d.addCallback(lambda res: self.node.get_uri())
1161         return d
1162hunk ./src/allmydata/web/filenode.py 327
1163 
1164-class MutableDownloadable:
1165-    #implements(IDownloadable)
1166-    def __init__(self, size, node):
1167-        self.size = size
1168-        self.node = node
1169-    def get_size(self):
1170-        return self.size
1171-    def is_mutable(self):
1172-        return True
1173-    def read(self, consumer, offset=0, size=None):
1174-        d = self.node.download_best_version()
1175-        d.addCallback(self._got_data, consumer, offset, size)
1176-        return d
1177-    def _got_data(self, contents, consumer, offset, size):
1178-        start = offset
1179-        if size is not None:
1180-            end = offset+size
1181-        else:
1182-            end = self.size
1183-        # SDMF: we can write the whole file in one big chunk
1184-        consumer.write(contents[start:end])
1185-        return consumer
1186-
1187-def makeMutableDownloadable(n):
1188-    d = defer.maybeDeferred(n.get_size_of_best_version)
1189-    d.addCallback(MutableDownloadable, n)
1190-    return d
1191 
1192 class FileDownloader(rend.Page):
1193     # since we override the rendering process (to let the tahoe Downloader
1194hunk ./src/allmydata/web/filenode.py 509
1195     data[1]['mutable'] = filenode.is_mutable()
1196     if edge_metadata is not None:
1197         data[1]['metadata'] = edge_metadata
1198+
1199+    if filenode.is_mutable() and filenode.get_version() is not None:
1200+        mutable_type = filenode.get_version()
1201+        assert mutable_type in (MDMF_VERSION, SDMF_VERSION)
1202+        if mutable_type == MDMF_VERSION:
1203+            mutable_type = "mdmf"
1204+        else:
1205+            mutable_type = "sdmf"
1206+        data[1]['mutable-type'] = mutable_type
1207+
1208     return text_plain(simplejson.dumps(data, indent=1) + "\n", ctx)
1209 
1210 def FileURI(ctx, filenode):
1211hunk ./src/allmydata/web/root.py 15
1212 from allmydata import get_package_versions_string
1213 from allmydata import provisioning
1214 from allmydata.util import idlib, log
1215-from allmydata.interfaces import IFileNode
1216+from allmydata.interfaces import IFileNode, MDMF_VERSION, SDMF_VERSION
1217 from allmydata.web import filenode, directory, unlinked, status, operations
1218 from allmydata.web import reliability, storage
1219 from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
1220hunk ./src/allmydata/web/root.py 19
1221-     get_arg, RenderMixin, boolean_of_arg
1222+     get_arg, RenderMixin, boolean_of_arg, parse_mutable_type_arg
1223 
1224 
1225 class URIHandler(RenderMixin, rend.Page):
1226hunk ./src/allmydata/web/root.py 50
1227         if t == "":
1228             mutable = boolean_of_arg(get_arg(req, "mutable", "false").strip())
1229             if mutable:
1230-                return unlinked.PUTUnlinkedSSK(req, self.client)
1231+                version = parse_mutable_type_arg(get_arg(req, "mutable-type",
1232+                                                 None))
1233+                return unlinked.PUTUnlinkedSSK(req, self.client, version)
1234             else:
1235                 return unlinked.PUTUnlinkedCHK(req, self.client)
1236         if t == "mkdir":
1237hunk ./src/allmydata/web/root.py 70
1238         if t in ("", "upload"):
1239             mutable = bool(get_arg(req, "mutable", "").strip())
1240             if mutable:
1241-                return unlinked.POSTUnlinkedSSK(req, self.client)
1242+                version = parse_mutable_type_arg(get_arg(req, "mutable-type",
1243+                                                         None))
1244+                return unlinked.POSTUnlinkedSSK(req, self.client, version)
1245             else:
1246                 return unlinked.POSTUnlinkedCHK(req, self.client)
1247         if t == "mkdir":
1248hunk ./src/allmydata/web/root.py 329
1249 
1250     def render_upload_form(self, ctx, data):
1251         # this is a form where users can upload unlinked files
1252+        #
1253+        # for mutable files, users can choose the format by selecting
1254+        # MDMF or SDMF from a radio button. They can also configure a
1255+        # default format in tahoe.cfg, which they rightly expect us to
1256+        # obey. we convey to them that we are obeying their choice by
1257+        # ensuring that the one that they've chosen is selected in the
1258+        # interface.
1259+        if self.client.mutable_file_default == MDMF_VERSION:
1260+            mdmf_input = T.input(type='radio', name='mutable-type',
1261+                                 value='mdmf', id='mutable-type-mdmf',
1262+                                 checked='checked')
1263+        else:
1264+            mdmf_input = T.input(type='radio', name='mutable-type',
1265+                                 value='mdmf', id='mutable-type-mdmf')
1266+
1267+        if self.client.mutable_file_default == SDMF_VERSION:
1268+            sdmf_input = T.input(type='radio', name='mutable-type',
1269+                                 value='sdmf', id='mutable-type-sdmf',
1270+                                 checked='checked')
1271+        else:
1272+            sdmf_input = T.input(type='radio', name='mutable-type',
1273+                                 value='sdmf', id='mutable-type-sdmf')
1274+
1275+
1276         form = T.form(action="uri", method="post",
1277                       enctype="multipart/form-data")[
1278             T.fieldset[
1279hunk ./src/allmydata/web/root.py 361
1280                   T.input(type="file", name="file", class_="freeform-input-file")],
1281             T.input(type="hidden", name="t", value="upload"),
1282             T.div[T.input(type="checkbox", name="mutable"), T.label(for_="mutable")["Create mutable file"],
1283+                  sdmf_input, T.label(for_="mutable-type-sdmf")["SDMF"],
1284+                  mdmf_input,
1285+                  T.label(for_='mutable-type-mdmf')['MDMF (experimental)'],
1286                   " ", T.input(type="submit", value="Upload!")],
1287             ]]
1288         return T.div[form]
1289hunk ./src/allmydata/web/unlinked.py 7
1290 from twisted.internet import defer
1291 from nevow import rend, url, tags as T
1292 from allmydata.immutable.upload import FileHandle
1293+from allmydata.mutable.publish import MutableFileHandle
1294 from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
1295      convert_children_json, WebError
1296 from allmydata.web import status
1297hunk ./src/allmydata/web/unlinked.py 20
1298     # that fires with the URI of the new file
1299     return d
1300 
1301-def PUTUnlinkedSSK(req, client):
1302+def PUTUnlinkedSSK(req, client, version):
1303     # SDMF: files are small, and we can only upload data
1304     req.content.seek(0)
1305hunk ./src/allmydata/web/unlinked.py 23
1306-    data = req.content.read()
1307-    d = client.create_mutable_file(data)
1308+    data = MutableFileHandle(req.content)
1309+    d = client.create_mutable_file(data, version=version)
1310     d.addCallback(lambda n: n.get_uri())
1311     return d
1312 
1313hunk ./src/allmydata/web/unlinked.py 83
1314                       ["/uri/" + res.uri])
1315         return d
1316 
1317-def POSTUnlinkedSSK(req, client):
1318+def POSTUnlinkedSSK(req, client, version):
1319     # "POST /uri", to create an unlinked file.
1320     # SDMF: files are small, and we can only upload data
1321hunk ./src/allmydata/web/unlinked.py 86
1322-    contents = req.fields["file"]
1323-    contents.file.seek(0)
1324-    data = contents.file.read()
1325-    d = client.create_mutable_file(data)
1326+    contents = req.fields["file"].file
1327+    data = MutableFileHandle(contents)
1328+    d = client.create_mutable_file(data, version=version)
1329     d.addCallback(lambda n: n.get_uri())
1330     return d
1331 
1332}
1333[client.py: learn how to create different kinds of mutable files
1334Kevan Carstensen <kevan@isnotajoke.com>**20100814225711
1335 Ignore-this: 61ff665bc050cba5f58bf2ed779d692b
1336] {
1337hunk ./src/allmydata/client.py 25
1338 from allmydata.util.time_format import parse_duration, parse_date
1339 from allmydata.stats import StatsProvider
1340 from allmydata.history import History
1341-from allmydata.interfaces import IStatsProducer, RIStubClient
1342+from allmydata.interfaces import IStatsProducer, RIStubClient, \
1343+                                 SDMF_VERSION, MDMF_VERSION
1344 from allmydata.nodemaker import NodeMaker
1345 
1346 
1347hunk ./src/allmydata/client.py 357
1348                                    self.terminator,
1349                                    self.get_encoding_parameters(),
1350                                    self._key_generator)
1351+        default = self.get_config("client", "mutable.format", default="sdmf")
1352+        if default == "mdmf":
1353+            self.mutable_file_default = MDMF_VERSION
1354+        else:
1355+            self.mutable_file_default = SDMF_VERSION
1356 
1357     def get_history(self):
1358         return self.history
1359hunk ./src/allmydata/client.py 500
1360     def create_immutable_dirnode(self, children, convergence=None):
1361         return self.nodemaker.create_immutable_directory(children, convergence)
1362 
1363-    def create_mutable_file(self, contents=None, keysize=None):
1364-        return self.nodemaker.create_mutable_file(contents, keysize)
1365+    def create_mutable_file(self, contents=None, keysize=None, version=None):
1366+        if not version:
1367+            version = self.mutable_file_default
1368+        return self.nodemaker.create_mutable_file(contents, keysize,
1369+                                                  version=version)
1370 
1371     def upload(self, uploadable):
1372         uploader = self.getServiceNamed("uploader")
1373}
1374[mutable/checker.py and mutable/repair.py: Modify checker and repairer to work with MDMF
1375Kevan Carstensen <kevan@isnotajoke.com>**20100819003216
1376 Ignore-this: d3bd3260742be8964877f0a53543b01b
1377 
1378 The checker and repairer required minimal changes to work with the MDMF
1379 modifications made elsewhere. The checker duplicated a lot of the code
1380 that was already in the downloader, so I modified the downloader
1381 slightly to expose this functionality to the checker and removed the
1382 duplicated code. The repairer only required a minor change to deal with
1383 data representation.
1384] {
1385hunk ./src/allmydata/mutable/checker.py 2
1386 
1387-from twisted.internet import defer
1388-from twisted.python import failure
1389-from allmydata import hashtree
1390 from allmydata.uri import from_string
1391hunk ./src/allmydata/mutable/checker.py 3
1392-from allmydata.util import hashutil, base32, idlib, log
1393+from allmydata.util import base32, idlib, log
1394 from allmydata.check_results import CheckAndRepairResults, CheckResults
1395 
1396 from allmydata.mutable.common import MODE_CHECK, CorruptShareError
1397hunk ./src/allmydata/mutable/checker.py 8
1398 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
1399-from allmydata.mutable.layout import unpack_share, SIGNED_PREFIX_LENGTH
1400+from allmydata.mutable.retrieve import Retrieve # for verifying
1401 
1402 class MutableChecker:
1403 
1404hunk ./src/allmydata/mutable/checker.py 25
1405 
1406     def check(self, verify=False, add_lease=False):
1407         servermap = ServerMap()
1408+        # Updating the servermap in MODE_CHECK will stand a good chance
1409+        # of finding all of the shares, and getting a good idea of
1410+        # recoverability, etc, without verifying.
1411         u = ServermapUpdater(self._node, self._storage_broker, self._monitor,
1412                              servermap, MODE_CHECK, add_lease=add_lease)
1413         if self._history:
1414hunk ./src/allmydata/mutable/checker.py 51
1415         if num_recoverable:
1416             self.best_version = servermap.best_recoverable_version()
1417 
1418+        # The file is unhealthy and needs to be repaired if:
1419+        # - There are unrecoverable versions.
1420         if servermap.unrecoverable_versions():
1421             self.need_repair = True
1422hunk ./src/allmydata/mutable/checker.py 55
1423+        # - There isn't a recoverable version.
1424         if num_recoverable != 1:
1425             self.need_repair = True
1426hunk ./src/allmydata/mutable/checker.py 58
1427+        # - The best recoverable version is missing some shares.
1428         if self.best_version:
1429             available_shares = servermap.shares_available()
1430             (num_distinct_shares, k, N) = available_shares[self.best_version]
1431hunk ./src/allmydata/mutable/checker.py 69
1432 
1433     def _verify_all_shares(self, servermap):
1434         # read every byte of each share
1435+        #
1436+        # This logic is going to be very nearly the same as the
1437+        # downloader. I bet we could pass the downloader a flag that
1438+        # makes it do this, and piggyback onto that instead of
1439+        # duplicating a bunch of code.
1440+        #
1441+        # Like:
1442+        #  r = Retrieve(blah, blah, blah, verify=True)
1443+        #  d = r.download()
1444+        #  (wait, wait, wait, d.callback)
1445+        # 
1446+        #  Then, when it has finished, we can check the servermap (which
1447+        #  we provided to Retrieve) to figure out which shares are bad,
1448+        #  since the Retrieve process will have updated the servermap as
1449+        #  it went along.
1450+        #
1451+        #  By passing the verify=True flag to the constructor, we are
1452+        #  telling the downloader a few things.
1453+        #
1454+        #  1. It needs to download all N shares, not just K shares.
1455+        #  2. It doesn't need to decrypt or decode the shares, only
1456+        #     verify them.
1457         if not self.best_version:
1458             return
1459hunk ./src/allmydata/mutable/checker.py 93
1460-        versionmap = servermap.make_versionmap()
1461-        shares = versionmap[self.best_version]
1462-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1463-         offsets_tuple) = self.best_version
1464-        offsets = dict(offsets_tuple)
1465-        readv = [ (0, offsets["EOF"]) ]
1466-        dl = []
1467-        for (shnum, peerid, timestamp) in shares:
1468-            ss = servermap.connections[peerid]
1469-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
1470-            d.addCallback(self._got_answer, peerid, servermap)
1471-            dl.append(d)
1472-        return defer.DeferredList(dl, fireOnOneErrback=True, consumeErrors=True)
1473 
1474hunk ./src/allmydata/mutable/checker.py 94
1475-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
1476-        # isolate the callRemote to a separate method, so tests can subclass
1477-        # Publish and override it
1478-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
1479+        r = Retrieve(self._node, servermap, self.best_version, verify=True)
1480+        d = r.download()
1481+        d.addCallback(self._process_bad_shares)
1482         return d
1483 
1484hunk ./src/allmydata/mutable/checker.py 99
1485-    def _got_answer(self, datavs, peerid, servermap):
1486-        for shnum,datav in datavs.items():
1487-            data = datav[0]
1488-            try:
1489-                self._got_results_one_share(shnum, peerid, data)
1490-            except CorruptShareError:
1491-                f = failure.Failure()
1492-                self.need_repair = True
1493-                self.bad_shares.append( (peerid, shnum, f) )
1494-                prefix = data[:SIGNED_PREFIX_LENGTH]
1495-                servermap.mark_bad_share(peerid, shnum, prefix)
1496-                ss = servermap.connections[peerid]
1497-                self.notify_server_corruption(ss, shnum, str(f.value))
1498-
1499-    def check_prefix(self, peerid, shnum, data):
1500-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1501-         offsets_tuple) = self.best_version
1502-        got_prefix = data[:SIGNED_PREFIX_LENGTH]
1503-        if got_prefix != prefix:
1504-            raise CorruptShareError(peerid, shnum,
1505-                                    "prefix mismatch: share changed while we were reading it")
1506-
1507-    def _got_results_one_share(self, shnum, peerid, data):
1508-        self.check_prefix(peerid, shnum, data)
1509-
1510-        # the [seqnum:signature] pieces are validated by _compare_prefix,
1511-        # which checks their signature against the pubkey known to be
1512-        # associated with this file.
1513 
1514hunk ./src/allmydata/mutable/checker.py 100
1515-        (seqnum, root_hash, IV, k, N, segsize, datalen, pubkey, signature,
1516-         share_hash_chain, block_hash_tree, share_data,
1517-         enc_privkey) = unpack_share(data)
1518-
1519-        # validate [share_hash_chain,block_hash_tree,share_data]
1520-
1521-        leaves = [hashutil.block_hash(share_data)]
1522-        t = hashtree.HashTree(leaves)
1523-        if list(t) != block_hash_tree:
1524-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
1525-        share_hash_leaf = t[0]
1526-        t2 = hashtree.IncompleteHashTree(N)
1527-        # root_hash was checked by the signature
1528-        t2.set_hashes({0: root_hash})
1529-        try:
1530-            t2.set_hashes(hashes=share_hash_chain,
1531-                          leaves={shnum: share_hash_leaf})
1532-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
1533-                IndexError), e:
1534-            msg = "corrupt hashes: %s" % (e,)
1535-            raise CorruptShareError(peerid, shnum, msg)
1536-
1537-        # validate enc_privkey: only possible if we have a write-cap
1538-        if not self._node.is_readonly():
1539-            alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
1540-            alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
1541-            if alleged_writekey != self._node.get_writekey():
1542-                raise CorruptShareError(peerid, shnum, "invalid privkey")
1543+    def _process_bad_shares(self, bad_shares):
1544+        if bad_shares:
1545+            self.need_repair = True
1546+        self.bad_shares = bad_shares
1547 
1548hunk ./src/allmydata/mutable/checker.py 105
1549-    def notify_server_corruption(self, ss, shnum, reason):
1550-        ss.callRemoteOnly("advise_corrupt_share",
1551-                          "mutable", self._storage_index, shnum, reason)
1552 
1553     def _count_shares(self, smap, version):
1554         available_shares = smap.shares_available()
1555hunk ./src/allmydata/mutable/repairer.py 5
1556 from zope.interface import implements
1557 from twisted.internet import defer
1558 from allmydata.interfaces import IRepairResults, ICheckResults
1559+from allmydata.mutable.publish import MutableData
1560 
1561 class RepairResults:
1562     implements(IRepairResults)
1563hunk ./src/allmydata/mutable/repairer.py 108
1564             raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.")
1565 
1566         d = self.node.download_version(smap, best_version, fetch_privkey=True)
1567+        d.addCallback(lambda data:
1568+            MutableData(data))
1569         d.addCallback(self.node.upload, smap)
1570         d.addCallback(self.get_results, smap)
1571         return d
1572}
1573[mutable/filenode.py: add versions and partial-file updates to the mutable file node
1574Kevan Carstensen <kevan@isnotajoke.com>**20100819003231
1575 Ignore-this: b7b5434201fdb9b48f902d7ab25ef45c
1576 
1577 One of the goals of MDMF as a GSoC project is to lay the groundwork for
1578 LDMF, a format that will allow Tahoe-LAFS to deal with and encourage
1579 multiple versions of a single cap on the grid. In line with this, there
1580 is a now a distinction between an overriding mutable file (which can be
1581 thought to correspond to the cap/unique identifier for that mutable
1582 file) and versions of the mutable file (which we can download, update,
1583 and so on). All download, upload, and modification operations end up
1584 happening on a particular version of a mutable file, but there are
1585 shortcut methods on the object representing the overriding mutable file
1586 that perform these operations on the best version of the mutable file
1587 (which is what code should be doing until we have LDMF and better
1588 support for other paradigms).
1589 
1590 Another goal of MDMF was to take advantage of segmentation to give
1591 callers more efficient partial file updates or appends. This patch
1592 implements methods that do that, too.
1593 
1594] {
1595hunk ./src/allmydata/mutable/filenode.py 7
1596 from zope.interface import implements
1597 from twisted.internet import defer, reactor
1598 from foolscap.api import eventually
1599-from allmydata.interfaces import IMutableFileNode, \
1600-     ICheckable, ICheckResults, NotEnoughSharesError
1601-from allmydata.util import hashutil, log
1602+from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
1603+     NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
1604+     IMutableFileVersion, IWritable
1605+from allmydata.util import hashutil, log, consumer, deferredutil, mathutil
1606 from allmydata.util.assertutil import precondition
1607 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
1608 from allmydata.monitor import Monitor
1609hunk ./src/allmydata/mutable/filenode.py 16
1610 from pycryptopp.cipher.aes import AES
1611 
1612-from allmydata.mutable.publish import Publish
1613+from allmydata.mutable.publish import Publish, MutableData,\
1614+                                      DEFAULT_MAX_SEGMENT_SIZE, \
1615+                                      TransformingUploadable
1616 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
1617      ResponseCache, UncoordinatedWriteError
1618 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
1619hunk ./src/allmydata/mutable/filenode.py 70
1620         self._sharemap = {} # known shares, shnum-to-[nodeids]
1621         self._cache = ResponseCache()
1622         self._most_recent_size = None
1623+        # filled in after __init__ if we're being created for the first time;
1624+        # filled in by the servermap updater before publishing, otherwise.
1625+        # set to this default value in case neither of those things happen,
1626+        # or in case the servermap can't find any shares to tell us what
1627+        # to publish as.
1628+        # TODO: Set this back to None, and find out why the tests fail
1629+        #       with it set to None.
1630+        self._protocol_version = None
1631 
1632         # all users of this MutableFileNode go through the serializer. This
1633         # takes advantage of the fact that Deferreds discard the callbacks
1634hunk ./src/allmydata/mutable/filenode.py 134
1635         return self._upload(initial_contents, None)
1636 
1637     def _get_initial_contents(self, contents):
1638-        if isinstance(contents, str):
1639-            return contents
1640         if contents is None:
1641hunk ./src/allmydata/mutable/filenode.py 135
1642-            return ""
1643+            return MutableData("")
1644+
1645+        if IMutableUploadable.providedBy(contents):
1646+            return contents
1647+
1648         assert callable(contents), "%s should be callable, not %s" % \
1649                (contents, type(contents))
1650         return contents(self)
1651hunk ./src/allmydata/mutable/filenode.py 209
1652 
1653     def get_size(self):
1654         return self._most_recent_size
1655+
1656     def get_current_size(self):
1657         d = self.get_size_of_best_version()
1658         d.addCallback(self._stash_size)
1659hunk ./src/allmydata/mutable/filenode.py 214
1660         return d
1661+
1662     def _stash_size(self, size):
1663         self._most_recent_size = size
1664         return size
1665hunk ./src/allmydata/mutable/filenode.py 273
1666             return cmp(self.__class__, them.__class__)
1667         return cmp(self._uri, them._uri)
1668 
1669-    def _do_serialized(self, cb, *args, **kwargs):
1670-        # note: to avoid deadlock, this callable is *not* allowed to invoke
1671-        # other serialized methods within this (or any other)
1672-        # MutableFileNode. The callable should be a bound method of this same
1673-        # MFN instance.
1674-        d = defer.Deferred()
1675-        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
1676-        # we need to put off d.callback until this Deferred is finished being
1677-        # processed. Otherwise the caller's subsequent activities (like,
1678-        # doing other things with this node) can cause reentrancy problems in
1679-        # the Deferred code itself
1680-        self._serializer.addBoth(lambda res: eventually(d.callback, res))
1681-        # add a log.err just in case something really weird happens, because
1682-        # self._serializer stays around forever, therefore we won't see the
1683-        # usual Unhandled Error in Deferred that would give us a hint.
1684-        self._serializer.addErrback(log.err)
1685-        return d
1686 
1687     #################################
1688     # ICheckable
1689hunk ./src/allmydata/mutable/filenode.py 298
1690 
1691 
1692     #################################
1693-    # IMutableFileNode
1694+    # IFileNode
1695+
1696+    def get_best_readable_version(self):
1697+        """
1698+        I return a Deferred that fires with a MutableFileVersion
1699+        representing the best readable version of the file that I
1700+        represent
1701+        """
1702+        return self.get_readable_version()
1703+
1704+
1705+    def get_readable_version(self, servermap=None, version=None):
1706+        """
1707+        I return a Deferred that fires with an MutableFileVersion for my
1708+        version argument, if there is a recoverable file of that version
1709+        on the grid. If there is no recoverable version, I fire with an
1710+        UnrecoverableFileError.
1711+
1712+        If a servermap is provided, I look in there for the requested
1713+        version. If no servermap is provided, I create and update a new
1714+        one.
1715+
1716+        If no version is provided, then I return a MutableFileVersion
1717+        representing the best recoverable version of the file.
1718+        """
1719+        d = self._get_version_from_servermap(MODE_READ, servermap, version)
1720+        def _build_version((servermap, their_version)):
1721+            assert their_version in servermap.recoverable_versions()
1722+            assert their_version in servermap.make_versionmap()
1723+
1724+            mfv = MutableFileVersion(self,
1725+                                     servermap,
1726+                                     their_version,
1727+                                     self._storage_index,
1728+                                     self._storage_broker,
1729+                                     self._readkey,
1730+                                     history=self._history)
1731+            assert mfv.is_readonly()
1732+            # our caller can use this to download the contents of the
1733+            # mutable file.
1734+            return mfv
1735+        return d.addCallback(_build_version)
1736+
1737+
1738+    def _get_version_from_servermap(self,
1739+                                    mode,
1740+                                    servermap=None,
1741+                                    version=None):
1742+        """
1743+        I return a Deferred that fires with (servermap, version).
1744+
1745+        This function performs validation and a servermap update. If it
1746+        returns (servermap, version), the caller can assume that:
1747+            - servermap was last updated in mode.
1748+            - version is recoverable, and corresponds to the servermap.
1749+
1750+        If version and servermap are provided to me, I will validate
1751+        that version exists in the servermap, and that the servermap was
1752+        updated correctly.
1753+
1754+        If version is not provided, but servermap is, I will validate
1755+        the servermap and return the best recoverable version that I can
1756+        find in the servermap.
1757+
1758+        If the version is provided but the servermap isn't, I will
1759+        obtain a servermap that has been updated in the correct mode and
1760+        validate that version is found and recoverable.
1761+
1762+        If neither servermap nor version are provided, I will obtain a
1763+        servermap updated in the correct mode, and return the best
1764+        recoverable version that I can find in there.
1765+        """
1766+        # XXX: wording ^^^^
1767+        if servermap and servermap.last_update_mode == mode:
1768+            d = defer.succeed(servermap)
1769+        else:
1770+            d = self._get_servermap(mode)
1771+
1772+        def _get_version(servermap, v):
1773+            if v and v not in servermap.recoverable_versions():
1774+                v = None
1775+            elif not v:
1776+                v = servermap.best_recoverable_version()
1777+            if not v:
1778+                raise UnrecoverableFileError("no recoverable versions")
1779+
1780+            return (servermap, v)
1781+        return d.addCallback(_get_version, version)
1782+
1783 
1784     def download_best_version(self):
1785hunk ./src/allmydata/mutable/filenode.py 389
1786+        """
1787+        I return a Deferred that fires with the contents of the best
1788+        version of this mutable file.
1789+        """
1790         return self._do_serialized(self._download_best_version)
1791hunk ./src/allmydata/mutable/filenode.py 394
1792+
1793+
1794     def _download_best_version(self):
1795hunk ./src/allmydata/mutable/filenode.py 397
1796-        servermap = ServerMap()
1797-        d = self._try_once_to_download_best_version(servermap, MODE_READ)
1798-        def _maybe_retry(f):
1799-            f.trap(NotEnoughSharesError)
1800-            # the download is worth retrying once. Make sure to use the
1801-            # old servermap, since it is what remembers the bad shares,
1802-            # but use MODE_WRITE to make it look for even more shares.
1803-            # TODO: consider allowing this to retry multiple times.. this
1804-            # approach will let us tolerate about 8 bad shares, I think.
1805-            return self._try_once_to_download_best_version(servermap,
1806-                                                           MODE_WRITE)
1807+        """
1808+        I am the serialized sibling of download_best_version.
1809+        """
1810+        d = self.get_best_readable_version()
1811+        d.addCallback(self._record_size)
1812+        d.addCallback(lambda version: version.download_to_data())
1813+
1814+        # It is possible that the download will fail because there
1815+        # aren't enough shares to be had. If so, we will try again after
1816+        # updating the servermap in MODE_WRITE, which may find more
1817+        # shares than updating in MODE_READ, as we just did. We can do
1818+        # this by getting the best mutable version and downloading from
1819+        # that -- the best mutable version will be a MutableFileVersion
1820+        # with a servermap that was last updated in MODE_WRITE, as we
1821+        # want. If this fails, then we give up.
1822+        def _maybe_retry(failure):
1823+            failure.trap(NotEnoughSharesError)
1824+
1825+            d = self.get_best_mutable_version()
1826+            d.addCallback(self._record_size)
1827+            d.addCallback(lambda version: version.download_to_data())
1828+            return d
1829+
1830         d.addErrback(_maybe_retry)
1831         return d
1832hunk ./src/allmydata/mutable/filenode.py 422
1833-    def _try_once_to_download_best_version(self, servermap, mode):
1834-        d = self._update_servermap(servermap, mode)
1835-        d.addCallback(self._once_updated_download_best_version, servermap)
1836-        return d
1837-    def _once_updated_download_best_version(self, ignored, servermap):
1838-        goal = servermap.best_recoverable_version()
1839-        if not goal:
1840-            raise UnrecoverableFileError("no recoverable versions")
1841-        return self._try_once_to_download_version(servermap, goal)
1842+
1843+
1844+    def _record_size(self, mfv):
1845+        """
1846+        I record the size of a mutable file version.
1847+        """
1848+        self._most_recent_size = mfv.get_size()
1849+        return mfv
1850+
1851 
1852     def get_size_of_best_version(self):
1853hunk ./src/allmydata/mutable/filenode.py 433
1854-        d = self.get_servermap(MODE_READ)
1855-        def _got_servermap(smap):
1856-            ver = smap.best_recoverable_version()
1857-            if not ver:
1858-                raise UnrecoverableFileError("no recoverable version")
1859-            return smap.size_of_version(ver)
1860-        d.addCallback(_got_servermap)
1861-        return d
1862+        """
1863+        I return the size of the best version of this mutable file.
1864 
1865hunk ./src/allmydata/mutable/filenode.py 436
1866+        This is equivalent to calling get_size() on the result of
1867+        get_best_readable_version().
1868+        """
1869+        d = self.get_best_readable_version()
1870+        return d.addCallback(lambda mfv: mfv.get_size())
1871+
1872+
1873+    #################################
1874+    # IMutableFileNode
1875+
1876+    def get_best_mutable_version(self, servermap=None):
1877+        """
1878+        I return a Deferred that fires with a MutableFileVersion
1879+        representing the best readable version of the file that I
1880+        represent. I am like get_best_readable_version, except that I
1881+        will try to make a writable version if I can.
1882+        """
1883+        return self.get_mutable_version(servermap=servermap)
1884+
1885+
1886+    def get_mutable_version(self, servermap=None, version=None):
1887+        """
1888+        I return a version of this mutable file. I return a Deferred
1889+        that fires with a MutableFileVersion
1890+
1891+        If version is provided, the Deferred will fire with a
1892+        MutableFileVersion initailized with that version. Otherwise, it
1893+        will fire with the best version that I can recover.
1894+
1895+        If servermap is provided, I will use that to find versions
1896+        instead of performing my own servermap update.
1897+        """
1898+        if self.is_readonly():
1899+            return self.get_readable_version(servermap=servermap,
1900+                                             version=version)
1901+
1902+        # get_mutable_version => write intent, so we require that the
1903+        # servermap is updated in MODE_WRITE
1904+        d = self._get_version_from_servermap(MODE_WRITE, servermap, version)
1905+        def _build_version((servermap, smap_version)):
1906+            # these should have been set by the servermap update.
1907+            assert self._secret_holder
1908+            assert self._writekey
1909+
1910+            mfv = MutableFileVersion(self,
1911+                                     servermap,
1912+                                     smap_version,
1913+                                     self._storage_index,
1914+                                     self._storage_broker,
1915+                                     self._readkey,
1916+                                     self._writekey,
1917+                                     self._secret_holder,
1918+                                     history=self._history)
1919+            assert not mfv.is_readonly()
1920+            return mfv
1921+
1922+        return d.addCallback(_build_version)
1923+
1924+
1925+    # XXX: I'm uncomfortable with the difference between upload and
1926+    #      overwrite, which, FWICT, is basically that you don't have to
1927+    #      do a servermap update before you overwrite. We split them up
1928+    #      that way anyway, so I guess there's no real difficulty in
1929+    #      offering both ways to callers, but it also makes the
1930+    #      public-facing API cluttery, and makes it hard to discern the
1931+    #      right way of doing things.
1932+
1933+    # In general, we leave it to callers to ensure that they aren't
1934+    # going to cause UncoordinatedWriteErrors when working with
1935+    # MutableFileVersions. We know that the next three operations
1936+    # (upload, overwrite, and modify) will all operate on the same
1937+    # version, so we say that only one of them can be going on at once,
1938+    # and serialize them to ensure that that actually happens, since as
1939+    # the caller in this situation it is our job to do that.
1940     def overwrite(self, new_contents):
1941hunk ./src/allmydata/mutable/filenode.py 511
1942+        """
1943+        I overwrite the contents of the best recoverable version of this
1944+        mutable file with new_contents. This is equivalent to calling
1945+        overwrite on the result of get_best_mutable_version with
1946+        new_contents as an argument. I return a Deferred that eventually
1947+        fires with the results of my replacement process.
1948+        """
1949         return self._do_serialized(self._overwrite, new_contents)
1950hunk ./src/allmydata/mutable/filenode.py 519
1951+
1952+
1953     def _overwrite(self, new_contents):
1954hunk ./src/allmydata/mutable/filenode.py 522
1955+        """
1956+        I am the serialized sibling of overwrite.
1957+        """
1958+        d = self.get_best_mutable_version()
1959+        d.addCallback(lambda mfv: mfv.overwrite(new_contents))
1960+        d.addCallback(self._did_upload, new_contents.get_size())
1961+        return d
1962+
1963+
1964+
1965+    def upload(self, new_contents, servermap):
1966+        """
1967+        I overwrite the contents of the best recoverable version of this
1968+        mutable file with new_contents, using servermap instead of
1969+        creating/updating our own servermap. I return a Deferred that
1970+        fires with the results of my upload.
1971+        """
1972+        return self._do_serialized(self._upload, new_contents, servermap)
1973+
1974+
1975+    def modify(self, modifier, backoffer=None):
1976+        """
1977+        I modify the contents of the best recoverable version of this
1978+        mutable file with the modifier. This is equivalent to calling
1979+        modify on the result of get_best_mutable_version. I return a
1980+        Deferred that eventually fires with an UploadResults instance
1981+        describing this process.
1982+        """
1983+        return self._do_serialized(self._modify, modifier, backoffer)
1984+
1985+
1986+    def _modify(self, modifier, backoffer):
1987+        """
1988+        I am the serialized sibling of modify.
1989+        """
1990+        d = self.get_best_mutable_version()
1991+        d.addCallback(lambda mfv: mfv.modify(modifier, backoffer))
1992+        return d
1993+
1994+
1995+    def download_version(self, servermap, version, fetch_privkey=False):
1996+        """
1997+        Download the specified version of this mutable file. I return a
1998+        Deferred that fires with the contents of the specified version
1999+        as a bytestring, or errbacks if the file is not recoverable.
2000+        """
2001+        d = self.get_readable_version(servermap, version)
2002+        return d.addCallback(lambda mfv: mfv.download_to_data(fetch_privkey))
2003+
2004+
2005+    def get_servermap(self, mode):
2006+        """
2007+        I return a servermap that has been updated in mode.
2008+
2009+        mode should be one of MODE_READ, MODE_WRITE, MODE_CHECK or
2010+        MODE_ANYTHING. See servermap.py for more on what these mean.
2011+        """
2012+        return self._do_serialized(self._get_servermap, mode)
2013+
2014+
2015+    def _get_servermap(self, mode):
2016+        """
2017+        I am a serialized twin to get_servermap.
2018+        """
2019         servermap = ServerMap()
2020hunk ./src/allmydata/mutable/filenode.py 587
2021-        d = self._update_servermap(servermap, mode=MODE_WRITE)
2022-        d.addCallback(lambda ignored: self._upload(new_contents, servermap))
2023+        d = self._update_servermap(servermap, mode)
2024+        # The servermap will tell us about the most recent size of the
2025+        # file, so we may as well set that so that callers might get
2026+        # more data about us.
2027+        if not self._most_recent_size:
2028+            d.addCallback(self._get_size_from_servermap)
2029+        return d
2030+
2031+
2032+    def _get_size_from_servermap(self, servermap):
2033+        """
2034+        I extract the size of the best version of this file and record
2035+        it in self._most_recent_size. I return the servermap that I was
2036+        given.
2037+        """
2038+        if servermap.recoverable_versions():
2039+            v = servermap.best_recoverable_version()
2040+            size = v[4] # verinfo[4] == size
2041+            self._most_recent_size = size
2042+        return servermap
2043+
2044+
2045+    def _update_servermap(self, servermap, mode):
2046+        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
2047+                             mode)
2048+        if self._history:
2049+            self._history.notify_mapupdate(u.get_status())
2050+        return u.update()
2051+
2052+
2053+    def set_version(self, version):
2054+        # I can be set in two ways:
2055+        #  1. When the node is created.
2056+        #  2. (for an existing share) when the Servermap is updated
2057+        #     before I am read.
2058+        assert version in (MDMF_VERSION, SDMF_VERSION)
2059+        self._protocol_version = version
2060+
2061+
2062+    def get_version(self):
2063+        return self._protocol_version
2064+
2065+
2066+    def _do_serialized(self, cb, *args, **kwargs):
2067+        # note: to avoid deadlock, this callable is *not* allowed to invoke
2068+        # other serialized methods within this (or any other)
2069+        # MutableFileNode. The callable should be a bound method of this same
2070+        # MFN instance.
2071+        d = defer.Deferred()
2072+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
2073+        # we need to put off d.callback until this Deferred is finished being
2074+        # processed. Otherwise the caller's subsequent activities (like,
2075+        # doing other things with this node) can cause reentrancy problems in
2076+        # the Deferred code itself
2077+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
2078+        # add a log.err just in case something really weird happens, because
2079+        # self._serializer stays around forever, therefore we won't see the
2080+        # usual Unhandled Error in Deferred that would give us a hint.
2081+        self._serializer.addErrback(log.err)
2082         return d
2083 
2084 
2085hunk ./src/allmydata/mutable/filenode.py 649
2086+    def _upload(self, new_contents, servermap):
2087+        """
2088+        A MutableFileNode still has to have some way of getting
2089+        published initially, which is what I am here for. After that,
2090+        all publishing, updating, modifying and so on happens through
2091+        MutableFileVersions.
2092+        """
2093+        assert self._pubkey, "update_servermap must be called before publish"
2094+
2095+        p = Publish(self, self._storage_broker, servermap)
2096+        if self._history:
2097+            self._history.notify_publish(p.get_status(),
2098+                                         new_contents.get_size())
2099+        d = p.publish(new_contents)
2100+        d.addCallback(self._did_upload, new_contents.get_size())
2101+        return d
2102+
2103+
2104+    def _did_upload(self, res, size):
2105+        self._most_recent_size = size
2106+        return res
2107+
2108+
2109+class MutableFileVersion:
2110+    """
2111+    I represent a specific version (most likely the best version) of a
2112+    mutable file.
2113+
2114+    Since I implement IReadable, instances which hold a
2115+    reference to an instance of me are guaranteed the ability (absent
2116+    connection difficulties or unrecoverable versions) to read the file
2117+    that I represent. Depending on whether I was initialized with a
2118+    write capability or not, I may also provide callers the ability to
2119+    overwrite or modify the contents of the mutable file that I
2120+    reference.
2121+    """
2122+    implements(IMutableFileVersion, IWritable)
2123+
2124+    def __init__(self,
2125+                 node,
2126+                 servermap,
2127+                 version,
2128+                 storage_index,
2129+                 storage_broker,
2130+                 readcap,
2131+                 writekey=None,
2132+                 write_secrets=None,
2133+                 history=None):
2134+
2135+        self._node = node
2136+        self._servermap = servermap
2137+        self._version = version
2138+        self._storage_index = storage_index
2139+        self._write_secrets = write_secrets
2140+        self._history = history
2141+        self._storage_broker = storage_broker
2142+
2143+        #assert isinstance(readcap, IURI)
2144+        self._readcap = readcap
2145+
2146+        self._writekey = writekey
2147+        self._serializer = defer.succeed(None)
2148+
2149+
2150+    def get_sequence_number(self):
2151+        """
2152+        Get the sequence number of the mutable version that I represent.
2153+        """
2154+        return self._version[0] # verinfo[0] == the sequence number
2155+
2156+
2157+    # TODO: Terminology?
2158+    def get_writekey(self):
2159+        """
2160+        I return a writekey or None if I don't have a writekey.
2161+        """
2162+        return self._writekey
2163+
2164+
2165+    def overwrite(self, new_contents):
2166+        """
2167+        I overwrite the contents of this mutable file version with the
2168+        data in new_contents.
2169+        """
2170+        assert not self.is_readonly()
2171+
2172+        return self._do_serialized(self._overwrite, new_contents)
2173+
2174+
2175+    def _overwrite(self, new_contents):
2176+        assert IMutableUploadable.providedBy(new_contents)
2177+        assert self._servermap.last_update_mode == MODE_WRITE
2178+
2179+        return self._upload(new_contents)
2180+
2181+
2182     def modify(self, modifier, backoffer=None):
2183         """I use a modifier callback to apply a change to the mutable file.
2184         I implement the following pseudocode::
2185hunk ./src/allmydata/mutable/filenode.py 785
2186         backoffer should not invoke any methods on this MutableFileNode
2187         instance, and it needs to be highly conscious of deadlock issues.
2188         """
2189+        assert not self.is_readonly()
2190+
2191         return self._do_serialized(self._modify, modifier, backoffer)
2192hunk ./src/allmydata/mutable/filenode.py 788
2193+
2194+
2195     def _modify(self, modifier, backoffer):
2196hunk ./src/allmydata/mutable/filenode.py 791
2197-        servermap = ServerMap()
2198         if backoffer is None:
2199             backoffer = BackoffAgent().delay
2200hunk ./src/allmydata/mutable/filenode.py 793
2201-        return self._modify_and_retry(servermap, modifier, backoffer, True)
2202-    def _modify_and_retry(self, servermap, modifier, backoffer, first_time):
2203-        d = self._modify_once(servermap, modifier, first_time)
2204+        return self._modify_and_retry(modifier, backoffer, True)
2205+
2206+
2207+    def _modify_and_retry(self, modifier, backoffer, first_time):
2208+        """
2209+        I try to apply modifier to the contents of this version of the
2210+        mutable file. If I succeed, I return an UploadResults instance
2211+        describing my success. If I fail, I try again after waiting for
2212+        a little bit.
2213+        """
2214+        log.msg("doing modify")
2215+        d = self._modify_once(modifier, first_time)
2216         def _retry(f):
2217             f.trap(UncoordinatedWriteError)
2218             d2 = defer.maybeDeferred(backoffer, self, f)
2219hunk ./src/allmydata/mutable/filenode.py 809
2220             d2.addCallback(lambda ignored:
2221-                           self._modify_and_retry(servermap, modifier,
2222+                           self._modify_and_retry(modifier,
2223                                                   backoffer, False))
2224             return d2
2225         d.addErrback(_retry)
2226hunk ./src/allmydata/mutable/filenode.py 814
2227         return d
2228-    def _modify_once(self, servermap, modifier, first_time):
2229-        d = self._update_servermap(servermap, MODE_WRITE)
2230-        d.addCallback(self._once_updated_download_best_version, servermap)
2231+
2232+
2233+    def _modify_once(self, modifier, first_time):
2234+        """
2235+        I attempt to apply a modifier to the contents of the mutable
2236+        file.
2237+        """
2238+        # XXX: This is wrong -- we could get more servers if we updated
2239+        # in MODE_ANYTHING and possibly MODE_CHECK. Probably we want to
2240+        # assert that the last update wasn't MODE_READ
2241+        assert self._servermap.last_update_mode == MODE_WRITE
2242+
2243+        # download_to_data is serialized, so we have to call this to
2244+        # avoid deadlock.
2245+        d = self._try_to_download_data()
2246         def _apply(old_contents):
2247hunk ./src/allmydata/mutable/filenode.py 830
2248-            new_contents = modifier(old_contents, servermap, first_time)
2249+            new_contents = modifier(old_contents, self._servermap, first_time)
2250+            precondition((isinstance(new_contents, str) or
2251+                          new_contents is None),
2252+                         "Modifier function must return a string "
2253+                         "or None")
2254+
2255             if new_contents is None or new_contents == old_contents:
2256hunk ./src/allmydata/mutable/filenode.py 837
2257+                log.msg("no changes")
2258                 # no changes need to be made
2259                 if first_time:
2260                     return
2261hunk ./src/allmydata/mutable/filenode.py 845
2262                 # recovery when it observes UCWE, we need to do a second
2263                 # publish. See #551 for details. We'll basically loop until
2264                 # we managed an uncontested publish.
2265-                new_contents = old_contents
2266-            precondition(isinstance(new_contents, str),
2267-                         "Modifier function must return a string or None")
2268-            return self._upload(new_contents, servermap)
2269+                old_uploadable = MutableData(old_contents)
2270+                new_contents = old_uploadable
2271+            else:
2272+                new_contents = MutableData(new_contents)
2273+
2274+            return self._upload(new_contents)
2275         d.addCallback(_apply)
2276         return d
2277 
2278hunk ./src/allmydata/mutable/filenode.py 854
2279-    def get_servermap(self, mode):
2280-        return self._do_serialized(self._get_servermap, mode)
2281-    def _get_servermap(self, mode):
2282-        servermap = ServerMap()
2283-        return self._update_servermap(servermap, mode)
2284-    def _update_servermap(self, servermap, mode):
2285-        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
2286-                             mode)
2287-        if self._history:
2288-            self._history.notify_mapupdate(u.get_status())
2289-        return u.update()
2290 
2291hunk ./src/allmydata/mutable/filenode.py 855
2292-    def download_version(self, servermap, version, fetch_privkey=False):
2293-        return self._do_serialized(self._try_once_to_download_version,
2294-                                   servermap, version, fetch_privkey)
2295-    def _try_once_to_download_version(self, servermap, version,
2296-                                      fetch_privkey=False):
2297-        r = Retrieve(self, servermap, version, fetch_privkey)
2298+    def is_readonly(self):
2299+        """
2300+        I return True if this MutableFileVersion provides no write
2301+        access to the file that it encapsulates, and False if it
2302+        provides the ability to modify the file.
2303+        """
2304+        return self._writekey is None
2305+
2306+
2307+    def is_mutable(self):
2308+        """
2309+        I return True, since mutable files are always mutable by
2310+        somebody.
2311+        """
2312+        return True
2313+
2314+
2315+    def get_storage_index(self):
2316+        """
2317+        I return the storage index of the reference that I encapsulate.
2318+        """
2319+        return self._storage_index
2320+
2321+
2322+    def get_size(self):
2323+        """
2324+        I return the length, in bytes, of this readable object.
2325+        """
2326+        return self._servermap.size_of_version(self._version)
2327+
2328+
2329+    def download_to_data(self, fetch_privkey=False):
2330+        """
2331+        I return a Deferred that fires with the contents of this
2332+        readable object as a byte string.
2333+
2334+        """
2335+        c = consumer.MemoryConsumer()
2336+        d = self.read(c, fetch_privkey=fetch_privkey)
2337+        d.addCallback(lambda mc: "".join(mc.chunks))
2338+        return d
2339+
2340+
2341+    def _try_to_download_data(self):
2342+        """
2343+        I am an unserialized cousin of download_to_data; I am called
2344+        from the children of modify() to download the data associated
2345+        with this mutable version.
2346+        """
2347+        c = consumer.MemoryConsumer()
2348+        # modify will almost certainly write, so we need the privkey.
2349+        d = self._read(c, fetch_privkey=True)
2350+        d.addCallback(lambda mc: "".join(mc.chunks))
2351+        return d
2352+
2353+
2354+    def read(self, consumer, offset=0, size=None, fetch_privkey=False):
2355+        """
2356+        I read a portion (possibly all) of the mutable file that I
2357+        reference into consumer.
2358+        """
2359+        return self._do_serialized(self._read, consumer, offset, size,
2360+                                   fetch_privkey)
2361+
2362+
2363+    def _read(self, consumer, offset=0, size=None, fetch_privkey=False):
2364+        """
2365+        I am the serialized companion of read.
2366+        """
2367+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
2368         if self._history:
2369             self._history.notify_retrieve(r.get_status())
2370hunk ./src/allmydata/mutable/filenode.py 927
2371-        d = r.download()
2372-        d.addCallback(self._downloaded_version)
2373+        d = r.download(consumer, offset, size)
2374         return d
2375hunk ./src/allmydata/mutable/filenode.py 929
2376-    def _downloaded_version(self, data):
2377-        self._most_recent_size = len(data)
2378-        return data
2379 
2380hunk ./src/allmydata/mutable/filenode.py 930
2381-    def upload(self, new_contents, servermap):
2382-        return self._do_serialized(self._upload, new_contents, servermap)
2383-    def _upload(self, new_contents, servermap):
2384-        assert self._pubkey, "update_servermap must be called before publish"
2385-        p = Publish(self, self._storage_broker, servermap)
2386+
2387+    def _do_serialized(self, cb, *args, **kwargs):
2388+        # note: to avoid deadlock, this callable is *not* allowed to invoke
2389+        # other serialized methods within this (or any other)
2390+        # MutableFileNode. The callable should be a bound method of this same
2391+        # MFN instance.
2392+        d = defer.Deferred()
2393+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
2394+        # we need to put off d.callback until this Deferred is finished being
2395+        # processed. Otherwise the caller's subsequent activities (like,
2396+        # doing other things with this node) can cause reentrancy problems in
2397+        # the Deferred code itself
2398+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
2399+        # add a log.err just in case something really weird happens, because
2400+        # self._serializer stays around forever, therefore we won't see the
2401+        # usual Unhandled Error in Deferred that would give us a hint.
2402+        self._serializer.addErrback(log.err)
2403+        return d
2404+
2405+
2406+    def _upload(self, new_contents):
2407+        #assert self._pubkey, "update_servermap must be called before publish"
2408+        p = Publish(self._node, self._storage_broker, self._servermap)
2409         if self._history:
2410hunk ./src/allmydata/mutable/filenode.py 954
2411-            self._history.notify_publish(p.get_status(), len(new_contents))
2412+            self._history.notify_publish(p.get_status(),
2413+                                         new_contents.get_size())
2414         d = p.publish(new_contents)
2415hunk ./src/allmydata/mutable/filenode.py 957
2416-        d.addCallback(self._did_upload, len(new_contents))
2417+        d.addCallback(self._did_upload, new_contents.get_size())
2418         return d
2419hunk ./src/allmydata/mutable/filenode.py 959
2420+
2421+
2422     def _did_upload(self, res, size):
2423         self._most_recent_size = size
2424         return res
2425hunk ./src/allmydata/mutable/filenode.py 964
2426+
2427+    def update(self, data, offset):
2428+        """
2429+        Do an update of this mutable file version by inserting data at
2430+        offset within the file. If offset is the EOF, this is an append
2431+        operation. I return a Deferred that fires with the results of
2432+        the update operation when it has completed.
2433+
2434+        In cases where update does not append any data, or where it does
2435+        not append so many blocks that the block count crosses a
2436+        power-of-two boundary, this operation will use roughly
2437+        O(data.get_size()) memory/bandwidth/CPU to perform the update.
2438+        Otherwise, it must download, re-encode, and upload the entire
2439+        file again, which will use O(filesize) resources.
2440+        """
2441+        return self._do_serialized(self._update, data, offset)
2442+
2443+
2444+    def _update(self, data, offset):
2445+        """
2446+        I update the mutable file version represented by this particular
2447+        IMutableVersion by inserting the data in data at the offset
2448+        offset. I return a Deferred that fires when this has been
2449+        completed.
2450+        """
2451+        # We have two cases here:
2452+        # 1. The new data will add few enough segments so that it does
2453+        #    not cross into the next power-of-two boundary.
2454+        # 2. It doesn't.
2455+        #
2456+        # In the former case, we can modify the file in place. In the
2457+        # latter case, we need to re-encode the file.
2458+        new_size = data.get_size() + offset
2459+        old_size = self.get_size()
2460+        segment_size = self._version[3]
2461+        num_old_segments = mathutil.div_ceil(old_size,
2462+                                             segment_size)
2463+        num_new_segments = mathutil.div_ceil(new_size,
2464+                                             segment_size)
2465+        log.msg("got %d old segments, %d new segments" % \
2466+                        (num_old_segments, num_new_segments))
2467+
2468+        # We also do a whole file re-encode if the file is an SDMF file.
2469+        if self._version[2]: # version[2] == SDMF salt, which MDMF lacks
2470+            log.msg("doing re-encode instead of in-place update")
2471+            return self._do_modify_update(data, offset)
2472+
2473+        log.msg("updating in place")
2474+        d = self._do_update_update(data, offset)
2475+        d.addCallback(self._decode_and_decrypt_segments, data, offset)
2476+        d.addCallback(self._build_uploadable_and_finish, data, offset)
2477+        return d
2478+
2479+
2480+    def _do_modify_update(self, data, offset):
2481+        """
2482+        I perform a file update by modifying the contents of the file
2483+        after downloading it, then reuploading it. I am less efficient
2484+        than _do_update_update, but am necessary for certain updates.
2485+        """
2486+        def m(old, servermap, first_time):
2487+            start = offset
2488+            rest = offset + data.get_size()
2489+            new = old[:start]
2490+            new += "".join(data.read(data.get_size()))
2491+            new += old[rest:]
2492+            return new
2493+        return self._modify(m, None)
2494+
2495+
2496+    def _do_update_update(self, data, offset):
2497+        """
2498+        I start the Servermap update that gets us the data we need to
2499+        continue the update process. I return a Deferred that fires when
2500+        the servermap update is done.
2501+        """
2502+        assert IMutableUploadable.providedBy(data)
2503+        assert self.is_mutable()
2504+        # offset == self.get_size() is valid and means that we are
2505+        # appending data to the file.
2506+        assert offset <= self.get_size()
2507+
2508+        # We'll need the segment that the data starts in, regardless of
2509+        # what we'll do later.
2510+        start_segment = mathutil.div_ceil(offset, DEFAULT_MAX_SEGMENT_SIZE)
2511+        start_segment -= 1
2512+
2513+        # We only need the end segment if the data we append does not go
2514+        # beyond the current end-of-file.
2515+        end_segment = start_segment
2516+        if offset + data.get_size() < self.get_size():
2517+            end_data = offset + data.get_size()
2518+            end_segment = mathutil.div_ceil(end_data, DEFAULT_MAX_SEGMENT_SIZE)
2519+            end_segment -= 1
2520+        self._start_segment = start_segment
2521+        self._end_segment = end_segment
2522+
2523+        # Now ask for the servermap to be updated in MODE_WRITE with
2524+        # this update range.
2525+        u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
2526+                             self._servermap,
2527+                             mode=MODE_WRITE,
2528+                             update_range=(start_segment, end_segment))
2529+        return u.update()
2530+
2531+
2532+    def _decode_and_decrypt_segments(self, ignored, data, offset):
2533+        """
2534+        After the servermap update, I take the encrypted and encoded
2535+        data that the servermap fetched while doing its update and
2536+        transform it into decoded-and-decrypted plaintext that can be
2537+        used by the new uploadable. I return a Deferred that fires with
2538+        the segments.
2539+        """
2540+        r = Retrieve(self._node, self._servermap, self._version)
2541+        # decode: takes in our blocks and salts from the servermap,
2542+        # returns a Deferred that fires with the corresponding plaintext
2543+        # segments. Does not download -- simply takes advantage of
2544+        # existing infrastructure within the Retrieve class to avoid
2545+        # duplicating code.
2546+        sm = self._servermap
2547+        # XXX: If the methods in the servermap don't work as
2548+        # abstractions, you should rewrite them instead of going around
2549+        # them.
2550+        update_data = sm.update_data
2551+        start_segments = {} # shnum -> start segment
2552+        end_segments = {} # shnum -> end segment
2553+        blockhashes = {} # shnum -> blockhash tree
2554+        for (shnum, data) in update_data.iteritems():
2555+            data = [d[1] for d in data if d[0] == self._version]
2556+
2557+            # Every data entry in our list should now be share shnum for
2558+            # a particular version of the mutable file, so all of the
2559+            # entries should be identical.
2560+            datum = data[0]
2561+            assert filter(lambda x: x != datum, data) == []
2562+
2563+            blockhashes[shnum] = datum[0]
2564+            start_segments[shnum] = datum[1]
2565+            end_segments[shnum] = datum[2]
2566+
2567+        d1 = r.decode(start_segments, self._start_segment)
2568+        d2 = r.decode(end_segments, self._end_segment)
2569+        d3 = defer.succeed(blockhashes)
2570+        return deferredutil.gatherResults([d1, d2, d3])
2571+
2572+
2573+    def _build_uploadable_and_finish(self, segments_and_bht, data, offset):
2574+        """
2575+        After the process has the plaintext segments, I build the
2576+        TransformingUploadable that the publisher will eventually
2577+        re-upload to the grid. I then invoke the publisher with that
2578+        uploadable, and return a Deferred when the publish operation has
2579+        completed without issue.
2580+        """
2581+        u = TransformingUploadable(data, offset,
2582+                                   self._version[3],
2583+                                   segments_and_bht[0],
2584+                                   segments_and_bht[1])
2585+        p = Publish(self._node, self._storage_broker, self._servermap)
2586+        return p.update(u, offset, segments_and_bht[2], self._version)
2587}
2588[mutable/publish.py: Modify the publish process to support MDMF
2589Kevan Carstensen <kevan@isnotajoke.com>**20100819003342
2590 Ignore-this: 2bb379974927e2e20cff75bae8302d1d
2591 
2592 The inner workings of the publishing process needed to be reworked to a
2593 large extend to cope with segmented mutable files, and to cope with
2594 partial-file updates of mutable files. This patch does that. It also
2595 introduces wrappers for uploadable data, allowing the use of
2596 filehandle-like objects as data sources, in addition to strings. This
2597 reduces memory inefficiency when dealing with large files through the
2598 webapi, and clarifies update code there.
2599] {
2600hunk ./src/allmydata/mutable/publish.py 3
2601 
2602 
2603-import os, struct, time
2604+import os, time
2605+from StringIO import StringIO
2606 from itertools import count
2607 from zope.interface import implements
2608 from twisted.internet import defer
2609hunk ./src/allmydata/mutable/publish.py 9
2610 from twisted.python import failure
2611-from allmydata.interfaces import IPublishStatus
2612+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \
2613+                                 IMutableUploadable
2614 from allmydata.util import base32, hashutil, mathutil, idlib, log
2615 from allmydata.util.dictutil import DictOfSets
2616 from allmydata import hashtree, codec
2617hunk ./src/allmydata/mutable/publish.py 21
2618 from allmydata.mutable.common import MODE_WRITE, MODE_CHECK, \
2619      UncoordinatedWriteError, NotEnoughServersError
2620 from allmydata.mutable.servermap import ServerMap
2621-from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
2622-     unpack_checkstring, SIGNED_PREFIX
2623+from allmydata.mutable.layout import unpack_checkstring, MDMFSlotWriteProxy, \
2624+                                     SDMFSlotWriteProxy
2625+
2626+KiB = 1024
2627+DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
2628+PUSHING_BLOCKS_STATE = 0
2629+PUSHING_EVERYTHING_ELSE_STATE = 1
2630+DONE_STATE = 2
2631 
2632 class PublishStatus:
2633     implements(IPublishStatus)
2634hunk ./src/allmydata/mutable/publish.py 118
2635         self._status.set_helper(False)
2636         self._status.set_progress(0.0)
2637         self._status.set_active(True)
2638+        self._version = self._node.get_version()
2639+        assert self._version in (SDMF_VERSION, MDMF_VERSION)
2640+
2641 
2642     def get_status(self):
2643         return self._status
2644hunk ./src/allmydata/mutable/publish.py 132
2645             kwargs["facility"] = "tahoe.mutable.publish"
2646         return log.msg(*args, **kwargs)
2647 
2648+
2649+    def update(self, data, offset, blockhashes, version):
2650+        """
2651+        I replace the contents of this file with the contents of data,
2652+        starting at offset. I return a Deferred that fires with None
2653+        when the replacement has been completed, or with an error if
2654+        something went wrong during the process.
2655+
2656+        Note that this process will not upload new shares. If the file
2657+        being updated is in need of repair, callers will have to repair
2658+        it on their own.
2659+        """
2660+        # How this works:
2661+        # 1: Make peer assignments. We'll assign each share that we know
2662+        # about on the grid to that peer that currently holds that
2663+        # share, and will not place any new shares.
2664+        # 2: Setup encoding parameters. Most of these will stay the same
2665+        # -- datalength will change, as will some of the offsets.
2666+        # 3. Upload the new segments.
2667+        # 4. Be done.
2668+        assert IMutableUploadable.providedBy(data)
2669+
2670+        self.data = data
2671+
2672+        # XXX: Use the MutableFileVersion instead.
2673+        self.datalength = self._node.get_size()
2674+        if data.get_size() > self.datalength:
2675+            self.datalength = data.get_size()
2676+
2677+        self.log("starting update")
2678+        self.log("adding new data of length %d at offset %d" % \
2679+                    (data.get_size(), offset))
2680+        self.log("new data length is %d" % self.datalength)
2681+        self._status.set_size(self.datalength)
2682+        self._status.set_status("Started")
2683+        self._started = time.time()
2684+
2685+        self.done_deferred = defer.Deferred()
2686+
2687+        self._writekey = self._node.get_writekey()
2688+        assert self._writekey, "need write capability to publish"
2689+
2690+        # first, which servers will we publish to? We require that the
2691+        # servermap was updated in MODE_WRITE, so we can depend upon the
2692+        # peerlist computed by that process instead of computing our own.
2693+        assert self._servermap
2694+        assert self._servermap.last_update_mode in (MODE_WRITE, MODE_CHECK)
2695+        # we will push a version that is one larger than anything present
2696+        # in the grid, according to the servermap.
2697+        self._new_seqnum = self._servermap.highest_seqnum() + 1
2698+        self._status.set_servermap(self._servermap)
2699+
2700+        self.log(format="new seqnum will be %(seqnum)d",
2701+                 seqnum=self._new_seqnum, level=log.NOISY)
2702+
2703+        # We're updating an existing file, so all of the following
2704+        # should be available.
2705+        self.readkey = self._node.get_readkey()
2706+        self.required_shares = self._node.get_required_shares()
2707+        assert self.required_shares is not None
2708+        self.total_shares = self._node.get_total_shares()
2709+        assert self.total_shares is not None
2710+        self._status.set_encoding(self.required_shares, self.total_shares)
2711+
2712+        self._pubkey = self._node.get_pubkey()
2713+        assert self._pubkey
2714+        self._privkey = self._node.get_privkey()
2715+        assert self._privkey
2716+        self._encprivkey = self._node.get_encprivkey()
2717+
2718+        sb = self._storage_broker
2719+        full_peerlist = sb.get_servers_for_index(self._storage_index)
2720+        self.full_peerlist = full_peerlist # for use later, immutable
2721+        self.bad_peers = set() # peerids who have errbacked/refused requests
2722+
2723+        # This will set self.segment_size, self.num_segments, and
2724+        # self.fec. TODO: Does it know how to do the offset? Probably
2725+        # not. So do that part next.
2726+        self.setup_encoding_parameters(offset=offset)
2727+
2728+        # if we experience any surprises (writes which were rejected because
2729+        # our test vector did not match, or shares which we didn't expect to
2730+        # see), we set this flag and report an UncoordinatedWriteError at the
2731+        # end of the publish process.
2732+        self.surprised = False
2733+
2734+        # we keep track of three tables. The first is our goal: which share
2735+        # we want to see on which servers. This is initially populated by the
2736+        # existing servermap.
2737+        self.goal = set() # pairs of (peerid, shnum) tuples
2738+
2739+        # the second table is our list of outstanding queries: those which
2740+        # are in flight and may or may not be delivered, accepted, or
2741+        # acknowledged. Items are added to this table when the request is
2742+        # sent, and removed when the response returns (or errbacks).
2743+        self.outstanding = set() # (peerid, shnum) tuples
2744+
2745+        # the third is a table of successes: share which have actually been
2746+        # placed. These are populated when responses come back with success.
2747+        # When self.placed == self.goal, we're done.
2748+        self.placed = set() # (peerid, shnum) tuples
2749+
2750+        # we also keep a mapping from peerid to RemoteReference. Each time we
2751+        # pull a connection out of the full peerlist, we add it to this for
2752+        # use later.
2753+        self.connections = {}
2754+
2755+        self.bad_share_checkstrings = {}
2756+
2757+        # This is set at the last step of the publishing process.
2758+        self.versioninfo = ""
2759+
2760+        # we use the servermap to populate the initial goal: this way we will
2761+        # try to update each existing share in place. Since we're
2762+        # updating, we ignore damaged and missing shares -- callers must
2763+        # do a repair to repair and recreate these.
2764+        for (peerid, shnum) in self._servermap.servermap:
2765+            self.goal.add( (peerid, shnum) )
2766+            self.connections[peerid] = self._servermap.connections[peerid]
2767+        self.writers = {}
2768+
2769+        # SDMF files are updated differently.
2770+        self._version = MDMF_VERSION
2771+        writer_class = MDMFSlotWriteProxy
2772+
2773+        # For each (peerid, shnum) in self.goal, we make a
2774+        # write proxy for that peer. We'll use this to write
2775+        # shares to the peer.
2776+        for key in self.goal:
2777+            peerid, shnum = key
2778+            write_enabler = self._node.get_write_enabler(peerid)
2779+            renew_secret = self._node.get_renewal_secret(peerid)
2780+            cancel_secret = self._node.get_cancel_secret(peerid)
2781+            secrets = (write_enabler, renew_secret, cancel_secret)
2782+
2783+            self.writers[shnum] =  writer_class(shnum,
2784+                                                self.connections[peerid],
2785+                                                self._storage_index,
2786+                                                secrets,
2787+                                                self._new_seqnum,
2788+                                                self.required_shares,
2789+                                                self.total_shares,
2790+                                                self.segment_size,
2791+                                                self.datalength)
2792+            self.writers[shnum].peerid = peerid
2793+            assert (peerid, shnum) in self._servermap.servermap
2794+            old_versionid, old_timestamp = self._servermap.servermap[key]
2795+            (old_seqnum, old_root_hash, old_salt, old_segsize,
2796+             old_datalength, old_k, old_N, old_prefix,
2797+             old_offsets_tuple) = old_versionid
2798+            self.writers[shnum].set_checkstring(old_seqnum,
2799+                                                old_root_hash,
2800+                                                old_salt)
2801+
2802+        # Our remote shares will not have a complete checkstring until
2803+        # after we are done writing share data and have started to write
2804+        # blocks. In the meantime, we need to know what to look for when
2805+        # writing, so that we can detect UncoordinatedWriteErrors.
2806+        self._checkstring = self.writers.values()[0].get_checkstring()
2807+
2808+        # Now, we start pushing shares.
2809+        self._status.timings["setup"] = time.time() - self._started
2810+        # First, we encrypt, encode, and publish the shares that we need
2811+        # to encrypt, encode, and publish.
2812+
2813+        # Our update process fetched these for us. We need to update
2814+        # them in place as publishing happens.
2815+        self.blockhashes = {} # (shnum, [blochashes])
2816+        for (i, bht) in blockhashes.iteritems():
2817+            # We need to extract the leaves from our old hash tree.
2818+            old_segcount = mathutil.div_ceil(version[4],
2819+                                             version[3])
2820+            h = hashtree.IncompleteHashTree(old_segcount)
2821+            bht = dict(enumerate(bht))
2822+            h.set_hashes(bht)
2823+            leaves = h[h.get_leaf_index(0):]
2824+            for j in xrange(self.num_segments - len(leaves)):
2825+                leaves.append(None)
2826+
2827+            assert len(leaves) >= self.num_segments
2828+            self.blockhashes[i] = leaves
2829+            # This list will now be the leaves that were set during the
2830+            # initial upload + enough empty hashes to make it a
2831+            # power-of-two. If we exceed a power of two boundary, we
2832+            # should be encoding the file over again, and should not be
2833+            # here. So, we have
2834+            #assert len(self.blockhashes[i]) == \
2835+            #    hashtree.roundup_pow2(self.num_segments), \
2836+            #        len(self.blockhashes[i])
2837+            # XXX: Except this doesn't work. Figure out why.
2838+
2839+        # These are filled in later, after we've modified the block hash
2840+        # tree suitably.
2841+        self.sharehash_leaves = None # eventually [sharehashes]
2842+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
2843+                              # validate the share]
2844+
2845+        self.log("Starting push")
2846+
2847+        self._state = PUSHING_BLOCKS_STATE
2848+        self._push()
2849+
2850+        return self.done_deferred
2851+
2852+
2853     def publish(self, newdata):
2854         """Publish the filenode's current contents.  Returns a Deferred that
2855         fires (with None) when the publish has done as much work as it's ever
2856hunk ./src/allmydata/mutable/publish.py 344
2857         simultaneous write.
2858         """
2859 
2860-        # 1: generate shares (SDMF: files are small, so we can do it in RAM)
2861-        # 2: perform peer selection, get candidate servers
2862-        #  2a: send queries to n+epsilon servers, to determine current shares
2863-        #  2b: based upon responses, create target map
2864-        # 3: send slot_testv_and_readv_and_writev messages
2865-        # 4: as responses return, update share-dispatch table
2866-        # 4a: may need to run recovery algorithm
2867-        # 5: when enough responses are back, we're done
2868+        # 0. Setup encoding parameters, encoder, and other such things.
2869+        # 1. Encrypt, encode, and publish segments.
2870+        assert IMutableUploadable.providedBy(newdata)
2871 
2872hunk ./src/allmydata/mutable/publish.py 348
2873-        self.log("starting publish, datalen is %s" % len(newdata))
2874-        self._status.set_size(len(newdata))
2875+        self.data = newdata
2876+        self.datalength = newdata.get_size()
2877+        #if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
2878+        #    self._version = MDMF_VERSION
2879+        #else:
2880+        #    self._version = SDMF_VERSION
2881+
2882+        self.log("starting publish, datalen is %s" % self.datalength)
2883+        self._status.set_size(self.datalength)
2884         self._status.set_status("Started")
2885         self._started = time.time()
2886 
2887hunk ./src/allmydata/mutable/publish.py 405
2888         self.full_peerlist = full_peerlist # for use later, immutable
2889         self.bad_peers = set() # peerids who have errbacked/refused requests
2890 
2891-        self.newdata = newdata
2892-        self.salt = os.urandom(16)
2893-
2894+        # This will set self.segment_size, self.num_segments, and
2895+        # self.fec.
2896         self.setup_encoding_parameters()
2897 
2898         # if we experience any surprises (writes which were rejected because
2899hunk ./src/allmydata/mutable/publish.py 415
2900         # end of the publish process.
2901         self.surprised = False
2902 
2903-        # as a failsafe, refuse to iterate through self.loop more than a
2904-        # thousand times.
2905-        self.looplimit = 1000
2906-
2907         # we keep track of three tables. The first is our goal: which share
2908         # we want to see on which servers. This is initially populated by the
2909         # existing servermap.
2910hunk ./src/allmydata/mutable/publish.py 438
2911 
2912         self.bad_share_checkstrings = {}
2913 
2914+        # This is set at the last step of the publishing process.
2915+        self.versioninfo = ""
2916+
2917         # we use the servermap to populate the initial goal: this way we will
2918         # try to update each existing share in place.
2919         for (peerid, shnum) in self._servermap.servermap:
2920hunk ./src/allmydata/mutable/publish.py 454
2921             self.bad_share_checkstrings[key] = old_checkstring
2922             self.connections[peerid] = self._servermap.connections[peerid]
2923 
2924-        # create the shares. We'll discard these as they are delivered. SDMF:
2925-        # we're allowed to hold everything in memory.
2926+        # TODO: Make this part do peer selection.
2927+        self.update_goal()
2928+        self.writers = {}
2929+        if self._version == MDMF_VERSION:
2930+            writer_class = MDMFSlotWriteProxy
2931+        else:
2932+            writer_class = SDMFSlotWriteProxy
2933 
2934hunk ./src/allmydata/mutable/publish.py 462
2935+        # For each (peerid, shnum) in self.goal, we make a
2936+        # write proxy for that peer. We'll use this to write
2937+        # shares to the peer.
2938+        for key in self.goal:
2939+            peerid, shnum = key
2940+            write_enabler = self._node.get_write_enabler(peerid)
2941+            renew_secret = self._node.get_renewal_secret(peerid)
2942+            cancel_secret = self._node.get_cancel_secret(peerid)
2943+            secrets = (write_enabler, renew_secret, cancel_secret)
2944+
2945+            self.writers[shnum] =  writer_class(shnum,
2946+                                                self.connections[peerid],
2947+                                                self._storage_index,
2948+                                                secrets,
2949+                                                self._new_seqnum,
2950+                                                self.required_shares,
2951+                                                self.total_shares,
2952+                                                self.segment_size,
2953+                                                self.datalength)
2954+            self.writers[shnum].peerid = peerid
2955+            if (peerid, shnum) in self._servermap.servermap:
2956+                old_versionid, old_timestamp = self._servermap.servermap[key]
2957+                (old_seqnum, old_root_hash, old_salt, old_segsize,
2958+                 old_datalength, old_k, old_N, old_prefix,
2959+                 old_offsets_tuple) = old_versionid
2960+                self.writers[shnum].set_checkstring(old_seqnum,
2961+                                                    old_root_hash,
2962+                                                    old_salt)
2963+            elif (peerid, shnum) in self.bad_share_checkstrings:
2964+                old_checkstring = self.bad_share_checkstrings[(peerid, shnum)]
2965+                self.writers[shnum].set_checkstring(old_checkstring)
2966+
2967+        # Our remote shares will not have a complete checkstring until
2968+        # after we are done writing share data and have started to write
2969+        # blocks. In the meantime, we need to know what to look for when
2970+        # writing, so that we can detect UncoordinatedWriteErrors.
2971+        self._checkstring = self.writers.values()[0].get_checkstring()
2972+
2973+        # Now, we start pushing shares.
2974         self._status.timings["setup"] = time.time() - self._started
2975hunk ./src/allmydata/mutable/publish.py 502
2976-        d = self._encrypt_and_encode()
2977-        d.addCallback(self._generate_shares)
2978-        def _start_pushing(res):
2979-            self._started_pushing = time.time()
2980-            return res
2981-        d.addCallback(_start_pushing)
2982-        d.addCallback(self.loop) # trigger delivery
2983-        d.addErrback(self._fatal_error)
2984+        # First, we encrypt, encode, and publish the shares that we need
2985+        # to encrypt, encode, and publish.
2986+
2987+        # This will eventually hold the block hash chain for each share
2988+        # that we publish. We define it this way so that empty publishes
2989+        # will still have something to write to the remote slot.
2990+        self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
2991+        for i in xrange(self.total_shares):
2992+            blocks = self.blockhashes[i]
2993+            for j in xrange(self.num_segments):
2994+                blocks.append(None)
2995+        self.sharehash_leaves = None # eventually [sharehashes]
2996+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
2997+                              # validate the share]
2998+
2999+        self.log("Starting push")
3000+
3001+        self._state = PUSHING_BLOCKS_STATE
3002+        self._push()
3003 
3004         return self.done_deferred
3005 
3006hunk ./src/allmydata/mutable/publish.py 524
3007-    def setup_encoding_parameters(self):
3008-        segment_size = len(self.newdata)
3009+
3010+    def _update_status(self):
3011+        self._status.set_status("Sending Shares: %d placed out of %d, "
3012+                                "%d messages outstanding" %
3013+                                (len(self.placed),
3014+                                 len(self.goal),
3015+                                 len(self.outstanding)))
3016+        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
3017+
3018+
3019+    def setup_encoding_parameters(self, offset=0):
3020+        if self._version == MDMF_VERSION:
3021+            segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
3022+        else:
3023+            segment_size = self.datalength # SDMF is only one segment
3024         # this must be a multiple of self.required_shares
3025         segment_size = mathutil.next_multiple(segment_size,
3026                                               self.required_shares)
3027hunk ./src/allmydata/mutable/publish.py 543
3028         self.segment_size = segment_size
3029+
3030+        # Calculate the starting segment for the upload.
3031         if segment_size:
3032hunk ./src/allmydata/mutable/publish.py 546
3033-            self.num_segments = mathutil.div_ceil(len(self.newdata),
3034+            self.num_segments = mathutil.div_ceil(self.datalength,
3035                                                   segment_size)
3036hunk ./src/allmydata/mutable/publish.py 548
3037+            self.starting_segment = mathutil.div_ceil(offset,
3038+                                                      segment_size)
3039+            self.starting_segment -= 1
3040+            if offset == 0:
3041+                self.starting_segment = 0
3042+
3043         else:
3044             self.num_segments = 0
3045hunk ./src/allmydata/mutable/publish.py 556
3046-        assert self.num_segments in [0, 1,] # SDMF restrictions
3047+            self.starting_segment = 0
3048+
3049+
3050+        self.log("building encoding parameters for file")
3051+        self.log("got segsize %d" % self.segment_size)
3052+        self.log("got %d segments" % self.num_segments)
3053+
3054+        if self._version == SDMF_VERSION:
3055+            assert self.num_segments in (0, 1) # SDMF
3056+        # calculate the tail segment size.
3057+
3058+        if segment_size and self.datalength:
3059+            self.tail_segment_size = self.datalength % segment_size
3060+            self.log("got tail segment size %d" % self.tail_segment_size)
3061+        else:
3062+            self.tail_segment_size = 0
3063+
3064+        if self.tail_segment_size == 0 and segment_size:
3065+            # The tail segment is the same size as the other segments.
3066+            self.tail_segment_size = segment_size
3067+
3068+        # Make FEC encoders
3069+        fec = codec.CRSEncoder()
3070+        fec.set_params(self.segment_size,
3071+                       self.required_shares, self.total_shares)
3072+        self.piece_size = fec.get_block_size()
3073+        self.fec = fec
3074+
3075+        if self.tail_segment_size == self.segment_size:
3076+            self.tail_fec = self.fec
3077+        else:
3078+            tail_fec = codec.CRSEncoder()
3079+            tail_fec.set_params(self.tail_segment_size,
3080+                                self.required_shares,
3081+                                self.total_shares)
3082+            self.tail_fec = tail_fec
3083+
3084+        self._current_segment = self.starting_segment
3085+        self.end_segment = self.num_segments - 1
3086+        # Now figure out where the last segment should be.
3087+        if self.data.get_size() != self.datalength:
3088+            end = self.data.get_size()
3089+            self.end_segment = mathutil.div_ceil(end,
3090+                                                 segment_size)
3091+            self.end_segment -= 1
3092+        self.log("got start segment %d" % self.starting_segment)
3093+        self.log("got end segment %d" % self.end_segment)
3094+
3095+
3096+    def _push(self, ignored=None):
3097+        """
3098+        I manage state transitions. In particular, I see that we still
3099+        have a good enough number of writers to complete the upload
3100+        successfully.
3101+        """
3102+        # Can we still successfully publish this file?
3103+        # TODO: Keep track of outstanding queries before aborting the
3104+        #       process.
3105+        if len(self.writers) <= self.required_shares or self.surprised:
3106+            return self._failure()
3107+
3108+        # Figure out what we need to do next. Each of these needs to
3109+        # return a deferred so that we don't block execution when this
3110+        # is first called in the upload method.
3111+        if self._state == PUSHING_BLOCKS_STATE:
3112+            return self.push_segment(self._current_segment)
3113+
3114+        elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
3115+            return self.push_everything_else()
3116+
3117+        # If we make it to this point, we were successful in placing the
3118+        # file.
3119+        return self._done(None)
3120+
3121+
3122+    def push_segment(self, segnum):
3123+        if self.num_segments == 0 and self._version == SDMF_VERSION:
3124+            self._add_dummy_salts()
3125 
3126hunk ./src/allmydata/mutable/publish.py 635
3127-    def _fatal_error(self, f):
3128-        self.log("error during loop", failure=f, level=log.UNUSUAL)
3129-        self._done(f)
3130+        if segnum > self.end_segment:
3131+            # We don't have any more segments to push.
3132+            self._state = PUSHING_EVERYTHING_ELSE_STATE
3133+            return self._push()
3134+
3135+        d = self._encode_segment(segnum)
3136+        d.addCallback(self._push_segment, segnum)
3137+        def _increment_segnum(ign):
3138+            self._current_segment += 1
3139+        # XXX: I don't think we need to do addBoth here -- any errBacks
3140+        # should be handled within push_segment.
3141+        d.addBoth(_increment_segnum)
3142+        d.addBoth(self._turn_barrier)
3143+        d.addBoth(self._push)
3144+
3145+
3146+    def _turn_barrier(self, result):
3147+        """
3148+        I help the publish process avoid the recursion limit issues
3149+        described in #237.
3150+        """
3151+        return fireEventually(result)
3152+
3153+
3154+    def _add_dummy_salts(self):
3155+        """
3156+        SDMF files need a salt even if they're empty, or the signature
3157+        won't make sense. This method adds a dummy salt to each of our
3158+        SDMF writers so that they can write the signature later.
3159+        """
3160+        salt = os.urandom(16)
3161+        assert self._version == SDMF_VERSION
3162+
3163+        for writer in self.writers.itervalues():
3164+            writer.put_salt(salt)
3165+
3166+
3167+    def _encode_segment(self, segnum):
3168+        """
3169+        I encrypt and encode the segment segnum.
3170+        """
3171+        started = time.time()
3172+
3173+        if segnum + 1 == self.num_segments:
3174+            segsize = self.tail_segment_size
3175+        else:
3176+            segsize = self.segment_size
3177+
3178+
3179+        self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
3180+        data = self.data.read(segsize)
3181+        # XXX: This is dumb. Why return a list?
3182+        data = "".join(data)
3183+
3184+        assert len(data) == segsize, len(data)
3185+
3186+        salt = os.urandom(16)
3187+
3188+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
3189+        self._status.set_status("Encrypting")
3190+        enc = AES(key)
3191+        crypttext = enc.process(data)
3192+        assert len(crypttext) == len(data)
3193+
3194+        now = time.time()
3195+        self._status.timings["encrypt"] = now - started
3196+        started = now
3197+
3198+        # now apply FEC
3199+        if segnum + 1 == self.num_segments:
3200+            fec = self.tail_fec
3201+        else:
3202+            fec = self.fec
3203+
3204+        self._status.set_status("Encoding")
3205+        crypttext_pieces = [None] * self.required_shares
3206+        piece_size = fec.get_block_size()
3207+        for i in range(len(crypttext_pieces)):
3208+            offset = i * piece_size
3209+            piece = crypttext[offset:offset+piece_size]
3210+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
3211+            crypttext_pieces[i] = piece
3212+            assert len(piece) == piece_size
3213+        d = fec.encode(crypttext_pieces)
3214+        def _done_encoding(res):
3215+            elapsed = time.time() - started
3216+            self._status.timings["encode"] = elapsed
3217+            return (res, salt)
3218+        d.addCallback(_done_encoding)
3219+        return d
3220+
3221+
3222+    def _push_segment(self, encoded_and_salt, segnum):
3223+        """
3224+        I push (data, salt) as segment number segnum.
3225+        """
3226+        results, salt = encoded_and_salt
3227+        shares, shareids = results
3228+        self._status.set_status("Pushing segment")
3229+        for i in xrange(len(shares)):
3230+            sharedata = shares[i]
3231+            shareid = shareids[i]
3232+            if self._version == MDMF_VERSION:
3233+                hashed = salt + sharedata
3234+            else:
3235+                hashed = sharedata
3236+            block_hash = hashutil.block_hash(hashed)
3237+            self.blockhashes[shareid][segnum] = block_hash
3238+            # find the writer for this share
3239+            writer = self.writers[shareid]
3240+            writer.put_block(sharedata, segnum, salt)
3241+
3242+
3243+    def push_everything_else(self):
3244+        """
3245+        I put everything else associated with a share.
3246+        """
3247+        self._pack_started = time.time()
3248+        self.push_encprivkey()
3249+        self.push_blockhashes()
3250+        self.push_sharehashes()
3251+        self.push_toplevel_hashes_and_signature()
3252+        d = self.finish_publishing()
3253+        def _change_state(ignored):
3254+            self._state = DONE_STATE
3255+        d.addCallback(_change_state)
3256+        d.addCallback(self._push)
3257+        return d
3258+
3259+
3260+    def push_encprivkey(self):
3261+        encprivkey = self._encprivkey
3262+        self._status.set_status("Pushing encrypted private key")
3263+        for writer in self.writers.itervalues():
3264+            writer.put_encprivkey(encprivkey)
3265+
3266+
3267+    def push_blockhashes(self):
3268+        self.sharehash_leaves = [None] * len(self.blockhashes)
3269+        self._status.set_status("Building and pushing block hash tree")
3270+        for shnum, blockhashes in self.blockhashes.iteritems():
3271+            t = hashtree.HashTree(blockhashes)
3272+            self.blockhashes[shnum] = list(t)
3273+            # set the leaf for future use.
3274+            self.sharehash_leaves[shnum] = t[0]
3275+
3276+            writer = self.writers[shnum]
3277+            writer.put_blockhashes(self.blockhashes[shnum])
3278+
3279+
3280+    def push_sharehashes(self):
3281+        self._status.set_status("Building and pushing share hash chain")
3282+        share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
3283+        for shnum in xrange(len(self.sharehash_leaves)):
3284+            needed_indices = share_hash_tree.needed_hashes(shnum)
3285+            self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
3286+                                             for i in needed_indices] )
3287+            writer = self.writers[shnum]
3288+            writer.put_sharehashes(self.sharehashes[shnum])
3289+        self.root_hash = share_hash_tree[0]
3290+
3291+
3292+    def push_toplevel_hashes_and_signature(self):
3293+        # We need to to three things here:
3294+        #   - Push the root hash and salt hash
3295+        #   - Get the checkstring of the resulting layout; sign that.
3296+        #   - Push the signature
3297+        self._status.set_status("Pushing root hashes and signature")
3298+        for shnum in xrange(self.total_shares):
3299+            writer = self.writers[shnum]
3300+            writer.put_root_hash(self.root_hash)
3301+        self._update_checkstring()
3302+        self._make_and_place_signature()
3303+
3304+
3305+    def _update_checkstring(self):
3306+        """
3307+        After putting the root hash, MDMF files will have the
3308+        checkstring written to the storage server. This means that we
3309+        can update our copy of the checkstring so we can detect
3310+        uncoordinated writes. SDMF files will have the same checkstring,
3311+        so we need not do anything.
3312+        """
3313+        self._checkstring = self.writers.values()[0].get_checkstring()
3314+
3315+
3316+    def _make_and_place_signature(self):
3317+        """
3318+        I create and place the signature.
3319+        """
3320+        started = time.time()
3321+        self._status.set_status("Signing prefix")
3322+        signable = self.writers[0].get_signable()
3323+        self.signature = self._privkey.sign(signable)
3324+
3325+        for (shnum, writer) in self.writers.iteritems():
3326+            writer.put_signature(self.signature)
3327+        self._status.timings['sign'] = time.time() - started
3328+
3329+
3330+    def finish_publishing(self):
3331+        # We're almost done -- we just need to put the verification key
3332+        # and the offsets
3333+        started = time.time()
3334+        self._status.set_status("Pushing shares")
3335+        self._started_pushing = started
3336+        ds = []
3337+        verification_key = self._pubkey.serialize()
3338+
3339+
3340+        # TODO: Bad, since we remove from this same dict. We need to
3341+        # make a copy, or just use a non-iterated value.
3342+        for (shnum, writer) in self.writers.iteritems():
3343+            writer.put_verification_key(verification_key)
3344+            d = writer.finish_publishing()
3345+            # Add the (peerid, shnum) tuple to our list of outstanding
3346+            # queries. This gets used by _loop if some of our queries
3347+            # fail to place shares.
3348+            self.outstanding.add((writer.peerid, writer.shnum))
3349+            d.addCallback(self._got_write_answer, writer, started)
3350+            d.addErrback(self._connection_problem, writer)
3351+            ds.append(d)
3352+        self._record_verinfo()
3353+        self._status.timings['pack'] = time.time() - started
3354+        return defer.DeferredList(ds)
3355+
3356+
3357+    def _record_verinfo(self):
3358+        self.versioninfo = self.writers.values()[0].get_verinfo()
3359+
3360+
3361+    def _connection_problem(self, f, writer):
3362+        """
3363+        We ran into a connection problem while working with writer, and
3364+        need to deal with that.
3365+        """
3366+        self.log("found problem: %s" % str(f))
3367+        self._last_failure = f
3368+        del(self.writers[writer.shnum])
3369 
3370hunk ./src/allmydata/mutable/publish.py 875
3371-    def _update_status(self):
3372-        self._status.set_status("Sending Shares: %d placed out of %d, "
3373-                                "%d messages outstanding" %
3374-                                (len(self.placed),
3375-                                 len(self.goal),
3376-                                 len(self.outstanding)))
3377-        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
3378 
3379hunk ./src/allmydata/mutable/publish.py 876
3380-    def loop(self, ignored=None):
3381-        self.log("entering loop", level=log.NOISY)
3382-        if not self._running:
3383-            return
3384-
3385-        self.looplimit -= 1
3386-        if self.looplimit <= 0:
3387-            raise LoopLimitExceededError("loop limit exceeded")
3388-
3389-        if self.surprised:
3390-            # don't send out any new shares, just wait for the outstanding
3391-            # ones to be retired.
3392-            self.log("currently surprised, so don't send any new shares",
3393-                     level=log.NOISY)
3394-        else:
3395-            self.update_goal()
3396-            # how far are we from our goal?
3397-            needed = self.goal - self.placed - self.outstanding
3398-            self._update_status()
3399-
3400-            if needed:
3401-                # we need to send out new shares
3402-                self.log(format="need to send %(needed)d new shares",
3403-                         needed=len(needed), level=log.NOISY)
3404-                self._send_shares(needed)
3405-                return
3406-
3407-        if self.outstanding:
3408-            # queries are still pending, keep waiting
3409-            self.log(format="%(outstanding)d queries still outstanding",
3410-                     outstanding=len(self.outstanding),
3411-                     level=log.NOISY)
3412-            return
3413-
3414-        # no queries outstanding, no placements needed: we're done
3415-        self.log("no queries outstanding, no placements needed: done",
3416-                 level=log.OPERATIONAL)
3417-        now = time.time()
3418-        elapsed = now - self._started_pushing
3419-        self._status.timings["push"] = elapsed
3420-        return self._done(None)
3421-
3422     def log_goal(self, goal, message=""):
3423         logmsg = [message]
3424         for (shnum, peerid) in sorted([(s,p) for (p,s) in goal]):
3425hunk ./src/allmydata/mutable/publish.py 957
3426             self.log_goal(self.goal, "after update: ")
3427 
3428 
3429+    def _got_write_answer(self, answer, writer, started):
3430+        if not answer:
3431+            # SDMF writers only pretend to write when readers set their
3432+            # blocks, salts, and so on -- they actually just write once,
3433+            # at the end of the upload process. In fake writes, they
3434+            # return defer.succeed(None). If we see that, we shouldn't
3435+            # bother checking it.
3436+            return
3437 
3438hunk ./src/allmydata/mutable/publish.py 966
3439-    def _encrypt_and_encode(self):
3440-        # this returns a Deferred that fires with a list of (sharedata,
3441-        # sharenum) tuples. TODO: cache the ciphertext, only produce the
3442-        # shares that we care about.
3443-        self.log("_encrypt_and_encode")
3444-
3445-        self._status.set_status("Encrypting")
3446-        started = time.time()
3447-
3448-        key = hashutil.ssk_readkey_data_hash(self.salt, self.readkey)
3449-        enc = AES(key)
3450-        crypttext = enc.process(self.newdata)
3451-        assert len(crypttext) == len(self.newdata)
3452+        peerid = writer.peerid
3453+        lp = self.log("_got_write_answer from %s, share %d" %
3454+                      (idlib.shortnodeid_b2a(peerid), writer.shnum))
3455 
3456         now = time.time()
3457hunk ./src/allmydata/mutable/publish.py 971
3458-        self._status.timings["encrypt"] = now - started
3459-        started = now
3460-
3461-        # now apply FEC
3462-
3463-        self._status.set_status("Encoding")
3464-        fec = codec.CRSEncoder()
3465-        fec.set_params(self.segment_size,
3466-                       self.required_shares, self.total_shares)
3467-        piece_size = fec.get_block_size()
3468-        crypttext_pieces = [None] * self.required_shares
3469-        for i in range(len(crypttext_pieces)):
3470-            offset = i * piece_size
3471-            piece = crypttext[offset:offset+piece_size]
3472-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
3473-            crypttext_pieces[i] = piece
3474-            assert len(piece) == piece_size
3475-
3476-        d = fec.encode(crypttext_pieces)
3477-        def _done_encoding(res):
3478-            elapsed = time.time() - started
3479-            self._status.timings["encode"] = elapsed
3480-            return res
3481-        d.addCallback(_done_encoding)
3482-        return d
3483-
3484-    def _generate_shares(self, shares_and_shareids):
3485-        # this sets self.shares and self.root_hash
3486-        self.log("_generate_shares")
3487-        self._status.set_status("Generating Shares")
3488-        started = time.time()
3489-
3490-        # we should know these by now
3491-        privkey = self._privkey
3492-        encprivkey = self._encprivkey
3493-        pubkey = self._pubkey
3494-
3495-        (shares, share_ids) = shares_and_shareids
3496-
3497-        assert len(shares) == len(share_ids)
3498-        assert len(shares) == self.total_shares
3499-        all_shares = {}
3500-        block_hash_trees = {}
3501-        share_hash_leaves = [None] * len(shares)
3502-        for i in range(len(shares)):
3503-            share_data = shares[i]
3504-            shnum = share_ids[i]
3505-            all_shares[shnum] = share_data
3506-
3507-            # build the block hash tree. SDMF has only one leaf.
3508-            leaves = [hashutil.block_hash(share_data)]
3509-            t = hashtree.HashTree(leaves)
3510-            block_hash_trees[shnum] = list(t)
3511-            share_hash_leaves[shnum] = t[0]
3512-        for leaf in share_hash_leaves:
3513-            assert leaf is not None
3514-        share_hash_tree = hashtree.HashTree(share_hash_leaves)
3515-        share_hash_chain = {}
3516-        for shnum in range(self.total_shares):
3517-            needed_hashes = share_hash_tree.needed_hashes(shnum)
3518-            share_hash_chain[shnum] = dict( [ (i, share_hash_tree[i])
3519-                                              for i in needed_hashes ] )
3520-        root_hash = share_hash_tree[0]
3521-        assert len(root_hash) == 32
3522-        self.log("my new root_hash is %s" % base32.b2a(root_hash))
3523-        self._new_version_info = (self._new_seqnum, root_hash, self.salt)
3524-
3525-        prefix = pack_prefix(self._new_seqnum, root_hash, self.salt,
3526-                             self.required_shares, self.total_shares,
3527-                             self.segment_size, len(self.newdata))
3528-
3529-        # now pack the beginning of the share. All shares are the same up
3530-        # to the signature, then they have divergent share hash chains,
3531-        # then completely different block hash trees + salt + share data,
3532-        # then they all share the same encprivkey at the end. The sizes
3533-        # of everything are the same for all shares.
3534-
3535-        sign_started = time.time()
3536-        signature = privkey.sign(prefix)
3537-        self._status.timings["sign"] = time.time() - sign_started
3538-
3539-        verification_key = pubkey.serialize()
3540-
3541-        final_shares = {}
3542-        for shnum in range(self.total_shares):
3543-            final_share = pack_share(prefix,
3544-                                     verification_key,
3545-                                     signature,
3546-                                     share_hash_chain[shnum],
3547-                                     block_hash_trees[shnum],
3548-                                     all_shares[shnum],
3549-                                     encprivkey)
3550-            final_shares[shnum] = final_share
3551-        elapsed = time.time() - started
3552-        self._status.timings["pack"] = elapsed
3553-        self.shares = final_shares
3554-        self.root_hash = root_hash
3555-
3556-        # we also need to build up the version identifier for what we're
3557-        # pushing. Extract the offsets from one of our shares.
3558-        assert final_shares
3559-        offsets = unpack_header(final_shares.values()[0])[-1]
3560-        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
3561-        verinfo = (self._new_seqnum, root_hash, self.salt,
3562-                   self.segment_size, len(self.newdata),
3563-                   self.required_shares, self.total_shares,
3564-                   prefix, offsets_tuple)
3565-        self.versioninfo = verinfo
3566-
3567-
3568-
3569-    def _send_shares(self, needed):
3570-        self.log("_send_shares")
3571-
3572-        # we're finally ready to send out our shares. If we encounter any
3573-        # surprises here, it's because somebody else is writing at the same
3574-        # time. (Note: in the future, when we remove the _query_peers() step
3575-        # and instead speculate about [or remember] which shares are where,
3576-        # surprises here are *not* indications of UncoordinatedWriteError,
3577-        # and we'll need to respond to them more gracefully.)
3578-
3579-        # needed is a set of (peerid, shnum) tuples. The first thing we do is
3580-        # organize it by peerid.
3581-
3582-        peermap = DictOfSets()
3583-        for (peerid, shnum) in needed:
3584-            peermap.add(peerid, shnum)
3585-
3586-        # the next thing is to build up a bunch of test vectors. The
3587-        # semantics of Publish are that we perform the operation if the world
3588-        # hasn't changed since the ServerMap was constructed (more or less).
3589-        # For every share we're trying to place, we create a test vector that
3590-        # tests to see if the server*share still corresponds to the
3591-        # map.
3592-
3593-        all_tw_vectors = {} # maps peerid to tw_vectors
3594-        sm = self._servermap.servermap
3595-
3596-        for key in needed:
3597-            (peerid, shnum) = key
3598-
3599-            if key in sm:
3600-                # an old version of that share already exists on the
3601-                # server, according to our servermap. We will create a
3602-                # request that attempts to replace it.
3603-                old_versionid, old_timestamp = sm[key]
3604-                (old_seqnum, old_root_hash, old_salt, old_segsize,
3605-                 old_datalength, old_k, old_N, old_prefix,
3606-                 old_offsets_tuple) = old_versionid
3607-                old_checkstring = pack_checkstring(old_seqnum,
3608-                                                   old_root_hash,
3609-                                                   old_salt)
3610-                testv = (0, len(old_checkstring), "eq", old_checkstring)
3611-
3612-            elif key in self.bad_share_checkstrings:
3613-                old_checkstring = self.bad_share_checkstrings[key]
3614-                testv = (0, len(old_checkstring), "eq", old_checkstring)
3615-
3616-            else:
3617-                # add a testv that requires the share not exist
3618-
3619-                # Unfortunately, foolscap-0.2.5 has a bug in the way inbound
3620-                # constraints are handled. If the same object is referenced
3621-                # multiple times inside the arguments, foolscap emits a
3622-                # 'reference' token instead of a distinct copy of the
3623-                # argument. The bug is that these 'reference' tokens are not
3624-                # accepted by the inbound constraint code. To work around
3625-                # this, we need to prevent python from interning the
3626-                # (constant) tuple, by creating a new copy of this vector
3627-                # each time.
3628-
3629-                # This bug is fixed in foolscap-0.2.6, and even though this
3630-                # version of Tahoe requires foolscap-0.3.1 or newer, we are
3631-                # supposed to be able to interoperate with older versions of
3632-                # Tahoe which are allowed to use older versions of foolscap,
3633-                # including foolscap-0.2.5 . In addition, I've seen other
3634-                # foolscap problems triggered by 'reference' tokens (see #541
3635-                # for details). So we must keep this workaround in place.
3636-
3637-                #testv = (0, 1, 'eq', "")
3638-                testv = tuple([0, 1, 'eq', ""])
3639-
3640-            testvs = [testv]
3641-            # the write vector is simply the share
3642-            writev = [(0, self.shares[shnum])]
3643-
3644-            if peerid not in all_tw_vectors:
3645-                all_tw_vectors[peerid] = {}
3646-                # maps shnum to (testvs, writevs, new_length)
3647-            assert shnum not in all_tw_vectors[peerid]
3648-
3649-            all_tw_vectors[peerid][shnum] = (testvs, writev, None)
3650-
3651-        # we read the checkstring back from each share, however we only use
3652-        # it to detect whether there was a new share that we didn't know
3653-        # about. The success or failure of the write will tell us whether
3654-        # there was a collision or not. If there is a collision, the first
3655-        # thing we'll do is update the servermap, which will find out what
3656-        # happened. We could conceivably reduce a roundtrip by using the
3657-        # readv checkstring to populate the servermap, but really we'd have
3658-        # to read enough data to validate the signatures too, so it wouldn't
3659-        # be an overall win.
3660-        read_vector = [(0, struct.calcsize(SIGNED_PREFIX))]
3661-
3662-        # ok, send the messages!
3663-        self.log("sending %d shares" % len(all_tw_vectors), level=log.NOISY)
3664-        started = time.time()
3665-        for (peerid, tw_vectors) in all_tw_vectors.items():
3666-
3667-            write_enabler = self._node.get_write_enabler(peerid)
3668-            renew_secret = self._node.get_renewal_secret(peerid)
3669-            cancel_secret = self._node.get_cancel_secret(peerid)
3670-            secrets = (write_enabler, renew_secret, cancel_secret)
3671-            shnums = tw_vectors.keys()
3672-
3673-            for shnum in shnums:
3674-                self.outstanding.add( (peerid, shnum) )
3675+        elapsed = now - started
3676 
3677hunk ./src/allmydata/mutable/publish.py 973
3678-            d = self._do_testreadwrite(peerid, secrets,
3679-                                       tw_vectors, read_vector)
3680-            d.addCallbacks(self._got_write_answer, self._got_write_error,
3681-                           callbackArgs=(peerid, shnums, started),
3682-                           errbackArgs=(peerid, shnums, started))
3683-            # tolerate immediate errback, like with DeadReferenceError
3684-            d.addBoth(fireEventually)
3685-            d.addCallback(self.loop)
3686-            d.addErrback(self._fatal_error)
3687+        self._status.add_per_server_time(peerid, elapsed)
3688 
3689hunk ./src/allmydata/mutable/publish.py 975
3690-        self._update_status()
3691-        self.log("%d shares sent" % len(all_tw_vectors), level=log.NOISY)
3692+        wrote, read_data = answer
3693 
3694hunk ./src/allmydata/mutable/publish.py 977
3695-    def _do_testreadwrite(self, peerid, secrets,
3696-                          tw_vectors, read_vector):
3697-        storage_index = self._storage_index
3698-        ss = self.connections[peerid]
3699+        surprise_shares = set(read_data.keys()) - set([writer.shnum])
3700 
3701hunk ./src/allmydata/mutable/publish.py 979
3702-        #print "SS[%s] is %s" % (idlib.shortnodeid_b2a(peerid), ss), ss.tracker.interfaceName
3703-        d = ss.callRemote("slot_testv_and_readv_and_writev",
3704-                          storage_index,
3705-                          secrets,
3706-                          tw_vectors,
3707-                          read_vector)
3708-        return d
3709+        # We need to remove from surprise_shares any shares that we are
3710+        # knowingly also writing to that peer from other writers.
3711 
3712hunk ./src/allmydata/mutable/publish.py 982
3713-    def _got_write_answer(self, answer, peerid, shnums, started):
3714-        lp = self.log("_got_write_answer from %s" %
3715-                      idlib.shortnodeid_b2a(peerid))
3716-        for shnum in shnums:
3717-            self.outstanding.discard( (peerid, shnum) )
3718+        # TODO: Precompute this.
3719+        known_shnums = [x.shnum for x in self.writers.values()
3720+                        if x.peerid == peerid]
3721+        surprise_shares -= set(known_shnums)
3722+        self.log("found the following surprise shares: %s" %
3723+                 str(surprise_shares))
3724 
3725hunk ./src/allmydata/mutable/publish.py 989
3726-        now = time.time()
3727-        elapsed = now - started
3728-        self._status.add_per_server_time(peerid, elapsed)
3729-
3730-        wrote, read_data = answer
3731-
3732-        surprise_shares = set(read_data.keys()) - set(shnums)
3733+        # Now surprise shares contains all of the shares that we did not
3734+        # expect to be there.
3735 
3736         surprised = False
3737         for shnum in surprise_shares:
3738hunk ./src/allmydata/mutable/publish.py 996
3739             # read_data is a dict mapping shnum to checkstring (SIGNED_PREFIX)
3740             checkstring = read_data[shnum][0]
3741-            their_version_info = unpack_checkstring(checkstring)
3742-            if their_version_info == self._new_version_info:
3743+            # What we want to do here is to see if their (seqnum,
3744+            # roothash, salt) is the same as our (seqnum, roothash,
3745+            # salt), or the equivalent for MDMF. The best way to do this
3746+            # is to store a packed representation of our checkstring
3747+            # somewhere, then not bother unpacking the other
3748+            # checkstring.
3749+            if checkstring == self._checkstring:
3750                 # they have the right share, somehow
3751 
3752                 if (peerid,shnum) in self.goal:
3753hunk ./src/allmydata/mutable/publish.py 1081
3754             self.log("our testv failed, so the write did not happen",
3755                      parent=lp, level=log.WEIRD, umid="8sc26g")
3756             self.surprised = True
3757-            self.bad_peers.add(peerid) # don't ask them again
3758+            self.bad_peers.add(writer) # don't ask them again
3759             # use the checkstring to add information to the log message
3760             for (shnum,readv) in read_data.items():
3761                 checkstring = readv[0]
3762hunk ./src/allmydata/mutable/publish.py 1103
3763                 # if expected_version==None, then we didn't expect to see a
3764                 # share on that peer, and the 'surprise_shares' clause above
3765                 # will have logged it.
3766-            # self.loop() will take care of finding new homes
3767             return
3768 
3769hunk ./src/allmydata/mutable/publish.py 1105
3770-        for shnum in shnums:
3771-            self.placed.add( (peerid, shnum) )
3772-            # and update the servermap
3773-            self._servermap.add_new_share(peerid, shnum,
3774+        # and update the servermap
3775+        # self.versioninfo is set during the last phase of publishing.
3776+        # If we get there, we know that responses correspond to placed
3777+        # shares, and can safely execute these statements.
3778+        if self.versioninfo:
3779+            self.log("wrote successfully: adding new share to servermap")
3780+            self._servermap.add_new_share(peerid, writer.shnum,
3781                                           self.versioninfo, started)
3782hunk ./src/allmydata/mutable/publish.py 1113
3783-
3784-        # self.loop() will take care of checking to see if we're done
3785+            self.placed.add( (peerid, writer.shnum) )
3786+        self._update_status()
3787+        # the next method in the deferred chain will check to see if
3788+        # we're done and successful.
3789         return
3790 
3791hunk ./src/allmydata/mutable/publish.py 1119
3792-    def _got_write_error(self, f, peerid, shnums, started):
3793-        for shnum in shnums:
3794-            self.outstanding.discard( (peerid, shnum) )
3795-        self.bad_peers.add(peerid)
3796-        if self._first_write_error is None:
3797-            self._first_write_error = f
3798-        self.log(format="error while writing shares %(shnums)s to peerid %(peerid)s",
3799-                 shnums=list(shnums), peerid=idlib.shortnodeid_b2a(peerid),
3800-                 failure=f,
3801-                 level=log.UNUSUAL)
3802-        # self.loop() will take care of checking to see if we're done
3803-        return
3804-
3805 
3806     def _done(self, res):
3807         if not self._running:
3808hunk ./src/allmydata/mutable/publish.py 1126
3809         self._running = False
3810         now = time.time()
3811         self._status.timings["total"] = now - self._started
3812+
3813+        elapsed = now - self._started_pushing
3814+        self._status.timings['push'] = elapsed
3815+
3816         self._status.set_active(False)
3817hunk ./src/allmydata/mutable/publish.py 1131
3818-        if isinstance(res, failure.Failure):
3819-            self.log("Publish done, with failure", failure=res,
3820-                     level=log.WEIRD, umid="nRsR9Q")
3821-            self._status.set_status("Failed")
3822-        elif self.surprised:
3823-            self.log("Publish done, UncoordinatedWriteError", level=log.UNUSUAL)
3824-            self._status.set_status("UncoordinatedWriteError")
3825-            # deliver a failure
3826-            res = failure.Failure(UncoordinatedWriteError())
3827-            # TODO: recovery
3828-        else:
3829-            self.log("Publish done, success")
3830-            self._status.set_status("Finished")
3831-            self._status.set_progress(1.0)
3832+        self.log("Publish done, success")
3833+        self._status.set_status("Finished")
3834+        self._status.set_progress(1.0)
3835         eventually(self.done_deferred.callback, res)
3836 
3837hunk ./src/allmydata/mutable/publish.py 1136
3838+    def _failure(self):
3839+
3840+        if not self.surprised:
3841+            # We ran out of servers
3842+            self.log("Publish ran out of good servers, "
3843+                     "last failure was: %s" % str(self._last_failure))
3844+            e = NotEnoughServersError("Ran out of non-bad servers, "
3845+                                      "last failure was %s" %
3846+                                      str(self._last_failure))
3847+        else:
3848+            # We ran into shares that we didn't recognize, which means
3849+            # that we need to return an UncoordinatedWriteError.
3850+            self.log("Publish failed with UncoordinatedWriteError")
3851+            e = UncoordinatedWriteError()
3852+        f = failure.Failure(e)
3853+        eventually(self.done_deferred.callback, f)
3854+
3855+
3856+class MutableFileHandle:
3857+    """
3858+    I am a mutable uploadable built around a filehandle-like object,
3859+    usually either a StringIO instance or a handle to an actual file.
3860+    """
3861+    implements(IMutableUploadable)
3862+
3863+    def __init__(self, filehandle):
3864+        # The filehandle is defined as a generally file-like object that
3865+        # has these two methods. We don't care beyond that.
3866+        assert hasattr(filehandle, "read")
3867+        assert hasattr(filehandle, "close")
3868+
3869+        self._filehandle = filehandle
3870+        # We must start reading at the beginning of the file, or we risk
3871+        # encountering errors when the data read does not match the size
3872+        # reported to the uploader.
3873+        self._filehandle.seek(0)
3874+
3875+        # We have not yet read anything, so our position is 0.
3876+        self._marker = 0
3877+
3878+
3879+    def get_size(self):
3880+        """
3881+        I return the amount of data in my filehandle.
3882+        """
3883+        if not hasattr(self, "_size"):
3884+            old_position = self._filehandle.tell()
3885+            # Seek to the end of the file by seeking 0 bytes from the
3886+            # file's end
3887+            self._filehandle.seek(0, 2) # 2 == os.SEEK_END in 2.5+
3888+            self._size = self._filehandle.tell()
3889+            # Restore the previous position, in case this was called
3890+            # after a read.
3891+            self._filehandle.seek(old_position)
3892+            assert self._filehandle.tell() == old_position
3893+
3894+        assert hasattr(self, "_size")
3895+        return self._size
3896+
3897+
3898+    def pos(self):
3899+        """
3900+        I return the position of my read marker -- i.e., how much data I
3901+        have already read and returned to callers.
3902+        """
3903+        return self._marker
3904+
3905+
3906+    def read(self, length):
3907+        """
3908+        I return some data (up to length bytes) from my filehandle.
3909+
3910+        In most cases, I return length bytes, but sometimes I won't --
3911+        for example, if I am asked to read beyond the end of a file, or
3912+        an error occurs.
3913+        """
3914+        results = self._filehandle.read(length)
3915+        self._marker += len(results)
3916+        return [results]
3917+
3918+
3919+    def close(self):
3920+        """
3921+        I close the underlying filehandle. Any further operations on the
3922+        filehandle fail at this point.
3923+        """
3924+        self._filehandle.close()
3925+
3926+
3927+class MutableData(MutableFileHandle):
3928+    """
3929+    I am a mutable uploadable built around a string, which I then cast
3930+    into a StringIO and treat as a filehandle.
3931+    """
3932+
3933+    def __init__(self, s):
3934+        # Take a string and return a file-like uploadable.
3935+        assert isinstance(s, str)
3936+
3937+        MutableFileHandle.__init__(self, StringIO(s))
3938+
3939+
3940+class TransformingUploadable:
3941+    """
3942+    I am an IMutableUploadable that wraps another IMutableUploadable,
3943+    and some segments that are already on the grid. When I am called to
3944+    read, I handle merging of boundary segments.
3945+    """
3946+    implements(IMutableUploadable)
3947+
3948+
3949+    def __init__(self, data, offset, segment_size, start, end):
3950+        assert IMutableUploadable.providedBy(data)
3951+
3952+        self._newdata = data
3953+        self._offset = offset
3954+        self._segment_size = segment_size
3955+        self._start = start
3956+        self._end = end
3957+
3958+        self._read_marker = 0
3959+
3960+        self._first_segment_offset = offset % segment_size
3961+
3962+        num = self.log("TransformingUploadable: starting", parent=None)
3963+        self._log_number = num
3964+        self.log("got fso: %d" % self._first_segment_offset)
3965+        self.log("got offset: %d" % self._offset)
3966+
3967+
3968+    def log(self, *args, **kwargs):
3969+        if 'parent' not in kwargs:
3970+            kwargs['parent'] = self._log_number
3971+        if "facility" not in kwargs:
3972+            kwargs["facility"] = "tahoe.mutable.transforminguploadable"
3973+        return log.msg(*args, **kwargs)
3974+
3975+
3976+    def get_size(self):
3977+        return self._offset + self._newdata.get_size()
3978+
3979+
3980+    def read(self, length):
3981+        # We can get data from 3 sources here.
3982+        #   1. The first of the segments provided to us.
3983+        #   2. The data that we're replacing things with.
3984+        #   3. The last of the segments provided to us.
3985+
3986+        # are we in state 0?
3987+        self.log("reading %d bytes" % length)
3988+
3989+        old_start_data = ""
3990+        old_data_length = self._first_segment_offset - self._read_marker
3991+        if old_data_length > 0:
3992+            if old_data_length > length:
3993+                old_data_length = length
3994+            self.log("returning %d bytes of old start data" % old_data_length)
3995+
3996+            old_data_end = old_data_length + self._read_marker
3997+            old_start_data = self._start[self._read_marker:old_data_end]
3998+            length -= old_data_length
3999+        else:
4000+            # otherwise calculations later get screwed up.
4001+            old_data_length = 0
4002+
4003+        # Is there enough new data to satisfy this read? If not, we need
4004+        # to pad the end of the data with data from our last segment.
4005+        old_end_length = length - \
4006+            (self._newdata.get_size() - self._newdata.pos())
4007+        old_end_data = ""
4008+        if old_end_length > 0:
4009+            self.log("reading %d bytes of old end data" % old_end_length)
4010+
4011+            # TODO: We're not explicitly checking for tail segment size
4012+            # here. Is that a problem?
4013+            old_data_offset = (length - old_end_length + \
4014+                               old_data_length) % self._segment_size
4015+            self.log("reading at offset %d" % old_data_offset)
4016+            old_end = old_data_offset + old_end_length
4017+            old_end_data = self._end[old_data_offset:old_end]
4018+            length -= old_end_length
4019+            assert length == self._newdata.get_size() - self._newdata.pos()
4020+
4021+        self.log("reading %d bytes of new data" % length)
4022+        new_data = self._newdata.read(length)
4023+        new_data = "".join(new_data)
4024+
4025+        self._read_marker += len(old_start_data + new_data + old_end_data)
4026+
4027+        return old_start_data + new_data + old_end_data
4028 
4029hunk ./src/allmydata/mutable/publish.py 1327
4030+    def close(self):
4031+        pass
4032}
4033[nodemaker.py: Make nodemaker expose a way to create MDMF files
4034Kevan Carstensen <kevan@isnotajoke.com>**20100819003509
4035 Ignore-this: a6701746d6b992fc07bc0556a2b4a61d
4036] {
4037hunk ./src/allmydata/nodemaker.py 3
4038 import weakref
4039 from zope.interface import implements
4040-from allmydata.interfaces import INodeMaker
4041+from allmydata.util.assertutil import precondition
4042+from allmydata.interfaces import INodeMaker, SDMF_VERSION
4043 from allmydata.immutable.literal import LiteralFileNode
4044 from allmydata.immutable.filenode import ImmutableFileNode, CiphertextFileNode
4045 from allmydata.immutable.upload import Data
4046hunk ./src/allmydata/nodemaker.py 9
4047 from allmydata.mutable.filenode import MutableFileNode
4048+from allmydata.mutable.publish import MutableData
4049 from allmydata.dirnode import DirectoryNode, pack_children
4050 from allmydata.unknown import UnknownNode
4051 from allmydata import uri
4052hunk ./src/allmydata/nodemaker.py 92
4053             return self._create_dirnode(filenode)
4054         return None
4055 
4056-    def create_mutable_file(self, contents=None, keysize=None):
4057+    def create_mutable_file(self, contents=None, keysize=None,
4058+                            version=SDMF_VERSION):
4059         n = MutableFileNode(self.storage_broker, self.secret_holder,
4060                             self.default_encoding_parameters, self.history)
4061hunk ./src/allmydata/nodemaker.py 96
4062+        n.set_version(version)
4063         d = self.key_generator.generate(keysize)
4064         d.addCallback(n.create_with_keys, contents)
4065         d.addCallback(lambda res: n)
4066hunk ./src/allmydata/nodemaker.py 103
4067         return d
4068 
4069     def create_new_mutable_directory(self, initial_children={}):
4070+        # mutable directories will always be SDMF for now, to help
4071+        # compatibility with older clients.
4072+        version = SDMF_VERSION
4073+        # initial_children must have metadata (i.e. {} instead of None)
4074+        for (name, (node, metadata)) in initial_children.iteritems():
4075+            precondition(isinstance(metadata, dict),
4076+                         "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
4077+            node.raise_error()
4078         d = self.create_mutable_file(lambda n:
4079hunk ./src/allmydata/nodemaker.py 112
4080-                                     pack_children(initial_children, n.get_writekey()))
4081+                                     MutableData(pack_children(initial_children,
4082+                                                    n.get_writekey())),
4083+                                     version=version)
4084         d.addCallback(self._create_dirnode)
4085         return d
4086 
4087}
4088[docs: update docs to mention MDMF
4089Kevan Carstensen <kevan@isnotajoke.com>**20100814225644
4090 Ignore-this: 1c3caa3cd44831007dcfbef297814308
4091] {
4092merger 0.0 (
4093hunk ./docs/configuration.rst 324
4094+Frontend Configuration
4095+======================
4096+
4097+The Tahoe client process can run a variety of frontend file-access protocols.
4098+You will use these to create and retrieve files from the virtual filesystem.
4099+Configuration details for each are documented in the following
4100+protocol-specific guides:
4101+
4102+HTTP
4103+
4104+    Tahoe runs a webserver by default on port 3456. This interface provides a
4105+    human-oriented "WUI", with pages to create, modify, and browse
4106+    directories and files, as well as a number of pages to check on the
4107+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
4108+    with a REST-ful HTTP interface that can be used by other programs
4109+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
4110+    details, and the ``web.port`` and ``web.static`` config variables above.
4111+    The `<frontends/download-status.rst>`_ document also describes a few WUI
4112+    status pages.
4113+
4114+CLI
4115+
4116+    The main "bin/tahoe" executable includes subcommands for manipulating the
4117+    filesystem, uploading/downloading files, and creating/running Tahoe
4118+    nodes. See `<frontends/CLI.rst>`_ for details.
4119+
4120+FTP, SFTP
4121+
4122+    Tahoe can also run both FTP and SFTP servers, and map a username/password
4123+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
4124+    for instructions on configuring these services, and the ``[ftpd]`` and
4125+    ``[sftpd]`` sections of ``tahoe.cfg``.
4126+
4127merger 0.0 (
4128replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
4129merger 0.0 (
4130hunk ./docs/configuration.rst 384
4131-shares.needed = (int, optional) aka "k", default 3
4132-shares.total = (int, optional) aka "N", N >= k, default 10
4133-shares.happy = (int, optional) 1 <= happy <= N, default 7
4134-
4135- These three values set the default encoding parameters. Each time a new file
4136- is uploaded, erasure-coding is used to break the ciphertext into separate
4137- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
4138- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
4139- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
4140- Setting k to 1 is equivalent to simple replication (uploading N copies of
4141- the file).
4142-
4143- These values control the tradeoff between storage overhead, performance, and
4144- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
4145- backend storage space (the actual value will be a bit more, because of other
4146- forms of overhead). Up to N-k shares can be lost before the file becomes
4147- unrecoverable, so assuming there are at least N servers, up to N-k servers
4148- can be offline without losing the file. So large N/k ratios are more
4149- reliable, and small N/k ratios use less disk space. Clearly, k must never be
4150- smaller than N.
4151-
4152- Large values of N will slow down upload operations slightly, since more
4153- servers must be involved, and will slightly increase storage overhead due to
4154- the hash trees that are created. Large values of k will cause downloads to
4155- be marginally slower, because more servers must be involved. N cannot be
4156- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
4157- uses.
4158-
4159- shares.happy allows you control over the distribution of your immutable file.
4160- For a successful upload, shares are guaranteed to be initially placed on
4161- at least 'shares.happy' distinct servers, the correct functioning of any
4162- k of which is sufficient to guarantee the availability of the uploaded file.
4163- This value should not be larger than the number of servers on your grid.
4164-
4165- A value of shares.happy <= k is allowed, but does not provide any redundancy
4166- if some servers fail or lose shares.
4167-
4168- (Mutable files use a different share placement algorithm that does not
4169-  consider this parameter.)
4170-
4171-
4172-== Storage Server Configuration ==
4173-
4174-[storage]
4175-enabled = (boolean, optional)
4176-
4177- If this is True, the node will run a storage server, offering space to other
4178- clients. If it is False, the node will not run a storage server, meaning
4179- that no shares will be stored on this node. Use False this for clients who
4180- do not wish to provide storage service. The default value is True.
4181-
4182-readonly = (boolean, optional)
4183-
4184- If True, the node will run a storage server but will not accept any shares,
4185- making it effectively read-only. Use this for storage servers which are
4186- being decommissioned: the storage/ directory could be mounted read-only,
4187- while shares are moved to other servers. Note that this currently only
4188- affects immutable shares. Mutable shares (used for directories) will be
4189- written and modified anyway. See ticket #390 for the current status of this
4190- bug. The default value is False.
4191-
4192-reserved_space = (str, optional)
4193-
4194- If provided, this value defines how much disk space is reserved: the storage
4195- server will not accept any share which causes the amount of free disk space
4196- to drop below this value. (The free space is measured by a call to statvfs(2)
4197- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
4198- user account under which the storage server runs.)
4199-
4200- This string contains a number, with an optional case-insensitive scale
4201- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
4202- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
4203- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
4204-
4205-expire.enabled =
4206-expire.mode =
4207-expire.override_lease_duration =
4208-expire.cutoff_date =
4209-expire.immutable =
4210-expire.mutable =
4211-
4212- These settings control garbage-collection, in which the server will delete
4213- shares that no longer have an up-to-date lease on them. Please see the
4214- neighboring "garbage-collection.txt" document for full details.
4215-
4216-
4217-== Running A Helper ==
4218+Running A Helper
4219+================
4220hunk ./docs/configuration.rst 424
4221+mutable.format = sdmf or mdmf
4222+
4223+ This value tells Tahoe-LAFS what the default mutable file format should
4224+ be. If mutable.format=sdmf, then newly created mutable files will be in
4225+ the old SDMF format. This is desirable for clients that operate on
4226+ grids where some peers run older versions of Tahoe-LAFS, as these older
4227+ versions cannot read the new MDMF mutable file format. If
4228+ mutable.format = mdmf, then newly created mutable files will use the
4229+ new MDMF format, which supports efficient in-place modification and
4230+ streaming downloads. You can overwrite this value using a special
4231+ mutable-type parameter in the webapi. If you do not specify a value
4232+ here, Tahoe-LAFS will use SDMF for all newly-created mutable files.
4233+
4234+ Note that this parameter only applies to mutable files. Mutable
4235+ directories, which are stored as mutable files, are not controlled by
4236+ this parameter and will always use SDMF. We may revisit this decision
4237+ in future versions of Tahoe-LAFS.
4238)
4239)
4240)
4241hunk ./docs/frontends/webapi.rst 363
4242  writeable mutable file, that file's contents will be overwritten in-place. If
4243  it is a read-cap for a mutable file, an error will occur. If it is an
4244  immutable file, the old file will be discarded, and a new one will be put in
4245- its place.
4246+ its place. If the target file is a writable mutable file, you may also
4247+ specify an "offset" parameter -- a byte offset that determines where in
4248+ the mutable file the data from the HTTP request body is placed. This
4249+ operation is relatively efficient for MDMF mutable files, and is
4250+ relatively inefficient (but still supported) for SDMF mutable files.
4251 
4252  When creating a new file, if "mutable=true" is in the query arguments, the
4253  operation will create a mutable file instead of an immutable one.
4254hunk ./docs/frontends/webapi.rst 388
4255 
4256  If "mutable=true" is in the query arguments, the operation will create a
4257  mutable file, and return its write-cap in the HTTP respose. The default is
4258- to create an immutable file, returning the read-cap as a response.
4259+ to create an immutable file, returning the read-cap as a response. If
4260+ you create a mutable file, you can also use the "mutable-type" query
4261+ parameter. If "mutable-type=sdmf", then the mutable file will be created
4262+ in the old SDMF mutable file format. This is desirable for files that
4263+ need to be read by old clients. If "mutable-type=mdmf", then the file
4264+ will be created in the new MDMF mutable file format. MDMF mutable files
4265+ can be downloaded more efficiently, and modified in-place efficiently,
4266+ but are not compatible with older versions of Tahoe-LAFS. If no
4267+ "mutable-type" argument is given, the file is created in whatever
4268+ format was configured in tahoe.cfg.
4269 
4270 Creating A New Directory
4271 ------------------------
4272hunk ./docs/frontends/webapi.rst 1082
4273  If a "mutable=true" argument is provided, the operation will create a
4274  mutable file, and the response body will contain the write-cap instead of
4275  the upload results page. The default is to create an immutable file,
4276- returning the upload results page as a response.
4277+ returning the upload results page as a response. If you create a
4278+ mutable file, you may choose to specify the format of that mutable file
4279+ with the "mutable-type" parameter. If "mutable-type=mdmf", then the
4280+ file will be created as an MDMF mutable file. If "mutable-type=sdmf",
4281+ then the file will be created as an SDMF mutable file. If no value is
4282+ specified, the file will be created in whatever format is specified in
4283+ tahoe.cfg.
4284 
4285 
4286 ``POST /uri/$DIRCAP/[SUBDIRS../]?t=upload``
4287}
4288[mutable/layout.py and interfaces.py: add MDMF writer and reader
4289Kevan Carstensen <kevan@isnotajoke.com>**20100819003304
4290 Ignore-this: 44400fec923987b62830da2ed5075fb4
4291 
4292 The MDMF writer is responsible for keeping state as plaintext is
4293 gradually processed into share data by the upload process. When the
4294 upload finishes, it will write all of its share data to a remote server,
4295 reporting its status back to the publisher.
4296 
4297 The MDMF reader is responsible for abstracting an MDMF file as it sits
4298 on the grid from the downloader; specifically, by receiving and
4299 responding to requests for arbitrary data within the MDMF file.
4300 
4301 The interfaces.py file has also been modified to contain an interface
4302 for the writer.
4303] {
4304hunk ./src/allmydata/interfaces.py 7
4305      ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
4306 
4307 HASH_SIZE=32
4308+SALT_SIZE=16
4309+
4310+SDMF_VERSION=0
4311+MDMF_VERSION=1
4312 
4313 Hash = StringConstraint(maxLength=HASH_SIZE,
4314                         minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
4315hunk ./src/allmydata/interfaces.py 424
4316         """
4317 
4318 
4319+class IMutableSlotWriter(Interface):
4320+    """
4321+    The interface for a writer around a mutable slot on a remote server.
4322+    """
4323+    def set_checkstring(checkstring, *args):
4324+        """
4325+        Set the checkstring that I will pass to the remote server when
4326+        writing.
4327+
4328+            @param checkstring A packed checkstring to use.
4329+
4330+        Note that implementations can differ in which semantics they
4331+        wish to support for set_checkstring -- they can, for example,
4332+        build the checkstring themselves from its constituents, or
4333+        some other thing.
4334+        """
4335+
4336+    def get_checkstring():
4337+        """
4338+        Get the checkstring that I think currently exists on the remote
4339+        server.
4340+        """
4341+
4342+    def put_block(data, segnum, salt):
4343+        """
4344+        Add a block and salt to the share.
4345+        """
4346+
4347+    def put_encprivey(encprivkey):
4348+        """
4349+        Add the encrypted private key to the share.
4350+        """
4351+
4352+    def put_blockhashes(blockhashes=list):
4353+        """
4354+        Add the block hash tree to the share.
4355+        """
4356+
4357+    def put_sharehashes(sharehashes=dict):
4358+        """
4359+        Add the share hash chain to the share.
4360+        """
4361+
4362+    def get_signable():
4363+        """
4364+        Return the part of the share that needs to be signed.
4365+        """
4366+
4367+    def put_signature(signature):
4368+        """
4369+        Add the signature to the share.
4370+        """
4371+
4372+    def put_verification_key(verification_key):
4373+        """
4374+        Add the verification key to the share.
4375+        """
4376+
4377+    def finish_publishing():
4378+        """
4379+        Do anything necessary to finish writing the share to a remote
4380+        server. I require that no further publishing needs to take place
4381+        after this method has been called.
4382+        """
4383+
4384+
4385 class IURI(Interface):
4386     def init_from_string(uri):
4387         """Accept a string (as created by my to_string() method) and populate
4388hunk ./src/allmydata/mutable/layout.py 4
4389 
4390 import struct
4391 from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError
4392+from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \
4393+                                 MDMF_VERSION, IMutableSlotWriter
4394+from allmydata.util import mathutil, observer
4395+from twisted.python import failure
4396+from twisted.internet import defer
4397+from zope.interface import implements
4398+
4399+
4400+# These strings describe the format of the packed structs they help process
4401+# Here's what they mean:
4402+#
4403+#  PREFIX:
4404+#    >: Big-endian byte order; the most significant byte is first (leftmost).
4405+#    B: The version information; an 8 bit version identifier. Stored as
4406+#       an unsigned char. This is currently 00 00 00 00; our modifications
4407+#       will turn it into 00 00 00 01.
4408+#    Q: The sequence number; this is sort of like a revision history for
4409+#       mutable files; they start at 1 and increase as they are changed after
4410+#       being uploaded. Stored as an unsigned long long, which is 8 bytes in
4411+#       length.
4412+#  32s: The root hash of the share hash tree. We use sha-256d, so we use 32
4413+#       characters = 32 bytes to store the value.
4414+#  16s: The salt for the readkey. This is a 16-byte random value, stored as
4415+#       16 characters.
4416+#
4417+#  SIGNED_PREFIX additions, things that are covered by the signature:
4418+#    B: The "k" encoding parameter. We store this as an 8-bit character,
4419+#       which is convenient because our erasure coding scheme cannot
4420+#       encode if you ask for more than 255 pieces.
4421+#    B: The "N" encoding parameter. Stored as an 8-bit character for the
4422+#       same reasons as above.
4423+#    Q: The segment size of the uploaded file. This will essentially be the
4424+#       length of the file in SDMF. An unsigned long long, so we can store
4425+#       files of quite large size.
4426+#    Q: The data length of the uploaded file. Modulo padding, this will be
4427+#       the same of the data length field. Like the data length field, it is
4428+#       an unsigned long long and can be quite large.
4429+#
4430+#   HEADER additions:
4431+#     L: The offset of the signature of this. An unsigned long.
4432+#     L: The offset of the share hash chain. An unsigned long.
4433+#     L: The offset of the block hash tree. An unsigned long.
4434+#     L: The offset of the share data. An unsigned long.
4435+#     Q: The offset of the encrypted private key. An unsigned long long, to
4436+#        account for the possibility of a lot of share data.
4437+#     Q: The offset of the EOF. An unsigned long long, to account for the
4438+#        possibility of a lot of share data.
4439+#
4440+#  After all of these, we have the following:
4441+#    - The verification key: Occupies the space between the end of the header
4442+#      and the start of the signature (i.e.: data[HEADER_LENGTH:o['signature']].
4443+#    - The signature, which goes from the signature offset to the share hash
4444+#      chain offset.
4445+#    - The share hash chain, which goes from the share hash chain offset to
4446+#      the block hash tree offset.
4447+#    - The share data, which goes from the share data offset to the encrypted
4448+#      private key offset.
4449+#    - The encrypted private key offset, which goes until the end of the file.
4450+#
4451+#  The block hash tree in this encoding has only one share, so the offset of
4452+#  the share data will be 32 bits more than the offset of the block hash tree.
4453+#  Given this, we may need to check to see how many bytes a reasonably sized
4454+#  block hash tree will take up.
4455 
4456 PREFIX = ">BQ32s16s" # each version has a different prefix
4457 SIGNED_PREFIX = ">BQ32s16s BBQQ" # this is covered by the signature
4458hunk ./src/allmydata/mutable/layout.py 73
4459 SIGNED_PREFIX_LENGTH = struct.calcsize(SIGNED_PREFIX)
4460 HEADER = ">BQ32s16s BBQQ LLLLQQ" # includes offsets
4461 HEADER_LENGTH = struct.calcsize(HEADER)
4462+OFFSETS = ">LLLLQQ"
4463+OFFSETS_LENGTH = struct.calcsize(OFFSETS)
4464 
4465hunk ./src/allmydata/mutable/layout.py 76
4466+# These are still used for some tests.
4467 def unpack_header(data):
4468     o = {}
4469     (version,
4470hunk ./src/allmydata/mutable/layout.py 92
4471      o['EOF']) = struct.unpack(HEADER, data[:HEADER_LENGTH])
4472     return (version, seqnum, root_hash, IV, k, N, segsize, datalen, o)
4473 
4474-def unpack_prefix_and_signature(data):
4475-    assert len(data) >= HEADER_LENGTH, len(data)
4476-    prefix = data[:SIGNED_PREFIX_LENGTH]
4477-
4478-    (version,
4479-     seqnum,
4480-     root_hash,
4481-     IV,
4482-     k, N, segsize, datalen,
4483-     o) = unpack_header(data)
4484-
4485-    if version != 0:
4486-        raise UnknownVersionError("got mutable share version %d, but I only understand version 0" % version)
4487-
4488-    if len(data) < o['share_hash_chain']:
4489-        raise NeedMoreDataError(o['share_hash_chain'],
4490-                                o['enc_privkey'], o['EOF']-o['enc_privkey'])
4491-
4492-    pubkey_s = data[HEADER_LENGTH:o['signature']]
4493-    signature = data[o['signature']:o['share_hash_chain']]
4494-
4495-    return (seqnum, root_hash, IV, k, N, segsize, datalen,
4496-            pubkey_s, signature, prefix)
4497-
4498 def unpack_share(data):
4499     assert len(data) >= HEADER_LENGTH
4500     o = {}
4501hunk ./src/allmydata/mutable/layout.py 139
4502             pubkey, signature, share_hash_chain, block_hash_tree,
4503             share_data, enc_privkey)
4504 
4505-def unpack_share_data(verinfo, hash_and_data):
4506-    (seqnum, root_hash, IV, segsize, datalength, k, N, prefix, o_t) = verinfo
4507-
4508-    # hash_and_data starts with the share_hash_chain, so figure out what the
4509-    # offsets really are
4510-    o = dict(o_t)
4511-    o_share_hash_chain = 0
4512-    o_block_hash_tree = o['block_hash_tree'] - o['share_hash_chain']
4513-    o_share_data = o['share_data'] - o['share_hash_chain']
4514-    o_enc_privkey = o['enc_privkey'] - o['share_hash_chain']
4515-
4516-    share_hash_chain_s = hash_and_data[o_share_hash_chain:o_block_hash_tree]
4517-    share_hash_format = ">H32s"
4518-    hsize = struct.calcsize(share_hash_format)
4519-    assert len(share_hash_chain_s) % hsize == 0, len(share_hash_chain_s)
4520-    share_hash_chain = []
4521-    for i in range(0, len(share_hash_chain_s), hsize):
4522-        chunk = share_hash_chain_s[i:i+hsize]
4523-        (hid, h) = struct.unpack(share_hash_format, chunk)
4524-        share_hash_chain.append( (hid, h) )
4525-    share_hash_chain = dict(share_hash_chain)
4526-    block_hash_tree_s = hash_and_data[o_block_hash_tree:o_share_data]
4527-    assert len(block_hash_tree_s) % 32 == 0, len(block_hash_tree_s)
4528-    block_hash_tree = []
4529-    for i in range(0, len(block_hash_tree_s), 32):
4530-        block_hash_tree.append(block_hash_tree_s[i:i+32])
4531-
4532-    share_data = hash_and_data[o_share_data:o_enc_privkey]
4533-
4534-    return (share_hash_chain, block_hash_tree, share_data)
4535-
4536-
4537-def pack_checkstring(seqnum, root_hash, IV):
4538-    return struct.pack(PREFIX,
4539-                       0, # version,
4540-                       seqnum,
4541-                       root_hash,
4542-                       IV)
4543-
4544 def unpack_checkstring(checkstring):
4545     cs_len = struct.calcsize(PREFIX)
4546     version, seqnum, root_hash, IV = struct.unpack(PREFIX, checkstring[:cs_len])
4547hunk ./src/allmydata/mutable/layout.py 146
4548         raise UnknownVersionError("got mutable share version %d, but I only understand version 0" % version)
4549     return (seqnum, root_hash, IV)
4550 
4551-def pack_prefix(seqnum, root_hash, IV,
4552-                required_shares, total_shares,
4553-                segment_size, data_length):
4554-    prefix = struct.pack(SIGNED_PREFIX,
4555-                         0, # version,
4556-                         seqnum,
4557-                         root_hash,
4558-                         IV,
4559-
4560-                         required_shares,
4561-                         total_shares,
4562-                         segment_size,
4563-                         data_length,
4564-                         )
4565-    return prefix
4566 
4567 def pack_offsets(verification_key_length, signature_length,
4568                  share_hash_chain_length, block_hash_tree_length,
4569hunk ./src/allmydata/mutable/layout.py 192
4570                            encprivkey])
4571     return final_share
4572 
4573+def pack_prefix(seqnum, root_hash, IV,
4574+                required_shares, total_shares,
4575+                segment_size, data_length):
4576+    prefix = struct.pack(SIGNED_PREFIX,
4577+                         0, # version,
4578+                         seqnum,
4579+                         root_hash,
4580+                         IV,
4581+                         required_shares,
4582+                         total_shares,
4583+                         segment_size,
4584+                         data_length,
4585+                         )
4586+    return prefix
4587+
4588+
4589+class SDMFSlotWriteProxy:
4590+    implements(IMutableSlotWriter)
4591+    """
4592+    I represent a remote write slot for an SDMF mutable file. I build a
4593+    share in memory, and then write it in one piece to the remote
4594+    server. This mimics how SDMF shares were built before MDMF (and the
4595+    new MDMF uploader), but provides that functionality in a way that
4596+    allows the MDMF uploader to be built without much special-casing for
4597+    file format, which makes the uploader code more readable.
4598+    """
4599+    def __init__(self,
4600+                 shnum,
4601+                 rref, # a remote reference to a storage server
4602+                 storage_index,
4603+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4604+                 seqnum, # the sequence number of the mutable file
4605+                 required_shares,
4606+                 total_shares,
4607+                 segment_size,
4608+                 data_length): # the length of the original file
4609+        self.shnum = shnum
4610+        self._rref = rref
4611+        self._storage_index = storage_index
4612+        self._secrets = secrets
4613+        self._seqnum = seqnum
4614+        self._required_shares = required_shares
4615+        self._total_shares = total_shares
4616+        self._segment_size = segment_size
4617+        self._data_length = data_length
4618+
4619+        # This is an SDMF file, so it should have only one segment, so,
4620+        # modulo padding of the data length, the segment size and the
4621+        # data length should be the same.
4622+        expected_segment_size = mathutil.next_multiple(data_length,
4623+                                                       self._required_shares)
4624+        assert expected_segment_size == segment_size
4625+
4626+        self._block_size = self._segment_size / self._required_shares
4627+
4628+        # This is meant to mimic how SDMF files were built before MDMF
4629+        # entered the picture: we generate each share in its entirety,
4630+        # then push it off to the storage server in one write. When
4631+        # callers call set_*, they are just populating this dict.
4632+        # finish_publishing will stitch these pieces together into a
4633+        # coherent share, and then write the coherent share to the
4634+        # storage server.
4635+        self._share_pieces = {}
4636+
4637+        # This tells the write logic what checkstring to use when
4638+        # writing remote shares.
4639+        self._testvs = []
4640+
4641+        self._readvs = [(0, struct.calcsize(PREFIX))]
4642+
4643+
4644+    def set_checkstring(self, checkstring_or_seqnum,
4645+                              root_hash=None,
4646+                              salt=None):
4647+        """
4648+        Set the checkstring that I will pass to the remote server when
4649+        writing.
4650+
4651+            @param checkstring_or_seqnum: A packed checkstring to use,
4652+                   or a sequence number. I will treat this as a checkstr
4653+
4654+        Note that implementations can differ in which semantics they
4655+        wish to support for set_checkstring -- they can, for example,
4656+        build the checkstring themselves from its constituents, or
4657+        some other thing.
4658+        """
4659+        if root_hash and salt:
4660+            checkstring = struct.pack(PREFIX,
4661+                                      0,
4662+                                      checkstring_or_seqnum,
4663+                                      root_hash,
4664+                                      salt)
4665+        else:
4666+            checkstring = checkstring_or_seqnum
4667+        self._testvs = [(0, len(checkstring), "eq", checkstring)]
4668+
4669+
4670+    def get_checkstring(self):
4671+        """
4672+        Get the checkstring that I think currently exists on the remote
4673+        server.
4674+        """
4675+        if self._testvs:
4676+            return self._testvs[0][3]
4677+        return ""
4678+
4679+
4680+    def put_block(self, data, segnum, salt):
4681+        """
4682+        Add a block and salt to the share.
4683+        """
4684+        # SDMF files have only one segment
4685+        assert segnum == 0
4686+        assert len(data) == self._block_size
4687+        assert len(salt) == SALT_SIZE
4688+
4689+        self._share_pieces['sharedata'] = data
4690+        self._share_pieces['salt'] = salt
4691+
4692+        # TODO: Figure out something intelligent to return.
4693+        return defer.succeed(None)
4694+
4695+
4696+    def put_encprivkey(self, encprivkey):
4697+        """
4698+        Add the encrypted private key to the share.
4699+        """
4700+        self._share_pieces['encprivkey'] = encprivkey
4701+
4702+        return defer.succeed(None)
4703+
4704+
4705+    def put_blockhashes(self, blockhashes):
4706+        """
4707+        Add the block hash tree to the share.
4708+        """
4709+        assert isinstance(blockhashes, list)
4710+        for h in blockhashes:
4711+            assert len(h) == HASH_SIZE
4712+
4713+        # serialize the blockhashes, then set them.
4714+        blockhashes_s = "".join(blockhashes)
4715+        self._share_pieces['block_hash_tree'] = blockhashes_s
4716+
4717+        return defer.succeed(None)
4718+
4719+
4720+    def put_sharehashes(self, sharehashes):
4721+        """
4722+        Add the share hash chain to the share.
4723+        """
4724+        assert isinstance(sharehashes, dict)
4725+        for h in sharehashes.itervalues():
4726+            assert len(h) == HASH_SIZE
4727+
4728+        # serialize the sharehashes, then set them.
4729+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4730+                                 for i in sorted(sharehashes.keys())])
4731+        self._share_pieces['share_hash_chain'] = sharehashes_s
4732+
4733+        return defer.succeed(None)
4734+
4735+
4736+    def put_root_hash(self, root_hash):
4737+        """
4738+        Add the root hash to the share.
4739+        """
4740+        assert len(root_hash) == HASH_SIZE
4741+
4742+        self._share_pieces['root_hash'] = root_hash
4743+
4744+        return defer.succeed(None)
4745+
4746+
4747+    def put_salt(self, salt):
4748+        """
4749+        Add a salt to an empty SDMF file.
4750+        """
4751+        assert len(salt) == SALT_SIZE
4752+
4753+        self._share_pieces['salt'] = salt
4754+        self._share_pieces['sharedata'] = ""
4755+
4756+
4757+    def get_signable(self):
4758+        """
4759+        Return the part of the share that needs to be signed.
4760+
4761+        SDMF writers need to sign the packed representation of the
4762+        first eight fields of the remote share, that is:
4763+            - version number (0)
4764+            - sequence number
4765+            - root of the share hash tree
4766+            - salt
4767+            - k
4768+            - n
4769+            - segsize
4770+            - datalen
4771+
4772+        This method is responsible for returning that to callers.
4773+        """
4774+        return struct.pack(SIGNED_PREFIX,
4775+                           0,
4776+                           self._seqnum,
4777+                           self._share_pieces['root_hash'],
4778+                           self._share_pieces['salt'],
4779+                           self._required_shares,
4780+                           self._total_shares,
4781+                           self._segment_size,
4782+                           self._data_length)
4783+
4784+
4785+    def put_signature(self, signature):
4786+        """
4787+        Add the signature to the share.
4788+        """
4789+        self._share_pieces['signature'] = signature
4790+
4791+        return defer.succeed(None)
4792+
4793+
4794+    def put_verification_key(self, verification_key):
4795+        """
4796+        Add the verification key to the share.
4797+        """
4798+        self._share_pieces['verification_key'] = verification_key
4799+
4800+        return defer.succeed(None)
4801+
4802+
4803+    def get_verinfo(self):
4804+        """
4805+        I return my verinfo tuple. This is used by the ServermapUpdater
4806+        to keep track of versions of mutable files.
4807+
4808+        The verinfo tuple for MDMF files contains:
4809+            - seqnum
4810+            - root hash
4811+            - a blank (nothing)
4812+            - segsize
4813+            - datalen
4814+            - k
4815+            - n
4816+            - prefix (the thing that you sign)
4817+            - a tuple of offsets
4818+
4819+        We include the nonce in MDMF to simplify processing of version
4820+        information tuples.
4821+
4822+        The verinfo tuple for SDMF files is the same, but contains a
4823+        16-byte IV instead of a hash of salts.
4824+        """
4825+        return (self._seqnum,
4826+                self._share_pieces['root_hash'],
4827+                self._share_pieces['salt'],
4828+                self._segment_size,
4829+                self._data_length,
4830+                self._required_shares,
4831+                self._total_shares,
4832+                self.get_signable(),
4833+                self._get_offsets_tuple())
4834+
4835+    def _get_offsets_dict(self):
4836+        post_offset = HEADER_LENGTH
4837+        offsets = {}
4838+
4839+        verification_key_length = len(self._share_pieces['verification_key'])
4840+        o1 = offsets['signature'] = post_offset + verification_key_length
4841+
4842+        signature_length = len(self._share_pieces['signature'])
4843+        o2 = offsets['share_hash_chain'] = o1 + signature_length
4844+
4845+        share_hash_chain_length = len(self._share_pieces['share_hash_chain'])
4846+        o3 = offsets['block_hash_tree'] = o2 + share_hash_chain_length
4847+
4848+        block_hash_tree_length = len(self._share_pieces['block_hash_tree'])
4849+        o4 = offsets['share_data'] = o3 + block_hash_tree_length
4850+
4851+        share_data_length = len(self._share_pieces['sharedata'])
4852+        o5 = offsets['enc_privkey'] = o4 + share_data_length
4853+
4854+        encprivkey_length = len(self._share_pieces['encprivkey'])
4855+        offsets['EOF'] = o5 + encprivkey_length
4856+        return offsets
4857+
4858+
4859+    def _get_offsets_tuple(self):
4860+        offsets = self._get_offsets_dict()
4861+        return tuple([(key, value) for key, value in offsets.items()])
4862+
4863+
4864+    def _pack_offsets(self):
4865+        offsets = self._get_offsets_dict()
4866+        return struct.pack(">LLLLQQ",
4867+                           offsets['signature'],
4868+                           offsets['share_hash_chain'],
4869+                           offsets['block_hash_tree'],
4870+                           offsets['share_data'],
4871+                           offsets['enc_privkey'],
4872+                           offsets['EOF'])
4873+
4874+
4875+    def finish_publishing(self):
4876+        """
4877+        Do anything necessary to finish writing the share to a remote
4878+        server. I require that no further publishing needs to take place
4879+        after this method has been called.
4880+        """
4881+        for k in ["sharedata", "encprivkey", "signature", "verification_key",
4882+                  "share_hash_chain", "block_hash_tree"]:
4883+            assert k in self._share_pieces
4884+        # This is the only method that actually writes something to the
4885+        # remote server.
4886+        # First, we need to pack the share into data that we can write
4887+        # to the remote server in one write.
4888+        offsets = self._pack_offsets()
4889+        prefix = self.get_signable()
4890+        final_share = "".join([prefix,
4891+                               offsets,
4892+                               self._share_pieces['verification_key'],
4893+                               self._share_pieces['signature'],
4894+                               self._share_pieces['share_hash_chain'],
4895+                               self._share_pieces['block_hash_tree'],
4896+                               self._share_pieces['sharedata'],
4897+                               self._share_pieces['encprivkey']])
4898+
4899+        # Our only data vector is going to be writing the final share,
4900+        # in its entirely.
4901+        datavs = [(0, final_share)]
4902+
4903+        if not self._testvs:
4904+            # Our caller has not provided us with another checkstring
4905+            # yet, so we assume that we are writing a new share, and set
4906+            # a test vector that will allow a new share to be written.
4907+            self._testvs = []
4908+            self._testvs.append(tuple([0, 1, "eq", ""]))
4909+
4910+        tw_vectors = {}
4911+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
4912+        return self._rref.callRemote("slot_testv_and_readv_and_writev",
4913+                                     self._storage_index,
4914+                                     self._secrets,
4915+                                     tw_vectors,
4916+                                     # TODO is it useful to read something?
4917+                                     self._readvs)
4918+
4919+
4920+MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
4921+MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
4922+MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
4923+MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
4924+MDMFCHECKSTRING = ">BQ32s"
4925+MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
4926+MDMFOFFSETS = ">QQQQQQ"
4927+MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
4928+
4929+class MDMFSlotWriteProxy:
4930+    implements(IMutableSlotWriter)
4931+
4932+    """
4933+    I represent a remote write slot for an MDMF mutable file.
4934+
4935+    I abstract away from my caller the details of block and salt
4936+    management, and the implementation of the on-disk format for MDMF
4937+    shares.
4938+    """
4939+    # Expected layout, MDMF:
4940+    # offset:     size:       name:
4941+    #-- signed part --
4942+    # 0           1           version number (01)
4943+    # 1           8           sequence number
4944+    # 9           32          share tree root hash
4945+    # 41          1           The "k" encoding parameter
4946+    # 42          1           The "N" encoding parameter
4947+    # 43          8           The segment size of the uploaded file
4948+    # 51          8           The data length of the original plaintext
4949+    #-- end signed part --
4950+    # 59          8           The offset of the encrypted private key
4951+    # 83          8           The offset of the signature
4952+    # 91          8           The offset of the verification key
4953+    # 67          8           The offset of the block hash tree
4954+    # 75          8           The offset of the share hash chain
4955+    # 99          8           The offset of the EOF
4956+    #
4957+    # followed by salts and share data, the encrypted private key, the
4958+    # block hash tree, the salt hash tree, the share hash chain, a
4959+    # signature over the first eight fields, and a verification key.
4960+    #
4961+    # The checkstring is the first three fields -- the version number,
4962+    # sequence number, root hash and root salt hash. This is consistent
4963+    # in meaning to what we have with SDMF files, except now instead of
4964+    # using the literal salt, we use a value derived from all of the
4965+    # salts -- the share hash root.
4966+    #
4967+    # The salt is stored before the block for each segment. The block
4968+    # hash tree is computed over the combination of block and salt for
4969+    # each segment. In this way, we get integrity checking for both
4970+    # block and salt with the current block hash tree arrangement.
4971+    #
4972+    # The ordering of the offsets is different to reflect the dependencies
4973+    # that we'll run into with an MDMF file. The expected write flow is
4974+    # something like this:
4975+    #
4976+    #   0: Initialize with the sequence number, encoding parameters and
4977+    #      data length. From this, we can deduce the number of segments,
4978+    #      and where they should go.. We can also figure out where the
4979+    #      encrypted private key should go, because we can figure out how
4980+    #      big the share data will be.
4981+    #
4982+    #   1: Encrypt, encode, and upload the file in chunks. Do something
4983+    #      like
4984+    #
4985+    #       put_block(data, segnum, salt)
4986+    #
4987+    #      to write a block and a salt to the disk. We can do both of
4988+    #      these operations now because we have enough of the offsets to
4989+    #      know where to put them.
4990+    #
4991+    #   2: Put the encrypted private key. Use:
4992+    #
4993+    #        put_encprivkey(encprivkey)
4994+    #
4995+    #      Now that we know the length of the private key, we can fill
4996+    #      in the offset for the block hash tree.
4997+    #
4998+    #   3: We're now in a position to upload the block hash tree for
4999+    #      a share. Put that using something like:
5000+    #       
5001+    #        put_blockhashes(block_hash_tree)
5002+    #
5003+    #      Note that block_hash_tree is a list of hashes -- we'll take
5004+    #      care of the details of serializing that appropriately. When
5005+    #      we get the block hash tree, we are also in a position to
5006+    #      calculate the offset for the share hash chain, and fill that
5007+    #      into the offsets table.
5008+    #
5009+    #   4: At the same time, we're in a position to upload the salt hash
5010+    #      tree. This is a Merkle tree over all of the salts. We use a
5011+    #      Merkle tree so that we can validate each block,salt pair as
5012+    #      we download them later. We do this using
5013+    #
5014+    #        put_salthashes(salt_hash_tree)
5015+    #
5016+    #      When you do this, I automatically put the root of the tree
5017+    #      (the hash at index 0 of the list) in its appropriate slot in
5018+    #      the signed prefix of the share.
5019+    #
5020+    #   5: We're now in a position to upload the share hash chain for
5021+    #      a share. Do that with something like:
5022+    #     
5023+    #        put_sharehashes(share_hash_chain)
5024+    #
5025+    #      share_hash_chain should be a dictionary mapping shnums to
5026+    #      32-byte hashes -- the wrapper handles serialization.
5027+    #      We'll know where to put the signature at this point, also.
5028+    #      The root of this tree will be put explicitly in the next
5029+    #      step.
5030+    #
5031+    #      TODO: Why? Why not just include it in the tree here?
5032+    #
5033+    #   6: Before putting the signature, we must first put the
5034+    #      root_hash. Do this with:
5035+    #
5036+    #        put_root_hash(root_hash).
5037+    #     
5038+    #      In terms of knowing where to put this value, it was always
5039+    #      possible to place it, but it makes sense semantically to
5040+    #      place it after the share hash tree, so that's why you do it
5041+    #      in this order.
5042+    #
5043+    #   6: With the root hash put, we can now sign the header. Use:
5044+    #
5045+    #        get_signable()
5046+    #
5047+    #      to get the part of the header that you want to sign, and use:
5048+    #       
5049+    #        put_signature(signature)
5050+    #
5051+    #      to write your signature to the remote server.
5052+    #
5053+    #   6: Add the verification key, and finish. Do:
5054+    #
5055+    #        put_verification_key(key)
5056+    #
5057+    #      and
5058+    #
5059+    #        finish_publish()
5060+    #
5061+    # Checkstring management:
5062+    #
5063+    # To write to a mutable slot, we have to provide test vectors to ensure
5064+    # that we are writing to the same data that we think we are. These
5065+    # vectors allow us to detect uncoordinated writes; that is, writes
5066+    # where both we and some other shareholder are writing to the
5067+    # mutable slot, and to report those back to the parts of the program
5068+    # doing the writing.
5069+    #
5070+    # With SDMF, this was easy -- all of the share data was written in
5071+    # one go, so it was easy to detect uncoordinated writes, and we only
5072+    # had to do it once. With MDMF, not all of the file is written at
5073+    # once.
5074+    #
5075+    # If a share is new, we write out as much of the header as we can
5076+    # before writing out anything else. This gives other writers a
5077+    # canary that they can use to detect uncoordinated writes, and, if
5078+    # they do the same thing, gives us the same canary. We them update
5079+    # the share. We won't be able to write out two fields of the header
5080+    # -- the share tree hash and the salt hash -- until we finish
5081+    # writing out the share. We only require the writer to provide the
5082+    # initial checkstring, and keep track of what it should be after
5083+    # updates ourselves.
5084+    #
5085+    # If we haven't written anything yet, then on the first write (which
5086+    # will probably be a block + salt of a share), we'll also write out
5087+    # the header. On subsequent passes, we'll expect to see the header.
5088+    # This changes in two places:
5089+    #
5090+    #   - When we write out the salt hash
5091+    #   - When we write out the root of the share hash tree
5092+    #
5093+    # since these values will change the header. It is possible that we
5094+    # can just make those be written in one operation to minimize
5095+    # disruption.
5096+    def __init__(self,
5097+                 shnum,
5098+                 rref, # a remote reference to a storage server
5099+                 storage_index,
5100+                 secrets, # (write_enabler, renew_secret, cancel_secret)
5101+                 seqnum, # the sequence number of the mutable file
5102+                 required_shares,
5103+                 total_shares,
5104+                 segment_size,
5105+                 data_length): # the length of the original file
5106+        self.shnum = shnum
5107+        self._rref = rref
5108+        self._storage_index = storage_index
5109+        self._seqnum = seqnum
5110+        self._required_shares = required_shares
5111+        assert self.shnum >= 0 and self.shnum < total_shares
5112+        self._total_shares = total_shares
5113+        # We build up the offset table as we write things. It is the
5114+        # last thing we write to the remote server.
5115+        self._offsets = {}
5116+        self._testvs = []
5117+        # This is a list of write vectors that will be sent to our
5118+        # remote server once we are directed to write things there.
5119+        self._writevs = []
5120+        self._secrets = secrets
5121+        # The segment size needs to be a multiple of the k parameter --
5122+        # any padding should have been carried out by the publisher
5123+        # already.
5124+        assert segment_size % required_shares == 0
5125+        self._segment_size = segment_size
5126+        self._data_length = data_length
5127+
5128+        # These are set later -- we define them here so that we can
5129+        # check for their existence easily
5130+
5131+        # This is the root of the share hash tree -- the Merkle tree
5132+        # over the roots of the block hash trees computed for shares in
5133+        # this upload.
5134+        self._root_hash = None
5135+
5136+        # We haven't yet written anything to the remote bucket. By
5137+        # setting this, we tell the _write method as much. The write
5138+        # method will then know that it also needs to add a write vector
5139+        # for the checkstring (or what we have of it) to the first write
5140+        # request. We'll then record that value for future use.  If
5141+        # we're expecting something to be there already, we need to call
5142+        # set_checkstring before we write anything to tell the first
5143+        # write about that.
5144+        self._written = False
5145+
5146+        # When writing data to the storage servers, we get a read vector
5147+        # for free. We'll read the checkstring, which will help us
5148+        # figure out what's gone wrong if a write fails.
5149+        self._readv = [(0, struct.calcsize(MDMFCHECKSTRING))]
5150+
5151+        # We calculate the number of segments because it tells us
5152+        # where the salt part of the file ends/share segment begins,
5153+        # and also because it provides a useful amount of bounds checking.
5154+        self._num_segments = mathutil.div_ceil(self._data_length,
5155+                                               self._segment_size)
5156+        self._block_size = self._segment_size / self._required_shares
5157+        # We also calculate the share size, to help us with block
5158+        # constraints later.
5159+        tail_size = self._data_length % self._segment_size
5160+        if not tail_size:
5161+            self._tail_block_size = self._block_size
5162+        else:
5163+            self._tail_block_size = mathutil.next_multiple(tail_size,
5164+                                                           self._required_shares)
5165+            self._tail_block_size /= self._required_shares
5166+
5167+        # We already know where the sharedata starts; right after the end
5168+        # of the header (which is defined as the signable part + the offsets)
5169+        # We can also calculate where the encrypted private key begins
5170+        # from what we know know.
5171+        self._actual_block_size = self._block_size + SALT_SIZE
5172+        data_size = self._actual_block_size * (self._num_segments - 1)
5173+        data_size += self._tail_block_size
5174+        data_size += SALT_SIZE
5175+        self._offsets['enc_privkey'] = MDMFHEADERSIZE
5176+        self._offsets['enc_privkey'] += data_size
5177+        # We'll wait for the rest. Callers can now call my "put_block" and
5178+        # "set_checkstring" methods.
5179+
5180+
5181+    def set_checkstring(self,
5182+                        seqnum_or_checkstring,
5183+                        root_hash=None,
5184+                        salt=None):
5185+        """
5186+        Set checkstring checkstring for the given shnum.
5187+
5188+        This can be invoked in one of two ways.
5189+
5190+        With one argument, I assume that you are giving me a literal
5191+        checkstring -- e.g., the output of get_checkstring. I will then
5192+        set that checkstring as it is. This form is used by unit tests.
5193+
5194+        With two arguments, I assume that you are giving me a sequence
5195+        number and root hash to make a checkstring from. In that case, I
5196+        will build a checkstring and set it for you. This form is used
5197+        by the publisher.
5198+
5199+        By default, I assume that I am writing new shares to the grid.
5200+        If you don't explcitly set your own checkstring, I will use
5201+        one that requires that the remote share not exist. You will want
5202+        to use this method if you are updating a share in-place;
5203+        otherwise, writes will fail.
5204+        """
5205+        # You're allowed to overwrite checkstrings with this method;
5206+        # I assume that users know what they are doing when they call
5207+        # it.
5208+        if root_hash:
5209+            checkstring = struct.pack(MDMFCHECKSTRING,
5210+                                      1,
5211+                                      seqnum_or_checkstring,
5212+                                      root_hash)
5213+        else:
5214+            checkstring = seqnum_or_checkstring
5215+
5216+        if checkstring == "":
5217+            # We special-case this, since len("") = 0, but we need
5218+            # length of 1 for the case of an empty share to work on the
5219+            # storage server, which is what a checkstring that is the
5220+            # empty string means.
5221+            self._testvs = []
5222+        else:
5223+            self._testvs = []
5224+            self._testvs.append((0, len(checkstring), "eq", checkstring))
5225+
5226+
5227+    def __repr__(self):
5228+        return "MDMFSlotWriteProxy for share %d" % self.shnum
5229+
5230+
5231+    def get_checkstring(self):
5232+        """
5233+        Given a share number, I return a representation of what the
5234+        checkstring for that share on the server will look like.
5235+
5236+        I am mostly used for tests.
5237+        """
5238+        if self._root_hash:
5239+            roothash = self._root_hash
5240+        else:
5241+            roothash = "\x00" * 32
5242+        return struct.pack(MDMFCHECKSTRING,
5243+                           1,
5244+                           self._seqnum,
5245+                           roothash)
5246+
5247+
5248+    def put_block(self, data, segnum, salt):
5249+        """
5250+        I queue a write vector for the data, salt, and segment number
5251+        provided to me. I return None, as I do not actually cause
5252+        anything to be written yet.
5253+        """
5254+        if segnum >= self._num_segments:
5255+            raise LayoutInvalid("I won't overwrite the private key")
5256+        if len(salt) != SALT_SIZE:
5257+            raise LayoutInvalid("I was given a salt of size %d, but "
5258+                                "I wanted a salt of size %d")
5259+        if segnum + 1 == self._num_segments:
5260+            if len(data) != self._tail_block_size:
5261+                raise LayoutInvalid("I was given the wrong size block to write")
5262+        elif len(data) != self._block_size:
5263+            raise LayoutInvalid("I was given the wrong size block to write")
5264+
5265+        # We want to write at len(MDMFHEADER) + segnum * block_size.
5266+
5267+        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
5268+        data = salt + data
5269+
5270+        self._writevs.append(tuple([offset, data]))
5271+
5272+
5273+    def put_encprivkey(self, encprivkey):
5274+        """
5275+        I queue a write vector for the encrypted private key provided to
5276+        me.
5277+        """
5278+        assert self._offsets
5279+        assert self._offsets['enc_privkey']
5280+        # You shouldn't re-write the encprivkey after the block hash
5281+        # tree is written, since that could cause the private key to run
5282+        # into the block hash tree. Before it writes the block hash
5283+        # tree, the block hash tree writing method writes the offset of
5284+        # the salt hash tree. So that's a good indicator of whether or
5285+        # not the block hash tree has been written.
5286+        if "share_hash_chain" in self._offsets:
5287+            raise LayoutInvalid("You must write this before the block hash tree")
5288+
5289+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + \
5290+            len(encprivkey)
5291+        self._writevs.append(tuple([self._offsets['enc_privkey'], encprivkey]))
5292+
5293+
5294+    def put_blockhashes(self, blockhashes):
5295+        """
5296+        I queue a write vector to put the block hash tree in blockhashes
5297+        onto the remote server.
5298+
5299+        The encrypted private key must be queued before the block hash
5300+        tree, since we need to know how large it is to know where the
5301+        block hash tree should go. The block hash tree must be put
5302+        before the salt hash tree, since its size determines the
5303+        offset of the share hash chain.
5304+        """
5305+        assert self._offsets
5306+        assert isinstance(blockhashes, list)
5307+        if "block_hash_tree" not in self._offsets:
5308+            raise LayoutInvalid("You must put the encrypted private key "
5309+                                "before you put the block hash tree")
5310+        # If written, the share hash chain causes the signature offset
5311+        # to be defined.
5312+        if "signature" in self._offsets:
5313+            raise LayoutInvalid("You must put the block hash tree before "
5314+                                "you put the share hash chain")
5315+        blockhashes_s = "".join(blockhashes)
5316+        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
5317+
5318+        self._writevs.append(tuple([self._offsets['block_hash_tree'],
5319+                                  blockhashes_s]))
5320+
5321+
5322+    def put_sharehashes(self, sharehashes):
5323+        """
5324+        I queue a write vector to put the share hash chain in my
5325+        argument onto the remote server.
5326+
5327+        The salt hash tree must be queued before the share hash chain,
5328+        since we need to know where the salt hash tree ends before we
5329+        can know where the share hash chain starts. The share hash chain
5330+        must be put before the signature, since the length of the packed
5331+        share hash chain determines the offset of the signature. Also,
5332+        semantically, you must know what the root of the salt hash tree
5333+        is before you can generate a valid signature.
5334+        """
5335+        assert isinstance(sharehashes, dict)
5336+        if "share_hash_chain" not in self._offsets:
5337+            raise LayoutInvalid("You need to put the salt hash tree before "
5338+                                "you can put the share hash chain")
5339+        # The signature comes after the share hash chain. If the
5340+        # signature has already been written, we must not write another
5341+        # share hash chain. The signature writes the verification key
5342+        # offset when it gets sent to the remote server, so we look for
5343+        # that.
5344+        if "verification_key" in self._offsets:
5345+            raise LayoutInvalid("You must write the share hash chain "
5346+                                "before you write the signature")
5347+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
5348+                                  for i in sorted(sharehashes.keys())])
5349+        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
5350+        self._writevs.append(tuple([self._offsets['share_hash_chain'],
5351+                            sharehashes_s]))
5352+
5353+
5354+    def put_root_hash(self, roothash):
5355+        """
5356+        Put the root hash (the root of the share hash tree) in the
5357+        remote slot.
5358+        """
5359+        # It does not make sense to be able to put the root
5360+        # hash without first putting the share hashes, since you need
5361+        # the share hashes to generate the root hash.
5362+        #
5363+        # Signature is defined by the routine that places the share hash
5364+        # chain, so it's a good thing to look for in finding out whether
5365+        # or not the share hash chain exists on the remote server.
5366+        if "signature" not in self._offsets:
5367+            raise LayoutInvalid("You need to put the share hash chain "
5368+                                "before you can put the root share hash")
5369+        if len(roothash) != HASH_SIZE:
5370+            raise LayoutInvalid("hashes and salts must be exactly %d bytes"
5371+                                 % HASH_SIZE)
5372+        self._root_hash = roothash
5373+        # To write both of these values, we update the checkstring on
5374+        # the remote server, which includes them
5375+        checkstring = self.get_checkstring()
5376+        self._writevs.append(tuple([0, checkstring]))
5377+        # This write, if successful, changes the checkstring, so we need
5378+        # to update our internal checkstring to be consistent with the
5379+        # one on the server.
5380+
5381+
5382+    def get_signable(self):
5383+        """
5384+        Get the first seven fields of the mutable file; the parts that
5385+        are signed.
5386+        """
5387+        if not self._root_hash:
5388+            raise LayoutInvalid("You need to set the root hash "
5389+                                "before getting something to "
5390+                                "sign")
5391+        return struct.pack(MDMFSIGNABLEHEADER,
5392+                           1,
5393+                           self._seqnum,
5394+                           self._root_hash,
5395+                           self._required_shares,
5396+                           self._total_shares,
5397+                           self._segment_size,
5398+                           self._data_length)
5399+
5400+
5401+    def put_signature(self, signature):
5402+        """
5403+        I queue a write vector for the signature of the MDMF share.
5404+
5405+        I require that the root hash and share hash chain have been put
5406+        to the grid before I will write the signature to the grid.
5407+        """
5408+        if "signature" not in self._offsets:
5409+            raise LayoutInvalid("You must put the share hash chain "
5410+        # It does not make sense to put a signature without first
5411+        # putting the root hash and the salt hash (since otherwise
5412+        # the signature would be incomplete), so we don't allow that.
5413+                       "before putting the signature")
5414+        if not self._root_hash:
5415+            raise LayoutInvalid("You must complete the signed prefix "
5416+                                "before computing a signature")
5417+        # If we put the signature after we put the verification key, we
5418+        # could end up running into the verification key, and will
5419+        # probably screw up the offsets as well. So we don't allow that.
5420+        # The method that writes the verification key defines the EOF
5421+        # offset before writing the verification key, so look for that.
5422+        if "EOF" in self._offsets:
5423+            raise LayoutInvalid("You must write the signature before the verification key")
5424+
5425+        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
5426+        self._writevs.append(tuple([self._offsets['signature'], signature]))
5427+
5428+
5429+    def put_verification_key(self, verification_key):
5430+        """
5431+        I queue a write vector for the verification key.
5432+
5433+        I require that the signature have been written to the storage
5434+        server before I allow the verification key to be written to the
5435+        remote server.
5436+        """
5437+        if "verification_key" not in self._offsets:
5438+            raise LayoutInvalid("You must put the signature before you "
5439+                                "can put the verification key")
5440+        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
5441+        self._writevs.append(tuple([self._offsets['verification_key'],
5442+                            verification_key]))
5443+
5444+
5445+    def _get_offsets_tuple(self):
5446+        return tuple([(key, value) for key, value in self._offsets.items()])
5447+
5448+
5449+    def get_verinfo(self):
5450+        return (self._seqnum,
5451+                self._root_hash,
5452+                self._required_shares,
5453+                self._total_shares,
5454+                self._segment_size,
5455+                self._data_length,
5456+                self.get_signable(),
5457+                self._get_offsets_tuple())
5458+
5459+
5460+    def finish_publishing(self):
5461+        """
5462+        I add a write vector for the offsets table, and then cause all
5463+        of the write vectors that I've dealt with so far to be published
5464+        to the remote server, ending the write process.
5465+        """
5466+        if "EOF" not in self._offsets:
5467+            raise LayoutInvalid("You must put the verification key before "
5468+                                "you can publish the offsets")
5469+        offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
5470+        offsets = struct.pack(MDMFOFFSETS,
5471+                              self._offsets['enc_privkey'],
5472+                              self._offsets['block_hash_tree'],
5473+                              self._offsets['share_hash_chain'],
5474+                              self._offsets['signature'],
5475+                              self._offsets['verification_key'],
5476+                              self._offsets['EOF'])
5477+        self._writevs.append(tuple([offsets_offset, offsets]))
5478+        encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
5479+        params = struct.pack(">BBQQ",
5480+                             self._required_shares,
5481+                             self._total_shares,
5482+                             self._segment_size,
5483+                             self._data_length)
5484+        self._writevs.append(tuple([encoding_parameters_offset, params]))
5485+        return self._write(self._writevs)
5486+
5487+
5488+    def _write(self, datavs, on_failure=None, on_success=None):
5489+        """I write the data vectors in datavs to the remote slot."""
5490+        tw_vectors = {}
5491+        if not self._testvs:
5492+            self._testvs = []
5493+            self._testvs.append(tuple([0, 1, "eq", ""]))
5494+        if not self._written:
5495+            # Write a new checkstring to the share when we write it, so
5496+            # that we have something to check later.
5497+            new_checkstring = self.get_checkstring()
5498+            datavs.append((0, new_checkstring))
5499+            def _first_write():
5500+                self._written = True
5501+                self._testvs = [(0, len(new_checkstring), "eq", new_checkstring)]
5502+            on_success = _first_write
5503+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
5504+        d = self._rref.callRemote("slot_testv_and_readv_and_writev",
5505+                                  self._storage_index,
5506+                                  self._secrets,
5507+                                  tw_vectors,
5508+                                  self._readv)
5509+        def _result(results):
5510+            if isinstance(results, failure.Failure) or not results[0]:
5511+                # Do nothing; the write was unsuccessful.
5512+                if on_failure: on_failure()
5513+            else:
5514+                if on_success: on_success()
5515+            return results
5516+        d.addCallback(_result)
5517+        return d
5518+
5519+
5520+class MDMFSlotReadProxy:
5521+    """
5522+    I read from a mutable slot filled with data written in the MDMF data
5523+    format (which is described above).
5524+
5525+    I can be initialized with some amount of data, which I will use (if
5526+    it is valid) to eliminate some of the need to fetch it from servers.
5527+    """
5528+    def __init__(self,
5529+                 rref,
5530+                 storage_index,
5531+                 shnum,
5532+                 data=""):
5533+        # Start the initialization process.
5534+        self._rref = rref
5535+        self._storage_index = storage_index
5536+        self.shnum = shnum
5537+
5538+        # Before doing anything, the reader is probably going to want to
5539+        # verify that the signature is correct. To do that, they'll need
5540+        # the verification key, and the signature. To get those, we'll
5541+        # need the offset table. So fetch the offset table on the
5542+        # assumption that that will be the first thing that a reader is
5543+        # going to do.
5544+
5545+        # The fact that these encoding parameters are None tells us
5546+        # that we haven't yet fetched them from the remote share, so we
5547+        # should. We could just not set them, but the checks will be
5548+        # easier to read if we don't have to use hasattr.
5549+        self._version_number = None
5550+        self._sequence_number = None
5551+        self._root_hash = None
5552+        # Filled in if we're dealing with an SDMF file. Unused
5553+        # otherwise.
5554+        self._salt = None
5555+        self._required_shares = None
5556+        self._total_shares = None
5557+        self._segment_size = None
5558+        self._data_length = None
5559+        self._offsets = None
5560+
5561+        # If the user has chosen to initialize us with some data, we'll
5562+        # try to satisfy subsequent data requests with that data before
5563+        # asking the storage server for it. If
5564+        self._data = data
5565+        # The way callers interact with cache in the filenode returns
5566+        # None if there isn't any cached data, but the way we index the
5567+        # cached data requires a string, so convert None to "".
5568+        if self._data == None:
5569+            self._data = ""
5570+
5571+        self._queue_observers = observer.ObserverList()
5572+        self._queue_errbacks = observer.ObserverList()
5573+        self._readvs = []
5574+
5575+
5576+    def _maybe_fetch_offsets_and_header(self, force_remote=False):
5577+        """
5578+        I fetch the offset table and the header from the remote slot if
5579+        I don't already have them. If I do have them, I do nothing and
5580+        return an empty Deferred.
5581+        """
5582+        if self._offsets:
5583+            return defer.succeed(None)
5584+        # At this point, we may be either SDMF or MDMF. Fetching 107
5585+        # bytes will be enough to get header and offsets for both SDMF and
5586+        # MDMF, though we'll be left with 4 more bytes than we
5587+        # need if this ends up being MDMF. This is probably less
5588+        # expensive than the cost of a second roundtrip.
5589+        readvs = [(0, 107)]
5590+        d = self._read(readvs, force_remote)
5591+        d.addCallback(self._process_encoding_parameters)
5592+        d.addCallback(self._process_offsets)
5593+        return d
5594+
5595+
5596+    def _process_encoding_parameters(self, encoding_parameters):
5597+        assert self.shnum in encoding_parameters
5598+        encoding_parameters = encoding_parameters[self.shnum][0]
5599+        # The first byte is the version number. It will tell us what
5600+        # to do next.
5601+        (verno,) = struct.unpack(">B", encoding_parameters[:1])
5602+        if verno == MDMF_VERSION:
5603+            read_size = MDMFHEADERWITHOUTOFFSETSSIZE
5604+            (verno,
5605+             seqnum,
5606+             root_hash,
5607+             k,
5608+             n,
5609+             segsize,
5610+             datalen) = struct.unpack(MDMFHEADERWITHOUTOFFSETS,
5611+                                      encoding_parameters[:read_size])
5612+            if segsize == 0 and datalen == 0:
5613+                # Empty file, no segments.
5614+                self._num_segments = 0
5615+            else:
5616+                self._num_segments = mathutil.div_ceil(datalen, segsize)
5617+
5618+        elif verno == SDMF_VERSION:
5619+            read_size = SIGNED_PREFIX_LENGTH
5620+            (verno,
5621+             seqnum,
5622+             root_hash,
5623+             salt,
5624+             k,
5625+             n,
5626+             segsize,
5627+             datalen) = struct.unpack(">BQ32s16s BBQQ",
5628+                                encoding_parameters[:SIGNED_PREFIX_LENGTH])
5629+            self._salt = salt
5630+            if segsize == 0 and datalen == 0:
5631+                # empty file
5632+                self._num_segments = 0
5633+            else:
5634+                # non-empty SDMF files have one segment.
5635+                self._num_segments = 1
5636+        else:
5637+            raise UnknownVersionError("You asked me to read mutable file "
5638+                                      "version %d, but I only understand "
5639+                                      "%d and %d" % (verno, SDMF_VERSION,
5640+                                                     MDMF_VERSION))
5641+
5642+        self._version_number = verno
5643+        self._sequence_number = seqnum
5644+        self._root_hash = root_hash
5645+        self._required_shares = k
5646+        self._total_shares = n
5647+        self._segment_size = segsize
5648+        self._data_length = datalen
5649+
5650+        self._block_size = self._segment_size / self._required_shares
5651+        # We can upload empty files, and need to account for this fact
5652+        # so as to avoid zero-division and zero-modulo errors.
5653+        if datalen > 0:
5654+            tail_size = self._data_length % self._segment_size
5655+        else:
5656+            tail_size = 0
5657+        if not tail_size:
5658+            self._tail_block_size = self._block_size
5659+        else:
5660+            self._tail_block_size = mathutil.next_multiple(tail_size,
5661+                                                    self._required_shares)
5662+            self._tail_block_size /= self._required_shares
5663+
5664+        return encoding_parameters
5665+
5666+
5667+    def _process_offsets(self, offsets):
5668+        if self._version_number == 0:
5669+            read_size = OFFSETS_LENGTH
5670+            read_offset = SIGNED_PREFIX_LENGTH
5671+            end = read_size + read_offset
5672+            (signature,
5673+             share_hash_chain,
5674+             block_hash_tree,
5675+             share_data,
5676+             enc_privkey,
5677+             EOF) = struct.unpack(">LLLLQQ",
5678+                                  offsets[read_offset:end])
5679+            self._offsets = {}
5680+            self._offsets['signature'] = signature
5681+            self._offsets['share_data'] = share_data
5682+            self._offsets['block_hash_tree'] = block_hash_tree
5683+            self._offsets['share_hash_chain'] = share_hash_chain
5684+            self._offsets['enc_privkey'] = enc_privkey
5685+            self._offsets['EOF'] = EOF
5686+
5687+        elif self._version_number == 1:
5688+            read_offset = MDMFHEADERWITHOUTOFFSETSSIZE
5689+            read_length = MDMFOFFSETS_LENGTH
5690+            end = read_offset + read_length
5691+            (encprivkey,
5692+             blockhashes,
5693+             sharehashes,
5694+             signature,
5695+             verification_key,
5696+             eof) = struct.unpack(MDMFOFFSETS,
5697+                                  offsets[read_offset:end])
5698+            self._offsets = {}
5699+            self._offsets['enc_privkey'] = encprivkey
5700+            self._offsets['block_hash_tree'] = blockhashes
5701+            self._offsets['share_hash_chain'] = sharehashes
5702+            self._offsets['signature'] = signature
5703+            self._offsets['verification_key'] = verification_key
5704+            self._offsets['EOF'] = eof
5705+
5706+
5707+    def get_block_and_salt(self, segnum, queue=False):
5708+        """
5709+        I return (block, salt), where block is the block data and
5710+        salt is the salt used to encrypt that segment.
5711+        """
5712+        d = self._maybe_fetch_offsets_and_header()
5713+        def _then(ignored):
5714+            if self._version_number == 1:
5715+                base_share_offset = MDMFHEADERSIZE
5716+            else:
5717+                base_share_offset = self._offsets['share_data']
5718+
5719+            if segnum + 1 > self._num_segments:
5720+                raise LayoutInvalid("Not a valid segment number")
5721+
5722+            if self._version_number == 0:
5723+                share_offset = base_share_offset + self._block_size * segnum
5724+            else:
5725+                share_offset = base_share_offset + (self._block_size + \
5726+                                                    SALT_SIZE) * segnum
5727+            if segnum + 1 == self._num_segments:
5728+                data = self._tail_block_size
5729+            else:
5730+                data = self._block_size
5731+
5732+            if self._version_number == 1:
5733+                data += SALT_SIZE
5734+
5735+            readvs = [(share_offset, data)]
5736+            return readvs
5737+        d.addCallback(_then)
5738+        d.addCallback(lambda readvs:
5739+            self._read(readvs, queue=queue))
5740+        def _process_results(results):
5741+            assert self.shnum in results
5742+            if self._version_number == 0:
5743+                # We only read the share data, but we know the salt from
5744+                # when we fetched the header
5745+                data = results[self.shnum]
5746+                if not data:
5747+                    data = ""
5748+                else:
5749+                    assert len(data) == 1
5750+                    data = data[0]
5751+                salt = self._salt
5752+            else:
5753+                data = results[self.shnum]
5754+                if not data:
5755+                    salt = data = ""
5756+                else:
5757+                    salt_and_data = results[self.shnum][0]
5758+                    salt = salt_and_data[:SALT_SIZE]
5759+                    data = salt_and_data[SALT_SIZE:]
5760+            return data, salt
5761+        d.addCallback(_process_results)
5762+        return d
5763+
5764+
5765+    def get_blockhashes(self, needed=None, queue=False, force_remote=False):
5766+        """
5767+        I return the block hash tree
5768+
5769+        I take an optional argument, needed, which is a set of indices
5770+        correspond to hashes that I should fetch. If this argument is
5771+        missing, I will fetch the entire block hash tree; otherwise, I
5772+        may attempt to fetch fewer hashes, based on what needed says
5773+        that I should do. Note that I may fetch as many hashes as I
5774+        want, so long as the set of hashes that I do fetch is a superset
5775+        of the ones that I am asked for, so callers should be prepared
5776+        to tolerate additional hashes.
5777+        """
5778+        # TODO: Return only the parts of the block hash tree necessary
5779+        # to validate the blocknum provided?
5780+        # This is a good idea, but it is hard to implement correctly. It
5781+        # is bad to fetch any one block hash more than once, so we
5782+        # probably just want to fetch the whole thing at once and then
5783+        # serve it.
5784+        if needed == set([]):
5785+            return defer.succeed([])
5786+        d = self._maybe_fetch_offsets_and_header()
5787+        def _then(ignored):
5788+            blockhashes_offset = self._offsets['block_hash_tree']
5789+            if self._version_number == 1:
5790+                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
5791+            else:
5792+                blockhashes_length = self._offsets['share_data'] - blockhashes_offset
5793+            readvs = [(blockhashes_offset, blockhashes_length)]
5794+            return readvs
5795+        d.addCallback(_then)
5796+        d.addCallback(lambda readvs:
5797+            self._read(readvs, queue=queue, force_remote=force_remote))
5798+        def _build_block_hash_tree(results):
5799+            assert self.shnum in results
5800+
5801+            rawhashes = results[self.shnum][0]
5802+            results = [rawhashes[i:i+HASH_SIZE]
5803+                       for i in range(0, len(rawhashes), HASH_SIZE)]
5804+            return results
5805+        d.addCallback(_build_block_hash_tree)
5806+        return d
5807+
5808+
5809+    def get_sharehashes(self, needed=None, queue=False, force_remote=False):
5810+        """
5811+        I return the part of the share hash chain placed to validate
5812+        this share.
5813+
5814+        I take an optional argument, needed. Needed is a set of indices
5815+        that correspond to the hashes that I should fetch. If needed is
5816+        not present, I will fetch and return the entire share hash
5817+        chain. Otherwise, I may fetch and return any part of the share
5818+        hash chain that is a superset of the part that I am asked to
5819+        fetch. Callers should be prepared to deal with more hashes than
5820+        they've asked for.
5821+        """
5822+        if needed == set([]):
5823+            return defer.succeed([])
5824+        d = self._maybe_fetch_offsets_and_header()
5825+
5826+        def _make_readvs(ignored):
5827+            sharehashes_offset = self._offsets['share_hash_chain']
5828+            if self._version_number == 0:
5829+                sharehashes_length = self._offsets['block_hash_tree'] - sharehashes_offset
5830+            else:
5831+                sharehashes_length = self._offsets['signature'] - sharehashes_offset
5832+            readvs = [(sharehashes_offset, sharehashes_length)]
5833+            return readvs
5834+        d.addCallback(_make_readvs)
5835+        d.addCallback(lambda readvs:
5836+            self._read(readvs, queue=queue, force_remote=force_remote))
5837+        def _build_share_hash_chain(results):
5838+            assert self.shnum in results
5839+
5840+            sharehashes = results[self.shnum][0]
5841+            results = [sharehashes[i:i+(HASH_SIZE + 2)]
5842+                       for i in range(0, len(sharehashes), HASH_SIZE + 2)]
5843+            results = dict([struct.unpack(">H32s", data)
5844+                            for data in results])
5845+            return results
5846+        d.addCallback(_build_share_hash_chain)
5847+        return d
5848+
5849+
5850+    def get_encprivkey(self, queue=False):
5851+        """
5852+        I return the encrypted private key.
5853+        """
5854+        d = self._maybe_fetch_offsets_and_header()
5855+
5856+        def _make_readvs(ignored):
5857+            privkey_offset = self._offsets['enc_privkey']
5858+            if self._version_number == 0:
5859+                privkey_length = self._offsets['EOF'] - privkey_offset
5860+            else:
5861+                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
5862+            readvs = [(privkey_offset, privkey_length)]
5863+            return readvs
5864+        d.addCallback(_make_readvs)
5865+        d.addCallback(lambda readvs:
5866+            self._read(readvs, queue=queue))
5867+        def _process_results(results):
5868+            assert self.shnum in results
5869+            privkey = results[self.shnum][0]
5870+            return privkey
5871+        d.addCallback(_process_results)
5872+        return d
5873+
5874+
5875+    def get_signature(self, queue=False):
5876+        """
5877+        I return the signature of my share.
5878+        """
5879+        d = self._maybe_fetch_offsets_and_header()
5880+
5881+        def _make_readvs(ignored):
5882+            signature_offset = self._offsets['signature']
5883+            if self._version_number == 1:
5884+                signature_length = self._offsets['verification_key'] - signature_offset
5885+            else:
5886+                signature_length = self._offsets['share_hash_chain'] - signature_offset
5887+            readvs = [(signature_offset, signature_length)]
5888+            return readvs
5889+        d.addCallback(_make_readvs)
5890+        d.addCallback(lambda readvs:
5891+            self._read(readvs, queue=queue))
5892+        def _process_results(results):
5893+            assert self.shnum in results
5894+            signature = results[self.shnum][0]
5895+            return signature
5896+        d.addCallback(_process_results)
5897+        return d
5898+
5899+
5900+    def get_verification_key(self, queue=False):
5901+        """
5902+        I return the verification key.
5903+        """
5904+        d = self._maybe_fetch_offsets_and_header()
5905+
5906+        def _make_readvs(ignored):
5907+            if self._version_number == 1:
5908+                vk_offset = self._offsets['verification_key']
5909+                vk_length = self._offsets['EOF'] - vk_offset
5910+            else:
5911+                vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5912+                vk_length = self._offsets['signature'] - vk_offset
5913+            readvs = [(vk_offset, vk_length)]
5914+            return readvs
5915+        d.addCallback(_make_readvs)
5916+        d.addCallback(lambda readvs:
5917+            self._read(readvs, queue=queue))
5918+        def _process_results(results):
5919+            assert self.shnum in results
5920+            verification_key = results[self.shnum][0]
5921+            return verification_key
5922+        d.addCallback(_process_results)
5923+        return d
5924+
5925+
5926+    def get_encoding_parameters(self):
5927+        """
5928+        I return (k, n, segsize, datalen)
5929+        """
5930+        d = self._maybe_fetch_offsets_and_header()
5931+        d.addCallback(lambda ignored:
5932+            (self._required_shares,
5933+             self._total_shares,
5934+             self._segment_size,
5935+             self._data_length))
5936+        return d
5937+
5938+
5939+    def get_seqnum(self):
5940+        """
5941+        I return the sequence number for this share.
5942+        """
5943+        d = self._maybe_fetch_offsets_and_header()
5944+        d.addCallback(lambda ignored:
5945+            self._sequence_number)
5946+        return d
5947+
5948+
5949+    def get_root_hash(self):
5950+        """
5951+        I return the root of the block hash tree
5952+        """
5953+        d = self._maybe_fetch_offsets_and_header()
5954+        d.addCallback(lambda ignored: self._root_hash)
5955+        return d
5956+
5957+
5958+    def get_checkstring(self):
5959+        """
5960+        I return the packed representation of the following:
5961+
5962+            - version number
5963+            - sequence number
5964+            - root hash
5965+            - salt hash
5966+
5967+        which my users use as a checkstring to detect other writers.
5968+        """
5969+        d = self._maybe_fetch_offsets_and_header()
5970+        def _build_checkstring(ignored):
5971+            if self._salt:
5972+                checkstring = struct.pack(PREFIX,
5973+                                          self._version_number,
5974+                                          self._sequence_number,
5975+                                          self._root_hash,
5976+                                          self._salt)
5977+            else:
5978+                checkstring = struct.pack(MDMFCHECKSTRING,
5979+                                          self._version_number,
5980+                                          self._sequence_number,
5981+                                          self._root_hash)
5982+
5983+            return checkstring
5984+        d.addCallback(_build_checkstring)
5985+        return d
5986+
5987+
5988+    def get_prefix(self, force_remote):
5989+        d = self._maybe_fetch_offsets_and_header(force_remote)
5990+        d.addCallback(lambda ignored:
5991+            self._build_prefix())
5992+        return d
5993+
5994+
5995+    def _build_prefix(self):
5996+        # The prefix is another name for the part of the remote share
5997+        # that gets signed. It consists of everything up to and
5998+        # including the datalength, packed by struct.
5999+        if self._version_number == SDMF_VERSION:
6000+            return struct.pack(SIGNED_PREFIX,
6001+                           self._version_number,
6002+                           self._sequence_number,
6003+                           self._root_hash,
6004+                           self._salt,
6005+                           self._required_shares,
6006+                           self._total_shares,
6007+                           self._segment_size,
6008+                           self._data_length)
6009+
6010+        else:
6011+            return struct.pack(MDMFSIGNABLEHEADER,
6012+                           self._version_number,
6013+                           self._sequence_number,
6014+                           self._root_hash,
6015+                           self._required_shares,
6016+                           self._total_shares,
6017+                           self._segment_size,
6018+                           self._data_length)
6019+
6020+
6021+    def _get_offsets_tuple(self):
6022+        # The offsets tuple is another component of the version
6023+        # information tuple. It is basically our offsets dictionary,
6024+        # itemized and in a tuple.
6025+        return self._offsets.copy()
6026+
6027+
6028+    def get_verinfo(self):
6029+        """
6030+        I return my verinfo tuple. This is used by the ServermapUpdater
6031+        to keep track of versions of mutable files.
6032+
6033+        The verinfo tuple for MDMF files contains:
6034+            - seqnum
6035+            - root hash
6036+            - a blank (nothing)
6037+            - segsize
6038+            - datalen
6039+            - k
6040+            - n
6041+            - prefix (the thing that you sign)
6042+            - a tuple of offsets
6043+
6044+        We include the nonce in MDMF to simplify processing of version
6045+        information tuples.
6046+
6047+        The verinfo tuple for SDMF files is the same, but contains a
6048+        16-byte IV instead of a hash of salts.
6049+        """
6050+        d = self._maybe_fetch_offsets_and_header()
6051+        def _build_verinfo(ignored):
6052+            if self._version_number == SDMF_VERSION:
6053+                salt_to_use = self._salt
6054+            else:
6055+                salt_to_use = None
6056+            return (self._sequence_number,
6057+                    self._root_hash,
6058+                    salt_to_use,
6059+                    self._segment_size,
6060+                    self._data_length,
6061+                    self._required_shares,
6062+                    self._total_shares,
6063+                    self._build_prefix(),
6064+                    self._get_offsets_tuple())
6065+        d.addCallback(_build_verinfo)
6066+        return d
6067+
6068+
6069+    def flush(self):
6070+        """
6071+        I flush my queue of read vectors.
6072+        """
6073+        d = self._read(self._readvs)
6074+        def _then(results):
6075+            self._readvs = []
6076+            if isinstance(results, failure.Failure):
6077+                self._queue_errbacks.notify(results)
6078+            else:
6079+                self._queue_observers.notify(results)
6080+            self._queue_observers = observer.ObserverList()
6081+            self._queue_errbacks = observer.ObserverList()
6082+        d.addBoth(_then)
6083+
6084+
6085+    def _read(self, readvs, force_remote=False, queue=False):
6086+        unsatisfiable = filter(lambda x: x[0] + x[1] > len(self._data), readvs)
6087+        # TODO: It's entirely possible to tweak this so that it just
6088+        # fulfills the requests that it can, and not demand that all
6089+        # requests are satisfiable before running it.
6090+        if not unsatisfiable and not force_remote:
6091+            results = [self._data[offset:offset+length]
6092+                       for (offset, length) in readvs]
6093+            results = {self.shnum: results}
6094+            return defer.succeed(results)
6095+        else:
6096+            if queue:
6097+                start = len(self._readvs)
6098+                self._readvs += readvs
6099+                end = len(self._readvs)
6100+                def _get_results(results, start, end):
6101+                    if not self.shnum in results:
6102+                        return {self._shnum: [""]}
6103+                    return {self.shnum: results[self.shnum][start:end]}
6104+                d = defer.Deferred()
6105+                d.addCallback(_get_results, start, end)
6106+                self._queue_observers.subscribe(d.callback)
6107+                self._queue_errbacks.subscribe(d.errback)
6108+                return d
6109+            return self._rref.callRemote("slot_readv",
6110+                                         self._storage_index,
6111+                                         [self.shnum],
6112+                                         readvs)
6113+
6114+
6115+    def is_sdmf(self):
6116+        """I tell my caller whether or not my remote file is SDMF or MDMF
6117+        """
6118+        d = self._maybe_fetch_offsets_and_header()
6119+        d.addCallback(lambda ignored:
6120+            self._version_number == 0)
6121+        return d
6122+
6123+
6124+class LayoutInvalid(Exception):
6125+    """
6126+    This isn't a valid MDMF mutable file
6127+    """
6128merger 0.0 (
6129hunk ./src/allmydata/test/test_storage.py 3
6130-from allmydata.util import log
6131-
6132merger 0.0 (
6133hunk ./src/allmydata/test/test_storage.py 3
6134-import time, os.path, stat, re, simplejson, struct
6135+from allmydata.util import log
6136+
6137+import mock
6138hunk ./src/allmydata/test/test_storage.py 3
6139-import time, os.path, stat, re, simplejson, struct
6140+import time, os.path, stat, re, simplejson, struct, shutil
6141)
6142)
6143hunk ./src/allmydata/test/test_storage.py 23
6144 from allmydata.storage.expirer import LeaseCheckingCrawler
6145 from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \
6146      ReadBucketProxy
6147-from allmydata.interfaces import BadWriteEnablerError
6148-from allmydata.test.common import LoggingServiceParent
6149+from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
6150+                                     LayoutInvalid, MDMFSIGNABLEHEADER, \
6151+                                     SIGNED_PREFIX, MDMFHEADER, \
6152+                                     MDMFOFFSETS, SDMFSlotWriteProxy
6153+from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
6154+                                 SDMF_VERSION
6155+from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
6156 from allmydata.test.common_web import WebRenderingMixin
6157 from allmydata.web.storage import StorageStatus, remove_prefix
6158 
6159hunk ./src/allmydata/test/test_storage.py 107
6160 
6161 class RemoteBucket:
6162 
6163+    def __init__(self):
6164+        self.read_count = 0
6165+        self.write_count = 0
6166+
6167     def callRemote(self, methname, *args, **kwargs):
6168         def _call():
6169             meth = getattr(self.target, "remote_" + methname)
6170hunk ./src/allmydata/test/test_storage.py 115
6171             return meth(*args, **kwargs)
6172+
6173+        if methname == "slot_readv":
6174+            self.read_count += 1
6175+        if "writev" in methname:
6176+            self.write_count += 1
6177+
6178         return defer.maybeDeferred(_call)
6179 
6180hunk ./src/allmydata/test/test_storage.py 123
6181+
6182 class BucketProxy(unittest.TestCase):
6183     def make_bucket(self, name, size):
6184         basedir = os.path.join("storage", "BucketProxy", name)
6185hunk ./src/allmydata/test/test_storage.py 1306
6186         self.failUnless(os.path.exists(prefixdir), prefixdir)
6187         self.failIf(os.path.exists(bucketdir), bucketdir)
6188 
6189+
6190+class MDMFProxies(unittest.TestCase, ShouldFailMixin):
6191+    def setUp(self):
6192+        self.sparent = LoggingServiceParent()
6193+        self._lease_secret = itertools.count()
6194+        self.ss = self.create("MDMFProxies storage test server")
6195+        self.rref = RemoteBucket()
6196+        self.rref.target = self.ss
6197+        self.secrets = (self.write_enabler("we_secret"),
6198+                        self.renew_secret("renew_secret"),
6199+                        self.cancel_secret("cancel_secret"))
6200+        self.segment = "aaaaaa"
6201+        self.block = "aa"
6202+        self.salt = "a" * 16
6203+        self.block_hash = "a" * 32
6204+        self.block_hash_tree = [self.block_hash for i in xrange(6)]
6205+        self.share_hash = self.block_hash
6206+        self.share_hash_chain = dict([(i, self.share_hash) for i in xrange(6)])
6207+        self.signature = "foobarbaz"
6208+        self.verification_key = "vvvvvv"
6209+        self.encprivkey = "private"
6210+        self.root_hash = self.block_hash
6211+        self.salt_hash = self.root_hash
6212+        self.salt_hash_tree = [self.salt_hash for i in xrange(6)]
6213+        self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree)
6214+        self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain)
6215+        # blockhashes and salt hashes are serialized in the same way,
6216+        # only we lop off the first element and store that in the
6217+        # header.
6218+        self.salt_hash_tree_s = self.serialize_blockhashes(self.salt_hash_tree[1:])
6219+
6220+
6221+    def tearDown(self):
6222+        self.sparent.stopService()
6223+        shutil.rmtree(self.workdir("MDMFProxies storage test server"))
6224+
6225+
6226+    def write_enabler(self, we_tag):
6227+        return hashutil.tagged_hash("we_blah", we_tag)
6228+
6229+
6230+    def renew_secret(self, tag):
6231+        return hashutil.tagged_hash("renew_blah", str(tag))
6232+
6233+
6234+    def cancel_secret(self, tag):
6235+        return hashutil.tagged_hash("cancel_blah", str(tag))
6236+
6237+
6238+    def workdir(self, name):
6239+        basedir = os.path.join("storage", "MutableServer", name)
6240+        return basedir
6241+
6242+
6243+    def create(self, name):
6244+        workdir = self.workdir(name)
6245+        ss = StorageServer(workdir, "\x00" * 20)
6246+        ss.setServiceParent(self.sparent)
6247+        return ss
6248+
6249+
6250+    def build_test_mdmf_share(self, tail_segment=False, empty=False):
6251+        # Start with the checkstring
6252+        data = struct.pack(">BQ32s",
6253+                           1,
6254+                           0,
6255+                           self.root_hash)
6256+        self.checkstring = data
6257+        # Next, the encoding parameters
6258+        if tail_segment:
6259+            data += struct.pack(">BBQQ",
6260+                                3,
6261+                                10,
6262+                                6,
6263+                                33)
6264+        elif empty:
6265+            data += struct.pack(">BBQQ",
6266+                                3,
6267+                                10,
6268+                                0,
6269+                                0)
6270+        else:
6271+            data += struct.pack(">BBQQ",
6272+                                3,
6273+                                10,
6274+                                6,
6275+                                36)
6276+        # Now we'll build the offsets.
6277+        sharedata = ""
6278+        if not tail_segment and not empty:
6279+            for i in xrange(6):
6280+                sharedata += self.salt + self.block
6281+        elif tail_segment:
6282+            for i in xrange(5):
6283+                sharedata += self.salt + self.block
6284+            sharedata += self.salt + "a"
6285+
6286+        # The encrypted private key comes after the shares + salts
6287+        offset_size = struct.calcsize(MDMFOFFSETS)
6288+        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
6289+        # The blockhashes come after the private key
6290+        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
6291+        # The sharehashes come after the salt hashes
6292+        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
6293+        # The signature comes after the share hash chain
6294+        signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
6295+        # The verification key comes after the signature
6296+        verification_offset = signature_offset + len(self.signature)
6297+        # The EOF comes after the verification key
6298+        eof_offset = verification_offset + len(self.verification_key)
6299+        data += struct.pack(MDMFOFFSETS,
6300+                            encrypted_private_key_offset,
6301+                            blockhashes_offset,
6302+                            sharehashes_offset,
6303+                            signature_offset,
6304+                            verification_offset,
6305+                            eof_offset)
6306+        self.offsets = {}
6307+        self.offsets['enc_privkey'] = encrypted_private_key_offset
6308+        self.offsets['block_hash_tree'] = blockhashes_offset
6309+        self.offsets['share_hash_chain'] = sharehashes_offset
6310+        self.offsets['signature'] = signature_offset
6311+        self.offsets['verification_key'] = verification_offset
6312+        self.offsets['EOF'] = eof_offset
6313+        # Next, we'll add in the salts and share data,
6314+        data += sharedata
6315+        # the private key,
6316+        data += self.encprivkey
6317+        # the block hash tree,
6318+        data += self.block_hash_tree_s
6319+        # the share hash chain,
6320+        data += self.share_hash_chain_s
6321+        # the signature,
6322+        data += self.signature
6323+        # and the verification key
6324+        data += self.verification_key
6325+        return data
6326+
6327+
6328+    def write_test_share_to_server(self,
6329+                                   storage_index,
6330+                                   tail_segment=False,
6331+                                   empty=False):
6332+        """
6333+        I write some data for the read tests to read to self.ss
6334+
6335+        If tail_segment=True, then I will write a share that has a
6336+        smaller tail segment than other segments.
6337+        """
6338+        write = self.ss.remote_slot_testv_and_readv_and_writev
6339+        data = self.build_test_mdmf_share(tail_segment, empty)
6340+        # Finally, we write the whole thing to the storage server in one
6341+        # pass.
6342+        testvs = [(0, 1, "eq", "")]
6343+        tws = {}
6344+        tws[0] = (testvs, [(0, data)], None)
6345+        readv = [(0, 1)]
6346+        results = write(storage_index, self.secrets, tws, readv)
6347+        self.failUnless(results[0])
6348+
6349+
6350+    def build_test_sdmf_share(self, empty=False):
6351+        if empty:
6352+            sharedata = ""
6353+        else:
6354+            sharedata = self.segment * 6
6355+        self.sharedata = sharedata
6356+        blocksize = len(sharedata) / 3
6357+        block = sharedata[:blocksize]
6358+        self.blockdata = block
6359+        prefix = struct.pack(">BQ32s16s BBQQ",
6360+                             0, # version,
6361+                             0,
6362+                             self.root_hash,
6363+                             self.salt,
6364+                             3,
6365+                             10,
6366+                             len(sharedata),
6367+                             len(sharedata),
6368+                            )
6369+        post_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
6370+        signature_offset = post_offset + len(self.verification_key)
6371+        sharehashes_offset = signature_offset + len(self.signature)
6372+        blockhashes_offset = sharehashes_offset + len(self.share_hash_chain_s)
6373+        sharedata_offset = blockhashes_offset + len(self.block_hash_tree_s)
6374+        encprivkey_offset = sharedata_offset + len(block)
6375+        eof_offset = encprivkey_offset + len(self.encprivkey)
6376+        offsets = struct.pack(">LLLLQQ",
6377+                              signature_offset,
6378+                              sharehashes_offset,
6379+                              blockhashes_offset,
6380+                              sharedata_offset,
6381+                              encprivkey_offset,
6382+                              eof_offset)
6383+        final_share = "".join([prefix,
6384+                           offsets,
6385+                           self.verification_key,
6386+                           self.signature,
6387+                           self.share_hash_chain_s,
6388+                           self.block_hash_tree_s,
6389+                           block,
6390+                           self.encprivkey])
6391+        self.offsets = {}
6392+        self.offsets['signature'] = signature_offset
6393+        self.offsets['share_hash_chain'] = sharehashes_offset
6394+        self.offsets['block_hash_tree'] = blockhashes_offset
6395+        self.offsets['share_data'] = sharedata_offset
6396+        self.offsets['enc_privkey'] = encprivkey_offset
6397+        self.offsets['EOF'] = eof_offset
6398+        return final_share
6399+
6400+
6401+    def write_sdmf_share_to_server(self,
6402+                                   storage_index,
6403+                                   empty=False):
6404+        # Some tests need SDMF shares to verify that we can still
6405+        # read them. This method writes one, which resembles but is not
6406+        assert self.rref
6407+        write = self.ss.remote_slot_testv_and_readv_and_writev
6408+        share = self.build_test_sdmf_share(empty)
6409+        testvs = [(0, 1, "eq", "")]
6410+        tws = {}
6411+        tws[0] = (testvs, [(0, share)], None)
6412+        readv = []
6413+        results = write(storage_index, self.secrets, tws, readv)
6414+        self.failUnless(results[0])
6415+
6416+
6417+    def test_read(self):
6418+        self.write_test_share_to_server("si1")
6419+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6420+        # Check that every method equals what we expect it to.
6421+        d = defer.succeed(None)
6422+        def _check_block_and_salt((block, salt)):
6423+            self.failUnlessEqual(block, self.block)
6424+            self.failUnlessEqual(salt, self.salt)
6425+
6426+        for i in xrange(6):
6427+            d.addCallback(lambda ignored, i=i:
6428+                mr.get_block_and_salt(i))
6429+            d.addCallback(_check_block_and_salt)
6430+
6431+        d.addCallback(lambda ignored:
6432+            mr.get_encprivkey())
6433+        d.addCallback(lambda encprivkey:
6434+            self.failUnlessEqual(self.encprivkey, encprivkey))
6435+
6436+        d.addCallback(lambda ignored:
6437+            mr.get_blockhashes())
6438+        d.addCallback(lambda blockhashes:
6439+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6440+
6441+        d.addCallback(lambda ignored:
6442+            mr.get_sharehashes())
6443+        d.addCallback(lambda sharehashes:
6444+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6445+
6446+        d.addCallback(lambda ignored:
6447+            mr.get_signature())
6448+        d.addCallback(lambda signature:
6449+            self.failUnlessEqual(signature, self.signature))
6450+
6451+        d.addCallback(lambda ignored:
6452+            mr.get_verification_key())
6453+        d.addCallback(lambda verification_key:
6454+            self.failUnlessEqual(verification_key, self.verification_key))
6455+
6456+        d.addCallback(lambda ignored:
6457+            mr.get_seqnum())
6458+        d.addCallback(lambda seqnum:
6459+            self.failUnlessEqual(seqnum, 0))
6460+
6461+        d.addCallback(lambda ignored:
6462+            mr.get_root_hash())
6463+        d.addCallback(lambda root_hash:
6464+            self.failUnlessEqual(self.root_hash, root_hash))
6465+
6466+        d.addCallback(lambda ignored:
6467+            mr.get_seqnum())
6468+        d.addCallback(lambda seqnum:
6469+            self.failUnlessEqual(0, seqnum))
6470+
6471+        d.addCallback(lambda ignored:
6472+            mr.get_encoding_parameters())
6473+        def _check_encoding_parameters((k, n, segsize, datalen)):
6474+            self.failUnlessEqual(k, 3)
6475+            self.failUnlessEqual(n, 10)
6476+            self.failUnlessEqual(segsize, 6)
6477+            self.failUnlessEqual(datalen, 36)
6478+        d.addCallback(_check_encoding_parameters)
6479+
6480+        d.addCallback(lambda ignored:
6481+            mr.get_checkstring())
6482+        d.addCallback(lambda checkstring:
6483+            self.failUnlessEqual(checkstring, checkstring))
6484+        return d
6485+
6486+
6487+    def test_read_with_different_tail_segment_size(self):
6488+        self.write_test_share_to_server("si1", tail_segment=True)
6489+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6490+        d = mr.get_block_and_salt(5)
6491+        def _check_tail_segment(results):
6492+            block, salt = results
6493+            self.failUnlessEqual(len(block), 1)
6494+            self.failUnlessEqual(block, "a")
6495+        d.addCallback(_check_tail_segment)
6496+        return d
6497+
6498+
6499+    def test_get_block_with_invalid_segnum(self):
6500+        self.write_test_share_to_server("si1")
6501+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6502+        d = defer.succeed(None)
6503+        d.addCallback(lambda ignored:
6504+            self.shouldFail(LayoutInvalid, "test invalid segnum",
6505+                            None,
6506+                            mr.get_block_and_salt, 7))
6507+        return d
6508+
6509+
6510+    def test_get_encoding_parameters_first(self):
6511+        self.write_test_share_to_server("si1")
6512+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6513+        d = mr.get_encoding_parameters()
6514+        def _check_encoding_parameters((k, n, segment_size, datalen)):
6515+            self.failUnlessEqual(k, 3)
6516+            self.failUnlessEqual(n, 10)
6517+            self.failUnlessEqual(segment_size, 6)
6518+            self.failUnlessEqual(datalen, 36)
6519+        d.addCallback(_check_encoding_parameters)
6520+        return d
6521+
6522+
6523+    def test_get_seqnum_first(self):
6524+        self.write_test_share_to_server("si1")
6525+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6526+        d = mr.get_seqnum()
6527+        d.addCallback(lambda seqnum:
6528+            self.failUnlessEqual(seqnum, 0))
6529+        return d
6530+
6531+
6532+    def test_get_root_hash_first(self):
6533+        self.write_test_share_to_server("si1")
6534+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6535+        d = mr.get_root_hash()
6536+        d.addCallback(lambda root_hash:
6537+            self.failUnlessEqual(root_hash, self.root_hash))
6538+        return d
6539+
6540+
6541+    def test_get_checkstring_first(self):
6542+        self.write_test_share_to_server("si1")
6543+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6544+        d = mr.get_checkstring()
6545+        d.addCallback(lambda checkstring:
6546+            self.failUnlessEqual(checkstring, self.checkstring))
6547+        return d
6548+
6549+
6550+    def test_write_read_vectors(self):
6551+        # When writing for us, the storage server will return to us a
6552+        # read vector, along with its result. If a write fails because
6553+        # the test vectors failed, this read vector can help us to
6554+        # diagnose the problem. This test ensures that the read vector
6555+        # is working appropriately.
6556+        mw = self._make_new_mw("si1", 0)
6557+
6558+        for i in xrange(6):
6559+            mw.put_block(self.block, i, self.salt)
6560+        mw.put_encprivkey(self.encprivkey)
6561+        mw.put_blockhashes(self.block_hash_tree)
6562+        mw.put_sharehashes(self.share_hash_chain)
6563+        mw.put_root_hash(self.root_hash)
6564+        mw.put_signature(self.signature)
6565+        mw.put_verification_key(self.verification_key)
6566+        d = mw.finish_publishing()
6567+        def _then(results):
6568+            self.failUnless(len(results), 2)
6569+            result, readv = results
6570+            self.failUnless(result)
6571+            self.failIf(readv)
6572+            self.old_checkstring = mw.get_checkstring()
6573+            mw.set_checkstring("")
6574+        d.addCallback(_then)
6575+        d.addCallback(lambda ignored:
6576+            mw.finish_publishing())
6577+        def _then_again(results):
6578+            self.failUnlessEqual(len(results), 2)
6579+            result, readvs = results
6580+            self.failIf(result)
6581+            self.failUnlessIn(0, readvs)
6582+            readv = readvs[0][0]
6583+            self.failUnlessEqual(readv, self.old_checkstring)
6584+        d.addCallback(_then_again)
6585+        # The checkstring remains the same for the rest of the process.
6586+        return d
6587+
6588+
6589+    def test_blockhashes_after_share_hash_chain(self):
6590+        mw = self._make_new_mw("si1", 0)
6591+        d = defer.succeed(None)
6592+        # Put everything up to and including the share hash chain
6593+        for i in xrange(6):
6594+            d.addCallback(lambda ignored, i=i:
6595+                mw.put_block(self.block, i, self.salt))
6596+        d.addCallback(lambda ignored:
6597+            mw.put_encprivkey(self.encprivkey))
6598+        d.addCallback(lambda ignored:
6599+            mw.put_blockhashes(self.block_hash_tree))
6600+        d.addCallback(lambda ignored:
6601+            mw.put_sharehashes(self.share_hash_chain))
6602+
6603+        # Now try to put the block hash tree again.
6604+        d.addCallback(lambda ignored:
6605+            self.shouldFail(LayoutInvalid, "test repeat salthashes",
6606+                            None,
6607+                            mw.put_blockhashes, self.block_hash_tree))
6608+        return d
6609+
6610+
6611+    def test_encprivkey_after_blockhashes(self):
6612+        mw = self._make_new_mw("si1", 0)
6613+        d = defer.succeed(None)
6614+        # Put everything up to and including the block hash tree
6615+        for i in xrange(6):
6616+            d.addCallback(lambda ignored, i=i:
6617+                mw.put_block(self.block, i, self.salt))
6618+        d.addCallback(lambda ignored:
6619+            mw.put_encprivkey(self.encprivkey))
6620+        d.addCallback(lambda ignored:
6621+            mw.put_blockhashes(self.block_hash_tree))
6622+        d.addCallback(lambda ignored:
6623+            self.shouldFail(LayoutInvalid, "out of order private key",
6624+                            None,
6625+                            mw.put_encprivkey, self.encprivkey))
6626+        return d
6627+
6628+
6629+    def test_share_hash_chain_after_signature(self):
6630+        mw = self._make_new_mw("si1", 0)
6631+        d = defer.succeed(None)
6632+        # Put everything up to and including the signature
6633+        for i in xrange(6):
6634+            d.addCallback(lambda ignored, i=i:
6635+                mw.put_block(self.block, i, self.salt))
6636+        d.addCallback(lambda ignored:
6637+            mw.put_encprivkey(self.encprivkey))
6638+        d.addCallback(lambda ignored:
6639+            mw.put_blockhashes(self.block_hash_tree))
6640+        d.addCallback(lambda ignored:
6641+            mw.put_sharehashes(self.share_hash_chain))
6642+        d.addCallback(lambda ignored:
6643+            mw.put_root_hash(self.root_hash))
6644+        d.addCallback(lambda ignored:
6645+            mw.put_signature(self.signature))
6646+        # Now try to put the share hash chain again. This should fail
6647+        d.addCallback(lambda ignored:
6648+            self.shouldFail(LayoutInvalid, "out of order share hash chain",
6649+                            None,
6650+                            mw.put_sharehashes, self.share_hash_chain))
6651+        return d
6652+
6653+
6654+    def test_signature_after_verification_key(self):
6655+        mw = self._make_new_mw("si1", 0)
6656+        d = defer.succeed(None)
6657+        # Put everything up to and including the verification key.
6658+        for i in xrange(6):
6659+            d.addCallback(lambda ignored, i=i:
6660+                mw.put_block(self.block, i, self.salt))
6661+        d.addCallback(lambda ignored:
6662+            mw.put_encprivkey(self.encprivkey))
6663+        d.addCallback(lambda ignored:
6664+            mw.put_blockhashes(self.block_hash_tree))
6665+        d.addCallback(lambda ignored:
6666+            mw.put_sharehashes(self.share_hash_chain))
6667+        d.addCallback(lambda ignored:
6668+            mw.put_root_hash(self.root_hash))
6669+        d.addCallback(lambda ignored:
6670+            mw.put_signature(self.signature))
6671+        d.addCallback(lambda ignored:
6672+            mw.put_verification_key(self.verification_key))
6673+        # Now try to put the signature again. This should fail
6674+        d.addCallback(lambda ignored:
6675+            self.shouldFail(LayoutInvalid, "signature after verification",
6676+                            None,
6677+                            mw.put_signature, self.signature))
6678+        return d
6679+
6680+
6681+    def test_uncoordinated_write(self):
6682+        # Make two mutable writers, both pointing to the same storage
6683+        # server, both at the same storage index, and try writing to the
6684+        # same share.
6685+        mw1 = self._make_new_mw("si1", 0)
6686+        mw2 = self._make_new_mw("si1", 0)
6687+
6688+        def _check_success(results):
6689+            result, readvs = results
6690+            self.failUnless(result)
6691+
6692+        def _check_failure(results):
6693+            result, readvs = results
6694+            self.failIf(result)
6695+
6696+        def _write_share(mw):
6697+            for i in xrange(6):
6698+                mw.put_block(self.block, i, self.salt)
6699+            mw.put_encprivkey(self.encprivkey)
6700+            mw.put_blockhashes(self.block_hash_tree)
6701+            mw.put_sharehashes(self.share_hash_chain)
6702+            mw.put_root_hash(self.root_hash)
6703+            mw.put_signature(self.signature)
6704+            mw.put_verification_key(self.verification_key)
6705+            return mw.finish_publishing()
6706+        d = _write_share(mw1)
6707+        d.addCallback(_check_success)
6708+        d.addCallback(lambda ignored:
6709+            _write_share(mw2))
6710+        d.addCallback(_check_failure)
6711+        return d
6712+
6713+
6714+    def test_invalid_salt_size(self):
6715+        # Salts need to be 16 bytes in size. Writes that attempt to
6716+        # write more or less than this should be rejected.
6717+        mw = self._make_new_mw("si1", 0)
6718+        invalid_salt = "a" * 17 # 17 bytes
6719+        another_invalid_salt = "b" * 15 # 15 bytes
6720+        d = defer.succeed(None)
6721+        d.addCallback(lambda ignored:
6722+            self.shouldFail(LayoutInvalid, "salt too big",
6723+                            None,
6724+                            mw.put_block, self.block, 0, invalid_salt))
6725+        d.addCallback(lambda ignored:
6726+            self.shouldFail(LayoutInvalid, "salt too small",
6727+                            None,
6728+                            mw.put_block, self.block, 0,
6729+                            another_invalid_salt))
6730+        return d
6731+
6732+
6733+    def test_write_test_vectors(self):
6734+        # If we give the write proxy a bogus test vector at
6735+        # any point during the process, it should fail to write when we
6736+        # tell it to write.
6737+        def _check_failure(results):
6738+            self.failUnlessEqual(len(results), 2)
6739+            res, d = results
6740+            self.failIf(res)
6741+
6742+        def _check_success(results):
6743+            self.failUnlessEqual(len(results), 2)
6744+            res, d = results
6745+            self.failUnless(results)
6746+
6747+        mw = self._make_new_mw("si1", 0)
6748+        mw.set_checkstring("this is a lie")
6749+        for i in xrange(6):
6750+            mw.put_block(self.block, i, self.salt)
6751+        mw.put_encprivkey(self.encprivkey)
6752+        mw.put_blockhashes(self.block_hash_tree)
6753+        mw.put_sharehashes(self.share_hash_chain)
6754+        mw.put_root_hash(self.root_hash)
6755+        mw.put_signature(self.signature)
6756+        mw.put_verification_key(self.verification_key)
6757+        d = mw.finish_publishing()
6758+        d.addCallback(_check_failure)
6759+        d.addCallback(lambda ignored:
6760+            mw.set_checkstring(""))
6761+        d.addCallback(lambda ignored:
6762+            mw.finish_publishing())
6763+        d.addCallback(_check_success)
6764+        return d
6765+
6766+
6767+    def serialize_blockhashes(self, blockhashes):
6768+        return "".join(blockhashes)
6769+
6770+
6771+    def serialize_sharehashes(self, sharehashes):
6772+        ret = "".join([struct.pack(">H32s", i, sharehashes[i])
6773+                        for i in sorted(sharehashes.keys())])
6774+        return ret
6775+
6776+
6777+    def test_write(self):
6778+        # This translates to a file with 6 6-byte segments, and with 2-byte
6779+        # blocks.
6780+        mw = self._make_new_mw("si1", 0)
6781+        # Test writing some blocks.
6782+        read = self.ss.remote_slot_readv
6783+        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
6784+        written_block_size = 2 + len(self.salt)
6785+        written_block = self.block + self.salt
6786+        for i in xrange(6):
6787+            mw.put_block(self.block, i, self.salt)
6788+
6789+        mw.put_encprivkey(self.encprivkey)
6790+        mw.put_blockhashes(self.block_hash_tree)
6791+        mw.put_sharehashes(self.share_hash_chain)
6792+        mw.put_root_hash(self.root_hash)
6793+        mw.put_signature(self.signature)
6794+        mw.put_verification_key(self.verification_key)
6795+        d = mw.finish_publishing()
6796+        def _check_publish(results):
6797+            self.failUnlessEqual(len(results), 2)
6798+            result, ign = results
6799+            self.failUnless(result, "publish failed")
6800+            for i in xrange(6):
6801+                self.failUnlessEqual(read("si1", [0], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
6802+                                {0: [written_block]})
6803+
6804+            expected_private_key_offset = expected_sharedata_offset + \
6805+                                      len(written_block) * 6
6806+            self.failUnlessEqual(len(self.encprivkey), 7)
6807+            self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
6808+                                 {0: [self.encprivkey]})
6809+
6810+            expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
6811+            self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
6812+            self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
6813+                                 {0: [self.block_hash_tree_s]})
6814+
6815+            expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
6816+            self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
6817+                                 {0: [self.share_hash_chain_s]})
6818+
6819+            self.failUnlessEqual(read("si1", [0], [(9, 32)]),
6820+                                 {0: [self.root_hash]})
6821+            expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
6822+            self.failUnlessEqual(len(self.signature), 9)
6823+            self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
6824+                                 {0: [self.signature]})
6825+
6826+            expected_verification_key_offset = expected_signature_offset + len(self.signature)
6827+            self.failUnlessEqual(len(self.verification_key), 6)
6828+            self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
6829+                                 {0: [self.verification_key]})
6830+
6831+            signable = mw.get_signable()
6832+            verno, seq, roothash, k, n, segsize, datalen = \
6833+                                            struct.unpack(">BQ32sBBQQ",
6834+                                                          signable)
6835+            self.failUnlessEqual(verno, 1)
6836+            self.failUnlessEqual(seq, 0)
6837+            self.failUnlessEqual(roothash, self.root_hash)
6838+            self.failUnlessEqual(k, 3)
6839+            self.failUnlessEqual(n, 10)
6840+            self.failUnlessEqual(segsize, 6)
6841+            self.failUnlessEqual(datalen, 36)
6842+            expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
6843+
6844+            # Check the version number to make sure that it is correct.
6845+            expected_version_number = struct.pack(">B", 1)
6846+            self.failUnlessEqual(read("si1", [0], [(0, 1)]),
6847+                                 {0: [expected_version_number]})
6848+            # Check the sequence number to make sure that it is correct
6849+            expected_sequence_number = struct.pack(">Q", 0)
6850+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
6851+                                 {0: [expected_sequence_number]})
6852+            # Check that the encoding parameters (k, N, segement size, data
6853+            # length) are what they should be. These are  3, 10, 6, 36
6854+            expected_k = struct.pack(">B", 3)
6855+            self.failUnlessEqual(read("si1", [0], [(41, 1)]),
6856+                                 {0: [expected_k]})
6857+            expected_n = struct.pack(">B", 10)
6858+            self.failUnlessEqual(read("si1", [0], [(42, 1)]),
6859+                                 {0: [expected_n]})
6860+            expected_segment_size = struct.pack(">Q", 6)
6861+            self.failUnlessEqual(read("si1", [0], [(43, 8)]),
6862+                                 {0: [expected_segment_size]})
6863+            expected_data_length = struct.pack(">Q", 36)
6864+            self.failUnlessEqual(read("si1", [0], [(51, 8)]),
6865+                                 {0: [expected_data_length]})
6866+            expected_offset = struct.pack(">Q", expected_private_key_offset)
6867+            self.failUnlessEqual(read("si1", [0], [(59, 8)]),
6868+                                 {0: [expected_offset]})
6869+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
6870+            self.failUnlessEqual(read("si1", [0], [(67, 8)]),
6871+                                 {0: [expected_offset]})
6872+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
6873+            self.failUnlessEqual(read("si1", [0], [(75, 8)]),
6874+                                 {0: [expected_offset]})
6875+            expected_offset = struct.pack(">Q", expected_signature_offset)
6876+            self.failUnlessEqual(read("si1", [0], [(83, 8)]),
6877+                                 {0: [expected_offset]})
6878+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
6879+            self.failUnlessEqual(read("si1", [0], [(91, 8)]),
6880+                                 {0: [expected_offset]})
6881+            expected_offset = struct.pack(">Q", expected_eof_offset)
6882+            self.failUnlessEqual(read("si1", [0], [(99, 8)]),
6883+                                 {0: [expected_offset]})
6884+        d.addCallback(_check_publish)
6885+        return d
6886+
6887+    def _make_new_mw(self, si, share, datalength=36):
6888+        # This is a file of size 36 bytes. Since it has a segment
6889+        # size of 6, we know that it has 6 byte segments, which will
6890+        # be split into blocks of 2 bytes because our FEC k
6891+        # parameter is 3.
6892+        mw = MDMFSlotWriteProxy(share, self.rref, si, self.secrets, 0, 3, 10,
6893+                                6, datalength)
6894+        return mw
6895+
6896+
6897+    def test_write_rejected_with_too_many_blocks(self):
6898+        mw = self._make_new_mw("si0", 0)
6899+
6900+        # Try writing too many blocks. We should not be able to write
6901+        # more than 6
6902+        # blocks into each share.
6903+        d = defer.succeed(None)
6904+        for i in xrange(6):
6905+            d.addCallback(lambda ignored, i=i:
6906+                mw.put_block(self.block, i, self.salt))
6907+        d.addCallback(lambda ignored:
6908+            self.shouldFail(LayoutInvalid, "too many blocks",
6909+                            None,
6910+                            mw.put_block, self.block, 7, self.salt))
6911+        return d
6912+
6913+
6914+    def test_write_rejected_with_invalid_salt(self):
6915+        # Try writing an invalid salt. Salts are 16 bytes -- any more or
6916+        # less should cause an error.
6917+        mw = self._make_new_mw("si1", 0)
6918+        bad_salt = "a" * 17 # 17 bytes
6919+        d = defer.succeed(None)
6920+        d.addCallback(lambda ignored:
6921+            self.shouldFail(LayoutInvalid, "test_invalid_salt",
6922+                            None, mw.put_block, self.block, 7, bad_salt))
6923+        return d
6924+
6925+
6926+    def test_write_rejected_with_invalid_root_hash(self):
6927+        # Try writing an invalid root hash. This should be SHA256d, and
6928+        # 32 bytes long as a result.
6929+        mw = self._make_new_mw("si2", 0)
6930+        # 17 bytes != 32 bytes
6931+        invalid_root_hash = "a" * 17
6932+        d = defer.succeed(None)
6933+        # Before this test can work, we need to put some blocks + salts,
6934+        # a block hash tree, and a share hash tree. Otherwise, we'll see
6935+        # failures that match what we are looking for, but are caused by
6936+        # the constraints imposed on operation ordering.
6937+        for i in xrange(6):
6938+            d.addCallback(lambda ignored, i=i:
6939+                mw.put_block(self.block, i, self.salt))
6940+        d.addCallback(lambda ignored:
6941+            mw.put_encprivkey(self.encprivkey))
6942+        d.addCallback(lambda ignored:
6943+            mw.put_blockhashes(self.block_hash_tree))
6944+        d.addCallback(lambda ignored:
6945+            mw.put_sharehashes(self.share_hash_chain))
6946+        d.addCallback(lambda ignored:
6947+            self.shouldFail(LayoutInvalid, "invalid root hash",
6948+                            None, mw.put_root_hash, invalid_root_hash))
6949+        return d
6950+
6951+
6952+    def test_write_rejected_with_invalid_blocksize(self):
6953+        # The blocksize implied by the writer that we get from
6954+        # _make_new_mw is 2bytes -- any more or any less than this
6955+        # should be cause for failure, unless it is the tail segment, in
6956+        # which case it may not be failure.
6957+        invalid_block = "a"
6958+        mw = self._make_new_mw("si3", 0, 33) # implies a tail segment with
6959+                                             # one byte blocks
6960+        # 1 bytes != 2 bytes
6961+        d = defer.succeed(None)
6962+        d.addCallback(lambda ignored, invalid_block=invalid_block:
6963+            self.shouldFail(LayoutInvalid, "test blocksize too small",
6964+                            None, mw.put_block, invalid_block, 0,
6965+                            self.salt))
6966+        invalid_block = invalid_block * 3
6967+        # 3 bytes != 2 bytes
6968+        d.addCallback(lambda ignored:
6969+            self.shouldFail(LayoutInvalid, "test blocksize too large",
6970+                            None,
6971+                            mw.put_block, invalid_block, 0, self.salt))
6972+        for i in xrange(5):
6973+            d.addCallback(lambda ignored, i=i:
6974+                mw.put_block(self.block, i, self.salt))
6975+        # Try to put an invalid tail segment
6976+        d.addCallback(lambda ignored:
6977+            self.shouldFail(LayoutInvalid, "test invalid tail segment",
6978+                            None,
6979+                            mw.put_block, self.block, 5, self.salt))
6980+        valid_block = "a"
6981+        d.addCallback(lambda ignored:
6982+            mw.put_block(valid_block, 5, self.salt))
6983+        return d
6984+
6985+
6986+    def test_write_enforces_order_constraints(self):
6987+        # We require that the MDMFSlotWriteProxy be interacted with in a
6988+        # specific way.
6989+        # That way is:
6990+        # 0: __init__
6991+        # 1: write blocks and salts
6992+        # 2: Write the encrypted private key
6993+        # 3: Write the block hashes
6994+        # 4: Write the share hashes
6995+        # 5: Write the root hash and salt hash
6996+        # 6: Write the signature and verification key
6997+        # 7: Write the file.
6998+        #
6999+        # Some of these can be performed out-of-order, and some can't.
7000+        # The dependencies that I want to test here are:
7001+        #  - Private key before block hashes
7002+        #  - share hashes and block hashes before root hash
7003+        #  - root hash before signature
7004+        #  - signature before verification key
7005+        mw0 = self._make_new_mw("si0", 0)
7006+        # Write some shares
7007+        d = defer.succeed(None)
7008+        for i in xrange(6):
7009+            d.addCallback(lambda ignored, i=i:
7010+                mw0.put_block(self.block, i, self.salt))
7011+        # Try to write the block hashes before writing the encrypted
7012+        # private key
7013+        d.addCallback(lambda ignored:
7014+            self.shouldFail(LayoutInvalid, "block hashes before key",
7015+                            None, mw0.put_blockhashes,
7016+                            self.block_hash_tree))
7017+
7018+        # Write the private key.
7019+        d.addCallback(lambda ignored:
7020+            mw0.put_encprivkey(self.encprivkey))
7021+
7022+
7023+        # Try to write the share hash chain without writing the block
7024+        # hash tree
7025+        d.addCallback(lambda ignored:
7026+            self.shouldFail(LayoutInvalid, "share hash chain before "
7027+                                           "salt hash tree",
7028+                            None,
7029+                            mw0.put_sharehashes, self.share_hash_chain))
7030+
7031+        # Try to write the root hash and without writing either the
7032+        # block hashes or the or the share hashes
7033+        d.addCallback(lambda ignored:
7034+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
7035+                            None,
7036+                            mw0.put_root_hash, self.root_hash))
7037+
7038+        # Now write the block hashes and try again
7039+        d.addCallback(lambda ignored:
7040+            mw0.put_blockhashes(self.block_hash_tree))
7041+
7042+        d.addCallback(lambda ignored:
7043+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
7044+                            None, mw0.put_root_hash, self.root_hash))
7045+
7046+        # We haven't yet put the root hash on the share, so we shouldn't
7047+        # be able to sign it.
7048+        d.addCallback(lambda ignored:
7049+            self.shouldFail(LayoutInvalid, "signature before root hash",
7050+                            None, mw0.put_signature, self.signature))
7051+
7052+        d.addCallback(lambda ignored:
7053+            self.failUnlessRaises(LayoutInvalid, mw0.get_signable))
7054+
7055+        # ..and, since that fails, we also shouldn't be able to put the
7056+        # verification key.
7057+        d.addCallback(lambda ignored:
7058+            self.shouldFail(LayoutInvalid, "key before signature",
7059+                            None, mw0.put_verification_key,
7060+                            self.verification_key))
7061+
7062+        # Now write the share hashes.
7063+        d.addCallback(lambda ignored:
7064+            mw0.put_sharehashes(self.share_hash_chain))
7065+        # We should be able to write the root hash now too
7066+        d.addCallback(lambda ignored:
7067+            mw0.put_root_hash(self.root_hash))
7068+
7069+        # We should still be unable to put the verification key
7070+        d.addCallback(lambda ignored:
7071+            self.shouldFail(LayoutInvalid, "key before signature",
7072+                            None, mw0.put_verification_key,
7073+                            self.verification_key))
7074+
7075+        d.addCallback(lambda ignored:
7076+            mw0.put_signature(self.signature))
7077+
7078+        # We shouldn't be able to write the offsets to the remote server
7079+        # until the offset table is finished; IOW, until we have written
7080+        # the verification key.
7081+        d.addCallback(lambda ignored:
7082+            self.shouldFail(LayoutInvalid, "offsets before verification key",
7083+                            None,
7084+                            mw0.finish_publishing))
7085+
7086+        d.addCallback(lambda ignored:
7087+            mw0.put_verification_key(self.verification_key))
7088+        return d
7089+
7090+
7091+    def test_end_to_end(self):
7092+        mw = self._make_new_mw("si1", 0)
7093+        # Write a share using the mutable writer, and make sure that the
7094+        # reader knows how to read everything back to us.
7095+        d = defer.succeed(None)
7096+        for i in xrange(6):
7097+            d.addCallback(lambda ignored, i=i:
7098+                mw.put_block(self.block, i, self.salt))
7099+        d.addCallback(lambda ignored:
7100+            mw.put_encprivkey(self.encprivkey))
7101+        d.addCallback(lambda ignored:
7102+            mw.put_blockhashes(self.block_hash_tree))
7103+        d.addCallback(lambda ignored:
7104+            mw.put_sharehashes(self.share_hash_chain))
7105+        d.addCallback(lambda ignored:
7106+            mw.put_root_hash(self.root_hash))
7107+        d.addCallback(lambda ignored:
7108+            mw.put_signature(self.signature))
7109+        d.addCallback(lambda ignored:
7110+            mw.put_verification_key(self.verification_key))
7111+        d.addCallback(lambda ignored:
7112+            mw.finish_publishing())
7113+
7114+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7115+        def _check_block_and_salt((block, salt)):
7116+            self.failUnlessEqual(block, self.block)
7117+            self.failUnlessEqual(salt, self.salt)
7118+
7119+        for i in xrange(6):
7120+            d.addCallback(lambda ignored, i=i:
7121+                mr.get_block_and_salt(i))
7122+            d.addCallback(_check_block_and_salt)
7123+
7124+        d.addCallback(lambda ignored:
7125+            mr.get_encprivkey())
7126+        d.addCallback(lambda encprivkey:
7127+            self.failUnlessEqual(self.encprivkey, encprivkey))
7128+
7129+        d.addCallback(lambda ignored:
7130+            mr.get_blockhashes())
7131+        d.addCallback(lambda blockhashes:
7132+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
7133+
7134+        d.addCallback(lambda ignored:
7135+            mr.get_sharehashes())
7136+        d.addCallback(lambda sharehashes:
7137+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
7138+
7139+        d.addCallback(lambda ignored:
7140+            mr.get_signature())
7141+        d.addCallback(lambda signature:
7142+            self.failUnlessEqual(signature, self.signature))
7143+
7144+        d.addCallback(lambda ignored:
7145+            mr.get_verification_key())
7146+        d.addCallback(lambda verification_key:
7147+            self.failUnlessEqual(verification_key, self.verification_key))
7148+
7149+        d.addCallback(lambda ignored:
7150+            mr.get_seqnum())
7151+        d.addCallback(lambda seqnum:
7152+            self.failUnlessEqual(seqnum, 0))
7153+
7154+        d.addCallback(lambda ignored:
7155+            mr.get_root_hash())
7156+        d.addCallback(lambda root_hash:
7157+            self.failUnlessEqual(self.root_hash, root_hash))
7158+
7159+        d.addCallback(lambda ignored:
7160+            mr.get_encoding_parameters())
7161+        def _check_encoding_parameters((k, n, segsize, datalen)):
7162+            self.failUnlessEqual(k, 3)
7163+            self.failUnlessEqual(n, 10)
7164+            self.failUnlessEqual(segsize, 6)
7165+            self.failUnlessEqual(datalen, 36)
7166+        d.addCallback(_check_encoding_parameters)
7167+
7168+        d.addCallback(lambda ignored:
7169+            mr.get_checkstring())
7170+        d.addCallback(lambda checkstring:
7171+            self.failUnlessEqual(checkstring, mw.get_checkstring()))
7172+        return d
7173+
7174+
7175+    def test_is_sdmf(self):
7176+        # The MDMFSlotReadProxy should also know how to read SDMF files,
7177+        # since it will encounter them on the grid. Callers use the
7178+        # is_sdmf method to test this.
7179+        self.write_sdmf_share_to_server("si1")
7180+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7181+        d = mr.is_sdmf()
7182+        d.addCallback(lambda issdmf:
7183+            self.failUnless(issdmf))
7184+        return d
7185+
7186+
7187+    def test_reads_sdmf(self):
7188+        # The slot read proxy should, naturally, know how to tell us
7189+        # about data in the SDMF format
7190+        self.write_sdmf_share_to_server("si1")
7191+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7192+        d = defer.succeed(None)
7193+        d.addCallback(lambda ignored:
7194+            mr.is_sdmf())
7195+        d.addCallback(lambda issdmf:
7196+            self.failUnless(issdmf))
7197+
7198+        # What do we need to read?
7199+        #  - The sharedata
7200+        #  - The salt
7201+        d.addCallback(lambda ignored:
7202+            mr.get_block_and_salt(0))
7203+        def _check_block_and_salt(results):
7204+            block, salt = results
7205+            # Our original file is 36 bytes long. Then each share is 12
7206+            # bytes in size. The share is composed entirely of the
7207+            # letter a. self.block contains 2 as, so 6 * self.block is
7208+            # what we are looking for.
7209+            self.failUnlessEqual(block, self.block * 6)
7210+            self.failUnlessEqual(salt, self.salt)
7211+        d.addCallback(_check_block_and_salt)
7212+
7213+        #  - The blockhashes
7214+        d.addCallback(lambda ignored:
7215+            mr.get_blockhashes())
7216+        d.addCallback(lambda blockhashes:
7217+            self.failUnlessEqual(self.block_hash_tree,
7218+                                 blockhashes,
7219+                                 blockhashes))
7220+        #  - The sharehashes
7221+        d.addCallback(lambda ignored:
7222+            mr.get_sharehashes())
7223+        d.addCallback(lambda sharehashes:
7224+            self.failUnlessEqual(self.share_hash_chain,
7225+                                 sharehashes))
7226+        #  - The keys
7227+        d.addCallback(lambda ignored:
7228+            mr.get_encprivkey())
7229+        d.addCallback(lambda encprivkey:
7230+            self.failUnlessEqual(encprivkey, self.encprivkey, encprivkey))
7231+        d.addCallback(lambda ignored:
7232+            mr.get_verification_key())
7233+        d.addCallback(lambda verification_key:
7234+            self.failUnlessEqual(verification_key,
7235+                                 self.verification_key,
7236+                                 verification_key))
7237+        #  - The signature
7238+        d.addCallback(lambda ignored:
7239+            mr.get_signature())
7240+        d.addCallback(lambda signature:
7241+            self.failUnlessEqual(signature, self.signature, signature))
7242+
7243+        #  - The sequence number
7244+        d.addCallback(lambda ignored:
7245+            mr.get_seqnum())
7246+        d.addCallback(lambda seqnum:
7247+            self.failUnlessEqual(seqnum, 0, seqnum))
7248+
7249+        #  - The root hash
7250+        d.addCallback(lambda ignored:
7251+            mr.get_root_hash())
7252+        d.addCallback(lambda root_hash:
7253+            self.failUnlessEqual(root_hash, self.root_hash, root_hash))
7254+        return d
7255+
7256+
7257+    def test_only_reads_one_segment_sdmf(self):
7258+        # SDMF shares have only one segment, so it doesn't make sense to
7259+        # read more segments than that. The reader should know this and
7260+        # complain if we try to do that.
7261+        self.write_sdmf_share_to_server("si1")
7262+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7263+        d = defer.succeed(None)
7264+        d.addCallback(lambda ignored:
7265+            mr.is_sdmf())
7266+        d.addCallback(lambda issdmf:
7267+            self.failUnless(issdmf))
7268+        d.addCallback(lambda ignored:
7269+            self.shouldFail(LayoutInvalid, "test bad segment",
7270+                            None,
7271+                            mr.get_block_and_salt, 1))
7272+        return d
7273+
7274+
7275+    def test_read_with_prefetched_mdmf_data(self):
7276+        # The MDMFSlotReadProxy will prefill certain fields if you pass
7277+        # it data that you have already fetched. This is useful for
7278+        # cases like the Servermap, which prefetches ~2kb of data while
7279+        # finding out which shares are on the remote peer so that it
7280+        # doesn't waste round trips.
7281+        mdmf_data = self.build_test_mdmf_share()
7282+        self.write_test_share_to_server("si1")
7283+        def _make_mr(ignored, length):
7284+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:length])
7285+            return mr
7286+
7287+        d = defer.succeed(None)
7288+        # This should be enough to fill in both the encoding parameters
7289+        # and the table of offsets, which will complete the version
7290+        # information tuple.
7291+        d.addCallback(_make_mr, 107)
7292+        d.addCallback(lambda mr:
7293+            mr.get_verinfo())
7294+        def _check_verinfo(verinfo):
7295+            self.failUnless(verinfo)
7296+            self.failUnlessEqual(len(verinfo), 9)
7297+            (seqnum,
7298+             root_hash,
7299+             salt_hash,
7300+             segsize,
7301+             datalen,
7302+             k,
7303+             n,
7304+             prefix,
7305+             offsets) = verinfo
7306+            self.failUnlessEqual(seqnum, 0)
7307+            self.failUnlessEqual(root_hash, self.root_hash)
7308+            self.failUnlessEqual(segsize, 6)
7309+            self.failUnlessEqual(datalen, 36)
7310+            self.failUnlessEqual(k, 3)
7311+            self.failUnlessEqual(n, 10)
7312+            expected_prefix = struct.pack(MDMFSIGNABLEHEADER,
7313+                                          1,
7314+                                          seqnum,
7315+                                          root_hash,
7316+                                          k,
7317+                                          n,
7318+                                          segsize,
7319+                                          datalen)
7320+            self.failUnlessEqual(expected_prefix, prefix)
7321+            self.failUnlessEqual(self.rref.read_count, 0)
7322+        d.addCallback(_check_verinfo)
7323+        # This is not enough data to read a block and a share, so the
7324+        # wrapper should attempt to read this from the remote server.
7325+        d.addCallback(_make_mr, 107)
7326+        d.addCallback(lambda mr:
7327+            mr.get_block_and_salt(0))
7328+        def _check_block_and_salt((block, salt)):
7329+            self.failUnlessEqual(block, self.block)
7330+            self.failUnlessEqual(salt, self.salt)
7331+            self.failUnlessEqual(self.rref.read_count, 1)
7332+        # This should be enough data to read one block.
7333+        d.addCallback(_make_mr, 249)
7334+        d.addCallback(lambda mr:
7335+            mr.get_block_and_salt(0))
7336+        d.addCallback(_check_block_and_salt)
7337+        return d
7338+
7339+
7340+    def test_read_with_prefetched_sdmf_data(self):
7341+        sdmf_data = self.build_test_sdmf_share()
7342+        self.write_sdmf_share_to_server("si1")
7343+        def _make_mr(ignored, length):
7344+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:length])
7345+            return mr
7346+
7347+        d = defer.succeed(None)
7348+        # This should be enough to get us the encoding parameters,
7349+        # offset table, and everything else we need to build a verinfo
7350+        # string.
7351+        d.addCallback(_make_mr, 107)
7352+        d.addCallback(lambda mr:
7353+            mr.get_verinfo())
7354+        def _check_verinfo(verinfo):
7355+            self.failUnless(verinfo)
7356+            self.failUnlessEqual(len(verinfo), 9)
7357+            (seqnum,
7358+             root_hash,
7359+             salt,
7360+             segsize,
7361+             datalen,
7362+             k,
7363+             n,
7364+             prefix,
7365+             offsets) = verinfo
7366+            self.failUnlessEqual(seqnum, 0)
7367+            self.failUnlessEqual(root_hash, self.root_hash)
7368+            self.failUnlessEqual(salt, self.salt)
7369+            self.failUnlessEqual(segsize, 36)
7370+            self.failUnlessEqual(datalen, 36)
7371+            self.failUnlessEqual(k, 3)
7372+            self.failUnlessEqual(n, 10)
7373+            expected_prefix = struct.pack(SIGNED_PREFIX,
7374+                                          0,
7375+                                          seqnum,
7376+                                          root_hash,
7377+                                          salt,
7378+                                          k,
7379+                                          n,
7380+                                          segsize,
7381+                                          datalen)
7382+            self.failUnlessEqual(expected_prefix, prefix)
7383+            self.failUnlessEqual(self.rref.read_count, 0)
7384+        d.addCallback(_check_verinfo)
7385+        # This shouldn't be enough to read any share data.
7386+        d.addCallback(_make_mr, 107)
7387+        d.addCallback(lambda mr:
7388+            mr.get_block_and_salt(0))
7389+        def _check_block_and_salt((block, salt)):
7390+            self.failUnlessEqual(block, self.block * 6)
7391+            self.failUnlessEqual(salt, self.salt)
7392+            # TODO: Fix the read routine so that it reads only the data
7393+            #       that it has cached if it can't read all of it.
7394+            self.failUnlessEqual(self.rref.read_count, 2)
7395+
7396+        # This should be enough to read share data.
7397+        d.addCallback(_make_mr, self.offsets['share_data'])
7398+        d.addCallback(lambda mr:
7399+            mr.get_block_and_salt(0))
7400+        d.addCallback(_check_block_and_salt)
7401+        return d
7402+
7403+
7404+    def test_read_with_empty_mdmf_file(self):
7405+        # Some tests upload a file with no contents to test things
7406+        # unrelated to the actual handling of the content of the file.
7407+        # The reader should behave intelligently in these cases.
7408+        self.write_test_share_to_server("si1", empty=True)
7409+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7410+        # We should be able to get the encoding parameters, and they
7411+        # should be correct.
7412+        d = defer.succeed(None)
7413+        d.addCallback(lambda ignored:
7414+            mr.get_encoding_parameters())
7415+        def _check_encoding_parameters(params):
7416+            self.failUnlessEqual(len(params), 4)
7417+            k, n, segsize, datalen = params
7418+            self.failUnlessEqual(k, 3)
7419+            self.failUnlessEqual(n, 10)
7420+            self.failUnlessEqual(segsize, 0)
7421+            self.failUnlessEqual(datalen, 0)
7422+        d.addCallback(_check_encoding_parameters)
7423+
7424+        # We should not be able to fetch a block, since there are no
7425+        # blocks to fetch
7426+        d.addCallback(lambda ignored:
7427+            self.shouldFail(LayoutInvalid, "get block on empty file",
7428+                            None,
7429+                            mr.get_block_and_salt, 0))
7430+        return d
7431+
7432+
7433+    def test_read_with_empty_sdmf_file(self):
7434+        self.write_sdmf_share_to_server("si1", empty=True)
7435+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7436+        # We should be able to get the encoding parameters, and they
7437+        # should be correct
7438+        d = defer.succeed(None)
7439+        d.addCallback(lambda ignored:
7440+            mr.get_encoding_parameters())
7441+        def _check_encoding_parameters(params):
7442+            self.failUnlessEqual(len(params), 4)
7443+            k, n, segsize, datalen = params
7444+            self.failUnlessEqual(k, 3)
7445+            self.failUnlessEqual(n, 10)
7446+            self.failUnlessEqual(segsize, 0)
7447+            self.failUnlessEqual(datalen, 0)
7448+        d.addCallback(_check_encoding_parameters)
7449+
7450+        # It does not make sense to get a block in this format, so we
7451+        # should not be able to.
7452+        d.addCallback(lambda ignored:
7453+            self.shouldFail(LayoutInvalid, "get block on an empty file",
7454+                            None,
7455+                            mr.get_block_and_salt, 0))
7456+        return d
7457+
7458+
7459+    def test_verinfo_with_sdmf_file(self):
7460+        self.write_sdmf_share_to_server("si1")
7461+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7462+        # We should be able to get the version information.
7463+        d = defer.succeed(None)
7464+        d.addCallback(lambda ignored:
7465+            mr.get_verinfo())
7466+        def _check_verinfo(verinfo):
7467+            self.failUnless(verinfo)
7468+            self.failUnlessEqual(len(verinfo), 9)
7469+            (seqnum,
7470+             root_hash,
7471+             salt,
7472+             segsize,
7473+             datalen,
7474+             k,
7475+             n,
7476+             prefix,
7477+             offsets) = verinfo
7478+            self.failUnlessEqual(seqnum, 0)
7479+            self.failUnlessEqual(root_hash, self.root_hash)
7480+            self.failUnlessEqual(salt, self.salt)
7481+            self.failUnlessEqual(segsize, 36)
7482+            self.failUnlessEqual(datalen, 36)
7483+            self.failUnlessEqual(k, 3)
7484+            self.failUnlessEqual(n, 10)
7485+            expected_prefix = struct.pack(">BQ32s16s BBQQ",
7486+                                          0,
7487+                                          seqnum,
7488+                                          root_hash,
7489+                                          salt,
7490+                                          k,
7491+                                          n,
7492+                                          segsize,
7493+                                          datalen)
7494+            self.failUnlessEqual(prefix, expected_prefix)
7495+            self.failUnlessEqual(offsets, self.offsets)
7496+        d.addCallback(_check_verinfo)
7497+        return d
7498+
7499+
7500+    def test_verinfo_with_mdmf_file(self):
7501+        self.write_test_share_to_server("si1")
7502+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7503+        d = defer.succeed(None)
7504+        d.addCallback(lambda ignored:
7505+            mr.get_verinfo())
7506+        def _check_verinfo(verinfo):
7507+            self.failUnless(verinfo)
7508+            self.failUnlessEqual(len(verinfo), 9)
7509+            (seqnum,
7510+             root_hash,
7511+             IV,
7512+             segsize,
7513+             datalen,
7514+             k,
7515+             n,
7516+             prefix,
7517+             offsets) = verinfo
7518+            self.failUnlessEqual(seqnum, 0)
7519+            self.failUnlessEqual(root_hash, self.root_hash)
7520+            self.failIf(IV)
7521+            self.failUnlessEqual(segsize, 6)
7522+            self.failUnlessEqual(datalen, 36)
7523+            self.failUnlessEqual(k, 3)
7524+            self.failUnlessEqual(n, 10)
7525+            expected_prefix = struct.pack(">BQ32s BBQQ",
7526+                                          1,
7527+                                          seqnum,
7528+                                          root_hash,
7529+                                          k,
7530+                                          n,
7531+                                          segsize,
7532+                                          datalen)
7533+            self.failUnlessEqual(prefix, expected_prefix)
7534+            self.failUnlessEqual(offsets, self.offsets)
7535+        d.addCallback(_check_verinfo)
7536+        return d
7537+
7538+
7539+    def test_reader_queue(self):
7540+        self.write_test_share_to_server('si1')
7541+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7542+        d1 = mr.get_block_and_salt(0, queue=True)
7543+        d2 = mr.get_blockhashes(queue=True)
7544+        d3 = mr.get_sharehashes(queue=True)
7545+        d4 = mr.get_signature(queue=True)
7546+        d5 = mr.get_verification_key(queue=True)
7547+        dl = defer.DeferredList([d1, d2, d3, d4, d5])
7548+        mr.flush()
7549+        def _print(results):
7550+            self.failUnlessEqual(len(results), 5)
7551+            # We have one read for version information and offsets, and
7552+            # one for everything else.
7553+            self.failUnlessEqual(self.rref.read_count, 2)
7554+            block, salt = results[0][1] # results[0] is a boolean that says
7555+                                           # whether or not the operation
7556+                                           # worked.
7557+            self.failUnlessEqual(self.block, block)
7558+            self.failUnlessEqual(self.salt, salt)
7559+
7560+            blockhashes = results[1][1]
7561+            self.failUnlessEqual(self.block_hash_tree, blockhashes)
7562+
7563+            sharehashes = results[2][1]
7564+            self.failUnlessEqual(self.share_hash_chain, sharehashes)
7565+
7566+            signature = results[3][1]
7567+            self.failUnlessEqual(self.signature, signature)
7568+
7569+            verification_key = results[4][1]
7570+            self.failUnlessEqual(self.verification_key, verification_key)
7571+        dl.addCallback(_print)
7572+        return dl
7573+
7574+
7575+    def test_sdmf_writer(self):
7576+        # Go through the motions of writing an SDMF share to the storage
7577+        # server. Then read the storage server to see that the share got
7578+        # written in the way that we think it should have.
7579+
7580+        # We do this first so that the necessary instance variables get
7581+        # set the way we want them for the tests below.
7582+        data = self.build_test_sdmf_share()
7583+        sdmfr = SDMFSlotWriteProxy(0,
7584+                                   self.rref,
7585+                                   "si1",
7586+                                   self.secrets,
7587+                                   0, 3, 10, 36, 36)
7588+        # Put the block and salt.
7589+        sdmfr.put_block(self.blockdata, 0, self.salt)
7590+
7591+        # Put the encprivkey
7592+        sdmfr.put_encprivkey(self.encprivkey)
7593+
7594+        # Put the block and share hash chains
7595+        sdmfr.put_blockhashes(self.block_hash_tree)
7596+        sdmfr.put_sharehashes(self.share_hash_chain)
7597+        sdmfr.put_root_hash(self.root_hash)
7598+
7599+        # Put the signature
7600+        sdmfr.put_signature(self.signature)
7601+
7602+        # Put the verification key
7603+        sdmfr.put_verification_key(self.verification_key)
7604+
7605+        # Now check to make sure that nothing has been written yet.
7606+        self.failUnlessEqual(self.rref.write_count, 0)
7607+
7608+        # Now finish publishing
7609+        d = sdmfr.finish_publishing()
7610+        def _then(ignored):
7611+            self.failUnlessEqual(self.rref.write_count, 1)
7612+            read = self.ss.remote_slot_readv
7613+            self.failUnlessEqual(read("si1", [0], [(0, len(data))]),
7614+                                 {0: [data]})
7615+        d.addCallback(_then)
7616+        return d
7617+
7618+
7619+    def test_sdmf_writer_preexisting_share(self):
7620+        data = self.build_test_sdmf_share()
7621+        self.write_sdmf_share_to_server("si1")
7622+
7623+        # Now there is a share on the storage server. To successfully
7624+        # write, we need to set the checkstring correctly. When we
7625+        # don't, no write should occur.
7626+        sdmfw = SDMFSlotWriteProxy(0,
7627+                                   self.rref,
7628+                                   "si1",
7629+                                   self.secrets,
7630+                                   1, 3, 10, 36, 36)
7631+        sdmfw.put_block(self.blockdata, 0, self.salt)
7632+
7633+        # Put the encprivkey
7634+        sdmfw.put_encprivkey(self.encprivkey)
7635+
7636+        # Put the block and share hash chains
7637+        sdmfw.put_blockhashes(self.block_hash_tree)
7638+        sdmfw.put_sharehashes(self.share_hash_chain)
7639+
7640+        # Put the root hash
7641+        sdmfw.put_root_hash(self.root_hash)
7642+
7643+        # Put the signature
7644+        sdmfw.put_signature(self.signature)
7645+
7646+        # Put the verification key
7647+        sdmfw.put_verification_key(self.verification_key)
7648+
7649+        # We shouldn't have a checkstring yet
7650+        self.failUnlessEqual(sdmfw.get_checkstring(), "")
7651+
7652+        d = sdmfw.finish_publishing()
7653+        def _then(results):
7654+            self.failIf(results[0])
7655+            # this is the correct checkstring
7656+            self._expected_checkstring = results[1][0][0]
7657+            return self._expected_checkstring
7658+
7659+        d.addCallback(_then)
7660+        d.addCallback(sdmfw.set_checkstring)
7661+        d.addCallback(lambda ignored:
7662+            sdmfw.get_checkstring())
7663+        d.addCallback(lambda checkstring:
7664+            self.failUnlessEqual(checkstring, self._expected_checkstring))
7665+        d.addCallback(lambda ignored:
7666+            sdmfw.finish_publishing())
7667+        def _then_again(results):
7668+            self.failUnless(results[0])
7669+            read = self.ss.remote_slot_readv
7670+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
7671+                                 {0: [struct.pack(">Q", 1)]})
7672+            self.failUnlessEqual(read("si1", [0], [(9, len(data) - 9)]),
7673+                                 {0: [data[9:]]})
7674+        d.addCallback(_then_again)
7675+        return d
7676+
7677+
7678 class Stats(unittest.TestCase):
7679 
7680     def setUp(self):
7681}
7682[mutable/retrieve.py: Modify the retrieval process to support MDMF
7683Kevan Carstensen <kevan@isnotajoke.com>**20100819003409
7684 Ignore-this: c03f4e41aaa0366a9bf44847f2caf9db
7685 
7686 The logic behind a mutable file download had to be adapted to work with
7687 segmented mutable files; this patch performs those adaptations. It also
7688 exposes some decoding and decrypting functionality to make partial-file
7689 updates a little easier, and supports efficient random-access downloads
7690 of parts of an MDMF file.
7691] {
7692hunk ./src/allmydata/mutable/retrieve.py 2
7693 
7694-import struct, time
7695+import time
7696 from itertools import count
7697 from zope.interface import implements
7698 from twisted.internet import defer
7699merger 0.0 (
7700hunk ./src/allmydata/mutable/retrieve.py 10
7701+from allmydata.util.dictutil import DictOfSets
7702hunk ./src/allmydata/mutable/retrieve.py 7
7703-from foolscap.api import DeadReferenceError, eventually, fireEventually
7704-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
7705-from allmydata.util import hashutil, idlib, log
7706+from twisted.internet.interfaces import IPushProducer, IConsumer
7707+from foolscap.api import eventually, fireEventually
7708+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
7709+                                 MDMF_VERSION, SDMF_VERSION
7710+from allmydata.util import hashutil, log, mathutil
7711)
7712hunk ./src/allmydata/mutable/retrieve.py 16
7713 from pycryptopp.publickey import rsa
7714 
7715 from allmydata.mutable.common import CorruptShareError, UncoordinatedWriteError
7716-from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data
7717+from allmydata.mutable.layout import MDMFSlotReadProxy
7718 
7719 class RetrieveStatus:
7720     implements(IRetrieveStatus)
7721hunk ./src/allmydata/mutable/retrieve.py 83
7722     # times, and each will have a separate response chain. However the
7723     # Retrieve object will remain tied to a specific version of the file, and
7724     # will use a single ServerMap instance.
7725+    implements(IPushProducer)
7726 
7727hunk ./src/allmydata/mutable/retrieve.py 85
7728-    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False):
7729+    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
7730+                 verify=False):
7731         self._node = filenode
7732         assert self._node.get_pubkey()
7733         self._storage_index = filenode.get_storage_index()
7734hunk ./src/allmydata/mutable/retrieve.py 104
7735         self.verinfo = verinfo
7736         # during repair, we may be called upon to grab the private key, since
7737         # it wasn't picked up during a verify=False checker run, and we'll
7738-        # need it for repair to generate the a new version.
7739-        self._need_privkey = fetch_privkey
7740-        if self._node.get_privkey():
7741+        # need it for repair to generate a new version.
7742+        self._need_privkey = fetch_privkey or verify
7743+        if self._node.get_privkey() and not verify:
7744             self._need_privkey = False
7745 
7746hunk ./src/allmydata/mutable/retrieve.py 109
7747+        if self._need_privkey:
7748+            # TODO: Evaluate the need for this. We'll use it if we want
7749+            # to limit how many queries are on the wire for the privkey
7750+            # at once.
7751+            self._privkey_query_markers = [] # one Marker for each time we've
7752+                                             # tried to get the privkey.
7753+
7754+        # verify means that we are using the downloader logic to verify all
7755+        # of our shares. This tells the downloader a few things.
7756+        #
7757+        # 1. We need to download all of the shares.
7758+        # 2. We don't need to decode or decrypt the shares, since our
7759+        #    caller doesn't care about the plaintext, only the
7760+        #    information about which shares are or are not valid.
7761+        # 3. When we are validating readers, we need to validate the
7762+        #    signature on the prefix. Do we? We already do this in the
7763+        #    servermap update?
7764+        self._verify = False
7765+        if verify:
7766+            self._verify = True
7767+
7768         self._status = RetrieveStatus()
7769         self._status.set_storage_index(self._storage_index)
7770         self._status.set_helper(False)
7771hunk ./src/allmydata/mutable/retrieve.py 139
7772          offsets_tuple) = self.verinfo
7773         self._status.set_size(datalength)
7774         self._status.set_encoding(k, N)
7775+        self.readers = {}
7776+        self._paused = False
7777+        self._paused_deferred = None
7778+        self._offset = None
7779+        self._read_length = None
7780+        self.log("got seqnum %d" % self.verinfo[0])
7781+
7782 
7783     def get_status(self):
7784         return self._status
7785hunk ./src/allmydata/mutable/retrieve.py 157
7786             kwargs["facility"] = "tahoe.mutable.retrieve"
7787         return log.msg(*args, **kwargs)
7788 
7789-    def download(self):
7790+
7791+    ###################
7792+    # IPushProducer
7793+
7794+    def pauseProducing(self):
7795+        """
7796+        I am called by my download target if we have produced too much
7797+        data for it to handle. I make the downloader stop producing new
7798+        data until my resumeProducing method is called.
7799+        """
7800+        if self._paused:
7801+            return
7802+
7803+        # fired when the download is unpaused.
7804+        self._old_status = self._status.get_status()
7805+        self._status.set_status("Paused")
7806+
7807+        self._pause_deferred = defer.Deferred()
7808+        self._paused = True
7809+
7810+
7811+    def resumeProducing(self):
7812+        """
7813+        I am called by my download target once it is ready to begin
7814+        receiving data again.
7815+        """
7816+        if not self._paused:
7817+            return
7818+
7819+        self._paused = False
7820+        p = self._pause_deferred
7821+        self._pause_deferred = None
7822+        self._status.set_status(self._old_status)
7823+
7824+        eventually(p.callback, None)
7825+
7826+
7827+    def _check_for_paused(self, res):
7828+        """
7829+        I am called just before a write to the consumer. I return a
7830+        Deferred that eventually fires with the data that is to be
7831+        written to the consumer. If the download has not been paused,
7832+        the Deferred fires immediately. Otherwise, the Deferred fires
7833+        when the downloader is unpaused.
7834+        """
7835+        if self._paused:
7836+            d = defer.Deferred()
7837+            self._pause_defered.addCallback(lambda ignored: d.callback(res))
7838+            return d
7839+        return defer.succeed(res)
7840+
7841+
7842+    def download(self, consumer=None, offset=0, size=None):
7843+        assert IConsumer.providedBy(consumer) or self._verify
7844+
7845+        if consumer:
7846+            self._consumer = consumer
7847+            # we provide IPushProducer, so streaming=True, per
7848+            # IConsumer.
7849+            self._consumer.registerProducer(self, streaming=True)
7850+
7851         self._done_deferred = defer.Deferred()
7852         self._started = time.time()
7853         self._status.set_status("Retrieving Shares")
7854hunk ./src/allmydata/mutable/retrieve.py 222
7855 
7856+        self._offset = offset
7857+        self._read_length = size
7858+
7859         # first, which servers can we use?
7860         versionmap = self.servermap.make_versionmap()
7861         shares = versionmap[self.verinfo]
7862hunk ./src/allmydata/mutable/retrieve.py 232
7863         self.remaining_sharemap = DictOfSets()
7864         for (shnum, peerid, timestamp) in shares:
7865             self.remaining_sharemap.add(shnum, peerid)
7866+            # If the servermap update fetched anything, it fetched at least 1
7867+            # KiB, so we ask for that much.
7868+            # TODO: Change the cache methods to allow us to fetch all of the
7869+            # data that they have, then change this method to do that.
7870+            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
7871+                                                               shnum,
7872+                                                               0,
7873+                                                               1000)
7874+            ss = self.servermap.connections[peerid]
7875+            reader = MDMFSlotReadProxy(ss,
7876+                                       self._storage_index,
7877+                                       shnum,
7878+                                       any_cache)
7879+            reader.peerid = peerid
7880+            self.readers[shnum] = reader
7881+
7882 
7883         self.shares = {} # maps shnum to validated blocks
7884hunk ./src/allmydata/mutable/retrieve.py 250
7885+        self._active_readers = [] # list of active readers for this dl.
7886+        self._validated_readers = set() # set of readers that we have
7887+                                        # validated the prefix of
7888+        self._block_hash_trees = {} # shnum => hashtree
7889 
7890         # how many shares do we need?
7891hunk ./src/allmydata/mutable/retrieve.py 256
7892-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
7893+        (seqnum,
7894+         root_hash,
7895+         IV,
7896+         segsize,
7897+         datalength,
7898+         k,
7899+         N,
7900+         prefix,
7901          offsets_tuple) = self.verinfo
7902hunk ./src/allmydata/mutable/retrieve.py 265
7903-        assert len(self.remaining_sharemap) >= k
7904-        # we start with the lowest shnums we have available, since FEC is
7905-        # faster if we're using "primary shares"
7906-        self.active_shnums = set(sorted(self.remaining_sharemap.keys())[:k])
7907-        for shnum in self.active_shnums:
7908-            # we use an arbitrary peer who has the share. If shares are
7909-            # doubled up (more than one share per peer), we could make this
7910-            # run faster by spreading the load among multiple peers. But the
7911-            # algorithm to do that is more complicated than I want to write
7912-            # right now, and a well-provisioned grid shouldn't have multiple
7913-            # shares per peer.
7914-            peerid = list(self.remaining_sharemap[shnum])[0]
7915-            self.get_data(shnum, peerid)
7916 
7917hunk ./src/allmydata/mutable/retrieve.py 266
7918-        # control flow beyond this point: state machine. Receiving responses
7919-        # from queries is the input. We might send out more queries, or we
7920-        # might produce a result.
7921 
7922hunk ./src/allmydata/mutable/retrieve.py 267
7923+        # We need one share hash tree for the entire file; its leaves
7924+        # are the roots of the block hash trees for the shares that
7925+        # comprise it, and its root is in the verinfo.
7926+        self.share_hash_tree = hashtree.IncompleteHashTree(N)
7927+        self.share_hash_tree.set_hashes({0: root_hash})
7928+
7929+        # This will set up both the segment decoder and the tail segment
7930+        # decoder, as well as a variety of other instance variables that
7931+        # the download process will use.
7932+        self._setup_encoding_parameters()
7933+        assert len(self.remaining_sharemap) >= k
7934+
7935+        self.log("starting download")
7936+        self._paused = False
7937+        self._started_fetching = time.time()
7938+
7939+        self._add_active_peers()
7940+        # The download process beyond this is a state machine.
7941+        # _add_active_peers will select the peers that we want to use
7942+        # for the download, and then attempt to start downloading. After
7943+        # each segment, it will check for doneness, reacting to broken
7944+        # peers and corrupt shares as necessary. If it runs out of good
7945+        # peers before downloading all of the segments, _done_deferred
7946+        # will errback.  Otherwise, it will eventually callback with the
7947+        # contents of the mutable file.
7948         return self._done_deferred
7949 
7950hunk ./src/allmydata/mutable/retrieve.py 294
7951-    def get_data(self, shnum, peerid):
7952-        self.log(format="sending sh#%(shnum)d request to [%(peerid)s]",
7953-                 shnum=shnum,
7954-                 peerid=idlib.shortnodeid_b2a(peerid),
7955-                 level=log.NOISY)
7956-        ss = self.servermap.connections[peerid]
7957-        started = time.time()
7958-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
7959+
7960+    def decode(self, blocks_and_salts, segnum):
7961+        """
7962+        I am a helper method that the mutable file update process uses
7963+        as a shortcut to decode and decrypt the segments that it needs
7964+        to fetch in order to perform a file update. I take in a
7965+        collection of blocks and salts, and pick some of those to make a
7966+        segment with. I return the plaintext associated with that
7967+        segment.
7968+        """
7969+        # shnum => block hash tree. Unusued, but setup_encoding_parameters will
7970+        # want to set this.
7971+        # XXX: Make it so that it won't set this if we're just decoding.
7972+        self._block_hash_trees = {}
7973+        self._setup_encoding_parameters()
7974+        # This is the form expected by decode.
7975+        blocks_and_salts = blocks_and_salts.items()
7976+        blocks_and_salts = [(True, [d]) for d in blocks_and_salts]
7977+
7978+        d = self._decode_blocks(blocks_and_salts, segnum)
7979+        d.addCallback(self._decrypt_segment)
7980+        return d
7981+
7982+
7983+    def _setup_encoding_parameters(self):
7984+        """
7985+        I set up the encoding parameters, including k, n, the number
7986+        of segments associated with this file, and the segment decoder.
7987+        """
7988+        (seqnum,
7989+         root_hash,
7990+         IV,
7991+         segsize,
7992+         datalength,
7993+         k,
7994+         n,
7995+         known_prefix,
7996          offsets_tuple) = self.verinfo
7997hunk ./src/allmydata/mutable/retrieve.py 332
7998-        offsets = dict(offsets_tuple)
7999+        self._required_shares = k
8000+        self._total_shares = n
8001+        self._segment_size = segsize
8002+        self._data_length = datalength
8003 
8004hunk ./src/allmydata/mutable/retrieve.py 337
8005-        # we read the checkstring, to make sure that the data we grab is from
8006-        # the right version.
8007-        readv = [ (0, struct.calcsize(SIGNED_PREFIX)) ]
8008+        if not IV:
8009+            self._version = MDMF_VERSION
8010+        else:
8011+            self._version = SDMF_VERSION
8012 
8013hunk ./src/allmydata/mutable/retrieve.py 342
8014-        # We also read the data, and the hashes necessary to validate them
8015-        # (share_hash_chain, block_hash_tree, share_data). We don't read the
8016-        # signature or the pubkey, since that was handled during the
8017-        # servermap phase, and we'll be comparing the share hash chain
8018-        # against the roothash that was validated back then.
8019+        if datalength and segsize:
8020+            self._num_segments = mathutil.div_ceil(datalength, segsize)
8021+            self._tail_data_size = datalength % segsize
8022+        else:
8023+            self._num_segments = 0
8024+            self._tail_data_size = 0
8025 
8026hunk ./src/allmydata/mutable/retrieve.py 349
8027-        readv.append( (offsets['share_hash_chain'],
8028-                       offsets['enc_privkey'] - offsets['share_hash_chain'] ) )
8029+        self._segment_decoder = codec.CRSDecoder()
8030+        self._segment_decoder.set_params(segsize, k, n)
8031 
8032hunk ./src/allmydata/mutable/retrieve.py 352
8033-        # if we need the private key (for repair), we also fetch that
8034-        if self._need_privkey:
8035-            readv.append( (offsets['enc_privkey'],
8036-                           offsets['EOF'] - offsets['enc_privkey']) )
8037+        if  not self._tail_data_size:
8038+            self._tail_data_size = segsize
8039+
8040+        self._tail_segment_size = mathutil.next_multiple(self._tail_data_size,
8041+                                                         self._required_shares)
8042+        if self._tail_segment_size == self._segment_size:
8043+            self._tail_decoder = self._segment_decoder
8044+        else:
8045+            self._tail_decoder = codec.CRSDecoder()
8046+            self._tail_decoder.set_params(self._tail_segment_size,
8047+                                          self._required_shares,
8048+                                          self._total_shares)
8049 
8050hunk ./src/allmydata/mutable/retrieve.py 365
8051-        m = Marker()
8052-        self._outstanding_queries[m] = (peerid, shnum, started)
8053+        self.log("got encoding parameters: "
8054+                 "k: %d "
8055+                 "n: %d "
8056+                 "%d segments of %d bytes each (%d byte tail segment)" % \
8057+                 (k, n, self._num_segments, self._segment_size,
8058+                  self._tail_segment_size))
8059 
8060         # ask the cache first
8061         got_from_cache = False
8062merger 0.0 (
8063hunk ./src/allmydata/mutable/retrieve.py 376
8064-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
8065-                                                            offset, length)
8066+            data = self._node._read_from_cache(self.verinfo, shnum, offset, length)
8067hunk ./src/allmydata/mutable/retrieve.py 372
8068-        # ask the cache first
8069-        got_from_cache = False
8070-        datavs = []
8071-        for (offset, length) in readv:
8072-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
8073-                                                            offset, length)
8074-            if data is not None:
8075-                datavs.append(data)
8076-        if len(datavs) == len(readv):
8077-            self.log("got data from cache")
8078-            got_from_cache = True
8079-            d = fireEventually({shnum: datavs})
8080-            # datavs is a dict mapping shnum to a pair of strings
8081+        for i in xrange(self._total_shares):
8082+            # So we don't have to do this later.
8083+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
8084+
8085+        # Our last task is to tell the downloader where to start and
8086+        # where to stop. We use three parameters for that:
8087+        #   - self._start_segment: the segment that we need to start
8088+        #     downloading from.
8089+        #   - self._current_segment: the next segment that we need to
8090+        #     download.
8091+        #   - self._last_segment: The last segment that we were asked to
8092+        #     download.
8093+        #
8094+        #  We say that the download is complete when
8095+        #  self._current_segment > self._last_segment. We use
8096+        #  self._start_segment and self._last_segment to know when to
8097+        #  strip things off of segments, and how much to strip.
8098+        if self._offset:
8099+            self.log("got offset: %d" % self._offset)
8100+            # our start segment is the first segment containing the
8101+            # offset we were given.
8102+            start = mathutil.div_ceil(self._offset,
8103+                                      self._segment_size)
8104+            # this gets us the first segment after self._offset. Then
8105+            # our start segment is the one before it.
8106+            start -= 1
8107+
8108+            assert start < self._num_segments
8109+            self._start_segment = start
8110+            self.log("got start segment: %d" % self._start_segment)
8111)
8112hunk ./src/allmydata/mutable/retrieve.py 386
8113             d = fireEventually({shnum: datavs})
8114             # datavs is a dict mapping shnum to a pair of strings
8115         else:
8116-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
8117-        self.remaining_sharemap.discard(shnum, peerid)
8118+            self._start_segment = 0
8119 
8120hunk ./src/allmydata/mutable/retrieve.py 388
8121-        d.addCallback(self._got_results, m, peerid, started, got_from_cache)
8122-        d.addErrback(self._query_failed, m, peerid)
8123-        # errors that aren't handled by _query_failed (and errors caused by
8124-        # _query_failed) get logged, but we still want to check for doneness.
8125-        def _oops(f):
8126-            self.log(format="problem in _query_failed for sh#%(shnum)d to %(peerid)s",
8127-                     shnum=shnum,
8128-                     peerid=idlib.shortnodeid_b2a(peerid),
8129-                     failure=f,
8130-                     level=log.WEIRD, umid="W0xnQA")
8131-        d.addErrback(_oops)
8132-        d.addBoth(self._check_for_done)
8133-        # any error during _check_for_done means the download fails. If the
8134-        # download is successful, _check_for_done will fire _done by itself.
8135-        d.addErrback(self._done)
8136-        d.addErrback(log.err)
8137-        return d # purely for testing convenience
8138 
8139hunk ./src/allmydata/mutable/retrieve.py 389
8140-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
8141-        # isolate the callRemote to a separate method, so tests can subclass
8142-        # Publish and override it
8143-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
8144-        return d
8145+        if self._read_length:
8146+            # our end segment is the last segment containing part of the
8147+            # segment that we were asked to read.
8148+            self.log("got read length %d" % self._read_length)
8149+            end_data = self._offset + self._read_length
8150+            end = mathutil.div_ceil(end_data,
8151+                                    self._segment_size)
8152+            end -= 1
8153+            assert end < self._num_segments
8154+            self._last_segment = end
8155+            self.log("got end segment: %d" % self._last_segment)
8156+        else:
8157+            self._last_segment = self._num_segments - 1
8158 
8159hunk ./src/allmydata/mutable/retrieve.py 403
8160-    def remove_peer(self, peerid):
8161-        for shnum in list(self.remaining_sharemap.keys()):
8162-            self.remaining_sharemap.discard(shnum, peerid)
8163+        self._current_segment = self._start_segment
8164 
8165hunk ./src/allmydata/mutable/retrieve.py 405
8166-    def _got_results(self, datavs, marker, peerid, started, got_from_cache):
8167-        now = time.time()
8168-        elapsed = now - started
8169-        if not got_from_cache:
8170-            self._status.add_fetch_timing(peerid, elapsed)
8171-        self.log(format="got results (%(shares)d shares) from [%(peerid)s]",
8172-                 shares=len(datavs),
8173-                 peerid=idlib.shortnodeid_b2a(peerid),
8174-                 level=log.NOISY)
8175-        self._outstanding_queries.pop(marker, None)
8176-        if not self._running:
8177-            return
8178+    def _add_active_peers(self):
8179+        """
8180+        I populate self._active_readers with enough active readers to
8181+        retrieve the contents of this mutable file. I am called before
8182+        downloading starts, and (eventually) after each validation
8183+        error, connection error, or other problem in the download.
8184+        """
8185+        # TODO: It would be cool to investigate other heuristics for
8186+        # reader selection. For instance, the cost (in time the user
8187+        # spends waiting for their file) of selecting a really slow peer
8188+        # that happens to have a primary share is probably more than
8189+        # selecting a really fast peer that doesn't have a primary
8190+        # share. Maybe the servermap could be extended to provide this
8191+        # information; it could keep track of latency information while
8192+        # it gathers more important data, and then this routine could
8193+        # use that to select active readers.
8194+        #
8195+        # (these and other questions would be easier to answer with a
8196+        #  robust, configurable tahoe-lafs simulator, which modeled node
8197+        #  failures, differences in node speed, and other characteristics
8198+        #  that we expect storage servers to have.  You could have
8199+        #  presets for really stable grids (like allmydata.com),
8200+        #  friendnets, make it easy to configure your own settings, and
8201+        #  then simulate the effect of big changes on these use cases
8202+        #  instead of just reasoning about what the effect might be. Out
8203+        #  of scope for MDMF, though.)
8204 
8205hunk ./src/allmydata/mutable/retrieve.py 432
8206-        # note that we only ask for a single share per query, so we only
8207-        # expect a single share back. On the other hand, we use the extra
8208-        # shares if we get them.. seems better than an assert().
8209+        # We need at least self._required_shares readers to download a
8210+        # segment.
8211+        if self._verify:
8212+            needed = self._total_shares
8213+        else:
8214+            needed = self._required_shares - len(self._active_readers)
8215+        # XXX: Why don't format= log messages work here?
8216+        self.log("adding %d peers to the active peers list" % needed)
8217 
8218hunk ./src/allmydata/mutable/retrieve.py 441
8219-        for shnum,datav in datavs.items():
8220-            (prefix, hash_and_data) = datav[:2]
8221-            try:
8222-                self._got_results_one_share(shnum, peerid,
8223-                                            prefix, hash_and_data)
8224-            except CorruptShareError, e:
8225-                # log it and give the other shares a chance to be processed
8226-                f = failure.Failure()
8227-                self.log(format="bad share: %(f_value)s",
8228-                         f_value=str(f.value), failure=f,
8229-                         level=log.WEIRD, umid="7fzWZw")
8230-                self.notify_server_corruption(peerid, shnum, str(e))
8231-                self.remove_peer(peerid)
8232-                self.servermap.mark_bad_share(peerid, shnum, prefix)
8233-                self._bad_shares.add( (peerid, shnum) )
8234-                self._status.problems[peerid] = f
8235-                self._last_failure = f
8236-                pass
8237-            if self._need_privkey and len(datav) > 2:
8238-                lp = None
8239-                self._try_to_validate_privkey(datav[2], peerid, shnum, lp)
8240-        # all done!
8241+        # We favor lower numbered shares, since FEC is faster with
8242+        # primary shares than with other shares, and lower-numbered
8243+        # shares are more likely to be primary than higher numbered
8244+        # shares.
8245+        active_shnums = set(sorted(self.remaining_sharemap.keys()))
8246+        # We shouldn't consider adding shares that we already have; this
8247+        # will cause problems later.
8248+        active_shnums -= set([reader.shnum for reader in self._active_readers])
8249+        active_shnums = list(active_shnums)[:needed]
8250+        if len(active_shnums) < needed and not self._verify:
8251+            # We don't have enough readers to retrieve the file; fail.
8252+            return self._failed()
8253 
8254hunk ./src/allmydata/mutable/retrieve.py 454
8255-    def notify_server_corruption(self, peerid, shnum, reason):
8256-        ss = self.servermap.connections[peerid]
8257-        ss.callRemoteOnly("advise_corrupt_share",
8258-                          "mutable", self._storage_index, shnum, reason)
8259+        for shnum in active_shnums:
8260+            self._active_readers.append(self.readers[shnum])
8261+            self.log("added reader for share %d" % shnum)
8262+        assert len(self._active_readers) >= self._required_shares
8263+        # Conceptually, this is part of the _add_active_peers step. It
8264+        # validates the prefixes of newly added readers to make sure
8265+        # that they match what we are expecting for self.verinfo. If
8266+        # validation is successful, _validate_active_prefixes will call
8267+        # _download_current_segment for us. If validation is
8268+        # unsuccessful, then _validate_prefixes will remove the peer and
8269+        # call _add_active_peers again, where we will attempt to rectify
8270+        # the problem by choosing another peer.
8271+        return self._validate_active_prefixes()
8272 
8273hunk ./src/allmydata/mutable/retrieve.py 468
8274-    def _got_results_one_share(self, shnum, peerid,
8275-                               got_prefix, got_hash_and_data):
8276-        self.log("_got_results: got shnum #%d from peerid %s"
8277-                 % (shnum, idlib.shortnodeid_b2a(peerid)))
8278-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
8279-         offsets_tuple) = self.verinfo
8280-        assert len(got_prefix) == len(prefix), (len(got_prefix), len(prefix))
8281-        if got_prefix != prefix:
8282-            msg = "someone wrote to the data since we read the servermap: prefix changed"
8283-            raise UncoordinatedWriteError(msg)
8284-        (share_hash_chain, block_hash_tree,
8285-         share_data) = unpack_share_data(self.verinfo, got_hash_and_data)
8286 
8287hunk ./src/allmydata/mutable/retrieve.py 469
8288-        assert isinstance(share_data, str)
8289-        # build the block hash tree. SDMF has only one leaf.
8290-        leaves = [hashutil.block_hash(share_data)]
8291-        t = hashtree.HashTree(leaves)
8292-        if list(t) != block_hash_tree:
8293-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
8294-        share_hash_leaf = t[0]
8295-        t2 = hashtree.IncompleteHashTree(N)
8296-        # root_hash was checked by the signature
8297-        t2.set_hashes({0: root_hash})
8298-        try:
8299-            t2.set_hashes(hashes=share_hash_chain,
8300-                          leaves={shnum: share_hash_leaf})
8301-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
8302-                IndexError), e:
8303-            msg = "corrupt hashes: %s" % (e,)
8304-            raise CorruptShareError(peerid, shnum, msg)
8305-        self.log(" data valid! len=%d" % len(share_data))
8306-        # each query comes down to this: placing validated share data into
8307-        # self.shares
8308-        self.shares[shnum] = share_data
8309+    def _validate_active_prefixes(self):
8310+        """
8311+        I check to make sure that the prefixes on the peers that I am
8312+        currently reading from match the prefix that we want to see, as
8313+        said in self.verinfo.
8314 
8315hunk ./src/allmydata/mutable/retrieve.py 475
8316-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
8317+        If I find that all of the active peers have acceptable prefixes,
8318+        I pass control to _download_current_segment, which will use
8319+        those peers to do cool things. If I find that some of the active
8320+        peers have unacceptable prefixes, I will remove them from active
8321+        peers (and from further consideration) and call
8322+        _add_active_peers to attempt to rectify the situation. I keep
8323+        track of which peers I have already validated so that I don't
8324+        need to do so again.
8325+        """
8326+        assert self._active_readers, "No more active readers"
8327 
8328hunk ./src/allmydata/mutable/retrieve.py 486
8329-        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
8330-        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
8331-        if alleged_writekey != self._node.get_writekey():
8332-            self.log("invalid privkey from %s shnum %d" %
8333-                     (idlib.nodeid_b2a(peerid)[:8], shnum),
8334-                     parent=lp, level=log.WEIRD, umid="YIw4tA")
8335-            return
8336+        ds = []
8337+        new_readers = set(self._active_readers) - self._validated_readers
8338+        self.log('validating %d newly-added active readers' % len(new_readers))
8339 
8340hunk ./src/allmydata/mutable/retrieve.py 490
8341-        # it's good
8342-        self.log("got valid privkey from shnum %d on peerid %s" %
8343-                 (shnum, idlib.shortnodeid_b2a(peerid)),
8344-                 parent=lp)
8345-        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
8346-        self._node._populate_encprivkey(enc_privkey)
8347-        self._node._populate_privkey(privkey)
8348-        self._need_privkey = False
8349+        for reader in new_readers:
8350+            # We force a remote read here -- otherwise, we are relying
8351+            # on cached data that we already verified as valid, and we
8352+            # won't detect an uncoordinated write that has occurred
8353+            # since the last servermap update.
8354+            d = reader.get_prefix(force_remote=True)
8355+            d.addCallback(self._try_to_validate_prefix, reader)
8356+            ds.append(d)
8357+        dl = defer.DeferredList(ds, consumeErrors=True)
8358+        def _check_results(results):
8359+            # Each result in results will be of the form (success, msg).
8360+            # We don't care about msg, but success will tell us whether
8361+            # or not the checkstring validated. If it didn't, we need to
8362+            # remove the offending (peer,share) from our active readers,
8363+            # and ensure that active readers is again populated.
8364+            bad_readers = []
8365+            for i, result in enumerate(results):
8366+                if not result[0]:
8367+                    reader = self._active_readers[i]
8368+                    f = result[1]
8369+                    assert isinstance(f, failure.Failure)
8370 
8371hunk ./src/allmydata/mutable/retrieve.py 512
8372-    def _query_failed(self, f, marker, peerid):
8373-        self.log(format="query to [%(peerid)s] failed",
8374-                 peerid=idlib.shortnodeid_b2a(peerid),
8375-                 level=log.NOISY)
8376-        self._status.problems[peerid] = f
8377-        self._outstanding_queries.pop(marker, None)
8378-        if not self._running:
8379-            return
8380-        self._last_failure = f
8381-        self.remove_peer(peerid)
8382-        level = log.WEIRD
8383-        if f.check(DeadReferenceError):
8384-            level = log.UNUSUAL
8385-        self.log(format="error during query: %(f_value)s",
8386-                 f_value=str(f.value), failure=f, level=level, umid="gOJB5g")
8387+                    self.log("The reader %s failed to "
8388+                             "properly validate: %s" % \
8389+                             (reader, str(f.value)))
8390+                    bad_readers.append((reader, f))
8391+                else:
8392+                    reader = self._active_readers[i]
8393+                    self.log("the reader %s checks out, so we'll use it" % \
8394+                             reader)
8395+                    self._validated_readers.add(reader)
8396+                    # Each time we validate a reader, we check to see if
8397+                    # we need the private key. If we do, we politely ask
8398+                    # for it and then continue computing. If we find
8399+                    # that we haven't gotten it at the end of
8400+                    # segment decoding, then we'll take more drastic
8401+                    # measures.
8402+                    if self._need_privkey and not self._node.is_readonly():
8403+                        d = reader.get_encprivkey()
8404+                        d.addCallback(self._try_to_validate_privkey, reader)
8405+            if bad_readers:
8406+                # We do them all at once, or else we screw up list indexing.
8407+                for (reader, f) in bad_readers:
8408+                    self._mark_bad_share(reader, f)
8409+                if self._verify:
8410+                    if len(self._active_readers) >= self._required_shares:
8411+                        return self._download_current_segment()
8412+                    else:
8413+                        return self._failed()
8414+                else:
8415+                    return self._add_active_peers()
8416+            else:
8417+                return self._download_current_segment()
8418+            # The next step will assert that it has enough active
8419+            # readers to fetch shares; we just need to remove it.
8420+        dl.addCallback(_check_results)
8421+        return dl
8422 
8423hunk ./src/allmydata/mutable/retrieve.py 548
8424-    def _check_for_done(self, res):
8425-        # exit paths:
8426-        #  return : keep waiting, no new queries
8427-        #  return self._send_more_queries(outstanding) : send some more queries
8428-        #  fire self._done(plaintext) : download successful
8429-        #  raise exception : download fails
8430 
8431hunk ./src/allmydata/mutable/retrieve.py 549
8432-        self.log(format="_check_for_done: running=%(running)s, decoding=%(decoding)s",
8433-                 running=self._running, decoding=self._decoding,
8434-                 level=log.NOISY)
8435-        if not self._running:
8436-            return
8437-        if self._decoding:
8438-            return
8439-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
8440+    def _try_to_validate_prefix(self, prefix, reader):
8441+        """
8442+        I check that the prefix returned by a candidate server for
8443+        retrieval matches the prefix that the servermap knows about
8444+        (and, hence, the prefix that was validated earlier). If it does,
8445+        I return True, which means that I approve of the use of the
8446+        candidate server for segment retrieval. If it doesn't, I return
8447+        False, which means that another server must be chosen.
8448+        """
8449+        (seqnum,
8450+         root_hash,
8451+         IV,
8452+         segsize,
8453+         datalength,
8454+         k,
8455+         N,
8456+         known_prefix,
8457          offsets_tuple) = self.verinfo
8458hunk ./src/allmydata/mutable/retrieve.py 567
8459+        if known_prefix != prefix:
8460+            self.log("prefix from share %d doesn't match" % reader.shnum)
8461+            raise UncoordinatedWriteError("Mismatched prefix -- this could "
8462+                                          "indicate an uncoordinated write")
8463+        # Otherwise, we're okay -- no issues.
8464 
8465hunk ./src/allmydata/mutable/retrieve.py 573
8466-        if len(self.shares) < k:
8467-            # we don't have enough shares yet
8468-            return self._maybe_send_more_queries(k)
8469-        if self._need_privkey:
8470-            # we got k shares, but none of them had a valid privkey. TODO:
8471-            # look further. Adding code to do this is a bit complicated, and
8472-            # I want to avoid that complication, and this should be pretty
8473-            # rare (k shares with bitflips in the enc_privkey but not in the
8474-            # data blocks). If we actually do get here, the subsequent repair
8475-            # will fail for lack of a privkey.
8476-            self.log("got k shares but still need_privkey, bummer",
8477-                     level=log.WEIRD, umid="MdRHPA")
8478 
8479hunk ./src/allmydata/mutable/retrieve.py 574
8480-        # we have enough to finish. All the shares have had their hashes
8481-        # checked, so if something fails at this point, we don't know how
8482-        # to fix it, so the download will fail.
8483+    def _remove_reader(self, reader):
8484+        """
8485+        At various points, we will wish to remove a peer from
8486+        consideration and/or use. These include, but are not necessarily
8487+        limited to:
8488 
8489hunk ./src/allmydata/mutable/retrieve.py 580
8490-        self._decoding = True # avoid reentrancy
8491-        self._status.set_status("decoding")
8492-        now = time.time()
8493-        elapsed = now - self._started
8494-        self._status.timings["fetch"] = elapsed
8495+            - A connection error.
8496+            - A mismatched prefix (that is, a prefix that does not match
8497+              our conception of the version information string).
8498+            - A failing block hash, salt hash, or share hash, which can
8499+              indicate disk failure/bit flips, or network trouble.
8500 
8501hunk ./src/allmydata/mutable/retrieve.py 586
8502-        d = defer.maybeDeferred(self._decode)
8503-        d.addCallback(self._decrypt, IV, self._node.get_readkey())
8504-        d.addBoth(self._done)
8505-        return d # purely for test convenience
8506+        This method will do that. I will make sure that the
8507+        (shnum,reader) combination represented by my reader argument is
8508+        not used for anything else during this download. I will not
8509+        advise the reader of any corruption, something that my callers
8510+        may wish to do on their own.
8511+        """
8512+        # TODO: When you're done writing this, see if this is ever
8513+        # actually used for something that _mark_bad_share isn't. I have
8514+        # a feeling that they will be used for very similar things, and
8515+        # that having them both here is just going to be an epic amount
8516+        # of code duplication.
8517+        #
8518+        # (well, okay, not epic, but meaningful)
8519+        self.log("removing reader %s" % reader)
8520+        # Remove the reader from _active_readers
8521+        self._active_readers.remove(reader)
8522+        # TODO: self.readers.remove(reader)?
8523+        for shnum in list(self.remaining_sharemap.keys()):
8524+            self.remaining_sharemap.discard(shnum, reader.peerid)
8525 
8526hunk ./src/allmydata/mutable/retrieve.py 606
8527-    def _maybe_send_more_queries(self, k):
8528-        # we don't have enough shares yet. Should we send out more queries?
8529-        # There are some number of queries outstanding, each for a single
8530-        # share. If we can generate 'needed_shares' additional queries, we do
8531-        # so. If we can't, then we know this file is a goner, and we raise
8532-        # NotEnoughSharesError.
8533-        self.log(format=("_maybe_send_more_queries, have=%(have)d, k=%(k)d, "
8534-                         "outstanding=%(outstanding)d"),
8535-                 have=len(self.shares), k=k,
8536-                 outstanding=len(self._outstanding_queries),
8537-                 level=log.NOISY)
8538 
8539hunk ./src/allmydata/mutable/retrieve.py 607
8540-        remaining_shares = k - len(self.shares)
8541-        needed = remaining_shares - len(self._outstanding_queries)
8542-        if not needed:
8543-            # we have enough queries in flight already
8544+    def _mark_bad_share(self, reader, f):
8545+        """
8546+        I mark the (peerid, shnum) encapsulated by my reader argument as
8547+        a bad share, which means that it will not be used anywhere else.
8548 
8549hunk ./src/allmydata/mutable/retrieve.py 612
8550-            # TODO: but if they've been in flight for a long time, and we
8551-            # have reason to believe that new queries might respond faster
8552-            # (i.e. we've seen other queries come back faster, then consider
8553-            # sending out new queries. This could help with peers which have
8554-            # silently gone away since the servermap was updated, for which
8555-            # we're still waiting for the 15-minute TCP disconnect to happen.
8556-            self.log("enough queries are in flight, no more are needed",
8557-                     level=log.NOISY)
8558-            return
8559+        There are several reasons to want to mark something as a bad
8560+        share. These include:
8561+
8562+            - A connection error to the peer.
8563+            - A mismatched prefix (that is, a prefix that does not match
8564+              our local conception of the version information string).
8565+            - A failing block hash, salt hash, share hash, or other
8566+              integrity check.
8567 
8568hunk ./src/allmydata/mutable/retrieve.py 621
8569-        outstanding_shnums = set([shnum
8570-                                  for (peerid, shnum, started)
8571-                                  in self._outstanding_queries.values()])
8572-        # prefer low-numbered shares, they are more likely to be primary
8573-        available_shnums = sorted(self.remaining_sharemap.keys())
8574-        for shnum in available_shnums:
8575-            if shnum in outstanding_shnums:
8576-                # skip ones that are already in transit
8577-                continue
8578-            if shnum not in self.remaining_sharemap:
8579-                # no servers for that shnum. note that DictOfSets removes
8580-                # empty sets from the dict for us.
8581-                continue
8582-            peerid = list(self.remaining_sharemap[shnum])[0]
8583-            # get_data will remove that peerid from the sharemap, and add the
8584-            # query to self._outstanding_queries
8585-            self._status.set_status("Retrieving More Shares")
8586-            self.get_data(shnum, peerid)
8587-            needed -= 1
8588-            if not needed:
8589+        This method will ensure that readers that we wish to mark bad
8590+        (for these reasons or other reasons) are not used for the rest
8591+        of the download. Additionally, it will attempt to tell the
8592+        remote peer (with no guarantee of success) that its share is
8593+        corrupt.
8594+        """
8595+        self.log("marking share %d on server %s as bad" % \
8596+                 (reader.shnum, reader))
8597+        prefix = self.verinfo[-2]
8598+        self.servermap.mark_bad_share(reader.peerid,
8599+                                      reader.shnum,
8600+                                      prefix)
8601+        self._remove_reader(reader)
8602+        self._bad_shares.add((reader.peerid, reader.shnum, f))
8603+        self._status.problems[reader.peerid] = f
8604+        self._last_failure = f
8605+        self.notify_server_corruption(reader.peerid, reader.shnum,
8606+                                      str(f.value))
8607+
8608+
8609+    def _download_current_segment(self):
8610+        """
8611+        I download, validate, decode, decrypt, and assemble the segment
8612+        that this Retrieve is currently responsible for downloading.
8613+        """
8614+        assert len(self._active_readers) >= self._required_shares
8615+        if self._current_segment <= self._last_segment:
8616+            d = self._process_segment(self._current_segment)
8617+        else:
8618+            d = defer.succeed(None)
8619+        d.addBoth(self._turn_barrier)
8620+        d.addCallback(self._check_for_done)
8621+        return d
8622+
8623+
8624+    def _turn_barrier(self, result):
8625+        """
8626+        I help the download process avoid the recursion limit issues
8627+        discussed in #237.
8628+        """
8629+        return fireEventually(result)
8630+
8631+
8632+    def _process_segment(self, segnum):
8633+        """
8634+        I download, validate, decode, and decrypt one segment of the
8635+        file that this Retrieve is retrieving. This means coordinating
8636+        the process of getting k blocks of that file, validating them,
8637+        assembling them into one segment with the decoder, and then
8638+        decrypting them.
8639+        """
8640+        self.log("processing segment %d" % segnum)
8641+
8642+        # TODO: The old code uses a marker. Should this code do that
8643+        # too? What did the Marker do?
8644+        assert len(self._active_readers) >= self._required_shares
8645+
8646+        # We need to ask each of our active readers for its block and
8647+        # salt. We will then validate those. If validation is
8648+        # successful, we will assemble the results into plaintext.
8649+        ds = []
8650+        for reader in self._active_readers:
8651+            started = time.time()
8652+            d = reader.get_block_and_salt(segnum, queue=True)
8653+            d2 = self._get_needed_hashes(reader, segnum)
8654+            dl = defer.DeferredList([d, d2], consumeErrors=True)
8655+            dl.addCallback(self._validate_block, segnum, reader, started)
8656+            dl.addErrback(self._validation_or_decoding_failed, [reader])
8657+            ds.append(dl)
8658+            reader.flush()
8659+        dl = defer.DeferredList(ds)
8660+        if self._verify:
8661+            dl.addCallback(lambda ignored: "")
8662+            dl.addCallback(self._set_segment)
8663+        else:
8664+            dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
8665+        return dl
8666+
8667+
8668+    def _maybe_decode_and_decrypt_segment(self, blocks_and_salts, segnum):
8669+        """
8670+        I take the results of fetching and validating the blocks from a
8671+        callback chain in another method. If the results are such that
8672+        they tell me that validation and fetching succeeded without
8673+        incident, I will proceed with decoding and decryption.
8674+        Otherwise, I will do nothing.
8675+        """
8676+        self.log("trying to decode and decrypt segment %d" % segnum)
8677+        failures = False
8678+        for block_and_salt in blocks_and_salts:
8679+            if not block_and_salt[0] or block_and_salt[1] == None:
8680+                self.log("some validation operations failed; not proceeding")
8681+                failures = True
8682                 break
8683hunk ./src/allmydata/mutable/retrieve.py 715
8684+        if not failures:
8685+            self.log("everything looks ok, building segment %d" % segnum)
8686+            d = self._decode_blocks(blocks_and_salts, segnum)
8687+            d.addCallback(self._decrypt_segment)
8688+            d.addErrback(self._validation_or_decoding_failed,
8689+                         self._active_readers)
8690+            # check to see whether we've been paused before writing
8691+            # anything.
8692+            d.addCallback(self._check_for_paused)
8693+            d.addCallback(self._set_segment)
8694+            return d
8695+        else:
8696+            return defer.succeed(None)
8697+
8698+
8699+    def _set_segment(self, segment):
8700+        """
8701+        Given a plaintext segment, I register that segment with the
8702+        target that is handling the file download.
8703+        """
8704+        self.log("got plaintext for segment %d" % self._current_segment)
8705+        if self._current_segment == self._start_segment:
8706+            # We're on the first segment. It's possible that we want
8707+            # only some part of the end of this segment, and that we
8708+            # just downloaded the whole thing to get that part. If so,
8709+            # we need to account for that and give the reader just the
8710+            # data that they want.
8711+            n = self._offset % self._segment_size
8712+            self.log("stripping %d bytes off of the first segment" % n)
8713+            self.log("original segment length: %d" % len(segment))
8714+            segment = segment[n:]
8715+            self.log("new segment length: %d" % len(segment))
8716+
8717+        if self._current_segment == self._last_segment and self._read_length is not None:
8718+            # We're on the last segment. It's possible that we only want
8719+            # part of the beginning of this segment, and that we
8720+            # downloaded the whole thing anyway. Make sure to give the
8721+            # caller only the portion of the segment that they want to
8722+            # receive.
8723+            extra = self._read_length
8724+            if self._start_segment != self._last_segment:
8725+                extra -= self._segment_size - \
8726+                            (self._offset % self._segment_size)
8727+            extra %= self._segment_size
8728+            self.log("original segment length: %d" % len(segment))
8729+            segment = segment[:extra]
8730+            self.log("new segment length: %d" % len(segment))
8731+            self.log("only taking %d bytes of the last segment" % extra)
8732+
8733+        if not self._verify:
8734+            self._consumer.write(segment)
8735+        else:
8736+            # we don't care about the plaintext if we are doing a verify.
8737+            segment = None
8738+        self._current_segment += 1
8739 
8740hunk ./src/allmydata/mutable/retrieve.py 771
8741-        # at this point, we have as many outstanding queries as we can. If
8742-        # needed!=0 then we might not have enough to recover the file.
8743-        if needed:
8744-            format = ("ran out of peers: "
8745-                      "have %(have)d shares (k=%(k)d), "
8746-                      "%(outstanding)d queries in flight, "
8747-                      "need %(need)d more, "
8748-                      "found %(bad)d bad shares")
8749-            args = {"have": len(self.shares),
8750-                    "k": k,
8751-                    "outstanding": len(self._outstanding_queries),
8752-                    "need": needed,
8753-                    "bad": len(self._bad_shares),
8754-                    }
8755-            self.log(format=format,
8756-                     level=log.WEIRD, umid="ezTfjw", **args)
8757-            err = NotEnoughSharesError("%s, last failure: %s" %
8758-                                      (format % args, self._last_failure))
8759-            if self._bad_shares:
8760-                self.log("We found some bad shares this pass. You should "
8761-                         "update the servermap and try again to check "
8762-                         "more peers",
8763-                         level=log.WEIRD, umid="EFkOlA")
8764-                err.servermap = self.servermap
8765-            raise err
8766 
8767hunk ./src/allmydata/mutable/retrieve.py 772
8768+    def _validation_or_decoding_failed(self, f, readers):
8769+        """
8770+        I am called when a block or a salt fails to correctly validate, or when
8771+        the decryption or decoding operation fails for some reason.  I react to
8772+        this failure by notifying the remote server of corruption, and then
8773+        removing the remote peer from further activity.
8774+        """
8775+        assert isinstance(readers, list)
8776+        bad_shnums = [reader.shnum for reader in readers]
8777+
8778+        self.log("validation or decoding failed on share(s) %s, peer(s) %s "
8779+                 ", segment %d: %s" % \
8780+                 (bad_shnums, readers, self._current_segment, str(f)))
8781+        for reader in readers:
8782+            self._mark_bad_share(reader, f)
8783         return
8784 
8785hunk ./src/allmydata/mutable/retrieve.py 789
8786-    def _decode(self):
8787-        started = time.time()
8788-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
8789-         offsets_tuple) = self.verinfo
8790 
8791hunk ./src/allmydata/mutable/retrieve.py 790
8792-        # shares_dict is a dict mapping shnum to share data, but the codec
8793-        # wants two lists.
8794-        shareids = []; shares = []
8795-        for shareid, share in self.shares.items():
8796+    def _validate_block(self, results, segnum, reader, started):
8797+        """
8798+        I validate a block from one share on a remote server.
8799+        """
8800+        # Grab the part of the block hash tree that is necessary to
8801+        # validate this block, then generate the block hash root.
8802+        self.log("validating share %d for segment %d" % (reader.shnum,
8803+                                                             segnum))
8804+        self._status.add_fetch_timing(reader.peerid, started)
8805+        self._status.set_status("Valdiating blocks for segment %d" % segnum)
8806+        # Did we fail to fetch either of the things that we were
8807+        # supposed to? Fail if so.
8808+        if not results[0][0] and results[1][0]:
8809+            # handled by the errback handler.
8810+
8811+            # These all get batched into one query, so the resulting
8812+            # failure should be the same for all of them, so we can just
8813+            # use the first one.
8814+            assert isinstance(results[0][1], failure.Failure)
8815+
8816+            f = results[0][1]
8817+            raise CorruptShareError(reader.peerid,
8818+                                    reader.shnum,
8819+                                    "Connection error: %s" % str(f))
8820+
8821+        block_and_salt, block_and_sharehashes = results
8822+        block, salt = block_and_salt[1]
8823+        blockhashes, sharehashes = block_and_sharehashes[1]
8824+
8825+        blockhashes = dict(enumerate(blockhashes[1]))
8826+        self.log("the reader gave me the following blockhashes: %s" % \
8827+                 blockhashes.keys())
8828+        self.log("the reader gave me the following sharehashes: %s" % \
8829+                 sharehashes[1].keys())
8830+        bht = self._block_hash_trees[reader.shnum]
8831+
8832+        if bht.needed_hashes(segnum, include_leaf=True):
8833+            try:
8834+                bht.set_hashes(blockhashes)
8835+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
8836+                    IndexError), e:
8837+                raise CorruptShareError(reader.peerid,
8838+                                        reader.shnum,
8839+                                        "block hash tree failure: %s" % e)
8840+
8841+        if self._version == MDMF_VERSION:
8842+            blockhash = hashutil.block_hash(salt + block)
8843+        else:
8844+            blockhash = hashutil.block_hash(block)
8845+        # If this works without an error, then validation is
8846+        # successful.
8847+        try:
8848+           bht.set_hashes(leaves={segnum: blockhash})
8849+        except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
8850+                IndexError), e:
8851+            raise CorruptShareError(reader.peerid,
8852+                                    reader.shnum,
8853+                                    "block hash tree failure: %s" % e)
8854+
8855+        # Reaching this point means that we know that this segment
8856+        # is correct. Now we need to check to see whether the share
8857+        # hash chain is also correct.
8858+        # SDMF wrote share hash chains that didn't contain the
8859+        # leaves, which would be produced from the block hash tree.
8860+        # So we need to validate the block hash tree first. If
8861+        # successful, then bht[0] will contain the root for the
8862+        # shnum, which will be a leaf in the share hash tree, which
8863+        # will allow us to validate the rest of the tree.
8864+        if self.share_hash_tree.needed_hashes(reader.shnum,
8865+                                              include_leaf=True) or \
8866+                                              self._verify:
8867+            try:
8868+                self.share_hash_tree.set_hashes(hashes=sharehashes[1],
8869+                                            leaves={reader.shnum: bht[0]})
8870+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
8871+                    IndexError), e:
8872+                raise CorruptShareError(reader.peerid,
8873+                                        reader.shnum,
8874+                                        "corrupt hashes: %s" % e)
8875+
8876+        self.log('share %d is valid for segment %d' % (reader.shnum,
8877+                                                       segnum))
8878+        return {reader.shnum: (block, salt)}
8879+
8880+
8881+    def _get_needed_hashes(self, reader, segnum):
8882+        """
8883+        I get the hashes needed to validate segnum from the reader, then return
8884+        to my caller when this is done.
8885+        """
8886+        bht = self._block_hash_trees[reader.shnum]
8887+        needed = bht.needed_hashes(segnum, include_leaf=True)
8888+        # The root of the block hash tree is also a leaf in the share
8889+        # hash tree. So we don't need to fetch it from the remote
8890+        # server. In the case of files with one segment, this means that
8891+        # we won't fetch any block hash tree from the remote server,
8892+        # since the hash of each share of the file is the entire block
8893+        # hash tree, and is a leaf in the share hash tree. This is fine,
8894+        # since any share corruption will be detected in the share hash
8895+        # tree.
8896+        #needed.discard(0)
8897+        self.log("getting blockhashes for segment %d, share %d: %s" % \
8898+                 (segnum, reader.shnum, str(needed)))
8899+        d1 = reader.get_blockhashes(needed, queue=True, force_remote=True)
8900+        if self.share_hash_tree.needed_hashes(reader.shnum):
8901+            need = self.share_hash_tree.needed_hashes(reader.shnum)
8902+            self.log("also need sharehashes for share %d: %s" % (reader.shnum,
8903+                                                                 str(need)))
8904+            d2 = reader.get_sharehashes(need, queue=True, force_remote=True)
8905+        else:
8906+            d2 = defer.succeed({}) # the logic in the next method
8907+                                   # expects a dict
8908+        dl = defer.DeferredList([d1, d2], consumeErrors=True)
8909+        return dl
8910+
8911+
8912+    def _decode_blocks(self, blocks_and_salts, segnum):
8913+        """
8914+        I take a list of k blocks and salts, and decode that into a
8915+        single encrypted segment.
8916+        """
8917+        d = {}
8918+        # We want to merge our dictionaries to the form
8919+        # {shnum: blocks_and_salts}
8920+        #
8921+        # The dictionaries come from validate block that way, so we just
8922+        # need to merge them.
8923+        for block_and_salt in blocks_and_salts:
8924+            d.update(block_and_salt[1])
8925+
8926+        # All of these blocks should have the same salt; in SDMF, it is
8927+        # the file-wide IV, while in MDMF it is the per-segment salt. In
8928+        # either case, we just need to get one of them and use it.
8929+        #
8930+        # d.items()[0] is like (shnum, (block, salt))
8931+        # d.items()[0][1] is like (block, salt)
8932+        # d.items()[0][1][1] is the salt.
8933+        salt = d.items()[0][1][1]
8934+        # Next, extract just the blocks from the dict. We'll use the
8935+        # salt in the next step.
8936+        share_and_shareids = [(k, v[0]) for k, v in d.items()]
8937+        d2 = dict(share_and_shareids)
8938+        shareids = []
8939+        shares = []
8940+        for shareid, share in d2.items():
8941             shareids.append(shareid)
8942             shares.append(share)
8943 
8944hunk ./src/allmydata/mutable/retrieve.py 938
8945-        assert len(shareids) >= k, len(shareids)
8946+        self._status.set_status("Decoding")
8947+        started = time.time()
8948+        assert len(shareids) >= self._required_shares, len(shareids)
8949         # zfec really doesn't want extra shares
8950hunk ./src/allmydata/mutable/retrieve.py 942
8951-        shareids = shareids[:k]
8952-        shares = shares[:k]
8953-
8954-        fec = codec.CRSDecoder()
8955-        fec.set_params(segsize, k, N)
8956-
8957-        self.log("params %s, we have %d shares" % ((segsize, k, N), len(shares)))
8958-        self.log("about to decode, shareids=%s" % (shareids,))
8959-        d = defer.maybeDeferred(fec.decode, shares, shareids)
8960-        def _done(buffers):
8961-            self._status.timings["decode"] = time.time() - started
8962-            self.log(" decode done, %d buffers" % len(buffers))
8963+        shareids = shareids[:self._required_shares]
8964+        shares = shares[:self._required_shares]
8965+        self.log("decoding segment %d" % segnum)
8966+        if segnum == self._num_segments - 1:
8967+            d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids)
8968+        else:
8969+            d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids)
8970+        def _process(buffers):
8971             segment = "".join(buffers)
8972hunk ./src/allmydata/mutable/retrieve.py 951
8973+            self.log(format="now decoding segment %(segnum)s of %(numsegs)s",
8974+                     segnum=segnum,
8975+                     numsegs=self._num_segments,
8976+                     level=log.NOISY)
8977             self.log(" joined length %d, datalength %d" %
8978hunk ./src/allmydata/mutable/retrieve.py 956
8979-                     (len(segment), datalength))
8980-            segment = segment[:datalength]
8981+                     (len(segment), self._data_length))
8982+            if segnum == self._num_segments - 1:
8983+                size_to_use = self._tail_data_size
8984+            else:
8985+                size_to_use = self._segment_size
8986+            segment = segment[:size_to_use]
8987             self.log(" segment len=%d" % len(segment))
8988hunk ./src/allmydata/mutable/retrieve.py 963
8989-            return segment
8990-        def _err(f):
8991-            self.log(" decode failed: %s" % f)
8992-            return f
8993-        d.addCallback(_done)
8994-        d.addErrback(_err)
8995+            self._status.timings.setdefault("decode", 0)
8996+            self._status.timings['decode'] = time.time() - started
8997+            return segment, salt
8998+        d.addCallback(_process)
8999         return d
9000 
9001hunk ./src/allmydata/mutable/retrieve.py 969
9002-    def _decrypt(self, crypttext, IV, readkey):
9003+
9004+    def _decrypt_segment(self, segment_and_salt):
9005+        """
9006+        I take a single segment and its salt, and decrypt it. I return
9007+        the plaintext of the segment that is in my argument.
9008+        """
9009+        segment, salt = segment_and_salt
9010         self._status.set_status("decrypting")
9011hunk ./src/allmydata/mutable/retrieve.py 977
9012+        self.log("decrypting segment %d" % self._current_segment)
9013         started = time.time()
9014hunk ./src/allmydata/mutable/retrieve.py 979
9015-        key = hashutil.ssk_readkey_data_hash(IV, readkey)
9016+        key = hashutil.ssk_readkey_data_hash(salt, self._node.get_readkey())
9017         decryptor = AES(key)
9018hunk ./src/allmydata/mutable/retrieve.py 981
9019-        plaintext = decryptor.process(crypttext)
9020-        self._status.timings["decrypt"] = time.time() - started
9021+        plaintext = decryptor.process(segment)
9022+        self._status.timings.setdefault("decrypt", 0)
9023+        self._status.timings['decrypt'] = time.time() - started
9024         return plaintext
9025 
9026hunk ./src/allmydata/mutable/retrieve.py 986
9027-    def _done(self, res):
9028-        if not self._running:
9029+
9030+    def notify_server_corruption(self, peerid, shnum, reason):
9031+        ss = self.servermap.connections[peerid]
9032+        ss.callRemoteOnly("advise_corrupt_share",
9033+                          "mutable", self._storage_index, shnum, reason)
9034+
9035+
9036+    def _try_to_validate_privkey(self, enc_privkey, reader):
9037+        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
9038+        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
9039+        if alleged_writekey != self._node.get_writekey():
9040+            self.log("invalid privkey from %s shnum %d" %
9041+                     (reader, reader.shnum),
9042+                     level=log.WEIRD, umid="YIw4tA")
9043+            if self._verify:
9044+                self.servermap.mark_bad_share(reader.peerid, reader.shnum,
9045+                                              self.verinfo[-2])
9046+                e = CorruptShareError(reader.peerid,
9047+                                      reader.shnum,
9048+                                      "invalid privkey")
9049+                f = failure.Failure(e)
9050+                self._bad_shares.add((reader.peerid, reader.shnum, f))
9051             return
9052hunk ./src/allmydata/mutable/retrieve.py 1009
9053+
9054+        # it's good
9055+        self.log("got valid privkey from shnum %d on reader %s" %
9056+                 (reader.shnum, reader))
9057+        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
9058+        self._node._populate_encprivkey(enc_privkey)
9059+        self._node._populate_privkey(privkey)
9060+        self._need_privkey = False
9061+
9062+
9063+    def _check_for_done(self, res):
9064+        """
9065+        I check to see if this Retrieve object has successfully finished
9066+        its work.
9067+
9068+        I can exit in the following ways:
9069+            - If there are no more segments to download, then I exit by
9070+              causing self._done_deferred to fire with the plaintext
9071+              content requested by the caller.
9072+            - If there are still segments to be downloaded, and there
9073+              are enough active readers (readers which have not broken
9074+              and have not given us corrupt data) to continue
9075+              downloading, I send control back to
9076+              _download_current_segment.
9077+            - If there are still segments to be downloaded but there are
9078+              not enough active peers to download them, I ask
9079+              _add_active_peers to add more peers. If it is successful,
9080+              it will call _download_current_segment. If there are not
9081+              enough peers to retrieve the file, then that will cause
9082+              _done_deferred to errback.
9083+        """
9084+        self.log("checking for doneness")
9085+        if self._current_segment > self._last_segment:
9086+            # No more segments to download, we're done.
9087+            self.log("got plaintext, done")
9088+            return self._done()
9089+
9090+        if len(self._active_readers) >= self._required_shares:
9091+            # More segments to download, but we have enough good peers
9092+            # in self._active_readers that we can do that without issue,
9093+            # so go nab the next segment.
9094+            self.log("not done yet: on segment %d of %d" % \
9095+                     (self._current_segment + 1, self._num_segments))
9096+            return self._download_current_segment()
9097+
9098+        self.log("not done yet: on segment %d of %d, need to add peers" % \
9099+                 (self._current_segment + 1, self._num_segments))
9100+        return self._add_active_peers()
9101+
9102+
9103+    def _done(self):
9104+        """
9105+        I am called by _check_for_done when the download process has
9106+        finished successfully. After making some useful logging
9107+        statements, I return the decrypted contents to the owner of this
9108+        Retrieve object through self._done_deferred.
9109+        """
9110         self._running = False
9111         self._status.set_active(False)
9112hunk ./src/allmydata/mutable/retrieve.py 1068
9113-        self._status.timings["total"] = time.time() - self._started
9114-        # res is either the new contents, or a Failure
9115-        if isinstance(res, failure.Failure):
9116-            self.log("Retrieve done, with failure", failure=res,
9117-                     level=log.UNUSUAL)
9118-            self._status.set_status("Failed")
9119+        now = time.time()
9120+        self._status.timings['total'] = now - self._started
9121+        self._status.timings['fetch'] = now - self._started_fetching
9122+
9123+        if self._verify:
9124+            ret = list(self._bad_shares)
9125+            self.log("done verifying, found %d bad shares" % len(ret))
9126         else:
9127hunk ./src/allmydata/mutable/retrieve.py 1076
9128-            self.log("Retrieve done, success!")
9129-            self._status.set_status("Finished")
9130-            self._status.set_progress(1.0)
9131-            # remember the encoding parameters, use them again next time
9132-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
9133-             offsets_tuple) = self.verinfo
9134-            self._node._populate_required_shares(k)
9135-            self._node._populate_total_shares(N)
9136-        eventually(self._done_deferred.callback, res)
9137+            # TODO: upload status here?
9138+            ret = self._consumer
9139+            self._consumer.unregisterProducer()
9140+        eventually(self._done_deferred.callback, ret)
9141+
9142 
9143hunk ./src/allmydata/mutable/retrieve.py 1082
9144+    def _failed(self):
9145+        """
9146+        I am called by _add_active_peers when there are not enough
9147+        active peers left to complete the download. After making some
9148+        useful logging statements, I return an exception to that effect
9149+        to the caller of this Retrieve object through
9150+        self._done_deferred.
9151+        """
9152+        self._running = False
9153+        self._status.set_active(False)
9154+        now = time.time()
9155+        self._status.timings['total'] = now - self._started
9156+        self._status.timings['fetch'] = now - self._started_fetching
9157+
9158+        if self._verify:
9159+            ret = list(self._bad_shares)
9160+        else:
9161+            format = ("ran out of peers: "
9162+                      "have %(have)d of %(total)d segments "
9163+                      "found %(bad)d bad shares "
9164+                      "encoding %(k)d-of-%(n)d")
9165+            args = {"have": self._current_segment,
9166+                    "total": self._num_segments,
9167+                    "need": self._last_segment,
9168+                    "k": self._required_shares,
9169+                    "n": self._total_shares,
9170+                    "bad": len(self._bad_shares)}
9171+            e = NotEnoughSharesError("%s, last failure: %s" % \
9172+                                     (format % args, str(self._last_failure)))
9173+            f = failure.Failure(e)
9174+            ret = f
9175+        eventually(self._done_deferred.callback, ret)
9176}
9177[mutable/servermap.py: Alter the servermap updater to work with MDMF files
9178Kevan Carstensen <kevan@isnotajoke.com>**20100819003439
9179 Ignore-this: 7e408303194834bd59a2f27efab3bdb
9180 
9181 These modifications were basically all to the end of having the
9182 servermap updater use the unified MDMF + SDMF read interface whenever
9183 possible -- this reduces the complexity of the code, making it easier to
9184 read and maintain. To do this, I needed to modify the process of
9185 updating the servermap a little bit.
9186 
9187 To support partial-file updates, I also modified the servermap updater
9188 to fetch the block hash trees and certain segments of files while it
9189 performed a servermap update (this can be done without adding any new
9190 roundtrips because of batch-read functionality that the read proxy has).
9191 
9192] {
9193hunk ./src/allmydata/mutable/servermap.py 2
9194 
9195-import sys, time
9196+import sys, time, struct
9197 from zope.interface import implements
9198 from itertools import count
9199 from twisted.internet import defer
9200merger 0.0 (
9201hunk ./src/allmydata/mutable/servermap.py 9
9202+from allmydata.util.dictutil import DictOfSets
9203hunk ./src/allmydata/mutable/servermap.py 7
9204-from foolscap.api import DeadReferenceError, RemoteException, eventually
9205-from allmydata.util import base32, hashutil, idlib, log
9206+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
9207+                         fireEventually
9208+from allmydata.util import base32, hashutil, idlib, log, deferredutil
9209)
9210merger 0.0 (
9211hunk ./src/allmydata/mutable/servermap.py 14
9212-     DictOfSets, CorruptShareError, NeedMoreDataError
9213+     CorruptShareError, NeedMoreDataError
9214hunk ./src/allmydata/mutable/servermap.py 14
9215-     DictOfSets, CorruptShareError, NeedMoreDataError
9216-from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
9217-     SIGNED_PREFIX_LENGTH
9218+     DictOfSets, CorruptShareError
9219+from allmydata.mutable.layout import SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
9220)
9221hunk ./src/allmydata/mutable/servermap.py 123
9222         self.bad_shares = {} # maps (peerid,shnum) to old checkstring
9223         self.last_update_mode = None
9224         self.last_update_time = 0
9225+        self.update_data = {} # (verinfo,shnum) => data
9226 
9227     def copy(self):
9228         s = ServerMap()
9229hunk ./src/allmydata/mutable/servermap.py 254
9230         """Return a set of versionids, one for each version that is currently
9231         recoverable."""
9232         versionmap = self.make_versionmap()
9233-
9234         recoverable_versions = set()
9235         for (verinfo, shares) in versionmap.items():
9236             (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
9237hunk ./src/allmydata/mutable/servermap.py 339
9238         return False
9239 
9240 
9241+    def get_update_data_for_share_and_verinfo(self, shnum, verinfo):
9242+        """
9243+        I return the update data for the given shnum
9244+        """
9245+        update_data = self.update_data[shnum]
9246+        update_datum = [i[1] for i in update_data if i[0] == verinfo][0]
9247+        return update_datum
9248+
9249+
9250+    def set_update_data_for_share_and_verinfo(self, shnum, verinfo, data):
9251+        """
9252+        I record the block hash tree for the given shnum.
9253+        """
9254+        self.update_data.setdefault(shnum , []).append((verinfo, data))
9255+
9256+
9257 class ServermapUpdater:
9258     def __init__(self, filenode, storage_broker, monitor, servermap,
9259hunk ./src/allmydata/mutable/servermap.py 357
9260-                 mode=MODE_READ, add_lease=False):
9261+                 mode=MODE_READ, add_lease=False, update_range=None):
9262         """I update a servermap, locating a sufficient number of useful
9263         shares and remembering where they are located.
9264 
9265hunk ./src/allmydata/mutable/servermap.py 382
9266         self._servers_responded = set()
9267 
9268         # how much data should we read?
9269+        # SDMF:
9270         #  * if we only need the checkstring, then [0:75]
9271         #  * if we need to validate the checkstring sig, then [543ish:799ish]
9272         #  * if we need the verification key, then [107:436ish]
9273merger 0.0 (
9274hunk ./src/allmydata/mutable/servermap.py 392
9275-        # read 2000 bytes, which also happens to read enough actual data to
9276-        # pre-fetch a 9-entry dirnode.
9277+        # read 4000 bytes, which also happens to read enough actual data to
9278+        # pre-fetch an 18-entry dirnode.
9279hunk ./src/allmydata/mutable/servermap.py 390
9280-        # A future version of the SMDF slot format should consider using
9281-        # fixed-size slots so we can retrieve less data. For now, we'll just
9282-        # read 2000 bytes, which also happens to read enough actual data to
9283-        # pre-fetch a 9-entry dirnode.
9284+        # MDMF:
9285+        #  * Checkstring? [0:72]
9286+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
9287+        #    the offset table will tell us for sure.
9288+        #  * If we need the verification key, we have to consult the offset
9289+        #    table as well.
9290+        # At this point, we don't know which we are. Our filenode can
9291+        # tell us, but it might be lying -- in some cases, we're
9292+        # responsible for telling it which kind of file it is.
9293)
9294hunk ./src/allmydata/mutable/servermap.py 399
9295             # we use unpack_prefix_and_signature, so we need 1k
9296             self._read_size = 1000
9297         self._need_privkey = False
9298+
9299         if mode == MODE_WRITE and not self._node.get_privkey():
9300             self._need_privkey = True
9301         # check+repair: repair requires the privkey, so if we didn't happen
9302hunk ./src/allmydata/mutable/servermap.py 406
9303         # to ask for it during the check, we'll have problems doing the
9304         # publish.
9305 
9306+        self.fetch_update_data = False
9307+        if mode == MODE_WRITE and update_range:
9308+            # We're updating the servermap in preparation for an
9309+            # in-place file update, so we need to fetch some additional
9310+            # data from each share that we find.
9311+            assert len(update_range) == 2
9312+
9313+            self.start_segment = update_range[0]
9314+            self.end_segment = update_range[1]
9315+            self.fetch_update_data = True
9316+
9317         prefix = si_b2a(self._storage_index)[:5]
9318         self._log_number = log.msg(format="SharemapUpdater(%(si)s): starting (%(mode)s)",
9319                                    si=prefix, mode=mode)
9320merger 0.0 (
9321hunk ./src/allmydata/mutable/servermap.py 455
9322-        full_peerlist = sb.get_servers_for_index(self._storage_index)
9323+        full_peerlist = [(s.get_serverid(), s.get_rref())
9324+                         for s in sb.get_servers_for_psi(self._storage_index)]
9325hunk ./src/allmydata/mutable/servermap.py 455
9326+        # All of the peers, permuted by the storage index, as usual.
9327)
9328hunk ./src/allmydata/mutable/servermap.py 461
9329         self._good_peers = set() # peers who had some shares
9330         self._empty_peers = set() # peers who don't have any shares
9331         self._bad_peers = set() # peers to whom our queries failed
9332+        self._readers = {} # peerid -> dict(sharewriters), filled in
9333+                           # after responses come in.
9334 
9335         k = self._node.get_required_shares()
9336hunk ./src/allmydata/mutable/servermap.py 465
9337+        # For what cases can these conditions work?
9338         if k is None:
9339             # make a guess
9340             k = 3
9341hunk ./src/allmydata/mutable/servermap.py 478
9342         self.num_peers_to_query = k + self.EPSILON
9343 
9344         if self.mode == MODE_CHECK:
9345+            # We want to query all of the peers.
9346             initial_peers_to_query = dict(full_peerlist)
9347             must_query = set(initial_peers_to_query.keys())
9348             self.extra_peers = []
9349hunk ./src/allmydata/mutable/servermap.py 486
9350             # we're planning to replace all the shares, so we want a good
9351             # chance of finding them all. We will keep searching until we've
9352             # seen epsilon that don't have a share.
9353+            # We don't query all of the peers because that could take a while.
9354             self.num_peers_to_query = N + self.EPSILON
9355             initial_peers_to_query, must_query = self._build_initial_querylist()
9356             self.required_num_empty_peers = self.EPSILON
9357hunk ./src/allmydata/mutable/servermap.py 496
9358             # might also avoid the round trip required to read the encrypted
9359             # private key.
9360 
9361-        else:
9362+        else: # MODE_READ, MODE_ANYTHING
9363+            # 2k peers is good enough.
9364             initial_peers_to_query, must_query = self._build_initial_querylist()
9365 
9366         # this is a set of peers that we are required to get responses from:
9367hunk ./src/allmydata/mutable/servermap.py 512
9368         # before we can consider ourselves finished, and self.extra_peers
9369         # contains the overflow (peers that we should tap if we don't get
9370         # enough responses)
9371+        # I guess that self._must_query is a subset of
9372+        # initial_peers_to_query?
9373+        assert set(must_query).issubset(set(initial_peers_to_query))
9374 
9375         self._send_initial_requests(initial_peers_to_query)
9376         self._status.timings["initial_queries"] = time.time() - self._started
9377hunk ./src/allmydata/mutable/servermap.py 571
9378         # errors that aren't handled by _query_failed (and errors caused by
9379         # _query_failed) get logged, but we still want to check for doneness.
9380         d.addErrback(log.err)
9381-        d.addBoth(self._check_for_done)
9382         d.addErrback(self._fatal_error)
9383hunk ./src/allmydata/mutable/servermap.py 572
9384+        d.addCallback(self._check_for_done)
9385         return d
9386 
9387     def _do_read(self, ss, peerid, storage_index, shnums, readv):
9388hunk ./src/allmydata/mutable/servermap.py 591
9389         d = ss.callRemote("slot_readv", storage_index, shnums, readv)
9390         return d
9391 
9392+
9393+    def _got_corrupt_share(self, e, shnum, peerid, data, lp):
9394+        """
9395+        I am called when a remote server returns a corrupt share in
9396+        response to one of our queries. By corrupt, I mean a share
9397+        without a valid signature. I then record the failure, notify the
9398+        server of the corruption, and record the share as bad.
9399+        """
9400+        f = failure.Failure(e)
9401+        self.log(format="bad share: %(f_value)s", f_value=str(f),
9402+                 failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
9403+        # Notify the server that its share is corrupt.
9404+        self.notify_server_corruption(peerid, shnum, str(e))
9405+        # By flagging this as a bad peer, we won't count any of
9406+        # the other shares on that peer as valid, though if we
9407+        # happen to find a valid version string amongst those
9408+        # shares, we'll keep track of it so that we don't need
9409+        # to validate the signature on those again.
9410+        self._bad_peers.add(peerid)
9411+        self._last_failure = f
9412+        # XXX: Use the reader for this?
9413+        checkstring = data[:SIGNED_PREFIX_LENGTH]
9414+        self._servermap.mark_bad_share(peerid, shnum, checkstring)
9415+        self._servermap.problems.append(f)
9416+
9417+
9418+    def _cache_good_sharedata(self, verinfo, shnum, now, data):
9419+        """
9420+        If one of my queries returns successfully (which means that we
9421+        were able to and successfully did validate the signature), I
9422+        cache the data that we initially fetched from the storage
9423+        server. This will help reduce the number of roundtrips that need
9424+        to occur when the file is downloaded, or when the file is
9425+        updated.
9426+        """
9427+        if verinfo:
9428+            self._node._add_to_cache(verinfo, shnum, 0, data, now)
9429+
9430+
9431     def _got_results(self, datavs, peerid, readsize, stuff, started):
9432         lp = self.log(format="got result from [%(peerid)s], %(numshares)d shares",
9433                       peerid=idlib.shortnodeid_b2a(peerid),
9434hunk ./src/allmydata/mutable/servermap.py 633
9435-                      numshares=len(datavs),
9436-                      level=log.NOISY)
9437+                      numshares=len(datavs))
9438         now = time.time()
9439         elapsed = now - started
9440hunk ./src/allmydata/mutable/servermap.py 636
9441-        self._queries_outstanding.discard(peerid)
9442-        self._servermap.reachable_peers.add(peerid)
9443-        self._must_query.discard(peerid)
9444-        self._queries_completed += 1
9445+        def _done_processing(ignored=None):
9446+            self._queries_outstanding.discard(peerid)
9447+            self._servermap.reachable_peers.add(peerid)
9448+            self._must_query.discard(peerid)
9449+            self._queries_completed += 1
9450         if not self._running:
9451hunk ./src/allmydata/mutable/servermap.py 642
9452-            self.log("but we're not running, so we'll ignore it", parent=lp,
9453-                     level=log.NOISY)
9454+            self.log("but we're not running, so we'll ignore it", parent=lp)
9455+            _done_processing()
9456             self._status.add_per_server_time(peerid, "late", started, elapsed)
9457             return
9458         self._status.add_per_server_time(peerid, "query", started, elapsed)
9459hunk ./src/allmydata/mutable/servermap.py 653
9460         else:
9461             self._empty_peers.add(peerid)
9462 
9463-        last_verinfo = None
9464-        last_shnum = None
9465+        ss, storage_index = stuff
9466+        ds = []
9467+
9468         for shnum,datav in datavs.items():
9469             data = datav[0]
9470             try:
9471merger 0.0 (
9472hunk ./src/allmydata/mutable/servermap.py 662
9473-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
9474+                self._node._add_to_cache(verinfo, shnum, 0, data)
9475hunk ./src/allmydata/mutable/servermap.py 658
9476-            try:
9477-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
9478-                last_verinfo = verinfo
9479-                last_shnum = shnum
9480-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
9481-            except CorruptShareError, e:
9482-                # log it and give the other shares a chance to be processed
9483-                f = failure.Failure()
9484-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
9485-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
9486-                self.notify_server_corruption(peerid, shnum, str(e))
9487-                self._bad_peers.add(peerid)
9488-                self._last_failure = f
9489-                checkstring = data[:SIGNED_PREFIX_LENGTH]
9490-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
9491-                self._servermap.problems.append(f)
9492-                pass
9493+            reader = MDMFSlotReadProxy(ss,
9494+                                       storage_index,
9495+                                       shnum,
9496+                                       data)
9497+            self._readers.setdefault(peerid, dict())[shnum] = reader
9498+            # our goal, with each response, is to validate the version
9499+            # information and share data as best we can at this point --
9500+            # we do this by validating the signature. To do this, we
9501+            # need to do the following:
9502+            #   - If we don't already have the public key, fetch the
9503+            #     public key. We use this to validate the signature.
9504+            if not self._node.get_pubkey():
9505+                # fetch and set the public key.
9506+                d = reader.get_verification_key(queue=True)
9507+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
9508+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
9509+                # XXX: Make self._pubkey_query_failed?
9510+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
9511+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
9512+            else:
9513+                # we already have the public key.
9514+                d = defer.succeed(None)
9515)
9516hunk ./src/allmydata/mutable/servermap.py 676
9517                 self._servermap.problems.append(f)
9518                 pass
9519 
9520-        self._status.timings["cumulative_verify"] += (time.time() - now)
9521+            # Neither of these two branches return anything of
9522+            # consequence, so the first entry in our deferredlist will
9523+            # be None.
9524 
9525hunk ./src/allmydata/mutable/servermap.py 680
9526-        if self._need_privkey and last_verinfo:
9527-            # send them a request for the privkey. We send one request per
9528-            # server.
9529-            lp2 = self.log("sending privkey request",
9530-                           parent=lp, level=log.NOISY)
9531-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
9532-             offsets_tuple) = last_verinfo
9533-            o = dict(offsets_tuple)
9534+            # - Next, we need the version information. We almost
9535+            #   certainly got this by reading the first thousand or so
9536+            #   bytes of the share on the storage server, so we
9537+            #   shouldn't need to fetch anything at this step.
9538+            d2 = reader.get_verinfo()
9539+            d2.addErrback(lambda error, shnum=shnum, peerid=peerid:
9540+                self._got_corrupt_share(error, shnum, peerid, data, lp))
9541+            # - Next, we need the signature. For an SDMF share, it is
9542+            #   likely that we fetched this when doing our initial fetch
9543+            #   to get the version information. In MDMF, this lives at
9544+            #   the end of the share, so unless the file is quite small,
9545+            #   we'll need to do a remote fetch to get it.
9546+            d3 = reader.get_signature(queue=True)
9547+            d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
9548+                self._got_corrupt_share(error, shnum, peerid, data, lp))
9549+            #  Once we have all three of these responses, we can move on
9550+            #  to validating the signature
9551 
9552hunk ./src/allmydata/mutable/servermap.py 698
9553-            self._queries_outstanding.add(peerid)
9554-            readv = [ (o['enc_privkey'], (o['EOF'] - o['enc_privkey'])) ]
9555-            ss = self._servermap.connections[peerid]
9556-            privkey_started = time.time()
9557-            d = self._do_read(ss, peerid, self._storage_index,
9558-                              [last_shnum], readv)
9559-            d.addCallback(self._got_privkey_results, peerid, last_shnum,
9560-                          privkey_started, lp2)
9561-            d.addErrback(self._privkey_query_failed, peerid, last_shnum, lp2)
9562-            d.addErrback(log.err)
9563-            d.addCallback(self._check_for_done)
9564-            d.addErrback(self._fatal_error)
9565+            # Does the node already have a privkey? If not, we'll try to
9566+            # fetch it here.
9567+            if self._need_privkey:
9568+                d4 = reader.get_encprivkey(queue=True)
9569+                d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
9570+                    self._try_to_validate_privkey(results, peerid, shnum, lp))
9571+                d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
9572+                    self._privkey_query_failed(error, shnum, data, lp))
9573+            else:
9574+                d4 = defer.succeed(None)
9575+
9576+
9577+            if self.fetch_update_data:
9578+                # fetch the block hash tree and first + last segment, as
9579+                # configured earlier.
9580+                # Then set them in wherever we happen to want to set
9581+                # them.
9582+                ds = []
9583+                # XXX: We do this above, too. Is there a good way to
9584+                # make the two routines share the value without
9585+                # introducing more roundtrips?
9586+                ds.append(reader.get_verinfo())
9587+                ds.append(reader.get_blockhashes(queue=True))
9588+                ds.append(reader.get_block_and_salt(self.start_segment,
9589+                                                    queue=True))
9590+                ds.append(reader.get_block_and_salt(self.end_segment,
9591+                                                    queue=True))
9592+                d5 = deferredutil.gatherResults(ds)
9593+                d5.addCallback(self._got_update_results_one_share, shnum)
9594+            else:
9595+                d5 = defer.succeed(None)
9596 
9597hunk ./src/allmydata/mutable/servermap.py 730
9598+            dl = defer.DeferredList([d, d2, d3, d4, d5])
9599+            dl.addBoth(self._turn_barrier)
9600+            reader.flush()
9601+            dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
9602+                self._got_signature_one_share(results, shnum, peerid, lp))
9603+            dl.addErrback(lambda error, shnum=shnum, data=data:
9604+               self._got_corrupt_share(error, shnum, peerid, data, lp))
9605+            dl.addCallback(lambda verinfo, shnum=shnum, peerid=peerid, data=data:
9606+                self._cache_good_sharedata(verinfo, shnum, now, data))
9607+            ds.append(dl)
9608+        # dl is a deferred list that will fire when all of the shares
9609+        # that we found on this peer are done processing. When dl fires,
9610+        # we know that processing is done, so we can decrement the
9611+        # semaphore-like thing that we incremented earlier.
9612+        dl = defer.DeferredList(ds, fireOnOneErrback=True)
9613+        # Are we done? Done means that there are no more queries to
9614+        # send, that there are no outstanding queries, and that we
9615+        # haven't received any queries that are still processing. If we
9616+        # are done, self._check_for_done will cause the done deferred
9617+        # that we returned to our caller to fire, which tells them that
9618+        # they have a complete servermap, and that we won't be touching
9619+        # the servermap anymore.
9620+        dl.addCallback(_done_processing)
9621+        dl.addCallback(self._check_for_done)
9622+        dl.addErrback(self._fatal_error)
9623         # all done!
9624         self.log("_got_results done", parent=lp, level=log.NOISY)
9625hunk ./src/allmydata/mutable/servermap.py 757
9626+        return dl
9627+
9628+
9629+    def _turn_barrier(self, result):
9630+        """
9631+        I help the servermap updater avoid the recursion limit issues
9632+        discussed in #237.
9633+        """
9634+        return fireEventually(result)
9635+
9636+
9637+    def _try_to_set_pubkey(self, pubkey_s, peerid, shnum, lp):
9638+        if self._node.get_pubkey():
9639+            return # don't go through this again if we don't have to
9640+        fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
9641+        assert len(fingerprint) == 32
9642+        if fingerprint != self._node.get_fingerprint():
9643+            raise CorruptShareError(peerid, shnum,
9644+                                "pubkey doesn't match fingerprint")
9645+        self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
9646+        assert self._node.get_pubkey()
9647+
9648 
9649     def notify_server_corruption(self, peerid, shnum, reason):
9650         ss = self._servermap.connections[peerid]
9651hunk ./src/allmydata/mutable/servermap.py 785
9652         ss.callRemoteOnly("advise_corrupt_share",
9653                           "mutable", self._storage_index, shnum, reason)
9654 
9655-    def _got_results_one_share(self, shnum, data, peerid, lp):
9656+
9657+    def _got_signature_one_share(self, results, shnum, peerid, lp):
9658+        # It is our job to give versioninfo to our caller. We need to
9659+        # raise CorruptShareError if the share is corrupt for any
9660+        # reason, something that our caller will handle.
9661         self.log(format="_got_results: got shnum #%(shnum)d from peerid %(peerid)s",
9662                  shnum=shnum,
9663                  peerid=idlib.shortnodeid_b2a(peerid),
9664hunk ./src/allmydata/mutable/servermap.py 795
9665                  level=log.NOISY,
9666                  parent=lp)
9667+        if not self._running:
9668+            # We can't process the results, since we can't touch the
9669+            # servermap anymore.
9670+            self.log("but we're not running anymore.")
9671+            return None
9672 
9673hunk ./src/allmydata/mutable/servermap.py 801
9674-        # this might raise NeedMoreDataError, if the pubkey and signature
9675-        # live at some weird offset. That shouldn't happen, so I'm going to
9676-        # treat it as a bad share.
9677-        (seqnum, root_hash, IV, k, N, segsize, datalength,
9678-         pubkey_s, signature, prefix) = unpack_prefix_and_signature(data)
9679-
9680-        if not self._node.get_pubkey():
9681-            fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
9682-            assert len(fingerprint) == 32
9683-            if fingerprint != self._node.get_fingerprint():
9684-                raise CorruptShareError(peerid, shnum,
9685-                                        "pubkey doesn't match fingerprint")
9686-            self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
9687-
9688-        if self._need_privkey:
9689-            self._try_to_extract_privkey(data, peerid, shnum, lp)
9690-
9691-        (ig_version, ig_seqnum, ig_root_hash, ig_IV, ig_k, ig_N,
9692-         ig_segsize, ig_datalen, offsets) = unpack_header(data)
9693+        _, verinfo, signature, __, ___ = results
9694+        (seqnum,
9695+         root_hash,
9696+         saltish,
9697+         segsize,
9698+         datalen,
9699+         k,
9700+         n,
9701+         prefix,
9702+         offsets) = verinfo[1]
9703         offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
9704 
9705hunk ./src/allmydata/mutable/servermap.py 813
9706-        verinfo = (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
9707+        # XXX: This should be done for us in the method, so
9708+        # presumably you can go in there and fix it.
9709+        verinfo = (seqnum,
9710+                   root_hash,
9711+                   saltish,
9712+                   segsize,
9713+                   datalen,
9714+                   k,
9715+                   n,
9716+                   prefix,
9717                    offsets_tuple)
9718hunk ./src/allmydata/mutable/servermap.py 824
9719+        # This tuple uniquely identifies a share on the grid; we use it
9720+        # to keep track of the ones that we've already seen.
9721 
9722         if verinfo not in self._valid_versions:
9723hunk ./src/allmydata/mutable/servermap.py 828
9724-            # it's a new pair. Verify the signature.
9725-            valid = self._node.get_pubkey().verify(prefix, signature)
9726+            # This is a new version tuple, and we need to validate it
9727+            # against the public key before keeping track of it.
9728+            assert self._node.get_pubkey()
9729+            valid = self._node.get_pubkey().verify(prefix, signature[1])
9730             if not valid:
9731hunk ./src/allmydata/mutable/servermap.py 833
9732-                raise CorruptShareError(peerid, shnum, "signature is invalid")
9733+                raise CorruptShareError(peerid, shnum,
9734+                                        "signature is invalid")
9735 
9736hunk ./src/allmydata/mutable/servermap.py 836
9737-            # ok, it's a valid verinfo. Add it to the list of validated
9738-            # versions.
9739-            self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
9740-                     % (seqnum, base32.b2a(root_hash)[:4],
9741-                        idlib.shortnodeid_b2a(peerid), shnum,
9742-                        k, N, segsize, datalength),
9743-                     parent=lp)
9744-            self._valid_versions.add(verinfo)
9745-        # We now know that this is a valid candidate verinfo.
9746+        # ok, it's a valid verinfo. Add it to the list of validated
9747+        # versions.
9748+        self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
9749+                 % (seqnum, base32.b2a(root_hash)[:4],
9750+                    idlib.shortnodeid_b2a(peerid), shnum,
9751+                    k, n, segsize, datalen),
9752+                    parent=lp)
9753+        self._valid_versions.add(verinfo)
9754+        # We now know that this is a valid candidate verinfo. Whether or
9755+        # not this instance of it is valid is a matter for the next
9756+        # statement; at this point, we just know that if we see this
9757+        # version info again, that its signature checks out and that
9758+        # we're okay to skip the signature-checking step.
9759 
9760hunk ./src/allmydata/mutable/servermap.py 850
9761+        # (peerid, shnum) are bound in the method invocation.
9762         if (peerid, shnum) in self._servermap.bad_shares:
9763             # we've been told that the rest of the data in this share is
9764             # unusable, so don't add it to the servermap.
9765hunk ./src/allmydata/mutable/servermap.py 863
9766         self._servermap.add_new_share(peerid, shnum, verinfo, timestamp)
9767         # and the versionmap
9768         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
9769+
9770+        # It's our job to set the protocol version of our parent
9771+        # filenode if it isn't already set.
9772+        if not self._node.get_version():
9773+            # The first byte of the prefix is the version.
9774+            v = struct.unpack(">B", prefix[:1])[0]
9775+            self.log("got version %d" % v)
9776+            self._node.set_version(v)
9777+
9778         return verinfo
9779 
9780hunk ./src/allmydata/mutable/servermap.py 874
9781-    def _deserialize_pubkey(self, pubkey_s):
9782-        verifier = rsa.create_verifying_key_from_string(pubkey_s)
9783-        return verifier
9784 
9785hunk ./src/allmydata/mutable/servermap.py 875
9786-    def _try_to_extract_privkey(self, data, peerid, shnum, lp):
9787-        try:
9788-            r = unpack_share(data)
9789-        except NeedMoreDataError, e:
9790-            # this share won't help us. oh well.
9791-            offset = e.encprivkey_offset
9792-            length = e.encprivkey_length
9793-            self.log("shnum %d on peerid %s: share was too short (%dB) "
9794-                     "to get the encprivkey; [%d:%d] ought to hold it" %
9795-                     (shnum, idlib.shortnodeid_b2a(peerid), len(data),
9796-                      offset, offset+length),
9797-                     parent=lp)
9798-            # NOTE: if uncoordinated writes are taking place, someone might
9799-            # change the share (and most probably move the encprivkey) before
9800-            # we get a chance to do one of these reads and fetch it. This
9801-            # will cause us to see a NotEnoughSharesError(unable to fetch
9802-            # privkey) instead of an UncoordinatedWriteError . This is a
9803-            # nuisance, but it will go away when we move to DSA-based mutable
9804-            # files (since the privkey will be small enough to fit in the
9805-            # write cap).
9806+    def _got_update_results_one_share(self, results, share):
9807+        """
9808+        I record the update results in results.
9809+        """
9810+        assert len(results) == 4
9811+        verinfo, blockhashes, start, end = results
9812+        (seqnum,
9813+         root_hash,
9814+         saltish,
9815+         segsize,
9816+         datalen,
9817+         k,
9818+         n,
9819+         prefix,
9820+         offsets) = verinfo
9821+        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
9822 
9823hunk ./src/allmydata/mutable/servermap.py 892
9824-            return
9825+        # XXX: This should be done for us in the method, so
9826+        # presumably you can go in there and fix it.
9827+        verinfo = (seqnum,
9828+                   root_hash,
9829+                   saltish,
9830+                   segsize,
9831+                   datalen,
9832+                   k,
9833+                   n,
9834+                   prefix,
9835+                   offsets_tuple)
9836 
9837hunk ./src/allmydata/mutable/servermap.py 904
9838-        (seqnum, root_hash, IV, k, N, segsize, datalen,
9839-         pubkey, signature, share_hash_chain, block_hash_tree,
9840-         share_data, enc_privkey) = r
9841+        update_data = (blockhashes, start, end)
9842+        self._servermap.set_update_data_for_share_and_verinfo(share,
9843+                                                              verinfo,
9844+                                                              update_data)
9845 
9846hunk ./src/allmydata/mutable/servermap.py 909
9847-        return self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
9848+
9849+    def _deserialize_pubkey(self, pubkey_s):
9850+        verifier = rsa.create_verifying_key_from_string(pubkey_s)
9851+        return verifier
9852 
9853hunk ./src/allmydata/mutable/servermap.py 914
9854-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
9855 
9856hunk ./src/allmydata/mutable/servermap.py 915
9857+    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
9858+        """
9859+        Given a writekey from a remote server, I validate it against the
9860+        writekey stored in my node. If it is valid, then I set the
9861+        privkey and encprivkey properties of the node.
9862+        """
9863         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
9864         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
9865         if alleged_writekey != self._node.get_writekey():
9866hunk ./src/allmydata/mutable/servermap.py 993
9867         self._queries_completed += 1
9868         self._last_failure = f
9869 
9870-    def _got_privkey_results(self, datavs, peerid, shnum, started, lp):
9871-        now = time.time()
9872-        elapsed = now - started
9873-        self._status.add_per_server_time(peerid, "privkey", started, elapsed)
9874-        self._queries_outstanding.discard(peerid)
9875-        if not self._need_privkey:
9876-            return
9877-        if shnum not in datavs:
9878-            self.log("privkey wasn't there when we asked it",
9879-                     level=log.WEIRD, umid="VA9uDQ")
9880-            return
9881-        datav = datavs[shnum]
9882-        enc_privkey = datav[0]
9883-        self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
9884 
9885     def _privkey_query_failed(self, f, peerid, shnum, lp):
9886         self._queries_outstanding.discard(peerid)
9887hunk ./src/allmydata/mutable/servermap.py 1007
9888         self._servermap.problems.append(f)
9889         self._last_failure = f
9890 
9891+
9892     def _check_for_done(self, res):
9893         # exit paths:
9894         #  return self._send_more_queries(outstanding) : send some more queries
9895hunk ./src/allmydata/mutable/servermap.py 1013
9896         #  return self._done() : all done
9897         #  return : keep waiting, no new queries
9898-
9899         lp = self.log(format=("_check_for_done, mode is '%(mode)s', "
9900                               "%(outstanding)d queries outstanding, "
9901                               "%(extra)d extra peers available, "
9902hunk ./src/allmydata/mutable/servermap.py 1204
9903 
9904     def _done(self):
9905         if not self._running:
9906+            self.log("not running; we're already done")
9907             return
9908         self._running = False
9909         now = time.time()
9910hunk ./src/allmydata/mutable/servermap.py 1219
9911         self._servermap.last_update_time = self._started
9912         # the servermap will not be touched after this
9913         self.log("servermap: %s" % self._servermap.summarize_versions())
9914+
9915         eventually(self._done_deferred.callback, self._servermap)
9916 
9917     def _fatal_error(self, f):
9918}
9919[tests:
9920Kevan Carstensen <kevan@isnotajoke.com>**20100819003531
9921 Ignore-this: 314e8bbcce532ea4d5d2cecc9f31cca0
9922 
9923     - A lot of existing tests relied on aspects of the mutable file
9924       implementation that were changed. This patch updates those tests
9925       to work with the changes.
9926     - This patch also adds tests for new features.
9927] {
9928hunk ./src/allmydata/test/common.py 11
9929 from foolscap.api import flushEventualQueue, fireEventually
9930 from allmydata import uri, dirnode, client
9931 from allmydata.introducer.server import IntroducerNode
9932-from allmydata.interfaces import IMutableFileNode, IImmutableFileNode, \
9933-     FileTooLargeError, NotEnoughSharesError, ICheckable
9934+from allmydata.interfaces import IMutableFileNode, IImmutableFileNode,\
9935+                                 NotEnoughSharesError, ICheckable, \
9936+                                 IMutableUploadable, SDMF_VERSION, \
9937+                                 MDMF_VERSION
9938 from allmydata.check_results import CheckResults, CheckAndRepairResults, \
9939      DeepCheckResults, DeepCheckAndRepairResults
9940 from allmydata.mutable.common import CorruptShareError
9941hunk ./src/allmydata/test/common.py 19
9942 from allmydata.mutable.layout import unpack_header
9943+from allmydata.mutable.publish import MutableData
9944 from allmydata.storage.server import storage_index_to_dir
9945 from allmydata.storage.mutable import MutableShareFile
9946 from allmydata.util import hashutil, log, fileutil, pollmixin
9947hunk ./src/allmydata/test/common.py 153
9948         consumer.write(data[start:end])
9949         return consumer
9950 
9951+
9952+    def get_best_readable_version(self):
9953+        return defer.succeed(self)
9954+
9955+
9956+    download_best_version = download_to_data
9957+
9958+
9959+    def download_to_data(self):
9960+        return download_to_data(self)
9961+
9962+
9963+    def get_size_of_best_version(self):
9964+        return defer.succeed(self.get_size)
9965+
9966+
9967 def make_chk_file_cap(size):
9968     return uri.CHKFileURI(key=os.urandom(16),
9969                           uri_extension_hash=os.urandom(32),
9970hunk ./src/allmydata/test/common.py 193
9971     MUTABLE_SIZELIMIT = 10000
9972     all_contents = {}
9973     bad_shares = {}
9974+    file_types = {} # storage index => MDMF_VERSION or SDMF_VERSION
9975 
9976     def __init__(self, storage_broker, secret_holder,
9977                  default_encoding_parameters, history):
9978hunk ./src/allmydata/test/common.py 200
9979         self.init_from_cap(make_mutable_file_cap())
9980     def create(self, contents, key_generator=None, keysize=None):
9981         initial_contents = self._get_initial_contents(contents)
9982-        if len(initial_contents) > self.MUTABLE_SIZELIMIT:
9983-            raise FileTooLargeError("SDMF is limited to one segment, and "
9984-                                    "%d > %d" % (len(initial_contents),
9985-                                                 self.MUTABLE_SIZELIMIT))
9986-        self.all_contents[self.storage_index] = initial_contents
9987+        data = initial_contents.read(initial_contents.get_size())
9988+        data = "".join(data)
9989+        self.all_contents[self.storage_index] = data
9990         return defer.succeed(self)
9991     def _get_initial_contents(self, contents):
9992hunk ./src/allmydata/test/common.py 205
9993-        if isinstance(contents, str):
9994-            return contents
9995         if contents is None:
9996hunk ./src/allmydata/test/common.py 206
9997-            return ""
9998+            return MutableData("")
9999+
10000+        if IMutableUploadable.providedBy(contents):
10001+            return contents
10002+
10003         assert callable(contents), "%s should be callable, not %s" % \
10004                (contents, type(contents))
10005         return contents(self)
10006hunk ./src/allmydata/test/common.py 258
10007     def get_storage_index(self):
10008         return self.storage_index
10009 
10010+    def get_servermap(self, mode):
10011+        return defer.succeed(None)
10012+
10013+    def set_version(self, version):
10014+        assert version in (SDMF_VERSION, MDMF_VERSION)
10015+        self.file_types[self.storage_index] = version
10016+
10017+    def get_version(self):
10018+        assert self.storage_index in self.file_types
10019+        return self.file_types[self.storage_index]
10020+
10021     def check(self, monitor, verify=False, add_lease=False):
10022         r = CheckResults(self.my_uri, self.storage_index)
10023         is_bad = self.bad_shares.get(self.storage_index, None)
10024hunk ./src/allmydata/test/common.py 327
10025         return d
10026 
10027     def download_best_version(self):
10028+        return defer.succeed(self._download_best_version())
10029+
10030+
10031+    def _download_best_version(self, ignored=None):
10032         if isinstance(self.my_uri, uri.LiteralFileURI):
10033hunk ./src/allmydata/test/common.py 332
10034-            return defer.succeed(self.my_uri.data)
10035+            return self.my_uri.data
10036         if self.storage_index not in self.all_contents:
10037hunk ./src/allmydata/test/common.py 334
10038-            return defer.fail(NotEnoughSharesError(None, 0, 3))
10039-        return defer.succeed(self.all_contents[self.storage_index])
10040+            raise NotEnoughSharesError(None, 0, 3)
10041+        return self.all_contents[self.storage_index]
10042+
10043 
10044     def overwrite(self, new_contents):
10045hunk ./src/allmydata/test/common.py 339
10046-        if len(new_contents) > self.MUTABLE_SIZELIMIT:
10047-            raise FileTooLargeError("SDMF is limited to one segment, and "
10048-                                    "%d > %d" % (len(new_contents),
10049-                                                 self.MUTABLE_SIZELIMIT))
10050         assert not self.is_readonly()
10051hunk ./src/allmydata/test/common.py 340
10052-        self.all_contents[self.storage_index] = new_contents
10053+        new_data = new_contents.read(new_contents.get_size())
10054+        new_data = "".join(new_data)
10055+        self.all_contents[self.storage_index] = new_data
10056         return defer.succeed(None)
10057     def modify(self, modifier):
10058         # this does not implement FileTooLargeError, but the real one does
10059hunk ./src/allmydata/test/common.py 350
10060     def _modify(self, modifier):
10061         assert not self.is_readonly()
10062         old_contents = self.all_contents[self.storage_index]
10063-        self.all_contents[self.storage_index] = modifier(old_contents, None, True)
10064+        new_data = modifier(old_contents, None, True)
10065+        self.all_contents[self.storage_index] = new_data
10066         return None
10067 
10068hunk ./src/allmydata/test/common.py 354
10069+    # As actually implemented, MutableFilenode and MutableFileVersion
10070+    # are distinct. However, nothing in the webapi uses (yet) that
10071+    # distinction -- it just uses the unified download interface
10072+    # provided by get_best_readable_version and read. When we start
10073+    # doing cooler things like LDMF, we will want to revise this code to
10074+    # be less simplistic.
10075+    def get_best_readable_version(self):
10076+        return defer.succeed(self)
10077+
10078+
10079+    def get_best_mutable_version(self):
10080+        return defer.succeed(self)
10081+
10082+    # Ditto for this, which is an implementation of IWritable.
10083+    # XXX: Declare that the same is implemented.
10084+    def update(self, data, offset):
10085+        assert not self.is_readonly()
10086+        def modifier(old, servermap, first_time):
10087+            new = old[:offset] + "".join(data.read(data.get_size()))
10088+            new += old[len(new):]
10089+            return new
10090+        return self.modify(modifier)
10091+
10092+
10093+    def read(self, consumer, offset=0, size=None):
10094+        data = self._download_best_version()
10095+        if size:
10096+            data = data[offset:offset+size]
10097+        consumer.write(data)
10098+        return defer.succeed(consumer)
10099+
10100+
10101 def make_mutable_file_cap():
10102     return uri.WriteableSSKFileURI(writekey=os.urandom(16),
10103                                    fingerprint=os.urandom(32))
10104hunk ./src/allmydata/test/test_checker.py 11
10105 from allmydata.test.no_network import GridTestMixin
10106 from allmydata.immutable.upload import Data
10107 from allmydata.test.common_web import WebRenderingMixin
10108+from allmydata.mutable.publish import MutableData
10109 
10110 class FakeClient:
10111     def get_storage_broker(self):
10112hunk ./src/allmydata/test/test_checker.py 291
10113         def _stash_immutable(ur):
10114             self.imm = c0.create_node_from_uri(ur.uri)
10115         d.addCallback(_stash_immutable)
10116-        d.addCallback(lambda ign: c0.create_mutable_file("contents"))
10117+        d.addCallback(lambda ign:
10118+            c0.create_mutable_file(MutableData("contents")))
10119         def _stash_mutable(node):
10120             self.mut = node
10121         d.addCallback(_stash_mutable)
10122hunk ./src/allmydata/test/test_cli.py 13
10123 from allmydata.util import fileutil, hashutil, base32
10124 from allmydata import uri
10125 from allmydata.immutable import upload
10126+from allmydata.mutable.publish import MutableData
10127 from allmydata.dirnode import normalize
10128 
10129 # Test that the scripts can be imported.
10130hunk ./src/allmydata/test/test_cli.py 707
10131 
10132         d = self.do_cli("create-alias", etudes_arg)
10133         def _check_create_unicode((rc, out, err)):
10134-            self.failUnlessReallyEqual(rc, 0)
10135+            #self.failUnlessReallyEqual(rc, 0)
10136             self.failUnlessReallyEqual(err, "")
10137             self.failUnlessIn("Alias %s created" % quote_output(u"\u00E9tudes"), out)
10138 
10139hunk ./src/allmydata/test/test_cli.py 1012
10140         d.addCallback(lambda (rc,out,err): self.failUnlessReallyEqual(out, DATA2))
10141         return d
10142 
10143+    def test_mutable_type(self):
10144+        self.basedir = "cli/Put/mutable_type"
10145+        self.set_up_grid()
10146+        data = "data" * 100000
10147+        fn1 = os.path.join(self.basedir, "data")
10148+        fileutil.write(fn1, data)
10149+        d = self.do_cli("create-alias", "tahoe")
10150+        d.addCallback(lambda ignored:
10151+            self.do_cli("put", "--mutable", "--mutable-type=mdmf",
10152+                        fn1, "tahoe:uploaded.txt"))
10153+        d.addCallback(lambda ignored:
10154+            self.do_cli("ls", "--json", "tahoe:uploaded.txt"))
10155+        d.addCallback(lambda (rc, json, err): self.failUnlessIn("mdmf", json))
10156+        d.addCallback(lambda ignored:
10157+            self.do_cli("put", "--mutable", "--mutable-type=sdmf",
10158+                        fn1, "tahoe:uploaded2.txt"))
10159+        d.addCallback(lambda ignored:
10160+            self.do_cli("ls", "--json", "tahoe:uploaded2.txt"))
10161+        d.addCallback(lambda (rc, json, err):
10162+            self.failUnlessIn("sdmf", json))
10163+        return d
10164+
10165+    def test_mutable_type_unlinked(self):
10166+        self.basedir = "cli/Put/mutable_type_unlinked"
10167+        self.set_up_grid()
10168+        data = "data" * 100000
10169+        fn1 = os.path.join(self.basedir, "data")
10170+        fileutil.write(fn1, data)
10171+        d = self.do_cli("put", "--mutable", "--mutable-type=mdmf", fn1)
10172+        d.addCallback(lambda (rc, cap, err):
10173+            self.do_cli("ls", "--json", cap))
10174+        d.addCallback(lambda (rc, json, err): self.failUnlessIn("mdmf", json))
10175+        d.addCallback(lambda ignored:
10176+            self.do_cli("put", "--mutable", "--mutable-type=sdmf", fn1))
10177+        d.addCallback(lambda (rc, cap, err):
10178+            self.do_cli("ls", "--json", cap))
10179+        d.addCallback(lambda (rc, json, err):
10180+            self.failUnlessIn("sdmf", json))
10181+        return d
10182+
10183+    def test_mutable_type_invalid_format(self):
10184+        self.basedir = "cli/Put/mutable_type_invalid_format"
10185+        self.set_up_grid()
10186+        data = "data" * 100000
10187+        fn1 = os.path.join(self.basedir, "data")
10188+        fileutil.write(fn1, data)
10189+        d = self.do_cli("put", "--mutable", "--mutable-type=ldmf", fn1)
10190+        def _check_failure((rc, out, err)):
10191+            self.failIfEqual(rc, 0)
10192+            self.failUnlessIn("invalid", err)
10193+        d.addCallback(_check_failure)
10194+        return d
10195+
10196     def test_put_with_nonexistent_alias(self):
10197         # when invoked with an alias that doesn't exist, 'tahoe put'
10198         # should output a useful error message, not a stack trace
10199hunk ./src/allmydata/test/test_cli.py 2532
10200         self.set_up_grid()
10201         c0 = self.g.clients[0]
10202         DATA = "data" * 100
10203-        d = c0.create_mutable_file(DATA)
10204+        DATA_uploadable = MutableData(DATA)
10205+        d = c0.create_mutable_file(DATA_uploadable)
10206         def _stash_uri(n):
10207             self.uri = n.get_uri()
10208         d.addCallback(_stash_uri)
10209hunk ./src/allmydata/test/test_cli.py 2634
10210                                            upload.Data("literal",
10211                                                         convergence="")))
10212         d.addCallback(_stash_uri, "small")
10213-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"1"))
10214+        d.addCallback(lambda ign:
10215+            c0.create_mutable_file(MutableData(DATA+"1")))
10216         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
10217         d.addCallback(_stash_uri, "mutable")
10218 
10219hunk ./src/allmydata/test/test_cli.py 2653
10220         # root/small
10221         # root/mutable
10222 
10223+        # We haven't broken anything yet, so this should all be healthy.
10224         d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
10225                                               self.rooturi))
10226         def _check2((rc, out, err)):
10227hunk ./src/allmydata/test/test_cli.py 2668
10228                             in lines, out)
10229         d.addCallback(_check2)
10230 
10231+        # Similarly, all of these results should be as we expect them to
10232+        # be for a healthy file layout.
10233         d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
10234         def _check_stats((rc, out, err)):
10235             self.failUnlessReallyEqual(err, "")
10236hunk ./src/allmydata/test/test_cli.py 2685
10237             self.failUnlessIn(" 317-1000 : 1    (1000 B, 1000 B)", lines)
10238         d.addCallback(_check_stats)
10239 
10240+        # Now we break things.
10241         def _clobber_shares(ignored):
10242             shares = self.find_uri_shares(self.uris[u"g\u00F6\u00F6d"])
10243             self.failUnlessReallyEqual(len(shares), 10)
10244hunk ./src/allmydata/test/test_cli.py 2710
10245 
10246         d.addCallback(lambda ign:
10247                       self.do_cli("deep-check", "--verbose", self.rooturi))
10248+        # This should reveal the missing share, but not the corrupt
10249+        # share, since we didn't tell the deep check operation to also
10250+        # verify.
10251         def _check3((rc, out, err)):
10252             self.failUnlessReallyEqual(err, "")
10253             self.failUnlessReallyEqual(rc, 0)
10254hunk ./src/allmydata/test/test_cli.py 2761
10255                                   "--verbose", "--verify", "--repair",
10256                                   self.rooturi))
10257         def _check6((rc, out, err)):
10258+            # We've just repaired the directory. There is no reason for
10259+            # that repair to be unsuccessful.
10260             self.failUnlessReallyEqual(err, "")
10261             self.failUnlessReallyEqual(rc, 0)
10262             lines = out.splitlines()
10263hunk ./src/allmydata/test/test_deepcheck.py 9
10264 from twisted.internet import threads # CLI tests use deferToThread
10265 from allmydata.immutable import upload
10266 from allmydata.mutable.common import UnrecoverableFileError
10267+from allmydata.mutable.publish import MutableData
10268 from allmydata.util import idlib
10269 from allmydata.util import base32
10270 from allmydata.scripts import runner
10271hunk ./src/allmydata/test/test_deepcheck.py 38
10272         self.basedir = "deepcheck/MutableChecker/good"
10273         self.set_up_grid()
10274         CONTENTS = "a little bit of data"
10275-        d = self.g.clients[0].create_mutable_file(CONTENTS)
10276+        CONTENTS_uploadable = MutableData(CONTENTS)
10277+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
10278         def _created(node):
10279             self.node = node
10280             self.fileurl = "uri/" + urllib.quote(node.get_uri())
10281hunk ./src/allmydata/test/test_deepcheck.py 61
10282         self.basedir = "deepcheck/MutableChecker/corrupt"
10283         self.set_up_grid()
10284         CONTENTS = "a little bit of data"
10285-        d = self.g.clients[0].create_mutable_file(CONTENTS)
10286+        CONTENTS_uploadable = MutableData(CONTENTS)
10287+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
10288         def _stash_and_corrupt(node):
10289             self.node = node
10290             self.fileurl = "uri/" + urllib.quote(node.get_uri())
10291hunk ./src/allmydata/test/test_deepcheck.py 99
10292         self.basedir = "deepcheck/MutableChecker/delete_share"
10293         self.set_up_grid()
10294         CONTENTS = "a little bit of data"
10295-        d = self.g.clients[0].create_mutable_file(CONTENTS)
10296+        CONTENTS_uploadable = MutableData(CONTENTS)
10297+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
10298         def _stash_and_delete(node):
10299             self.node = node
10300             self.fileurl = "uri/" + urllib.quote(node.get_uri())
10301hunk ./src/allmydata/test/test_deepcheck.py 223
10302             self.root = n
10303             self.root_uri = n.get_uri()
10304         d.addCallback(_created_root)
10305-        d.addCallback(lambda ign: c0.create_mutable_file("mutable file contents"))
10306+        d.addCallback(lambda ign:
10307+            c0.create_mutable_file(MutableData("mutable file contents")))
10308         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
10309         def _created_mutable(n):
10310             self.mutable = n
10311hunk ./src/allmydata/test/test_deepcheck.py 965
10312     def create_mangled(self, ignored, name):
10313         nodetype, mangletype = name.split("-", 1)
10314         if nodetype == "mutable":
10315-            d = self.g.clients[0].create_mutable_file("mutable file contents")
10316+            mutable_uploadable = MutableData("mutable file contents")
10317+            d = self.g.clients[0].create_mutable_file(mutable_uploadable)
10318             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
10319         elif nodetype == "large":
10320             large = upload.Data("Lots of data\n" * 1000 + name + "\n", None)
10321hunk ./src/allmydata/test/test_dirnode.py 1304
10322     implements(IMutableFileNode)
10323     counter = 0
10324     def __init__(self, initial_contents=""):
10325-        self.data = self._get_initial_contents(initial_contents)
10326+        data = self._get_initial_contents(initial_contents)
10327+        self.data = data.read(data.get_size())
10328+        self.data = "".join(self.data)
10329+
10330         counter = FakeMutableFile.counter
10331         FakeMutableFile.counter += 1
10332         writekey = hashutil.ssk_writekey_hash(str(counter))
10333hunk ./src/allmydata/test/test_dirnode.py 1354
10334         pass
10335 
10336     def modify(self, modifier):
10337-        self.data = modifier(self.data, None, True)
10338+        data = modifier(self.data, None, True)
10339+        self.data = data
10340         return defer.succeed(None)
10341 
10342 class FakeNodeMaker(NodeMaker):
10343hunk ./src/allmydata/test/test_dirnode.py 1359
10344-    def create_mutable_file(self, contents="", keysize=None):
10345+    def create_mutable_file(self, contents="", keysize=None, version=None):
10346         return defer.succeed(FakeMutableFile(contents))
10347 
10348 class FakeClient2(Client):
10349hunk ./src/allmydata/test/test_filenode.py 98
10350         def _check_segment(res):
10351             self.failUnlessEqual(res, DATA[1:1+5])
10352         d.addCallback(_check_segment)
10353+        d.addCallback(lambda ignored: fn1.get_best_readable_version())
10354+        d.addCallback(lambda fn2: self.failUnlessEqual(fn1, fn2))
10355+        d.addCallback(lambda ignored:
10356+            fn1.get_size_of_best_version())
10357+        d.addCallback(lambda size:
10358+            self.failUnlessEqual(size, len(DATA)))
10359+        d.addCallback(lambda ignored:
10360+            fn1.download_to_data())
10361+        d.addCallback(lambda data:
10362+            self.failUnlessEqual(data, DATA))
10363+        d.addCallback(lambda ignored:
10364+            fn1.download_best_version())
10365+        d.addCallback(lambda data:
10366+            self.failUnlessEqual(data, DATA))
10367 
10368         return d
10369 
10370hunk ./src/allmydata/test/test_hung_server.py 10
10371 from allmydata.util.consumer import download_to_data
10372 from allmydata.immutable import upload
10373 from allmydata.mutable.common import UnrecoverableFileError
10374+from allmydata.mutable.publish import MutableData
10375 from allmydata.storage.common import storage_index_to_dir
10376 from allmydata.test.no_network import GridTestMixin
10377 from allmydata.test.common import ShouldFailMixin
10378hunk ./src/allmydata/test/test_hung_server.py 110
10379         self.servers = self.servers[5:] + self.servers[:5]
10380 
10381         if mutable:
10382-            d = nm.create_mutable_file(mutable_plaintext)
10383+            uploadable = MutableData(mutable_plaintext)
10384+            d = nm.create_mutable_file(uploadable)
10385             def _uploaded_mutable(node):
10386                 self.uri = node.get_uri()
10387                 self.shares = self.find_uri_shares(self.uri)
10388hunk ./src/allmydata/test/test_immutable.py 267
10389         d.addCallback(_after_attempt)
10390         return d
10391 
10392+    def test_download_to_data(self):
10393+        d = self.n.download_to_data()
10394+        d.addCallback(lambda data:
10395+            self.failUnlessEqual(data, common.TEST_DATA))
10396+        return d
10397 
10398hunk ./src/allmydata/test/test_immutable.py 273
10399+
10400+    def test_download_best_version(self):
10401+        d = self.n.download_best_version()
10402+        d.addCallback(lambda data:
10403+            self.failUnlessEqual(data, common.TEST_DATA))
10404+        return d
10405+
10406+
10407+    def test_get_best_readable_version(self):
10408+        d = self.n.get_best_readable_version()
10409+        d.addCallback(lambda n2:
10410+            self.failUnlessEqual(n2, self.n))
10411+        return d
10412+
10413+    def test_get_size_of_best_version(self):
10414+        d = self.n.get_size_of_best_version()
10415+        d.addCallback(lambda size:
10416+            self.failUnlessEqual(size, len(common.TEST_DATA)))
10417+        return d
10418+
10419+
10420 # XXX extend these tests to show bad behavior of various kinds from servers:
10421 # raising exception from each remove_foo() method, for example
10422 
10423hunk ./src/allmydata/test/test_mutable.py 2
10424 
10425-import struct
10426+import os
10427 from cStringIO import StringIO
10428 from twisted.trial import unittest
10429 from twisted.internet import defer, reactor
10430hunk ./src/allmydata/test/test_mutable.py 8
10431 from allmydata import uri, client
10432 from allmydata.nodemaker import NodeMaker
10433-from allmydata.util import base32
10434+from allmydata.util import base32, consumer
10435 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
10436      ssk_pubkey_fingerprint_hash
10437hunk ./src/allmydata/test/test_mutable.py 11
10438+from allmydata.util.deferredutil import gatherResults
10439 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
10440hunk ./src/allmydata/test/test_mutable.py 13
10441-     NotEnoughSharesError
10442+     NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
10443 from allmydata.monitor import Monitor
10444 from allmydata.test.common import ShouldFailMixin
10445 from allmydata.test.no_network import GridTestMixin
10446hunk ./src/allmydata/test/test_mutable.py 27
10447      NeedMoreDataError, UnrecoverableFileError, UncoordinatedWriteError, \
10448      NotEnoughServersError, CorruptShareError
10449 from allmydata.mutable.retrieve import Retrieve
10450-from allmydata.mutable.publish import Publish
10451+from allmydata.mutable.publish import Publish, MutableFileHandle, \
10452+                                      MutableData, \
10453+                                      DEFAULT_MAX_SEGMENT_SIZE
10454 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
10455hunk ./src/allmydata/test/test_mutable.py 31
10456-from allmydata.mutable.layout import unpack_header, unpack_share
10457+from allmydata.mutable.layout import unpack_header, MDMFSlotReadProxy
10458 from allmydata.mutable.repairer import MustForceRepairError
10459 
10460 import allmydata.test.common_util as testutil
10461hunk ./src/allmydata/test/test_mutable.py 100
10462         self.storage = storage
10463         self.queries = 0
10464     def callRemote(self, methname, *args, **kwargs):
10465+        self.queries += 1
10466         def _call():
10467             meth = getattr(self, methname)
10468             return meth(*args, **kwargs)
10469hunk ./src/allmydata/test/test_mutable.py 107
10470         d = fireEventually()
10471         d.addCallback(lambda res: _call())
10472         return d
10473+
10474     def callRemoteOnly(self, methname, *args, **kwargs):
10475hunk ./src/allmydata/test/test_mutable.py 109
10476+        self.queries += 1
10477         d = self.callRemote(methname, *args, **kwargs)
10478         d.addBoth(lambda ignore: None)
10479         pass
10480hunk ./src/allmydata/test/test_mutable.py 157
10481             chr(ord(original[byte_offset]) ^ 0x01) +
10482             original[byte_offset+1:])
10483 
10484+def add_two(original, byte_offset):
10485+    # It isn't enough to simply flip the bit for the version number,
10486+    # because 1 is a valid version number. So we add two instead.
10487+    return (original[:byte_offset] +
10488+            chr(ord(original[byte_offset]) ^ 0x02) +
10489+            original[byte_offset+1:])
10490+
10491 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
10492     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
10493     # list of shnums to corrupt.
10494hunk ./src/allmydata/test/test_mutable.py 167
10495+    ds = []
10496     for peerid in s._peers:
10497         shares = s._peers[peerid]
10498         for shnum in shares:
10499hunk ./src/allmydata/test/test_mutable.py 175
10500                 and shnum not in shnums_to_corrupt):
10501                 continue
10502             data = shares[shnum]
10503-            (version,
10504-             seqnum,
10505-             root_hash,
10506-             IV,
10507-             k, N, segsize, datalen,
10508-             o) = unpack_header(data)
10509-            if isinstance(offset, tuple):
10510-                offset1, offset2 = offset
10511-            else:
10512-                offset1 = offset
10513-                offset2 = 0
10514-            if offset1 == "pubkey":
10515-                real_offset = 107
10516-            elif offset1 in o:
10517-                real_offset = o[offset1]
10518-            else:
10519-                real_offset = offset1
10520-            real_offset = int(real_offset) + offset2 + offset_offset
10521-            assert isinstance(real_offset, int), offset
10522-            shares[shnum] = flip_bit(data, real_offset)
10523-    return res
10524+            # We're feeding the reader all of the share data, so it
10525+            # won't need to use the rref that we didn't provide, nor the
10526+            # storage index that we didn't provide. We do this because
10527+            # the reader will work for both MDMF and SDMF.
10528+            reader = MDMFSlotReadProxy(None, None, shnum, data)
10529+            # We need to get the offsets for the next part.
10530+            d = reader.get_verinfo()
10531+            def _do_corruption(verinfo, data, shnum):
10532+                (seqnum,
10533+                 root_hash,
10534+                 IV,
10535+                 segsize,
10536+                 datalen,
10537+                 k, n, prefix, o) = verinfo
10538+                if isinstance(offset, tuple):
10539+                    offset1, offset2 = offset
10540+                else:
10541+                    offset1 = offset
10542+                    offset2 = 0
10543+                if offset1 == "pubkey" and IV:
10544+                    real_offset = 107
10545+                elif offset1 == "share_data" and not IV:
10546+                    real_offset = 107
10547+                elif offset1 in o:
10548+                    real_offset = o[offset1]
10549+                else:
10550+                    real_offset = offset1
10551+                real_offset = int(real_offset) + offset2 + offset_offset
10552+                assert isinstance(real_offset, int), offset
10553+                if offset1 == 0: # verbyte
10554+                    f = add_two
10555+                else:
10556+                    f = flip_bit
10557+                shares[shnum] = f(data, real_offset)
10558+            d.addCallback(_do_corruption, data, shnum)
10559+            ds.append(d)
10560+    dl = defer.DeferredList(ds)
10561+    dl.addCallback(lambda ignored: res)
10562+    return dl
10563 
10564 def make_storagebroker(s=None, num_peers=10):
10565     if not s:
10566hunk ./src/allmydata/test/test_mutable.py 256
10567             self.failUnlessEqual(len(shnums), 1)
10568         d.addCallback(_created)
10569         return d
10570+    test_create.timeout = 15
10571+
10572+
10573+    def test_create_mdmf(self):
10574+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
10575+        def _created(n):
10576+            self.failUnless(isinstance(n, MutableFileNode))
10577+            self.failUnlessEqual(n.get_storage_index(), n._storage_index)
10578+            sb = self.nodemaker.storage_broker
10579+            peer0 = sorted(sb.get_all_serverids())[0]
10580+            shnums = self._storage._peers[peer0].keys()
10581+            self.failUnlessEqual(len(shnums), 1)
10582+        d.addCallback(_created)
10583+        return d
10584+
10585 
10586     def test_serialize(self):
10587         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
10588hunk ./src/allmydata/test/test_mutable.py 301
10589             d.addCallback(lambda smap: smap.dump(StringIO()))
10590             d.addCallback(lambda sio:
10591                           self.failUnless("3-of-10" in sio.getvalue()))
10592-            d.addCallback(lambda res: n.overwrite("contents 1"))
10593+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
10594             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
10595             d.addCallback(lambda res: n.download_best_version())
10596             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
10597hunk ./src/allmydata/test/test_mutable.py 308
10598             d.addCallback(lambda res: n.get_size_of_best_version())
10599             d.addCallback(lambda size:
10600                           self.failUnlessEqual(size, len("contents 1")))
10601-            d.addCallback(lambda res: n.overwrite("contents 2"))
10602+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
10603             d.addCallback(lambda res: n.download_best_version())
10604             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
10605             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
10606hunk ./src/allmydata/test/test_mutable.py 312
10607-            d.addCallback(lambda smap: n.upload("contents 3", smap))
10608+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
10609             d.addCallback(lambda res: n.download_best_version())
10610             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
10611             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
10612hunk ./src/allmydata/test/test_mutable.py 324
10613             # mapupdate-to-retrieve data caching (i.e. make the shares larger
10614             # than the default readsize, which is 2000 bytes). A 15kB file
10615             # will have 5kB shares.
10616-            d.addCallback(lambda res: n.overwrite("large size file" * 1000))
10617+            d.addCallback(lambda res: n.overwrite(MutableData("large size file" * 1000)))
10618             d.addCallback(lambda res: n.download_best_version())
10619             d.addCallback(lambda res:
10620                           self.failUnlessEqual(res, "large size file" * 1000))
10621hunk ./src/allmydata/test/test_mutable.py 332
10622         d.addCallback(_created)
10623         return d
10624 
10625+
10626+    def test_upload_and_download_mdmf(self):
10627+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
10628+        def _created(n):
10629+            d = defer.succeed(None)
10630+            d.addCallback(lambda ignored:
10631+                n.get_servermap(MODE_READ))
10632+            def _then(servermap):
10633+                dumped = servermap.dump(StringIO())
10634+                self.failUnlessIn("3-of-10", dumped.getvalue())
10635+            d.addCallback(_then)
10636+            # Now overwrite the contents with some new contents. We want
10637+            # to make them big enough to force the file to be uploaded
10638+            # in more than one segment.
10639+            big_contents = "contents1" * 100000 # about 900 KiB
10640+            big_contents_uploadable = MutableData(big_contents)
10641+            d.addCallback(lambda ignored:
10642+                n.overwrite(big_contents_uploadable))
10643+            d.addCallback(lambda ignored:
10644+                n.download_best_version())
10645+            d.addCallback(lambda data:
10646+                self.failUnlessEqual(data, big_contents))
10647+            # Overwrite the contents again with some new contents. As
10648+            # before, they need to be big enough to force multiple
10649+            # segments, so that we make the downloader deal with
10650+            # multiple segments.
10651+            bigger_contents = "contents2" * 1000000 # about 9MiB
10652+            bigger_contents_uploadable = MutableData(bigger_contents)
10653+            d.addCallback(lambda ignored:
10654+                n.overwrite(bigger_contents_uploadable))
10655+            d.addCallback(lambda ignored:
10656+                n.download_best_version())
10657+            d.addCallback(lambda data:
10658+                self.failUnlessEqual(data, bigger_contents))
10659+            return d
10660+        d.addCallback(_created)
10661+        return d
10662+
10663+
10664+    def test_mdmf_write_count(self):
10665+        # Publishing an MDMF file should only cause one write for each
10666+        # share that is to be published. Otherwise, we introduce
10667+        # undesirable semantics that are a regression from SDMF
10668+        upload = MutableData("MDMF" * 100000) # about 400 KiB
10669+        d = self.nodemaker.create_mutable_file(upload,
10670+                                               version=MDMF_VERSION)
10671+        def _check_server_write_counts(ignored):
10672+            sb = self.nodemaker.storage_broker
10673+            peers = sb.test_servers.values()
10674+            for peer in peers:
10675+                self.failUnlessEqual(peer.queries, 1)
10676+        d.addCallback(_check_server_write_counts)
10677+        return d
10678+
10679+
10680     def test_create_with_initial_contents(self):
10681hunk ./src/allmydata/test/test_mutable.py 388
10682-        d = self.nodemaker.create_mutable_file("contents 1")
10683+        upload1 = MutableData("contents 1")
10684+        d = self.nodemaker.create_mutable_file(upload1)
10685         def _created(n):
10686             d = n.download_best_version()
10687             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
10688hunk ./src/allmydata/test/test_mutable.py 393
10689-            d.addCallback(lambda res: n.overwrite("contents 2"))
10690+            upload2 = MutableData("contents 2")
10691+            d.addCallback(lambda res: n.overwrite(upload2))
10692             d.addCallback(lambda res: n.download_best_version())
10693             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
10694             return d
10695hunk ./src/allmydata/test/test_mutable.py 400
10696         d.addCallback(_created)
10697         return d
10698+    test_create_with_initial_contents.timeout = 15
10699+
10700+
10701+    def test_create_mdmf_with_initial_contents(self):
10702+        initial_contents = "foobarbaz" * 131072 # 900KiB
10703+        initial_contents_uploadable = MutableData(initial_contents)
10704+        d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
10705+                                               version=MDMF_VERSION)
10706+        def _created(n):
10707+            d = n.download_best_version()
10708+            d.addCallback(lambda data:
10709+                self.failUnlessEqual(data, initial_contents))
10710+            uploadable2 = MutableData(initial_contents + "foobarbaz")
10711+            d.addCallback(lambda ignored:
10712+                n.overwrite(uploadable2))
10713+            d.addCallback(lambda ignored:
10714+                n.download_best_version())
10715+            d.addCallback(lambda data:
10716+                self.failUnlessEqual(data, initial_contents +
10717+                                           "foobarbaz"))
10718+            return d
10719+        d.addCallback(_created)
10720+        return d
10721+    test_create_mdmf_with_initial_contents.timeout = 20
10722+
10723 
10724     def test_response_cache_memory_leak(self):
10725         d = self.nodemaker.create_mutable_file("contents")
10726hunk ./src/allmydata/test/test_mutable.py 451
10727             key = n.get_writekey()
10728             self.failUnless(isinstance(key, str), key)
10729             self.failUnlessEqual(len(key), 16) # AES key size
10730-            return data
10731+            return MutableData(data)
10732         d = self.nodemaker.create_mutable_file(_make_contents)
10733         def _created(n):
10734             return n.download_best_version()
10735hunk ./src/allmydata/test/test_mutable.py 459
10736         d.addCallback(lambda data2: self.failUnlessEqual(data2, data))
10737         return d
10738 
10739+
10740+    def test_create_mdmf_with_initial_contents_function(self):
10741+        data = "initial contents" * 100000
10742+        def _make_contents(n):
10743+            self.failUnless(isinstance(n, MutableFileNode))
10744+            key = n.get_writekey()
10745+            self.failUnless(isinstance(key, str), key)
10746+            self.failUnlessEqual(len(key), 16)
10747+            return MutableData(data)
10748+        d = self.nodemaker.create_mutable_file(_make_contents,
10749+                                               version=MDMF_VERSION)
10750+        d.addCallback(lambda n:
10751+            n.download_best_version())
10752+        d.addCallback(lambda data2:
10753+            self.failUnlessEqual(data2, data))
10754+        return d
10755+
10756+
10757     def test_create_with_too_large_contents(self):
10758         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
10759hunk ./src/allmydata/test/test_mutable.py 479
10760-        d = self.nodemaker.create_mutable_file(BIG)
10761+        BIG_uploadable = MutableData(BIG)
10762+        d = self.nodemaker.create_mutable_file(BIG_uploadable)
10763         def _created(n):
10764hunk ./src/allmydata/test/test_mutable.py 482
10765-            d = n.overwrite(BIG)
10766+            other_BIG_uploadable = MutableData(BIG)
10767+            d = n.overwrite(other_BIG_uploadable)
10768             return d
10769         d.addCallback(_created)
10770         return d
10771hunk ./src/allmydata/test/test_mutable.py 497
10772 
10773     def test_modify(self):
10774         def _modifier(old_contents, servermap, first_time):
10775-            return old_contents + "line2"
10776+            new_contents = old_contents + "line2"
10777+            return new_contents
10778         def _non_modifier(old_contents, servermap, first_time):
10779             return old_contents
10780         def _none_modifier(old_contents, servermap, first_time):
10781hunk ./src/allmydata/test/test_mutable.py 506
10782         def _error_modifier(old_contents, servermap, first_time):
10783             raise ValueError("oops")
10784         def _toobig_modifier(old_contents, servermap, first_time):
10785-            return "b" * (self.OLD_MAX_SEGMENT_SIZE+1)
10786+            new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
10787+            return new_content
10788         calls = []
10789         def _ucw_error_modifier(old_contents, servermap, first_time):
10790             # simulate an UncoordinatedWriteError once
10791hunk ./src/allmydata/test/test_mutable.py 514
10792             calls.append(1)
10793             if len(calls) <= 1:
10794                 raise UncoordinatedWriteError("simulated")
10795-            return old_contents + "line3"
10796+            new_contents = old_contents + "line3"
10797+            return new_contents
10798         def _ucw_error_non_modifier(old_contents, servermap, first_time):
10799             # simulate an UncoordinatedWriteError once, and don't actually
10800             # modify the contents on subsequent invocations
10801hunk ./src/allmydata/test/test_mutable.py 524
10802                 raise UncoordinatedWriteError("simulated")
10803             return old_contents
10804 
10805-        d = self.nodemaker.create_mutable_file("line1")
10806+        initial_contents = "line1"
10807+        d = self.nodemaker.create_mutable_file(MutableData(initial_contents))
10808         def _created(n):
10809             d = n.modify(_modifier)
10810             d.addCallback(lambda res: n.download_best_version())
10811hunk ./src/allmydata/test/test_mutable.py 582
10812             return d
10813         d.addCallback(_created)
10814         return d
10815+    test_modify.timeout = 15
10816+
10817 
10818     def test_modify_backoffer(self):
10819         def _modifier(old_contents, servermap, first_time):
10820hunk ./src/allmydata/test/test_mutable.py 609
10821         giveuper._delay = 0.1
10822         giveuper.factor = 1
10823 
10824-        d = self.nodemaker.create_mutable_file("line1")
10825+        d = self.nodemaker.create_mutable_file(MutableData("line1"))
10826         def _created(n):
10827             d = n.modify(_modifier)
10828             d.addCallback(lambda res: n.download_best_version())
10829hunk ./src/allmydata/test/test_mutable.py 659
10830             d.addCallback(lambda smap: smap.dump(StringIO()))
10831             d.addCallback(lambda sio:
10832                           self.failUnless("3-of-10" in sio.getvalue()))
10833-            d.addCallback(lambda res: n.overwrite("contents 1"))
10834+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
10835             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
10836             d.addCallback(lambda res: n.download_best_version())
10837             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
10838hunk ./src/allmydata/test/test_mutable.py 663
10839-            d.addCallback(lambda res: n.overwrite("contents 2"))
10840+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
10841             d.addCallback(lambda res: n.download_best_version())
10842             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
10843             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
10844hunk ./src/allmydata/test/test_mutable.py 667
10845-            d.addCallback(lambda smap: n.upload("contents 3", smap))
10846+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
10847             d.addCallback(lambda res: n.download_best_version())
10848             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
10849             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
10850hunk ./src/allmydata/test/test_mutable.py 680
10851         return d
10852 
10853 
10854-class MakeShares(unittest.TestCase):
10855-    def test_encrypt(self):
10856-        nm = make_nodemaker()
10857-        CONTENTS = "some initial contents"
10858-        d = nm.create_mutable_file(CONTENTS)
10859-        def _created(fn):
10860-            p = Publish(fn, nm.storage_broker, None)
10861-            p.salt = "SALT" * 4
10862-            p.readkey = "\x00" * 16
10863-            p.newdata = CONTENTS
10864-            p.required_shares = 3
10865-            p.total_shares = 10
10866-            p.setup_encoding_parameters()
10867-            return p._encrypt_and_encode()
10868+    def test_size_after_servermap_update(self):
10869+        # a mutable file node should have something to say about how big
10870+        # it is after a servermap update is performed, since this tells
10871+        # us how large the best version of that mutable file is.
10872+        d = self.nodemaker.create_mutable_file()
10873+        def _created(n):
10874+            self.n = n
10875+            return n.get_servermap(MODE_READ)
10876+        d.addCallback(_created)
10877+        d.addCallback(lambda ignored:
10878+            self.failUnlessEqual(self.n.get_size(), 0))
10879+        d.addCallback(lambda ignored:
10880+            self.n.overwrite(MutableData("foobarbaz")))
10881+        d.addCallback(lambda ignored:
10882+            self.failUnlessEqual(self.n.get_size(), 9))
10883+        d.addCallback(lambda ignored:
10884+            self.nodemaker.create_mutable_file(MutableData("foobarbaz")))
10885+        d.addCallback(_created)
10886+        d.addCallback(lambda ignored:
10887+            self.failUnlessEqual(self.n.get_size(), 9))
10888+        return d
10889+
10890+
10891+class PublishMixin:
10892+    def publish_one(self):
10893+        # publish a file and create shares, which can then be manipulated
10894+        # later.
10895+        self.CONTENTS = "New contents go here" * 1000
10896+        self.uploadable = MutableData(self.CONTENTS)
10897+        self._storage = FakeStorage()
10898+        self._nodemaker = make_nodemaker(self._storage)
10899+        self._storage_broker = self._nodemaker.storage_broker
10900+        d = self._nodemaker.create_mutable_file(self.uploadable)
10901+        def _created(node):
10902+            self._fn = node
10903+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
10904         d.addCallback(_created)
10905hunk ./src/allmydata/test/test_mutable.py 717
10906-        def _done(shares_and_shareids):
10907-            (shares, share_ids) = shares_and_shareids
10908-            self.failUnlessEqual(len(shares), 10)
10909-            for sh in shares:
10910-                self.failUnless(isinstance(sh, str))
10911-                self.failUnlessEqual(len(sh), 7)
10912-            self.failUnlessEqual(len(share_ids), 10)
10913-        d.addCallback(_done)
10914         return d
10915 
10916hunk ./src/allmydata/test/test_mutable.py 719
10917-    def test_generate(self):
10918-        nm = make_nodemaker()
10919-        CONTENTS = "some initial contents"
10920-        d = nm.create_mutable_file(CONTENTS)
10921-        def _created(fn):
10922-            self._fn = fn
10923-            p = Publish(fn, nm.storage_broker, None)
10924-            self._p = p
10925-            p.newdata = CONTENTS
10926-            p.required_shares = 3
10927-            p.total_shares = 10
10928-            p.setup_encoding_parameters()
10929-            p._new_seqnum = 3
10930-            p.salt = "SALT" * 4
10931-            # make some fake shares
10932-            shares_and_ids = ( ["%07d" % i for i in range(10)], range(10) )
10933-            p._privkey = fn.get_privkey()
10934-            p._encprivkey = fn.get_encprivkey()
10935-            p._pubkey = fn.get_pubkey()
10936-            return p._generate_shares(shares_and_ids)
10937+    def publish_mdmf(self):
10938+        # like publish_one, except that the result is guaranteed to be
10939+        # an MDMF file.
10940+        # self.CONTENTS should have more than one segment.
10941+        self.CONTENTS = "This is an MDMF file" * 100000
10942+        self.uploadable = MutableData(self.CONTENTS)
10943+        self._storage = FakeStorage()
10944+        self._nodemaker = make_nodemaker(self._storage)
10945+        self._storage_broker = self._nodemaker.storage_broker
10946+        d = self._nodemaker.create_mutable_file(self.uploadable, version=MDMF_VERSION)
10947+        def _created(node):
10948+            self._fn = node
10949+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
10950         d.addCallback(_created)
10951hunk ./src/allmydata/test/test_mutable.py 733
10952-        def _generated(res):
10953-            p = self._p
10954-            final_shares = p.shares
10955-            root_hash = p.root_hash
10956-            self.failUnlessEqual(len(root_hash), 32)
10957-            self.failUnless(isinstance(final_shares, dict))
10958-            self.failUnlessEqual(len(final_shares), 10)
10959-            self.failUnlessEqual(sorted(final_shares.keys()), range(10))
10960-            for i,sh in final_shares.items():
10961-                self.failUnless(isinstance(sh, str))
10962-                # feed the share through the unpacker as a sanity-check
10963-                pieces = unpack_share(sh)
10964-                (u_seqnum, u_root_hash, IV, k, N, segsize, datalen,
10965-                 pubkey, signature, share_hash_chain, block_hash_tree,
10966-                 share_data, enc_privkey) = pieces
10967-                self.failUnlessEqual(u_seqnum, 3)
10968-                self.failUnlessEqual(u_root_hash, root_hash)
10969-                self.failUnlessEqual(k, 3)
10970-                self.failUnlessEqual(N, 10)
10971-                self.failUnlessEqual(segsize, 21)
10972-                self.failUnlessEqual(datalen, len(CONTENTS))
10973-                self.failUnlessEqual(pubkey, p._pubkey.serialize())
10974-                sig_material = struct.pack(">BQ32s16s BBQQ",
10975-                                           0, p._new_seqnum, root_hash, IV,
10976-                                           k, N, segsize, datalen)
10977-                self.failUnless(p._pubkey.verify(sig_material, signature))
10978-                #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
10979-                self.failUnless(isinstance(share_hash_chain, dict))
10980-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
10981-                for shnum,share_hash in share_hash_chain.items():
10982-                    self.failUnless(isinstance(shnum, int))
10983-                    self.failUnless(isinstance(share_hash, str))
10984-                    self.failUnlessEqual(len(share_hash), 32)
10985-                self.failUnless(isinstance(block_hash_tree, list))
10986-                self.failUnlessEqual(len(block_hash_tree), 1) # very small tree
10987-                self.failUnlessEqual(IV, "SALT"*4)
10988-                self.failUnlessEqual(len(share_data), len("%07d" % 1))
10989-                self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
10990-        d.addCallback(_generated)
10991         return d
10992 
10993hunk ./src/allmydata/test/test_mutable.py 735
10994-    # TODO: when we publish to 20 peers, we should get one share per peer on 10
10995-    # when we publish to 3 peers, we should get either 3 or 4 shares per peer
10996-    # when we publish to zero peers, we should get a NotEnoughSharesError
10997 
10998hunk ./src/allmydata/test/test_mutable.py 736
10999-class PublishMixin:
11000-    def publish_one(self):
11001-        # publish a file and create shares, which can then be manipulated
11002-        # later.
11003-        self.CONTENTS = "New contents go here" * 1000
11004+    def publish_sdmf(self):
11005+        # like publish_one, except that the result is guaranteed to be
11006+        # an SDMF file
11007+        self.CONTENTS = "This is an SDMF file" * 1000
11008+        self.uploadable = MutableData(self.CONTENTS)
11009         self._storage = FakeStorage()
11010         self._nodemaker = make_nodemaker(self._storage)
11011         self._storage_broker = self._nodemaker.storage_broker
11012hunk ./src/allmydata/test/test_mutable.py 744
11013-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
11014+        d = self._nodemaker.create_mutable_file(self.uploadable, version=SDMF_VERSION)
11015         def _created(node):
11016             self._fn = node
11017             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
11018hunk ./src/allmydata/test/test_mutable.py 751
11019         d.addCallback(_created)
11020         return d
11021 
11022-    def publish_multiple(self):
11023+
11024+    def publish_multiple(self, version=0):
11025         self.CONTENTS = ["Contents 0",
11026                          "Contents 1",
11027                          "Contents 2",
11028hunk ./src/allmydata/test/test_mutable.py 758
11029                          "Contents 3a",
11030                          "Contents 3b"]
11031+        self.uploadables = [MutableData(d) for d in self.CONTENTS]
11032         self._copied_shares = {}
11033         self._storage = FakeStorage()
11034         self._nodemaker = make_nodemaker(self._storage)
11035hunk ./src/allmydata/test/test_mutable.py 762
11036-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0]) # seqnum=1
11037+        d = self._nodemaker.create_mutable_file(self.uploadables[0], version=version) # seqnum=1
11038         def _created(node):
11039             self._fn = node
11040             # now create multiple versions of the same file, and accumulate
11041hunk ./src/allmydata/test/test_mutable.py 769
11042             # their shares, so we can mix and match them later.
11043             d = defer.succeed(None)
11044             d.addCallback(self._copy_shares, 0)
11045-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[1])) #s2
11046+            d.addCallback(lambda res: node.overwrite(self.uploadables[1])) #s2
11047             d.addCallback(self._copy_shares, 1)
11048hunk ./src/allmydata/test/test_mutable.py 771
11049-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[2])) #s3
11050+            d.addCallback(lambda res: node.overwrite(self.uploadables[2])) #s3
11051             d.addCallback(self._copy_shares, 2)
11052hunk ./src/allmydata/test/test_mutable.py 773
11053-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[3])) #s4a
11054+            d.addCallback(lambda res: node.overwrite(self.uploadables[3])) #s4a
11055             d.addCallback(self._copy_shares, 3)
11056             # now we replace all the shares with version s3, and upload a new
11057             # version to get s4b.
11058hunk ./src/allmydata/test/test_mutable.py 779
11059             rollback = dict([(i,2) for i in range(10)])
11060             d.addCallback(lambda res: self._set_versions(rollback))
11061-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[4])) #s4b
11062+            d.addCallback(lambda res: node.overwrite(self.uploadables[4])) #s4b
11063             d.addCallback(self._copy_shares, 4)
11064             # we leave the storage in state 4
11065             return d
11066hunk ./src/allmydata/test/test_mutable.py 786
11067         d.addCallback(_created)
11068         return d
11069 
11070+
11071     def _copy_shares(self, ignored, index):
11072         shares = self._storage._peers
11073         # we need a deep copy
11074hunk ./src/allmydata/test/test_mutable.py 810
11075                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
11076 
11077 
11078+
11079+
11080 class Servermap(unittest.TestCase, PublishMixin):
11081     def setUp(self):
11082         return self.publish_one()
11083hunk ./src/allmydata/test/test_mutable.py 816
11084 
11085-    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None):
11086+    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None,
11087+                       update_range=None):
11088         if fn is None:
11089             fn = self._fn
11090         if sb is None:
11091hunk ./src/allmydata/test/test_mutable.py 823
11092             sb = self._storage_broker
11093         smu = ServermapUpdater(fn, sb, Monitor(),
11094-                               ServerMap(), mode)
11095+                               ServerMap(), mode, update_range=update_range)
11096         d = smu.update()
11097         return d
11098 
11099hunk ./src/allmydata/test/test_mutable.py 889
11100         # create a new file, which is large enough to knock the privkey out
11101         # of the early part of the file
11102         LARGE = "These are Larger contents" * 200 # about 5KB
11103-        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE))
11104+        LARGE_uploadable = MutableData(LARGE)
11105+        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
11106         def _created(large_fn):
11107             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
11108             return self.make_servermap(MODE_WRITE, large_fn2)
11109hunk ./src/allmydata/test/test_mutable.py 898
11110         d.addCallback(lambda sm: self.failUnlessOneRecoverable(sm, 10))
11111         return d
11112 
11113+
11114     def test_mark_bad(self):
11115         d = defer.succeed(None)
11116         ms = self.make_servermap
11117hunk ./src/allmydata/test/test_mutable.py 944
11118         self._storage._peers = {} # delete all shares
11119         ms = self.make_servermap
11120         d = defer.succeed(None)
11121-
11122+#
11123         d.addCallback(lambda res: ms(mode=MODE_CHECK))
11124         d.addCallback(lambda sm: self.failUnlessNoneRecoverable(sm))
11125 
11126hunk ./src/allmydata/test/test_mutable.py 996
11127         return d
11128 
11129 
11130+    def test_servermapupdater_finds_mdmf_files(self):
11131+        # setUp already published an MDMF file for us. We just need to
11132+        # make sure that when we run the ServermapUpdater, the file is
11133+        # reported to have one recoverable version.
11134+        d = defer.succeed(None)
11135+        d.addCallback(lambda ignored:
11136+            self.publish_mdmf())
11137+        d.addCallback(lambda ignored:
11138+            self.make_servermap(mode=MODE_CHECK))
11139+        # Calling make_servermap also updates the servermap in the mode
11140+        # that we specify, so we just need to see what it says.
11141+        def _check_servermap(sm):
11142+            self.failUnlessEqual(len(sm.recoverable_versions()), 1)
11143+        d.addCallback(_check_servermap)
11144+        return d
11145+
11146+
11147+    def test_fetch_update(self):
11148+        d = defer.succeed(None)
11149+        d.addCallback(lambda ignored:
11150+            self.publish_mdmf())
11151+        d.addCallback(lambda ignored:
11152+            self.make_servermap(mode=MODE_WRITE, update_range=(1, 2)))
11153+        def _check_servermap(sm):
11154+            # 10 shares
11155+            self.failUnlessEqual(len(sm.update_data), 10)
11156+            # one version
11157+            for data in sm.update_data.itervalues():
11158+                self.failUnlessEqual(len(data), 1)
11159+        d.addCallback(_check_servermap)
11160+        return d
11161+
11162+
11163+    def test_servermapupdater_finds_sdmf_files(self):
11164+        d = defer.succeed(None)
11165+        d.addCallback(lambda ignored:
11166+            self.publish_sdmf())
11167+        d.addCallback(lambda ignored:
11168+            self.make_servermap(mode=MODE_CHECK))
11169+        d.addCallback(lambda servermap:
11170+            self.failUnlessEqual(len(servermap.recoverable_versions()), 1))
11171+        return d
11172+
11173 
11174 class Roundtrip(unittest.TestCase, testutil.ShouldFailMixin, PublishMixin):
11175     def setUp(self):
11176hunk ./src/allmydata/test/test_mutable.py 1079
11177         if version is None:
11178             version = servermap.best_recoverable_version()
11179         r = Retrieve(self._fn, servermap, version)
11180-        return r.download()
11181+        c = consumer.MemoryConsumer()
11182+        d = r.download(consumer=c)
11183+        d.addCallback(lambda mc: "".join(mc.chunks))
11184+        return d
11185+
11186 
11187     def test_basic(self):
11188         d = self.make_servermap()
11189hunk ./src/allmydata/test/test_mutable.py 1160
11190         return d
11191     test_no_servers_download.timeout = 15
11192 
11193+
11194     def _test_corrupt_all(self, offset, substring,
11195hunk ./src/allmydata/test/test_mutable.py 1162
11196-                          should_succeed=False, corrupt_early=True,
11197-                          failure_checker=None):
11198+                          should_succeed=False,
11199+                          corrupt_early=True,
11200+                          failure_checker=None,
11201+                          fetch_privkey=False):
11202         d = defer.succeed(None)
11203         if corrupt_early:
11204             d.addCallback(corrupt, self._storage, offset)
11205hunk ./src/allmydata/test/test_mutable.py 1182
11206                     self.failUnlessIn(substring, "".join(allproblems))
11207                 return servermap
11208             if should_succeed:
11209-                d1 = self._fn.download_version(servermap, ver)
11210+                d1 = self._fn.download_version(servermap, ver,
11211+                                               fetch_privkey)
11212                 d1.addCallback(lambda new_contents:
11213                                self.failUnlessEqual(new_contents, self.CONTENTS))
11214             else:
11215hunk ./src/allmydata/test/test_mutable.py 1190
11216                 d1 = self.shouldFail(NotEnoughSharesError,
11217                                      "_corrupt_all(offset=%s)" % (offset,),
11218                                      substring,
11219-                                     self._fn.download_version, servermap, ver)
11220+                                     self._fn.download_version, servermap,
11221+                                                                ver,
11222+                                                                fetch_privkey)
11223             if failure_checker:
11224                 d1.addCallback(failure_checker)
11225             d1.addCallback(lambda res: servermap)
11226hunk ./src/allmydata/test/test_mutable.py 1201
11227         return d
11228 
11229     def test_corrupt_all_verbyte(self):
11230-        # when the version byte is not 0, we hit an UnknownVersionError error
11231-        # in unpack_share().
11232+        # when the version byte is not 0 or 1, we hit an UnknownVersionError
11233+        # error in unpack_share().
11234         d = self._test_corrupt_all(0, "UnknownVersionError")
11235         def _check_servermap(servermap):
11236             # and the dump should mention the problems
11237hunk ./src/allmydata/test/test_mutable.py 1208
11238             s = StringIO()
11239             dump = servermap.dump(s).getvalue()
11240-            self.failUnless("10 PROBLEMS" in dump, dump)
11241+            self.failUnless("30 PROBLEMS" in dump, dump)
11242         d.addCallback(_check_servermap)
11243         return d
11244 
11245hunk ./src/allmydata/test/test_mutable.py 1278
11246         return self._test_corrupt_all("enc_privkey", None, should_succeed=True)
11247 
11248 
11249+    def test_corrupt_all_encprivkey_late(self):
11250+        # this should work for the same reason as above, but we corrupt
11251+        # after the servermap update to exercise the error handling
11252+        # code.
11253+        # We need to remove the privkey from the node, or the retrieve
11254+        # process won't know to update it.
11255+        self._fn._privkey = None
11256+        return self._test_corrupt_all("enc_privkey",
11257+                                      None, # this shouldn't fail
11258+                                      should_succeed=True,
11259+                                      corrupt_early=False,
11260+                                      fetch_privkey=True)
11261+
11262+
11263     def test_corrupt_all_seqnum_late(self):
11264         # corrupting the seqnum between mapupdate and retrieve should result
11265         # in NotEnoughSharesError, since each share will look invalid
11266hunk ./src/allmydata/test/test_mutable.py 1298
11267         def _check(res):
11268             f = res[0]
11269             self.failUnless(f.check(NotEnoughSharesError))
11270-            self.failUnless("someone wrote to the data since we read the servermap" in str(f))
11271+            self.failUnless("uncoordinated write" in str(f))
11272         return self._test_corrupt_all(1, "ran out of peers",
11273                                       corrupt_early=False,
11274                                       failure_checker=_check)
11275hunk ./src/allmydata/test/test_mutable.py 1342
11276                             in str(servermap.problems[0]))
11277             ver = servermap.best_recoverable_version()
11278             r = Retrieve(self._fn, servermap, ver)
11279-            return r.download()
11280+            c = consumer.MemoryConsumer()
11281+            return r.download(c)
11282         d.addCallback(_do_retrieve)
11283hunk ./src/allmydata/test/test_mutable.py 1345
11284+        d.addCallback(lambda mc: "".join(mc.chunks))
11285         d.addCallback(lambda new_contents:
11286                       self.failUnlessEqual(new_contents, self.CONTENTS))
11287         return d
11288hunk ./src/allmydata/test/test_mutable.py 1350
11289 
11290-    def test_corrupt_some(self):
11291-        # corrupt the data of first five shares (so the servermap thinks
11292-        # they're good but retrieve marks them as bad), so that the
11293-        # MODE_READ set of 6 will be insufficient, forcing node.download to
11294-        # retry with more servers.
11295-        corrupt(None, self._storage, "share_data", range(5))
11296-        d = self.make_servermap()
11297+
11298+    def _test_corrupt_some(self, offset, mdmf=False):
11299+        if mdmf:
11300+            d = self.publish_mdmf()
11301+        else:
11302+            d = defer.succeed(None)
11303+        d.addCallback(lambda ignored:
11304+            corrupt(None, self._storage, offset, range(5)))
11305+        d.addCallback(lambda ignored:
11306+            self.make_servermap())
11307         def _do_retrieve(servermap):
11308             ver = servermap.best_recoverable_version()
11309             self.failUnless(ver)
11310hunk ./src/allmydata/test/test_mutable.py 1366
11311             return self._fn.download_best_version()
11312         d.addCallback(_do_retrieve)
11313         d.addCallback(lambda new_contents:
11314-                      self.failUnlessEqual(new_contents, self.CONTENTS))
11315+            self.failUnlessEqual(new_contents, self.CONTENTS))
11316         return d
11317 
11318hunk ./src/allmydata/test/test_mutable.py 1369
11319+
11320+    def test_corrupt_some(self):
11321+        # corrupt the data of first five shares (so the servermap thinks
11322+        # they're good but retrieve marks them as bad), so that the
11323+        # MODE_READ set of 6 will be insufficient, forcing node.download to
11324+        # retry with more servers.
11325+        return self._test_corrupt_some("share_data")
11326+
11327+
11328     def test_download_fails(self):
11329hunk ./src/allmydata/test/test_mutable.py 1379
11330-        corrupt(None, self._storage, "signature")
11331-        d = self.shouldFail(UnrecoverableFileError, "test_download_anyway",
11332+        d = corrupt(None, self._storage, "signature")
11333+        d.addCallback(lambda ignored:
11334+            self.shouldFail(UnrecoverableFileError, "test_download_anyway",
11335                             "no recoverable versions",
11336hunk ./src/allmydata/test/test_mutable.py 1383
11337-                            self._fn.download_best_version)
11338+                            self._fn.download_best_version))
11339         return d
11340 
11341 
11342hunk ./src/allmydata/test/test_mutable.py 1387
11343+
11344+    def test_corrupt_mdmf_block_hash_tree(self):
11345+        d = self.publish_mdmf()
11346+        d.addCallback(lambda ignored:
11347+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
11348+                                   "block hash tree failure",
11349+                                   corrupt_early=False,
11350+                                   should_succeed=False))
11351+        return d
11352+
11353+
11354+    def test_corrupt_mdmf_block_hash_tree_late(self):
11355+        d = self.publish_mdmf()
11356+        d.addCallback(lambda ignored:
11357+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
11358+                                   "block hash tree failure",
11359+                                   corrupt_early=True,
11360+                                   should_succeed=False))
11361+        return d
11362+
11363+
11364+    def test_corrupt_mdmf_share_data(self):
11365+        d = self.publish_mdmf()
11366+        d.addCallback(lambda ignored:
11367+            # TODO: Find out what the block size is and corrupt a
11368+            # specific block, rather than just guessing.
11369+            self._test_corrupt_all(("share_data", 12 * 40),
11370+                                    "block hash tree failure",
11371+                                    corrupt_early=True,
11372+                                    should_succeed=False))
11373+        return d
11374+
11375+
11376+    def test_corrupt_some_mdmf(self):
11377+        return self._test_corrupt_some(("share_data", 12 * 40),
11378+                                       mdmf=True)
11379+
11380+
11381 class CheckerMixin:
11382     def check_good(self, r, where):
11383         self.failUnless(r.is_healthy(), where)
11384hunk ./src/allmydata/test/test_mutable.py 1455
11385         d.addCallback(self.check_good, "test_check_good")
11386         return d
11387 
11388+    def test_check_mdmf_good(self):
11389+        d = self.publish_mdmf()
11390+        d.addCallback(lambda ignored:
11391+            self._fn.check(Monitor()))
11392+        d.addCallback(self.check_good, "test_check_mdmf_good")
11393+        return d
11394+
11395     def test_check_no_shares(self):
11396         for shares in self._storage._peers.values():
11397             shares.clear()
11398hunk ./src/allmydata/test/test_mutable.py 1469
11399         d.addCallback(self.check_bad, "test_check_no_shares")
11400         return d
11401 
11402+    def test_check_mdmf_no_shares(self):
11403+        d = self.publish_mdmf()
11404+        def _then(ignored):
11405+            for share in self._storage._peers.values():
11406+                share.clear()
11407+        d.addCallback(_then)
11408+        d.addCallback(lambda ignored:
11409+            self._fn.check(Monitor()))
11410+        d.addCallback(self.check_bad, "test_check_mdmf_no_shares")
11411+        return d
11412+
11413     def test_check_not_enough_shares(self):
11414         for shares in self._storage._peers.values():
11415             for shnum in shares.keys():
11416hunk ./src/allmydata/test/test_mutable.py 1489
11417         d.addCallback(self.check_bad, "test_check_not_enough_shares")
11418         return d
11419 
11420+    def test_check_mdmf_not_enough_shares(self):
11421+        d = self.publish_mdmf()
11422+        def _then(ignored):
11423+            for shares in self._storage._peers.values():
11424+                for shnum in shares.keys():
11425+                    if shnum > 0:
11426+                        del shares[shnum]
11427+        d.addCallback(_then)
11428+        d.addCallback(lambda ignored:
11429+            self._fn.check(Monitor()))
11430+        d.addCallback(self.check_bad, "test_check_mdmf_not_enougH_shares")
11431+        return d
11432+
11433+
11434     def test_check_all_bad_sig(self):
11435hunk ./src/allmydata/test/test_mutable.py 1504
11436-        corrupt(None, self._storage, 1) # bad sig
11437-        d = self._fn.check(Monitor())
11438+        d = corrupt(None, self._storage, 1) # bad sig
11439+        d.addCallback(lambda ignored:
11440+            self._fn.check(Monitor()))
11441         d.addCallback(self.check_bad, "test_check_all_bad_sig")
11442         return d
11443 
11444hunk ./src/allmydata/test/test_mutable.py 1510
11445+    def test_check_mdmf_all_bad_sig(self):
11446+        d = self.publish_mdmf()
11447+        d.addCallback(lambda ignored:
11448+            corrupt(None, self._storage, 1))
11449+        d.addCallback(lambda ignored:
11450+            self._fn.check(Monitor()))
11451+        d.addCallback(self.check_bad, "test_check_mdmf_all_bad_sig")
11452+        return d
11453+
11454     def test_check_all_bad_blocks(self):
11455hunk ./src/allmydata/test/test_mutable.py 1520
11456-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
11457+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
11458         # the Checker won't notice this.. it doesn't look at actual data
11459hunk ./src/allmydata/test/test_mutable.py 1522
11460-        d = self._fn.check(Monitor())
11461+        d.addCallback(lambda ignored:
11462+            self._fn.check(Monitor()))
11463         d.addCallback(self.check_good, "test_check_all_bad_blocks")
11464         return d
11465 
11466hunk ./src/allmydata/test/test_mutable.py 1527
11467+
11468+    def test_check_mdmf_all_bad_blocks(self):
11469+        d = self.publish_mdmf()
11470+        d.addCallback(lambda ignored:
11471+            corrupt(None, self._storage, "share_data"))
11472+        d.addCallback(lambda ignored:
11473+            self._fn.check(Monitor()))
11474+        d.addCallback(self.check_good, "test_check_mdmf_all_bad_blocks")
11475+        return d
11476+
11477     def test_verify_good(self):
11478         d = self._fn.check(Monitor(), verify=True)
11479         d.addCallback(self.check_good, "test_verify_good")
11480hunk ./src/allmydata/test/test_mutable.py 1541
11481         return d
11482+    test_verify_good.timeout = 15
11483 
11484     def test_verify_all_bad_sig(self):
11485hunk ./src/allmydata/test/test_mutable.py 1544
11486-        corrupt(None, self._storage, 1) # bad sig
11487-        d = self._fn.check(Monitor(), verify=True)
11488+        d = corrupt(None, self._storage, 1) # bad sig
11489+        d.addCallback(lambda ignored:
11490+            self._fn.check(Monitor(), verify=True))
11491         d.addCallback(self.check_bad, "test_verify_all_bad_sig")
11492         return d
11493 
11494hunk ./src/allmydata/test/test_mutable.py 1551
11495     def test_verify_one_bad_sig(self):
11496-        corrupt(None, self._storage, 1, [9]) # bad sig
11497-        d = self._fn.check(Monitor(), verify=True)
11498+        d = corrupt(None, self._storage, 1, [9]) # bad sig
11499+        d.addCallback(lambda ignored:
11500+            self._fn.check(Monitor(), verify=True))
11501         d.addCallback(self.check_bad, "test_verify_one_bad_sig")
11502         return d
11503 
11504hunk ./src/allmydata/test/test_mutable.py 1558
11505     def test_verify_one_bad_block(self):
11506-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
11507+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
11508         # the Verifier *will* notice this, since it examines every byte
11509hunk ./src/allmydata/test/test_mutable.py 1560
11510-        d = self._fn.check(Monitor(), verify=True)
11511+        d.addCallback(lambda ignored:
11512+            self._fn.check(Monitor(), verify=True))
11513         d.addCallback(self.check_bad, "test_verify_one_bad_block")
11514         d.addCallback(self.check_expected_failure,
11515                       CorruptShareError, "block hash tree failure",
11516hunk ./src/allmydata/test/test_mutable.py 1569
11517         return d
11518 
11519     def test_verify_one_bad_sharehash(self):
11520-        corrupt(None, self._storage, "share_hash_chain", [9], 5)
11521-        d = self._fn.check(Monitor(), verify=True)
11522+        d = corrupt(None, self._storage, "share_hash_chain", [9], 5)
11523+        d.addCallback(lambda ignored:
11524+            self._fn.check(Monitor(), verify=True))
11525         d.addCallback(self.check_bad, "test_verify_one_bad_sharehash")
11526         d.addCallback(self.check_expected_failure,
11527                       CorruptShareError, "corrupt hashes",
11528hunk ./src/allmydata/test/test_mutable.py 1579
11529         return d
11530 
11531     def test_verify_one_bad_encprivkey(self):
11532-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
11533-        d = self._fn.check(Monitor(), verify=True)
11534+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
11535+        d.addCallback(lambda ignored:
11536+            self._fn.check(Monitor(), verify=True))
11537         d.addCallback(self.check_bad, "test_verify_one_bad_encprivkey")
11538         d.addCallback(self.check_expected_failure,
11539                       CorruptShareError, "invalid privkey",
11540hunk ./src/allmydata/test/test_mutable.py 1589
11541         return d
11542 
11543     def test_verify_one_bad_encprivkey_uncheckable(self):
11544-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
11545+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
11546         readonly_fn = self._fn.get_readonly()
11547         # a read-only node has no way to validate the privkey
11548hunk ./src/allmydata/test/test_mutable.py 1592
11549-        d = readonly_fn.check(Monitor(), verify=True)
11550+        d.addCallback(lambda ignored:
11551+            readonly_fn.check(Monitor(), verify=True))
11552         d.addCallback(self.check_good,
11553                       "test_verify_one_bad_encprivkey_uncheckable")
11554         return d
11555hunk ./src/allmydata/test/test_mutable.py 1598
11556 
11557+
11558+    def test_verify_mdmf_good(self):
11559+        d = self.publish_mdmf()
11560+        d.addCallback(lambda ignored:
11561+            self._fn.check(Monitor(), verify=True))
11562+        d.addCallback(self.check_good, "test_verify_mdmf_good")
11563+        return d
11564+
11565+
11566+    def test_verify_mdmf_one_bad_block(self):
11567+        d = self.publish_mdmf()
11568+        d.addCallback(lambda ignored:
11569+            corrupt(None, self._storage, "share_data", [1]))
11570+        d.addCallback(lambda ignored:
11571+            self._fn.check(Monitor(), verify=True))
11572+        # We should find one bad block here
11573+        d.addCallback(self.check_bad, "test_verify_mdmf_one_bad_block")
11574+        d.addCallback(self.check_expected_failure,
11575+                      CorruptShareError, "block hash tree failure",
11576+                      "test_verify_mdmf_one_bad_block")
11577+        return d
11578+
11579+
11580+    def test_verify_mdmf_bad_encprivkey(self):
11581+        d = self.publish_mdmf()
11582+        d.addCallback(lambda ignored:
11583+            corrupt(None, self._storage, "enc_privkey", [1]))
11584+        d.addCallback(lambda ignored:
11585+            self._fn.check(Monitor(), verify=True))
11586+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
11587+        d.addCallback(self.check_expected_failure,
11588+                      CorruptShareError, "privkey",
11589+                      "test_verify_mdmf_bad_encprivkey")
11590+        return d
11591+
11592+
11593+    def test_verify_mdmf_bad_sig(self):
11594+        d = self.publish_mdmf()
11595+        d.addCallback(lambda ignored:
11596+            corrupt(None, self._storage, 1, [1]))
11597+        d.addCallback(lambda ignored:
11598+            self._fn.check(Monitor(), verify=True))
11599+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_sig")
11600+        return d
11601+
11602+
11603+    def test_verify_mdmf_bad_encprivkey_uncheckable(self):
11604+        d = self.publish_mdmf()
11605+        d.addCallback(lambda ignored:
11606+            corrupt(None, self._storage, "enc_privkey", [1]))
11607+        d.addCallback(lambda ignored:
11608+            self._fn.get_readonly())
11609+        d.addCallback(lambda fn:
11610+            fn.check(Monitor(), verify=True))
11611+        d.addCallback(self.check_good,
11612+                      "test_verify_mdmf_bad_encprivkey_uncheckable")
11613+        return d
11614+
11615+
11616 class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
11617 
11618     def get_shares(self, s):
11619hunk ./src/allmydata/test/test_mutable.py 1722
11620         current_shares = self.old_shares[-1]
11621         self.failUnlessEqual(old_shares, current_shares)
11622 
11623+
11624     def test_unrepairable_0shares(self):
11625         d = self.publish_one()
11626         def _delete_all_shares(ign):
11627hunk ./src/allmydata/test/test_mutable.py 1737
11628         d.addCallback(_check)
11629         return d
11630 
11631+    def test_mdmf_unrepairable_0shares(self):
11632+        d = self.publish_mdmf()
11633+        def _delete_all_shares(ign):
11634+            shares = self._storage._peers
11635+            for peerid in shares:
11636+                shares[peerid] = {}
11637+        d.addCallback(_delete_all_shares)
11638+        d.addCallback(lambda ign: self._fn.check(Monitor()))
11639+        d.addCallback(lambda check_results: self._fn.repair(check_results))
11640+        d.addCallback(lambda crr: self.failIf(crr.get_successful()))
11641+        return d
11642+
11643+
11644     def test_unrepairable_1share(self):
11645         d = self.publish_one()
11646         def _delete_all_shares(ign):
11647hunk ./src/allmydata/test/test_mutable.py 1766
11648         d.addCallback(_check)
11649         return d
11650 
11651+    def test_mdmf_unrepairable_1share(self):
11652+        d = self.publish_mdmf()
11653+        def _delete_all_shares(ign):
11654+            shares = self._storage._peers
11655+            for peerid in shares:
11656+                for shnum in list(shares[peerid]):
11657+                    if shnum > 0:
11658+                        del shares[peerid][shnum]
11659+        d.addCallback(_delete_all_shares)
11660+        d.addCallback(lambda ign: self._fn.check(Monitor()))
11661+        d.addCallback(lambda check_results: self._fn.repair(check_results))
11662+        def _check(crr):
11663+            self.failUnlessEqual(crr.get_successful(), False)
11664+        d.addCallback(_check)
11665+        return d
11666+
11667+    def test_repairable_5shares(self):
11668+        d = self.publish_mdmf()
11669+        def _delete_all_shares(ign):
11670+            shares = self._storage._peers
11671+            for peerid in shares:
11672+                for shnum in list(shares[peerid]):
11673+                    if shnum > 4:
11674+                        del shares[peerid][shnum]
11675+        d.addCallback(_delete_all_shares)
11676+        d.addCallback(lambda ign: self._fn.check(Monitor()))
11677+        d.addCallback(lambda check_results: self._fn.repair(check_results))
11678+        def _check(crr):
11679+            self.failUnlessEqual(crr.get_successful(), True)
11680+        d.addCallback(_check)
11681+        return d
11682+
11683+    def test_mdmf_repairable_5shares(self):
11684+        d = self.publish_mdmf()
11685+        def _delete_some_shares(ign):
11686+            shares = self._storage._peers
11687+            for peerid in shares:
11688+                for shnum in list(shares[peerid]):
11689+                    if shnum > 5:
11690+                        del shares[peerid][shnum]
11691+        d.addCallback(_delete_some_shares)
11692+        d.addCallback(lambda ign: self._fn.check(Monitor()))
11693+        def _check(cr):
11694+            self.failIf(cr.is_healthy())
11695+            self.failUnless(cr.is_recoverable())
11696+            return cr
11697+        d.addCallback(_check)
11698+        d.addCallback(lambda check_results: self._fn.repair(check_results))
11699+        def _check1(crr):
11700+            self.failUnlessEqual(crr.get_successful(), True)
11701+        d.addCallback(_check1)
11702+        return d
11703+
11704+
11705     def test_merge(self):
11706         self.old_shares = []
11707         d = self.publish_multiple()
11708hunk ./src/allmydata/test/test_mutable.py 1934
11709 class MultipleEncodings(unittest.TestCase):
11710     def setUp(self):
11711         self.CONTENTS = "New contents go here"
11712+        self.uploadable = MutableData(self.CONTENTS)
11713         self._storage = FakeStorage()
11714         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
11715         self._storage_broker = self._nodemaker.storage_broker
11716hunk ./src/allmydata/test/test_mutable.py 1938
11717-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
11718+        d = self._nodemaker.create_mutable_file(self.uploadable)
11719         def _created(node):
11720             self._fn = node
11721         d.addCallback(_created)
11722hunk ./src/allmydata/test/test_mutable.py 1944
11723         return d
11724 
11725-    def _encode(self, k, n, data):
11726+    def _encode(self, k, n, data, version=SDMF_VERSION):
11727         # encode 'data' into a peerid->shares dict.
11728 
11729         fn = self._fn
11730hunk ./src/allmydata/test/test_mutable.py 1960
11731         # and set the encoding parameters to something completely different
11732         fn2._required_shares = k
11733         fn2._total_shares = n
11734+        # Normally a servermap update would occur before a publish.
11735+        # Here, it doesn't, so we have to do it ourselves.
11736+        fn2.set_version(version)
11737 
11738         s = self._storage
11739         s._peers = {} # clear existing storage
11740hunk ./src/allmydata/test/test_mutable.py 1967
11741         p2 = Publish(fn2, self._storage_broker, None)
11742-        d = p2.publish(data)
11743+        uploadable = MutableData(data)
11744+        d = p2.publish(uploadable)
11745         def _published(res):
11746             shares = s._peers
11747             s._peers = {}
11748hunk ./src/allmydata/test/test_mutable.py 2235
11749         self.basedir = "mutable/Problems/test_publish_surprise"
11750         self.set_up_grid()
11751         nm = self.g.clients[0].nodemaker
11752-        d = nm.create_mutable_file("contents 1")
11753+        d = nm.create_mutable_file(MutableData("contents 1"))
11754         def _created(n):
11755             d = defer.succeed(None)
11756             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
11757hunk ./src/allmydata/test/test_mutable.py 2245
11758             d.addCallback(_got_smap1)
11759             # then modify the file, leaving the old map untouched
11760             d.addCallback(lambda res: log.msg("starting winning write"))
11761-            d.addCallback(lambda res: n.overwrite("contents 2"))
11762+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
11763             # now attempt to modify the file with the old servermap. This
11764             # will look just like an uncoordinated write, in which every
11765             # single share got updated between our mapupdate and our publish
11766hunk ./src/allmydata/test/test_mutable.py 2254
11767                           self.shouldFail(UncoordinatedWriteError,
11768                                           "test_publish_surprise", None,
11769                                           n.upload,
11770-                                          "contents 2a", self.old_map))
11771+                                          MutableData("contents 2a"), self.old_map))
11772             return d
11773         d.addCallback(_created)
11774         return d
11775hunk ./src/allmydata/test/test_mutable.py 2263
11776         self.basedir = "mutable/Problems/test_retrieve_surprise"
11777         self.set_up_grid()
11778         nm = self.g.clients[0].nodemaker
11779-        d = nm.create_mutable_file("contents 1")
11780+        d = nm.create_mutable_file(MutableData("contents 1"))
11781         def _created(n):
11782             d = defer.succeed(None)
11783             d.addCallback(lambda res: n.get_servermap(MODE_READ))
11784hunk ./src/allmydata/test/test_mutable.py 2273
11785             d.addCallback(_got_smap1)
11786             # then modify the file, leaving the old map untouched
11787             d.addCallback(lambda res: log.msg("starting winning write"))
11788-            d.addCallback(lambda res: n.overwrite("contents 2"))
11789+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
11790             # now attempt to retrieve the old version with the old servermap.
11791             # This will look like someone has changed the file since we
11792             # updated the servermap.
11793hunk ./src/allmydata/test/test_mutable.py 2282
11794             d.addCallback(lambda res:
11795                           self.shouldFail(NotEnoughSharesError,
11796                                           "test_retrieve_surprise",
11797-                                          "ran out of peers: have 0 shares (k=3)",
11798+                                          "ran out of peers: have 0 of 1",
11799                                           n.download_version,
11800                                           self.old_map,
11801                                           self.old_map.best_recoverable_version(),
11802hunk ./src/allmydata/test/test_mutable.py 2291
11803         d.addCallback(_created)
11804         return d
11805 
11806+
11807     def test_unexpected_shares(self):
11808         # upload the file, take a servermap, shut down one of the servers,
11809         # upload it again (causing shares to appear on a new server), then
11810hunk ./src/allmydata/test/test_mutable.py 2301
11811         self.basedir = "mutable/Problems/test_unexpected_shares"
11812         self.set_up_grid()
11813         nm = self.g.clients[0].nodemaker
11814-        d = nm.create_mutable_file("contents 1")
11815+        d = nm.create_mutable_file(MutableData("contents 1"))
11816         def _created(n):
11817             d = defer.succeed(None)
11818             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
11819hunk ./src/allmydata/test/test_mutable.py 2313
11820                 self.g.remove_server(peer0)
11821                 # then modify the file, leaving the old map untouched
11822                 log.msg("starting winning write")
11823-                return n.overwrite("contents 2")
11824+                return n.overwrite(MutableData("contents 2"))
11825             d.addCallback(_got_smap1)
11826             # now attempt to modify the file with the old servermap. This
11827             # will look just like an uncoordinated write, in which every
11828hunk ./src/allmydata/test/test_mutable.py 2323
11829                           self.shouldFail(UncoordinatedWriteError,
11830                                           "test_surprise", None,
11831                                           n.upload,
11832-                                          "contents 2a", self.old_map))
11833+                                          MutableData("contents 2a"), self.old_map))
11834             return d
11835         d.addCallback(_created)
11836         return d
11837hunk ./src/allmydata/test/test_mutable.py 2327
11838+    test_unexpected_shares.timeout = 15
11839 
11840     def test_bad_server(self):
11841         # Break one server, then create the file: the initial publish should
11842hunk ./src/allmydata/test/test_mutable.py 2361
11843         d.addCallback(_break_peer0)
11844         # now "create" the file, using the pre-established key, and let the
11845         # initial publish finally happen
11846-        d.addCallback(lambda res: nm.create_mutable_file("contents 1"))
11847+        d.addCallback(lambda res: nm.create_mutable_file(MutableData("contents 1")))
11848         # that ought to work
11849         def _got_node(n):
11850             d = n.download_best_version()
11851hunk ./src/allmydata/test/test_mutable.py 2370
11852             def _break_peer1(res):
11853                 self.g.break_server(self.server1.get_serverid())
11854             d.addCallback(_break_peer1)
11855-            d.addCallback(lambda res: n.overwrite("contents 2"))
11856+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
11857             # that ought to work too
11858             d.addCallback(lambda res: n.download_best_version())
11859             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
11860hunk ./src/allmydata/test/test_mutable.py 2402
11861         peerids = [s.get_serverid() for s in sb.get_connected_servers()]
11862         self.g.break_server(peerids[0])
11863 
11864-        d = nm.create_mutable_file("contents 1")
11865+        d = nm.create_mutable_file(MutableData("contents 1"))
11866         def _created(n):
11867             d = n.download_best_version()
11868             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
11869hunk ./src/allmydata/test/test_mutable.py 2410
11870             def _break_second_server(res):
11871                 self.g.break_server(peerids[1])
11872             d.addCallback(_break_second_server)
11873-            d.addCallback(lambda res: n.overwrite("contents 2"))
11874+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
11875             # that ought to work too
11876             d.addCallback(lambda res: n.download_best_version())
11877             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
11878hunk ./src/allmydata/test/test_mutable.py 2429
11879         d = self.shouldFail(NotEnoughServersError,
11880                             "test_publish_all_servers_bad",
11881                             "Ran out of non-bad servers",
11882-                            nm.create_mutable_file, "contents")
11883+                            nm.create_mutable_file, MutableData("contents"))
11884         return d
11885 
11886     def test_publish_no_servers(self):
11887hunk ./src/allmydata/test/test_mutable.py 2441
11888         d = self.shouldFail(NotEnoughServersError,
11889                             "test_publish_no_servers",
11890                             "Ran out of non-bad servers",
11891-                            nm.create_mutable_file, "contents")
11892+                            nm.create_mutable_file, MutableData("contents"))
11893         return d
11894     test_publish_no_servers.timeout = 30
11895 
11896hunk ./src/allmydata/test/test_mutable.py 2459
11897         # we need some contents that are large enough to push the privkey out
11898         # of the early part of the file
11899         LARGE = "These are Larger contents" * 2000 # about 50KB
11900-        d = nm.create_mutable_file(LARGE)
11901+        LARGE_uploadable = MutableData(LARGE)
11902+        d = nm.create_mutable_file(LARGE_uploadable)
11903         def _created(n):
11904             self.uri = n.get_uri()
11905             self.n2 = nm.create_from_cap(self.uri)
11906hunk ./src/allmydata/test/test_mutable.py 2495
11907         self.basedir = "mutable/Problems/test_privkey_query_missing"
11908         self.set_up_grid(num_servers=20)
11909         nm = self.g.clients[0].nodemaker
11910-        LARGE = "These are Larger contents" * 2000 # about 50KB
11911+        LARGE = "These are Larger contents" * 2000 # about 50KiB
11912+        LARGE_uploadable = MutableData(LARGE)
11913         nm._node_cache = DevNullDictionary() # disable the nodecache
11914 
11915hunk ./src/allmydata/test/test_mutable.py 2499
11916-        d = nm.create_mutable_file(LARGE)
11917+        d = nm.create_mutable_file(LARGE_uploadable)
11918         def _created(n):
11919             self.uri = n.get_uri()
11920             self.n2 = nm.create_from_cap(self.uri)
11921hunk ./src/allmydata/test/test_mutable.py 2509
11922         d.addCallback(_created)
11923         d.addCallback(lambda res: self.n2.get_servermap(MODE_WRITE))
11924         return d
11925+
11926+
11927+    def test_block_and_hash_query_error(self):
11928+        # This tests for what happens when a query to a remote server
11929+        # fails in either the hash validation step or the block getting
11930+        # step (because of batching, this is the same actual query).
11931+        # We need to have the storage server persist up until the point
11932+        # that its prefix is validated, then suddenly die. This
11933+        # exercises some exception handling code in Retrieve.
11934+        self.basedir = "mutable/Problems/test_block_and_hash_query_error"
11935+        self.set_up_grid(num_servers=20)
11936+        nm = self.g.clients[0].nodemaker
11937+        CONTENTS = "contents" * 2000
11938+        CONTENTS_uploadable = MutableData(CONTENTS)
11939+        d = nm.create_mutable_file(CONTENTS_uploadable)
11940+        def _created(node):
11941+            self._node = node
11942+        d.addCallback(_created)
11943+        d.addCallback(lambda ignored:
11944+            self._node.get_servermap(MODE_READ))
11945+        def _then(servermap):
11946+            # we have our servermap. Now we set up the servers like the
11947+            # tests above -- the first one that gets a read call should
11948+            # start throwing errors, but only after returning its prefix
11949+            # for validation. Since we'll download without fetching the
11950+            # private key, the next query to the remote server will be
11951+            # for either a block and salt or for hashes, either of which
11952+            # will exercise the error handling code.
11953+            killer = FirstServerGetsKilled()
11954+            for (serverid, ss) in nm.storage_broker.get_all_servers():
11955+                ss.post_call_notifier = killer.notify
11956+            ver = servermap.best_recoverable_version()
11957+            assert ver
11958+            return self._node.download_version(servermap, ver)
11959+        d.addCallback(_then)
11960+        d.addCallback(lambda data:
11961+            self.failUnlessEqual(data, CONTENTS))
11962+        return d
11963+
11964+
11965+class FileHandle(unittest.TestCase):
11966+    def setUp(self):
11967+        self.test_data = "Test Data" * 50000
11968+        self.sio = StringIO(self.test_data)
11969+        self.uploadable = MutableFileHandle(self.sio)
11970+
11971+
11972+    def test_filehandle_read(self):
11973+        self.basedir = "mutable/FileHandle/test_filehandle_read"
11974+        chunk_size = 10
11975+        for i in xrange(0, len(self.test_data), chunk_size):
11976+            data = self.uploadable.read(chunk_size)
11977+            data = "".join(data)
11978+            start = i
11979+            end = i + chunk_size
11980+            self.failUnlessEqual(data, self.test_data[start:end])
11981+
11982+
11983+    def test_filehandle_get_size(self):
11984+        self.basedir = "mutable/FileHandle/test_filehandle_get_size"
11985+        actual_size = len(self.test_data)
11986+        size = self.uploadable.get_size()
11987+        self.failUnlessEqual(size, actual_size)
11988+
11989+
11990+    def test_filehandle_get_size_out_of_order(self):
11991+        # We should be able to call get_size whenever we want without
11992+        # disturbing the location of the seek pointer.
11993+        chunk_size = 100
11994+        data = self.uploadable.read(chunk_size)
11995+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
11996+
11997+        # Now get the size.
11998+        size = self.uploadable.get_size()
11999+        self.failUnlessEqual(size, len(self.test_data))
12000+
12001+        # Now get more data. We should be right where we left off.
12002+        more_data = self.uploadable.read(chunk_size)
12003+        start = chunk_size
12004+        end = chunk_size * 2
12005+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
12006+
12007+
12008+    def test_filehandle_file(self):
12009+        # Make sure that the MutableFileHandle works on a file as well
12010+        # as a StringIO object, since in some cases it will be asked to
12011+        # deal with files.
12012+        self.basedir = self.mktemp()
12013+        # necessary? What am I doing wrong here?
12014+        os.mkdir(self.basedir)
12015+        f_path = os.path.join(self.basedir, "test_file")
12016+        f = open(f_path, "w")
12017+        f.write(self.test_data)
12018+        f.close()
12019+        f = open(f_path, "r")
12020+
12021+        uploadable = MutableFileHandle(f)
12022+
12023+        data = uploadable.read(len(self.test_data))
12024+        self.failUnlessEqual("".join(data), self.test_data)
12025+        size = uploadable.get_size()
12026+        self.failUnlessEqual(size, len(self.test_data))
12027+
12028+
12029+    def test_close(self):
12030+        # Make sure that the MutableFileHandle closes its handle when
12031+        # told to do so.
12032+        self.uploadable.close()
12033+        self.failUnless(self.sio.closed)
12034+
12035+
12036+class DataHandle(unittest.TestCase):
12037+    def setUp(self):
12038+        self.test_data = "Test Data" * 50000
12039+        self.uploadable = MutableData(self.test_data)
12040+
12041+
12042+    def test_datahandle_read(self):
12043+        chunk_size = 10
12044+        for i in xrange(0, len(self.test_data), chunk_size):
12045+            data = self.uploadable.read(chunk_size)
12046+            data = "".join(data)
12047+            start = i
12048+            end = i + chunk_size
12049+            self.failUnlessEqual(data, self.test_data[start:end])
12050+
12051+
12052+    def test_datahandle_get_size(self):
12053+        actual_size = len(self.test_data)
12054+        size = self.uploadable.get_size()
12055+        self.failUnlessEqual(size, actual_size)
12056+
12057+
12058+    def test_datahandle_get_size_out_of_order(self):
12059+        # We should be able to call get_size whenever we want without
12060+        # disturbing the location of the seek pointer.
12061+        chunk_size = 100
12062+        data = self.uploadable.read(chunk_size)
12063+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
12064+
12065+        # Now get the size.
12066+        size = self.uploadable.get_size()
12067+        self.failUnlessEqual(size, len(self.test_data))
12068+
12069+        # Now get more data. We should be right where we left off.
12070+        more_data = self.uploadable.read(chunk_size)
12071+        start = chunk_size
12072+        end = chunk_size * 2
12073+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
12074+
12075+
12076+class Version(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin, \
12077+              PublishMixin):
12078+    def setUp(self):
12079+        GridTestMixin.setUp(self)
12080+        self.basedir = self.mktemp()
12081+        self.set_up_grid()
12082+        self.c = self.g.clients[0]
12083+        self.nm = self.c.nodemaker
12084+        self.data = "test data" * 100000 # about 900 KiB; MDMF
12085+        self.small_data = "test data" * 10 # about 90 B; SDMF
12086+        return self.do_upload()
12087+
12088+
12089+    def do_upload(self):
12090+        d1 = self.nm.create_mutable_file(MutableData(self.data),
12091+                                         version=MDMF_VERSION)
12092+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
12093+        dl = gatherResults([d1, d2])
12094+        def _then((n1, n2)):
12095+            assert isinstance(n1, MutableFileNode)
12096+            assert isinstance(n2, MutableFileNode)
12097+
12098+            self.mdmf_node = n1
12099+            self.sdmf_node = n2
12100+        dl.addCallback(_then)
12101+        return dl
12102+
12103+
12104+    def test_get_readonly_mutable_version(self):
12105+        # Attempting to get a mutable version of a mutable file from a
12106+        # filenode initialized with a readcap should return a readonly
12107+        # version of that same node.
12108+        ro = self.mdmf_node.get_readonly()
12109+        d = ro.get_best_mutable_version()
12110+        d.addCallback(lambda version:
12111+            self.failUnless(version.is_readonly()))
12112+        d.addCallback(lambda ignored:
12113+            self.sdmf_node.get_readonly())
12114+        d.addCallback(lambda version:
12115+            self.failUnless(version.is_readonly()))
12116+        return d
12117+
12118+
12119+    def test_get_sequence_number(self):
12120+        d = self.mdmf_node.get_best_readable_version()
12121+        d.addCallback(lambda bv:
12122+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12123+        d.addCallback(lambda ignored:
12124+            self.sdmf_node.get_best_readable_version())
12125+        d.addCallback(lambda bv:
12126+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12127+        # Now update. The sequence number in both cases should be 1 in
12128+        # both cases.
12129+        def _do_update(ignored):
12130+            new_data = MutableData("foo bar baz" * 100000)
12131+            new_small_data = MutableData("foo bar baz" * 10)
12132+            d1 = self.mdmf_node.overwrite(new_data)
12133+            d2 = self.sdmf_node.overwrite(new_small_data)
12134+            dl = gatherResults([d1, d2])
12135+            return dl
12136+        d.addCallback(_do_update)
12137+        d.addCallback(lambda ignored:
12138+            self.mdmf_node.get_best_readable_version())
12139+        d.addCallback(lambda bv:
12140+            self.failUnlessEqual(bv.get_sequence_number(), 2))
12141+        d.addCallback(lambda ignored:
12142+            self.sdmf_node.get_best_readable_version())
12143+        d.addCallback(lambda bv:
12144+            self.failUnlessEqual(bv.get_sequence_number(), 2))
12145+        return d
12146+
12147+
12148+    def test_get_writekey(self):
12149+        d = self.mdmf_node.get_best_mutable_version()
12150+        d.addCallback(lambda bv:
12151+            self.failUnlessEqual(bv.get_writekey(),
12152+                                 self.mdmf_node.get_writekey()))
12153+        d.addCallback(lambda ignored:
12154+            self.sdmf_node.get_best_mutable_version())
12155+        d.addCallback(lambda bv:
12156+            self.failUnlessEqual(bv.get_writekey(),
12157+                                 self.sdmf_node.get_writekey()))
12158+        return d
12159+
12160+
12161+    def test_get_storage_index(self):
12162+        d = self.mdmf_node.get_best_mutable_version()
12163+        d.addCallback(lambda bv:
12164+            self.failUnlessEqual(bv.get_storage_index(),
12165+                                 self.mdmf_node.get_storage_index()))
12166+        d.addCallback(lambda ignored:
12167+            self.sdmf_node.get_best_mutable_version())
12168+        d.addCallback(lambda bv:
12169+            self.failUnlessEqual(bv.get_storage_index(),
12170+                                 self.sdmf_node.get_storage_index()))
12171+        return d
12172+
12173+
12174+    def test_get_readonly_version(self):
12175+        d = self.mdmf_node.get_best_readable_version()
12176+        d.addCallback(lambda bv:
12177+            self.failUnless(bv.is_readonly()))
12178+        d.addCallback(lambda ignored:
12179+            self.sdmf_node.get_best_readable_version())
12180+        d.addCallback(lambda bv:
12181+            self.failUnless(bv.is_readonly()))
12182+        return d
12183+
12184+
12185+    def test_get_mutable_version(self):
12186+        d = self.mdmf_node.get_best_mutable_version()
12187+        d.addCallback(lambda bv:
12188+            self.failIf(bv.is_readonly()))
12189+        d.addCallback(lambda ignored:
12190+            self.sdmf_node.get_best_mutable_version())
12191+        d.addCallback(lambda bv:
12192+            self.failIf(bv.is_readonly()))
12193+        return d
12194+
12195+
12196+    def test_toplevel_overwrite(self):
12197+        new_data = MutableData("foo bar baz" * 100000)
12198+        new_small_data = MutableData("foo bar baz" * 10)
12199+        d = self.mdmf_node.overwrite(new_data)
12200+        d.addCallback(lambda ignored:
12201+            self.mdmf_node.download_best_version())
12202+        d.addCallback(lambda data:
12203+            self.failUnlessEqual(data, "foo bar baz" * 100000))
12204+        d.addCallback(lambda ignored:
12205+            self.sdmf_node.overwrite(new_small_data))
12206+        d.addCallback(lambda ignored:
12207+            self.sdmf_node.download_best_version())
12208+        d.addCallback(lambda data:
12209+            self.failUnlessEqual(data, "foo bar baz" * 10))
12210+        return d
12211+
12212+
12213+    def test_toplevel_modify(self):
12214+        def modifier(old_contents, servermap, first_time):
12215+            return old_contents + "modified"
12216+        d = self.mdmf_node.modify(modifier)
12217+        d.addCallback(lambda ignored:
12218+            self.mdmf_node.download_best_version())
12219+        d.addCallback(lambda data:
12220+            self.failUnlessIn("modified", data))
12221+        d.addCallback(lambda ignored:
12222+            self.sdmf_node.modify(modifier))
12223+        d.addCallback(lambda ignored:
12224+            self.sdmf_node.download_best_version())
12225+        d.addCallback(lambda data:
12226+            self.failUnlessIn("modified", data))
12227+        return d
12228+
12229+
12230+    def test_version_modify(self):
12231+        # TODO: When we can publish multiple versions, alter this test
12232+        # to modify a version other than the best usable version, then
12233+        # test to see that the best recoverable version is that.
12234+        def modifier(old_contents, servermap, first_time):
12235+            return old_contents + "modified"
12236+        d = self.mdmf_node.modify(modifier)
12237+        d.addCallback(lambda ignored:
12238+            self.mdmf_node.download_best_version())
12239+        d.addCallback(lambda data:
12240+            self.failUnlessIn("modified", data))
12241+        d.addCallback(lambda ignored:
12242+            self.sdmf_node.modify(modifier))
12243+        d.addCallback(lambda ignored:
12244+            self.sdmf_node.download_best_version())
12245+        d.addCallback(lambda data:
12246+            self.failUnlessIn("modified", data))
12247+        return d
12248+
12249+
12250+    def test_download_version(self):
12251+        d = self.publish_multiple()
12252+        # We want to have two recoverable versions on the grid.
12253+        d.addCallback(lambda res:
12254+                      self._set_versions({0:0,2:0,4:0,6:0,8:0,
12255+                                          1:1,3:1,5:1,7:1,9:1}))
12256+        # Now try to download each version. We should get the plaintext
12257+        # associated with that version.
12258+        d.addCallback(lambda ignored:
12259+            self._fn.get_servermap(mode=MODE_READ))
12260+        def _got_servermap(smap):
12261+            versions = smap.recoverable_versions()
12262+            assert len(versions) == 2
12263+
12264+            self.servermap = smap
12265+            self.version1, self.version2 = versions
12266+            assert self.version1 != self.version2
12267+
12268+            self.version1_seqnum = self.version1[0]
12269+            self.version2_seqnum = self.version2[0]
12270+            self.version1_index = self.version1_seqnum - 1
12271+            self.version2_index = self.version2_seqnum - 1
12272+
12273+        d.addCallback(_got_servermap)
12274+        d.addCallback(lambda ignored:
12275+            self._fn.download_version(self.servermap, self.version1))
12276+        d.addCallback(lambda results:
12277+            self.failUnlessEqual(self.CONTENTS[self.version1_index],
12278+                                 results))
12279+        d.addCallback(lambda ignored:
12280+            self._fn.download_version(self.servermap, self.version2))
12281+        d.addCallback(lambda results:
12282+            self.failUnlessEqual(self.CONTENTS[self.version2_index],
12283+                                 results))
12284+        return d
12285+
12286+
12287+    def test_download_nonexistent_version(self):
12288+        d = self.mdmf_node.get_servermap(mode=MODE_WRITE)
12289+        def _set_servermap(servermap):
12290+            self.servermap = servermap
12291+        d.addCallback(_set_servermap)
12292+        d.addCallback(lambda ignored:
12293+           self.shouldFail(UnrecoverableFileError, "nonexistent version",
12294+                           None,
12295+                           self.mdmf_node.download_version, self.servermap,
12296+                           "not a version"))
12297+        return d
12298+
12299+
12300+    def test_partial_read(self):
12301+        # read only a few bytes at a time, and see that the results are
12302+        # what we expect.
12303+        d = self.mdmf_node.get_best_readable_version()
12304+        def _read_data(version):
12305+            c = consumer.MemoryConsumer()
12306+            d2 = defer.succeed(None)
12307+            for i in xrange(0, len(self.data), 10000):
12308+                d2.addCallback(lambda ignored, i=i: version.read(c, i, 10000))
12309+            d2.addCallback(lambda ignored:
12310+                self.failUnlessEqual(self.data, "".join(c.chunks)))
12311+            return d2
12312+        d.addCallback(_read_data)
12313+        return d
12314+
12315+
12316+    def test_read(self):
12317+        d = self.mdmf_node.get_best_readable_version()
12318+        def _read_data(version):
12319+            c = consumer.MemoryConsumer()
12320+            d2 = defer.succeed(None)
12321+            d2.addCallback(lambda ignored: version.read(c))
12322+            d2.addCallback(lambda ignored:
12323+                self.failUnlessEqual("".join(c.chunks), self.data))
12324+            return d2
12325+        d.addCallback(_read_data)
12326+        return d
12327+
12328+
12329+    def test_download_best_version(self):
12330+        d = self.mdmf_node.download_best_version()
12331+        d.addCallback(lambda data:
12332+            self.failUnlessEqual(data, self.data))
12333+        d.addCallback(lambda ignored:
12334+            self.sdmf_node.download_best_version())
12335+        d.addCallback(lambda data:
12336+            self.failUnlessEqual(data, self.small_data))
12337+        return d
12338+
12339+
12340+class Update(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
12341+    def setUp(self):
12342+        GridTestMixin.setUp(self)
12343+        self.basedir = self.mktemp()
12344+        self.set_up_grid()
12345+        self.c = self.g.clients[0]
12346+        self.nm = self.c.nodemaker
12347+        self.data = "test data" * 100000 # about 900 KiB; MDMF
12348+        self.small_data = "test data" * 10 # about 90 B; SDMF
12349+        return self.do_upload()
12350+
12351+
12352+    def do_upload(self):
12353+        d1 = self.nm.create_mutable_file(MutableData(self.data),
12354+                                         version=MDMF_VERSION)
12355+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
12356+        dl = gatherResults([d1, d2])
12357+        def _then((n1, n2)):
12358+            assert isinstance(n1, MutableFileNode)
12359+            assert isinstance(n2, MutableFileNode)
12360+
12361+            self.mdmf_node = n1
12362+            self.sdmf_node = n2
12363+        dl.addCallback(_then)
12364+        return dl
12365+
12366+
12367+    def test_append(self):
12368+        # We should be able to append data to the middle of a mutable
12369+        # file and get what we expect.
12370+        new_data = self.data + "appended"
12371+        d = self.mdmf_node.get_best_mutable_version()
12372+        d.addCallback(lambda mv:
12373+            mv.update(MutableData("appended"), len(self.data)))
12374+        d.addCallback(lambda ignored:
12375+            self.mdmf_node.download_best_version())
12376+        d.addCallback(lambda results:
12377+            self.failUnlessEqual(results, new_data))
12378+        return d
12379+    test_append.timeout = 15
12380+
12381+
12382+    def test_replace(self):
12383+        # We should be able to replace data in the middle of a mutable
12384+        # file and get what we expect back.
12385+        new_data = self.data[:100]
12386+        new_data += "appended"
12387+        new_data += self.data[108:]
12388+        d = self.mdmf_node.get_best_mutable_version()
12389+        d.addCallback(lambda mv:
12390+            mv.update(MutableData("appended"), 100))
12391+        d.addCallback(lambda ignored:
12392+            self.mdmf_node.download_best_version())
12393+        d.addCallback(lambda results:
12394+            self.failUnlessEqual(results, new_data))
12395+        return d
12396+
12397+
12398+    def test_replace_and_extend(self):
12399+        # We should be able to replace data in the middle of a mutable
12400+        # file and extend that mutable file and get what we expect.
12401+        new_data = self.data[:100]
12402+        new_data += "modified " * 100000
12403+        d = self.mdmf_node.get_best_mutable_version()
12404+        d.addCallback(lambda mv:
12405+            mv.update(MutableData("modified " * 100000), 100))
12406+        d.addCallback(lambda ignored:
12407+            self.mdmf_node.download_best_version())
12408+        d.addCallback(lambda results:
12409+            self.failUnlessEqual(results, new_data))
12410+        return d
12411+
12412+
12413+    def test_append_power_of_two(self):
12414+        # If we attempt to extend a mutable file so that its segment
12415+        # count crosses a power-of-two boundary, the update operation
12416+        # should know how to reencode the file.
12417+
12418+        # Note that the data populating self.mdmf_node is about 900 KiB
12419+        # long -- this is 7 segments in the default segment size. So we
12420+        # need to add 2 segments worth of data to push it over a
12421+        # power-of-two boundary.
12422+        segment = "a" * DEFAULT_MAX_SEGMENT_SIZE
12423+        new_data = self.data + (segment * 2)
12424+        d = self.mdmf_node.get_best_mutable_version()
12425+        d.addCallback(lambda mv:
12426+            mv.update(MutableData(segment * 2), len(self.data)))
12427+        d.addCallback(lambda ignored:
12428+            self.mdmf_node.download_best_version())
12429+        d.addCallback(lambda results:
12430+            self.failUnlessEqual(results, new_data))
12431+        return d
12432+    test_append_power_of_two.timeout = 15
12433+
12434+
12435+    def test_update_sdmf(self):
12436+        # Running update on a single-segment file should still work.
12437+        new_data = self.small_data + "appended"
12438+        d = self.sdmf_node.get_best_mutable_version()
12439+        d.addCallback(lambda mv:
12440+            mv.update(MutableData("appended"), len(self.small_data)))
12441+        d.addCallback(lambda ignored:
12442+            self.sdmf_node.download_best_version())
12443+        d.addCallback(lambda results:
12444+            self.failUnlessEqual(results, new_data))
12445+        return d
12446+
12447+    def test_replace_in_last_segment(self):
12448+        # The wrapper should know how to handle the tail segment
12449+        # appropriately.
12450+        replace_offset = len(self.data) - 100
12451+        new_data = self.data[:replace_offset] + "replaced"
12452+        rest_offset = replace_offset + len("replaced")
12453+        new_data += self.data[rest_offset:]
12454+        d = self.mdmf_node.get_best_mutable_version()
12455+        d.addCallback(lambda mv:
12456+            mv.update(MutableData("replaced"), replace_offset))
12457+        d.addCallback(lambda ignored:
12458+            self.mdmf_node.download_best_version())
12459+        d.addCallback(lambda results:
12460+            self.failUnlessEqual(results, new_data))
12461+        return d
12462+
12463+
12464+    def test_multiple_segment_replace(self):
12465+        replace_offset = 2 * DEFAULT_MAX_SEGMENT_SIZE
12466+        new_data = self.data[:replace_offset]
12467+        new_segment = "a" * DEFAULT_MAX_SEGMENT_SIZE
12468+        new_data += 2 * new_segment
12469+        new_data += "replaced"
12470+        rest_offset = len(new_data)
12471+        new_data += self.data[rest_offset:]
12472+        d = self.mdmf_node.get_best_mutable_version()
12473+        d.addCallback(lambda mv:
12474+            mv.update(MutableData((2 * new_segment) + "replaced"),
12475+                      replace_offset))
12476+        d.addCallback(lambda ignored:
12477+            self.mdmf_node.download_best_version())
12478+        d.addCallback(lambda results:
12479+            self.failUnlessEqual(results, new_data))
12480+        return d
12481hunk ./src/allmydata/test/test_sftp.py 32
12482 
12483 from allmydata.util.consumer import download_to_data
12484 from allmydata.immutable import upload
12485+from allmydata.mutable import publish
12486 from allmydata.test.no_network import GridTestMixin
12487 from allmydata.test.common import ShouldFailMixin
12488 from allmydata.test.common_util import ReallyEqualMixin
12489hunk ./src/allmydata/test/test_sftp.py 80
12490         return d
12491 
12492     def _set_up_tree(self):
12493-        d = self.client.create_mutable_file("mutable file contents")
12494+        u = publish.MutableData("mutable file contents")
12495+        d = self.client.create_mutable_file(u)
12496         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
12497         def _created_mutable(n):
12498             self.mutable = n
12499hunk ./src/allmydata/test/test_sftp.py 1330
12500         d.addCallback(lambda ign: self.failUnlessEqual(sftpd.all_heisenfiles, {}))
12501         d.addCallback(lambda ign: self.failUnlessEqual(self.handler._heisenfiles, {}))
12502         return d
12503+    test_makeDirectory.timeout = 15
12504 
12505     def test_execCommand_and_openShell(self):
12506         class MockProtocol:
12507hunk ./src/allmydata/test/test_storage.py 27
12508                                      LayoutInvalid, MDMFSIGNABLEHEADER, \
12509                                      SIGNED_PREFIX, MDMFHEADER, \
12510                                      MDMFOFFSETS, SDMFSlotWriteProxy
12511-from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
12512-                                 SDMF_VERSION
12513+from allmydata.interfaces import BadWriteEnablerError
12514 from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
12515 from allmydata.test.common_web import WebRenderingMixin
12516 from allmydata.web.storage import StorageStatus, remove_prefix
12517hunk ./src/allmydata/test/test_system.py 26
12518 from allmydata.monitor import Monitor
12519 from allmydata.mutable.common import NotWriteableError
12520 from allmydata.mutable import layout as mutable_layout
12521+from allmydata.mutable.publish import MutableData
12522 from foolscap.api import DeadReferenceError
12523 from twisted.python.failure import Failure
12524 from twisted.web.client import getPage
12525hunk ./src/allmydata/test/test_system.py 467
12526     def test_mutable(self):
12527         self.basedir = "system/SystemTest/test_mutable"
12528         DATA = "initial contents go here."  # 25 bytes % 3 != 0
12529+        DATA_uploadable = MutableData(DATA)
12530         NEWDATA = "new contents yay"
12531hunk ./src/allmydata/test/test_system.py 469
12532+        NEWDATA_uploadable = MutableData(NEWDATA)
12533         NEWERDATA = "this is getting old"
12534hunk ./src/allmydata/test/test_system.py 471
12535+        NEWERDATA_uploadable = MutableData(NEWERDATA)
12536 
12537         d = self.set_up_nodes(use_key_generator=True)
12538 
12539hunk ./src/allmydata/test/test_system.py 478
12540         def _create_mutable(res):
12541             c = self.clients[0]
12542             log.msg("starting create_mutable_file")
12543-            d1 = c.create_mutable_file(DATA)
12544+            d1 = c.create_mutable_file(DATA_uploadable)
12545             def _done(res):
12546                 log.msg("DONE: %s" % (res,))
12547                 self._mutable_node_1 = res
12548hunk ./src/allmydata/test/test_system.py 565
12549             self.failUnlessEqual(res, DATA)
12550             # replace the data
12551             log.msg("starting replace1")
12552-            d1 = newnode.overwrite(NEWDATA)
12553+            d1 = newnode.overwrite(NEWDATA_uploadable)
12554             d1.addCallback(lambda res: newnode.download_best_version())
12555             return d1
12556         d.addCallback(_check_download_3)
12557hunk ./src/allmydata/test/test_system.py 579
12558             newnode2 = self.clients[3].create_node_from_uri(uri)
12559             self._newnode3 = self.clients[3].create_node_from_uri(uri)
12560             log.msg("starting replace2")
12561-            d1 = newnode1.overwrite(NEWERDATA)
12562+            d1 = newnode1.overwrite(NEWERDATA_uploadable)
12563             d1.addCallback(lambda res: newnode2.download_best_version())
12564             return d1
12565         d.addCallback(_check_download_4)
12566hunk ./src/allmydata/test/test_system.py 649
12567         def _check_empty_file(res):
12568             # make sure we can create empty files, this usually screws up the
12569             # segsize math
12570-            d1 = self.clients[2].create_mutable_file("")
12571+            d1 = self.clients[2].create_mutable_file(MutableData(""))
12572             d1.addCallback(lambda newnode: newnode.download_best_version())
12573             d1.addCallback(lambda res: self.failUnlessEqual("", res))
12574             return d1
12575hunk ./src/allmydata/test/test_system.py 680
12576                                  self.key_generator_svc.key_generator.pool_size + size_delta)
12577 
12578         d.addCallback(check_kg_poolsize, 0)
12579-        d.addCallback(lambda junk: self.clients[3].create_mutable_file('hello, world'))
12580+        d.addCallback(lambda junk:
12581+            self.clients[3].create_mutable_file(MutableData('hello, world')))
12582         d.addCallback(check_kg_poolsize, -1)
12583         d.addCallback(lambda junk: self.clients[3].create_dirnode())
12584         d.addCallback(check_kg_poolsize, -2)
12585hunk ./src/allmydata/test/test_web.py 28
12586 from allmydata.util.encodingutil import to_str
12587 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
12588      create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
12589-from allmydata.interfaces import IMutableFileNode
12590+from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
12591 from allmydata.mutable import servermap, publish, retrieve
12592 import allmydata.test.common_util as testutil
12593 from allmydata.test.no_network import GridTestMixin
12594hunk ./src/allmydata/test/test_web.py 57
12595         return FakeCHKFileNode(cap)
12596     def _create_mutable(self, cap):
12597         return FakeMutableFileNode(None, None, None, None).init_from_cap(cap)
12598-    def create_mutable_file(self, contents="", keysize=None):
12599+    def create_mutable_file(self, contents="", keysize=None,
12600+                            version=SDMF_VERSION):
12601         n = FakeMutableFileNode(None, None, None, None)
12602hunk ./src/allmydata/test/test_web.py 60
12603+        n.set_version(version)
12604         return n.create(contents)
12605 
12606 class FakeUploader(service.Service):
12607hunk ./src/allmydata/test/test_web.py 162
12608         self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
12609                                        self.uploader, None,
12610                                        None, None)
12611+        self.mutable_file_default = SDMF_VERSION
12612 
12613     def startService(self):
12614         return service.MultiService.startService(self)
12615hunk ./src/allmydata/test/test_web.py 781
12616                              self.PUT, base + "/@@name=/blah.txt", "")
12617         return d
12618 
12619+
12620     def test_GET_DIRURL_named_bad(self):
12621         base = "/file/%s" % urllib.quote(self._foo_uri)
12622         d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
12623hunk ./src/allmydata/test/test_web.py 897
12624                                                       self.NEWFILE_CONTENTS))
12625         return d
12626 
12627+    def test_PUT_NEWFILEURL_unlinked_mdmf(self):
12628+        # this should get us a few segments of an MDMF mutable file,
12629+        # which we can then test for.
12630+        contents = self.NEWFILE_CONTENTS * 300000
12631+        d = self.PUT("/uri?mutable=true&mutable-type=mdmf",
12632+                     contents)
12633+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12634+        d.addCallback(lambda json: self.failUnlessIn("mdmf", json))
12635+        return d
12636+
12637+    def test_PUT_NEWFILEURL_unlinked_sdmf(self):
12638+        contents = self.NEWFILE_CONTENTS * 300000
12639+        d = self.PUT("/uri?mutable=true&mutable-type=sdmf",
12640+                     contents)
12641+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12642+        d.addCallback(lambda json: self.failUnlessIn("sdmf", json))
12643+        return d
12644+
12645     def test_PUT_NEWFILEURL_range_bad(self):
12646         headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
12647         target = self.public_url + "/foo/new.txt"
12648hunk ./src/allmydata/test/test_web.py 947
12649         return d
12650 
12651     def test_PUT_NEWFILEURL_mutable_toobig(self):
12652-        d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
12653-                             "413 Request Entity Too Large",
12654-                             "SDMF is limited to one segment, and 10001 > 10000",
12655-                             self.PUT,
12656-                             self.public_url + "/foo/new.txt?mutable=true",
12657-                             "b" * (self.s.MUTABLE_SIZELIMIT+1))
12658+        # It is okay to upload large mutable files, so we should be able
12659+        # to do that.
12660+        d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
12661+                     "b" * (self.s.MUTABLE_SIZELIMIT + 1))
12662         return d
12663 
12664     def test_PUT_NEWFILEURL_replace(self):
12665hunk ./src/allmydata/test/test_web.py 1045
12666         d.addCallback(_check1)
12667         return d
12668 
12669+    def test_GET_FILEURL_json_mutable_type(self):
12670+        # The JSON should include mutable-type, which says whether the
12671+        # file is SDMF or MDMF
12672+        d = self.PUT("/uri?mutable=true&mutable-type=mdmf",
12673+                     self.NEWFILE_CONTENTS * 300000)
12674+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12675+        def _got_json(json, version):
12676+            data = simplejson.loads(json)
12677+            assert "filenode" == data[0]
12678+            data = data[1]
12679+            assert isinstance(data, dict)
12680+
12681+            self.failUnlessIn("mutable-type", data)
12682+            self.failUnlessEqual(data['mutable-type'], version)
12683+
12684+        d.addCallback(_got_json, "mdmf")
12685+        # Now make an SDMF file and check that it is reported correctly.
12686+        d.addCallback(lambda ignored:
12687+            self.PUT("/uri?mutable=true&mutable-type=sdmf",
12688+                      self.NEWFILE_CONTENTS * 300000))
12689+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12690+        d.addCallback(_got_json, "sdmf")
12691+        return d
12692+
12693     def test_GET_FILEURL_json_missing(self):
12694         d = self.GET(self.public_url + "/foo/missing?json")
12695         d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
12696hunk ./src/allmydata/test/test_web.py 1107
12697         d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
12698         return d
12699 
12700-    def test_GET_DIRECTORY_html_banner(self):
12701+    def test_GET_DIRECTORY_html(self):
12702         d = self.GET(self.public_url + "/foo", followRedirect=True)
12703         def _check(res):
12704             self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>',res)
12705hunk ./src/allmydata/test/test_web.py 1111
12706+            self.failUnlessIn("mutable-type-mdmf", res)
12707+            self.failUnlessIn("mutable-type-sdmf", res)
12708         d.addCallback(_check)
12709         return d
12710 
12711hunk ./src/allmydata/test/test_web.py 1116
12712+    def test_GET_root_html(self):
12713+        # make sure that we have the option to upload an unlinked
12714+        # mutable file in SDMF and MDMF formats.
12715+        d = self.GET("/")
12716+        def _got_html(html):
12717+            # These are radio buttons that allow the user to toggle
12718+            # whether a particular mutable file is MDMF or SDMF.
12719+            self.failUnlessIn("mutable-type-mdmf", html)
12720+            self.failUnlessIn("mutable-type-sdmf", html)
12721+        d.addCallback(_got_html)
12722+        return d
12723+
12724+    def test_mutable_type_defaults(self):
12725+        # The checked="checked" attribute of the inputs corresponding to
12726+        # the mutable-type parameter should change as expected with the
12727+        # value configured in tahoe.cfg.
12728+        #
12729+        # By default, the value configured with the client is
12730+        # SDMF_VERSION, so that should be checked.
12731+        assert self.s.mutable_file_default == SDMF_VERSION
12732+
12733+        d = self.GET("/")
12734+        def _got_html(html, value):
12735+            i = 'input checked="checked" type="radio" id="mutable-type-%s"'
12736+            self.failUnlessIn(i % value, html)
12737+        d.addCallback(_got_html, "sdmf")
12738+        d.addCallback(lambda ignored:
12739+            self.GET(self.public_url + "/foo", followRedirect=True))
12740+        d.addCallback(_got_html, "sdmf")
12741+        # Now switch the configuration value to MDMF. The MDMF radio
12742+        # buttons should now be checked on these pages.
12743+        def _swap_values(ignored):
12744+            self.s.mutable_file_default = MDMF_VERSION
12745+        d.addCallback(_swap_values)
12746+        d.addCallback(lambda ignored: self.GET("/"))
12747+        d.addCallback(_got_html, "mdmf")
12748+        d.addCallback(lambda ignored:
12749+            self.GET(self.public_url + "/foo", followRedirect=True))
12750+        d.addCallback(_got_html, "mdmf")
12751+        return d
12752+
12753     def test_GET_DIRURL(self):
12754         # the addSlash means we get a redirect here
12755         # from /uri/$URI/foo/ , we need ../../../ to get back to the root
12756hunk ./src/allmydata/test/test_web.py 1246
12757         d.addCallback(self.failUnlessIsFooJSON)
12758         return d
12759 
12760+    def test_GET_DIRURL_json_mutable_type(self):
12761+        d = self.PUT(self.public_url + \
12762+                     "/foo/sdmf.txt?mutable=true&mutable-type=sdmf",
12763+                     self.NEWFILE_CONTENTS * 300000)
12764+        d.addCallback(lambda ignored:
12765+            self.PUT(self.public_url + \
12766+                     "/foo/mdmf.txt?mutable=true&mutable-type=mdmf",
12767+                     self.NEWFILE_CONTENTS * 300000))
12768+        # Now we have an MDMF and SDMF file in the directory. If we GET
12769+        # its JSON, we should see their encodings.
12770+        d.addCallback(lambda ignored:
12771+            self.GET(self.public_url + "/foo?t=json"))
12772+        def _got_json(json):
12773+            data = simplejson.loads(json)
12774+            assert data[0] == "dirnode"
12775+
12776+            data = data[1]
12777+            kids = data['children']
12778+
12779+            mdmf_data = kids['mdmf.txt'][1]
12780+            self.failUnlessIn("mutable-type", mdmf_data)
12781+            self.failUnlessEqual(mdmf_data['mutable-type'], "mdmf")
12782+
12783+            sdmf_data = kids['sdmf.txt'][1]
12784+            self.failUnlessIn("mutable-type", sdmf_data)
12785+            self.failUnlessEqual(sdmf_data['mutable-type'], "sdmf")
12786+        d.addCallback(_got_json)
12787+        return d
12788+
12789 
12790     def test_POST_DIRURL_manifest_no_ophandle(self):
12791         d = self.shouldFail2(error.Error,
12792hunk ./src/allmydata/test/test_web.py 1829
12793         return d
12794 
12795     def test_POST_upload_no_link_mutable_toobig(self):
12796-        d = self.shouldFail2(error.Error,
12797-                             "test_POST_upload_no_link_mutable_toobig",
12798-                             "413 Request Entity Too Large",
12799-                             "SDMF is limited to one segment, and 10001 > 10000",
12800-                             self.POST,
12801-                             "/uri", t="upload", mutable="true",
12802-                             file=("new.txt",
12803-                                   "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
12804+        # The SDMF size limit is no longer in place, so we should be
12805+        # able to upload mutable files that are as large as we want them
12806+        # to be.
12807+        d = self.POST("/uri", t="upload", mutable="true",
12808+                      file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
12809         return d
12810 
12811hunk ./src/allmydata/test/test_web.py 1836
12812+
12813+    def test_POST_upload_mutable_type_unlinked(self):
12814+        d = self.POST("/uri?t=upload&mutable=true&mutable-type=sdmf",
12815+                      file=("sdmf.txt", self.NEWFILE_CONTENTS * 300000))
12816+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12817+        def _got_json(json, version):
12818+            data = simplejson.loads(json)
12819+            data = data[1]
12820+
12821+            self.failUnlessIn("mutable-type", data)
12822+            self.failUnlessEqual(data['mutable-type'], version)
12823+        d.addCallback(_got_json, "sdmf")
12824+        d.addCallback(lambda ignored:
12825+            self.POST("/uri?t=upload&mutable=true&mutable-type=mdmf",
12826+                      file=('mdmf.txt', self.NEWFILE_CONTENTS * 300000)))
12827+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12828+        d.addCallback(_got_json, "mdmf")
12829+        return d
12830+
12831+    def test_POST_upload_mutable_type(self):
12832+        d = self.POST(self.public_url + \
12833+                      "/foo?t=upload&mutable=true&mutable-type=sdmf",
12834+                      file=("sdmf.txt", self.NEWFILE_CONTENTS * 300000))
12835+        fn = self._foo_node
12836+        def _got_cap(filecap, filename):
12837+            filenameu = unicode(filename)
12838+            self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
12839+            return self.GET(self.public_url + "/foo/%s?t=json" % filename)
12840+        d.addCallback(_got_cap, "sdmf.txt")
12841+        def _got_json(json, version):
12842+            data = simplejson.loads(json)
12843+            data = data[1]
12844+
12845+            self.failUnlessIn("mutable-type", data)
12846+            self.failUnlessEqual(data['mutable-type'], version)
12847+        d.addCallback(_got_json, "sdmf")
12848+        d.addCallback(lambda ignored:
12849+            self.POST(self.public_url + \
12850+                      "/foo?t=upload&mutable=true&mutable-type=mdmf",
12851+                      file=("mdmf.txt", self.NEWFILE_CONTENTS * 300000)))
12852+        d.addCallback(_got_cap, "mdmf.txt")
12853+        d.addCallback(_got_json, "mdmf")
12854+        return d
12855+
12856     def test_POST_upload_mutable(self):
12857         # this creates a mutable file
12858         d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
12859hunk ./src/allmydata/test/test_web.py 2004
12860             self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
12861         d.addCallback(_got_headers)
12862 
12863-        # make sure that size errors are displayed correctly for overwrite
12864-        d.addCallback(lambda res:
12865-                      self.shouldFail2(error.Error,
12866-                                       "test_POST_upload_mutable-toobig",
12867-                                       "413 Request Entity Too Large",
12868-                                       "SDMF is limited to one segment, and 10001 > 10000",
12869-                                       self.POST,
12870-                                       self.public_url + "/foo", t="upload",
12871-                                       mutable="true",
12872-                                       file=("new.txt",
12873-                                             "b" * (self.s.MUTABLE_SIZELIMIT+1)),
12874-                                       ))
12875-
12876+        # make sure that outdated size limits aren't enforced anymore.
12877+        d.addCallback(lambda ignored:
12878+            self.POST(self.public_url + "/foo", t="upload",
12879+                      mutable="true",
12880+                      file=("new.txt",
12881+                            "b" * (self.s.MUTABLE_SIZELIMIT+1))))
12882         d.addErrback(self.dump_error)
12883         return d
12884 
12885hunk ./src/allmydata/test/test_web.py 2014
12886     def test_POST_upload_mutable_toobig(self):
12887-        d = self.shouldFail2(error.Error,
12888-                             "test_POST_upload_mutable_toobig",
12889-                             "413 Request Entity Too Large",
12890-                             "SDMF is limited to one segment, and 10001 > 10000",
12891-                             self.POST,
12892-                             self.public_url + "/foo",
12893-                             t="upload", mutable="true",
12894-                             file=("new.txt",
12895-                                   "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
12896+        # SDMF had a size limti that was removed a while ago. MDMF has
12897+        # never had a size limit. Test to make sure that we do not
12898+        # encounter errors when trying to upload large mutable files,
12899+        # since there should be no coded prohibitions regarding large
12900+        # mutable files.
12901+        d = self.POST(self.public_url + "/foo",
12902+                      t="upload", mutable="true",
12903+                      file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
12904         return d
12905 
12906     def dump_error(self, f):
12907hunk ./src/allmydata/test/test_web.py 3024
12908                                                       contents))
12909         return d
12910 
12911+    def test_PUT_NEWFILEURL_mdmf(self):
12912+        new_contents = self.NEWFILE_CONTENTS * 300000
12913+        d = self.PUT(self.public_url + \
12914+                     "/foo/mdmf.txt?mutable=true&mutable-type=mdmf",
12915+                     new_contents)
12916+        d.addCallback(lambda ignored:
12917+            self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
12918+        def _got_json(json):
12919+            data = simplejson.loads(json)
12920+            data = data[1]
12921+            self.failUnlessIn("mutable-type", data)
12922+            self.failUnlessEqual(data['mutable-type'], "mdmf")
12923+        d.addCallback(_got_json)
12924+        return d
12925+
12926+    def test_PUT_NEWFILEURL_sdmf(self):
12927+        new_contents = self.NEWFILE_CONTENTS * 300000
12928+        d = self.PUT(self.public_url + \
12929+                     "/foo/sdmf.txt?mutable=true&mutable-type=sdmf",
12930+                     new_contents)
12931+        d.addCallback(lambda ignored:
12932+            self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
12933+        def _got_json(json):
12934+            data = simplejson.loads(json)
12935+            data = data[1]
12936+            self.failUnlessIn("mutable-type", data)
12937+            self.failUnlessEqual(data['mutable-type'], "sdmf")
12938+        d.addCallback(_got_json)
12939+        return d
12940+
12941     def test_PUT_NEWFILEURL_uri_replace(self):
12942         contents, n, new_uri = self.makefile(8)
12943         d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
12944hunk ./src/allmydata/test/test_web.py 3175
12945         d.addCallback(_done)
12946         return d
12947 
12948+
12949+    def test_PUT_update_at_offset(self):
12950+        file_contents = "test file" * 100000 # about 900 KiB
12951+        d = self.PUT("/uri?mutable=true", file_contents)
12952+        def _then(filecap):
12953+            self.filecap = filecap
12954+            new_data = file_contents[:100]
12955+            new = "replaced and so on"
12956+            new_data += new
12957+            new_data += file_contents[len(new_data):]
12958+            assert len(new_data) == len(file_contents)
12959+            self.new_data = new_data
12960+        d.addCallback(_then)
12961+        d.addCallback(lambda ignored:
12962+            self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
12963+                     "replaced and so on"))
12964+        def _get_data(filecap):
12965+            n = self.s.create_node_from_uri(filecap)
12966+            return n.download_best_version()
12967+        d.addCallback(_get_data)
12968+        d.addCallback(lambda results:
12969+            self.failUnlessEqual(results, self.new_data))
12970+        # Now try appending things to the file
12971+        d.addCallback(lambda ignored:
12972+            self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
12973+                     "puppies" * 100))
12974+        d.addCallback(_get_data)
12975+        d.addCallback(lambda results:
12976+            self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
12977+        return d
12978+
12979+
12980+    def test_PUT_update_at_offset_immutable(self):
12981+        file_contents = "Test file" * 100000
12982+        d = self.PUT("/uri", file_contents)
12983+        def _then(filecap):
12984+            self.filecap = filecap
12985+        d.addCallback(_then)
12986+        d.addCallback(lambda ignored:
12987+            self.shouldHTTPError("test immutable update",
12988+                                 400, "Bad Request",
12989+                                 "immutable",
12990+                                 self.PUT,
12991+                                 "/uri/%s?offset=50" % self.filecap,
12992+                                 "foo"))
12993+        return d
12994+
12995+
12996     def test_bad_method(self):
12997         url = self.webish_url + self.public_url + "/foo/bar.txt"
12998         d = self.shouldHTTPError("test_bad_method",
12999hunk ./src/allmydata/test/test_web.py 3492
13000         def _stash_mutable_uri(n, which):
13001             self.uris[which] = n.get_uri()
13002             assert isinstance(self.uris[which], str)
13003-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
13004+        d.addCallback(lambda ign:
13005+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
13006         d.addCallback(_stash_mutable_uri, "corrupt")
13007         d.addCallback(lambda ign:
13008                       c0.upload(upload.Data("literal", convergence="")))
13009hunk ./src/allmydata/test/test_web.py 3639
13010         def _stash_mutable_uri(n, which):
13011             self.uris[which] = n.get_uri()
13012             assert isinstance(self.uris[which], str)
13013-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
13014+        d.addCallback(lambda ign:
13015+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
13016         d.addCallback(_stash_mutable_uri, "corrupt")
13017 
13018         def _compute_fileurls(ignored):
13019hunk ./src/allmydata/test/test_web.py 4302
13020         def _stash_mutable_uri(n, which):
13021             self.uris[which] = n.get_uri()
13022             assert isinstance(self.uris[which], str)
13023-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
13024+        d.addCallback(lambda ign:
13025+            c0.create_mutable_file(publish.MutableData(DATA+"2")))
13026         d.addCallback(_stash_mutable_uri, "mutable")
13027 
13028         def _compute_fileurls(ignored):
13029hunk ./src/allmydata/test/test_web.py 4402
13030                                                         convergence="")))
13031         d.addCallback(_stash_uri, "small")
13032 
13033-        d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
13034+        d.addCallback(lambda ign:
13035+            c0.create_mutable_file(publish.MutableData("mutable")))
13036         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
13037         d.addCallback(_stash_uri, "mutable")
13038 
13039}
13040[resolve conflicts between 393-MDMF patches and trunk as of 1.8.2
13041"Brian Warner <warner@lothar.com>"**20110220230201
13042 Ignore-this: 9bbf5d26c994e8069202331dcb4cdd95
13043] {
13044merger 0.0 (
13045merger 0.0 (
13046merger 0.0 (
13047replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
13048merger 0.0 (
13049hunk ./docs/configuration.rst 384
13050-shares.needed = (int, optional) aka "k", default 3
13051-shares.total = (int, optional) aka "N", N >= k, default 10
13052-shares.happy = (int, optional) 1 <= happy <= N, default 7
13053-
13054- These three values set the default encoding parameters. Each time a new file
13055- is uploaded, erasure-coding is used to break the ciphertext into separate
13056- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13057- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13058- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13059- Setting k to 1 is equivalent to simple replication (uploading N copies of
13060- the file).
13061-
13062- These values control the tradeoff between storage overhead, performance, and
13063- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13064- backend storage space (the actual value will be a bit more, because of other
13065- forms of overhead). Up to N-k shares can be lost before the file becomes
13066- unrecoverable, so assuming there are at least N servers, up to N-k servers
13067- can be offline without losing the file. So large N/k ratios are more
13068- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13069- smaller than N.
13070-
13071- Large values of N will slow down upload operations slightly, since more
13072- servers must be involved, and will slightly increase storage overhead due to
13073- the hash trees that are created. Large values of k will cause downloads to
13074- be marginally slower, because more servers must be involved. N cannot be
13075- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13076- uses.
13077-
13078- shares.happy allows you control over the distribution of your immutable file.
13079- For a successful upload, shares are guaranteed to be initially placed on
13080- at least 'shares.happy' distinct servers, the correct functioning of any
13081- k of which is sufficient to guarantee the availability of the uploaded file.
13082- This value should not be larger than the number of servers on your grid.
13083-
13084- A value of shares.happy <= k is allowed, but does not provide any redundancy
13085- if some servers fail or lose shares.
13086-
13087- (Mutable files use a different share placement algorithm that does not
13088-  consider this parameter.)
13089-
13090-
13091-== Storage Server Configuration ==
13092-
13093-[storage]
13094-enabled = (boolean, optional)
13095-
13096- If this is True, the node will run a storage server, offering space to other
13097- clients. If it is False, the node will not run a storage server, meaning
13098- that no shares will be stored on this node. Use False this for clients who
13099- do not wish to provide storage service. The default value is True.
13100-
13101-readonly = (boolean, optional)
13102-
13103- If True, the node will run a storage server but will not accept any shares,
13104- making it effectively read-only. Use this for storage servers which are
13105- being decommissioned: the storage/ directory could be mounted read-only,
13106- while shares are moved to other servers. Note that this currently only
13107- affects immutable shares. Mutable shares (used for directories) will be
13108- written and modified anyway. See ticket #390 for the current status of this
13109- bug. The default value is False.
13110-
13111-reserved_space = (str, optional)
13112-
13113- If provided, this value defines how much disk space is reserved: the storage
13114- server will not accept any share which causes the amount of free disk space
13115- to drop below this value. (The free space is measured by a call to statvfs(2)
13116- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13117- user account under which the storage server runs.)
13118-
13119- This string contains a number, with an optional case-insensitive scale
13120- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13121- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13122- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13123-
13124-expire.enabled =
13125-expire.mode =
13126-expire.override_lease_duration =
13127-expire.cutoff_date =
13128-expire.immutable =
13129-expire.mutable =
13130-
13131- These settings control garbage-collection, in which the server will delete
13132- shares that no longer have an up-to-date lease on them. Please see the
13133- neighboring "garbage-collection.txt" document for full details.
13134-
13135-
13136-== Running A Helper ==
13137+Running A Helper
13138+================
13139hunk ./docs/configuration.rst 424
13140+mutable.format = sdmf or mdmf
13141+
13142+ This value tells Tahoe-LAFS what the default mutable file format should
13143+ be. If mutable.format=sdmf, then newly created mutable files will be in
13144+ the old SDMF format. This is desirable for clients that operate on
13145+ grids where some peers run older versions of Tahoe-LAFS, as these older
13146+ versions cannot read the new MDMF mutable file format. If
13147+ mutable.format = mdmf, then newly created mutable files will use the
13148+ new MDMF format, which supports efficient in-place modification and
13149+ streaming downloads. You can overwrite this value using a special
13150+ mutable-type parameter in the webapi. If you do not specify a value
13151+ here, Tahoe-LAFS will use SDMF for all newly-created mutable files.
13152+
13153+ Note that this parameter only applies to mutable files. Mutable
13154+ directories, which are stored as mutable files, are not controlled by
13155+ this parameter and will always use SDMF. We may revisit this decision
13156+ in future versions of Tahoe-LAFS.
13157)
13158)
13159hunk ./docs/configuration.rst 324
13160+Frontend Configuration
13161+======================
13162+
13163+The Tahoe client process can run a variety of frontend file-access protocols.
13164+You will use these to create and retrieve files from the virtual filesystem.
13165+Configuration details for each are documented in the following
13166+protocol-specific guides:
13167+
13168+HTTP
13169+
13170+    Tahoe runs a webserver by default on port 3456. This interface provides a
13171+    human-oriented "WUI", with pages to create, modify, and browse
13172+    directories and files, as well as a number of pages to check on the
13173+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
13174+    with a REST-ful HTTP interface that can be used by other programs
13175+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
13176+    details, and the ``web.port`` and ``web.static`` config variables above.
13177+    The `<frontends/download-status.rst>`_ document also describes a few WUI
13178+    status pages.
13179+
13180+CLI
13181+
13182+    The main "bin/tahoe" executable includes subcommands for manipulating the
13183+    filesystem, uploading/downloading files, and creating/running Tahoe
13184+    nodes. See `<frontends/CLI.rst>`_ for details.
13185+
13186+FTP, SFTP
13187+
13188+    Tahoe can also run both FTP and SFTP servers, and map a username/password
13189+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
13190+    for instructions on configuring these services, and the ``[ftpd]`` and
13191+    ``[sftpd]`` sections of ``tahoe.cfg``.
13192+
13193)
13194hunk ./docs/configuration.rst 324
13195+``mutable.format = sdmf or mdmf``
13196+
13197+    This value tells Tahoe what the default mutable file format should
13198+    be. If ``mutable.format=sdmf``, then newly created mutable files will be
13199+    in the old SDMF format. This is desirable for clients that operate on
13200+    grids where some peers run older versions of Tahoe, as these older
13201+    versions cannot read the new MDMF mutable file format. If
13202+    ``mutable.format`` is ``mdmf``, then newly created mutable files will use
13203+    the new MDMF format, which supports efficient in-place modification and
13204+    streaming downloads. You can overwrite this value using a special
13205+    mutable-type parameter in the webapi. If you do not specify a value here,
13206+    Tahoe will use SDMF for all newly-created mutable files.
13207+
13208+    Note that this parameter only applies to mutable files. Mutable
13209+    directories, which are stored as mutable files, are not controlled by
13210+    this parameter and will always use SDMF. We may revisit this decision
13211+    in future versions of Tahoe-LAFS.
13212+
13213)
13214merger 0.0 (
13215merger 0.0 (
13216hunk ./docs/configuration.rst 324
13217+``mutable.format = sdmf or mdmf``
13218+
13219+    This value tells Tahoe what the default mutable file format should
13220+    be. If ``mutable.format=sdmf``, then newly created mutable files will be
13221+    in the old SDMF format. This is desirable for clients that operate on
13222+    grids where some peers run older versions of Tahoe, as these older
13223+    versions cannot read the new MDMF mutable file format. If
13224+    ``mutable.format`` is ``mdmf``, then newly created mutable files will use
13225+    the new MDMF format, which supports efficient in-place modification and
13226+    streaming downloads. You can overwrite this value using a special
13227+    mutable-type parameter in the webapi. If you do not specify a value here,
13228+    Tahoe will use SDMF for all newly-created mutable files.
13229+
13230+    Note that this parameter only applies to mutable files. Mutable
13231+    directories, which are stored as mutable files, are not controlled by
13232+    this parameter and will always use SDMF. We may revisit this decision
13233+    in future versions of Tahoe-LAFS.
13234+
13235merger 0.0 (
13236merger 0.0 (
13237replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
13238merger 0.0 (
13239hunk ./docs/configuration.rst 384
13240-shares.needed = (int, optional) aka "k", default 3
13241-shares.total = (int, optional) aka "N", N >= k, default 10
13242-shares.happy = (int, optional) 1 <= happy <= N, default 7
13243-
13244- These three values set the default encoding parameters. Each time a new file
13245- is uploaded, erasure-coding is used to break the ciphertext into separate
13246- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13247- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13248- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13249- Setting k to 1 is equivalent to simple replication (uploading N copies of
13250- the file).
13251-
13252- These values control the tradeoff between storage overhead, performance, and
13253- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13254- backend storage space (the actual value will be a bit more, because of other
13255- forms of overhead). Up to N-k shares can be lost before the file becomes
13256- unrecoverable, so assuming there are at least N servers, up to N-k servers
13257- can be offline without losing the file. So large N/k ratios are more
13258- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13259- smaller than N.
13260-
13261- Large values of N will slow down upload operations slightly, since more
13262- servers must be involved, and will slightly increase storage overhead due to
13263- the hash trees that are created. Large values of k will cause downloads to
13264- be marginally slower, because more servers must be involved. N cannot be
13265- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13266- uses.
13267-
13268- shares.happy allows you control over the distribution of your immutable file.
13269- For a successful upload, shares are guaranteed to be initially placed on
13270- at least 'shares.happy' distinct servers, the correct functioning of any
13271- k of which is sufficient to guarantee the availability of the uploaded file.
13272- This value should not be larger than the number of servers on your grid.
13273-
13274- A value of shares.happy <= k is allowed, but does not provide any redundancy
13275- if some servers fail or lose shares.
13276-
13277- (Mutable files use a different share placement algorithm that does not
13278-  consider this parameter.)
13279-
13280-
13281-== Storage Server Configuration ==
13282-
13283-[storage]
13284-enabled = (boolean, optional)
13285-
13286- If this is True, the node will run a storage server, offering space to other
13287- clients. If it is False, the node will not run a storage server, meaning
13288- that no shares will be stored on this node. Use False this for clients who
13289- do not wish to provide storage service. The default value is True.
13290-
13291-readonly = (boolean, optional)
13292-
13293- If True, the node will run a storage server but will not accept any shares,
13294- making it effectively read-only. Use this for storage servers which are
13295- being decommissioned: the storage/ directory could be mounted read-only,
13296- while shares are moved to other servers. Note that this currently only
13297- affects immutable shares. Mutable shares (used for directories) will be
13298- written and modified anyway. See ticket #390 for the current status of this
13299- bug. The default value is False.
13300-
13301-reserved_space = (str, optional)
13302-
13303- If provided, this value defines how much disk space is reserved: the storage
13304- server will not accept any share which causes the amount of free disk space
13305- to drop below this value. (The free space is measured by a call to statvfs(2)
13306- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13307- user account under which the storage server runs.)
13308-
13309- This string contains a number, with an optional case-insensitive scale
13310- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13311- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13312- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13313-
13314-expire.enabled =
13315-expire.mode =
13316-expire.override_lease_duration =
13317-expire.cutoff_date =
13318-expire.immutable =
13319-expire.mutable =
13320-
13321- These settings control garbage-collection, in which the server will delete
13322- shares that no longer have an up-to-date lease on them. Please see the
13323- neighboring "garbage-collection.txt" document for full details.
13324-
13325-
13326-== Running A Helper ==
13327+Running A Helper
13328+================
13329hunk ./docs/configuration.rst 424
13330+mutable.format = sdmf or mdmf
13331+
13332+ This value tells Tahoe-LAFS what the default mutable file format should
13333+ be. If mutable.format=sdmf, then newly created mutable files will be in
13334+ the old SDMF format. This is desirable for clients that operate on
13335+ grids where some peers run older versions of Tahoe-LAFS, as these older
13336+ versions cannot read the new MDMF mutable file format. If
13337+ mutable.format = mdmf, then newly created mutable files will use the
13338+ new MDMF format, which supports efficient in-place modification and
13339+ streaming downloads. You can overwrite this value using a special
13340+ mutable-type parameter in the webapi. If you do not specify a value
13341+ here, Tahoe-LAFS will use SDMF for all newly-created mutable files.
13342+
13343+ Note that this parameter only applies to mutable files. Mutable
13344+ directories, which are stored as mutable files, are not controlled by
13345+ this parameter and will always use SDMF. We may revisit this decision
13346+ in future versions of Tahoe-LAFS.
13347)
13348)
13349hunk ./docs/configuration.rst 324
13350+Frontend Configuration
13351+======================
13352+
13353+The Tahoe client process can run a variety of frontend file-access protocols.
13354+You will use these to create and retrieve files from the virtual filesystem.
13355+Configuration details for each are documented in the following
13356+protocol-specific guides:
13357+
13358+HTTP
13359+
13360+    Tahoe runs a webserver by default on port 3456. This interface provides a
13361+    human-oriented "WUI", with pages to create, modify, and browse
13362+    directories and files, as well as a number of pages to check on the
13363+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
13364+    with a REST-ful HTTP interface that can be used by other programs
13365+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
13366+    details, and the ``web.port`` and ``web.static`` config variables above.
13367+    The `<frontends/download-status.rst>`_ document also describes a few WUI
13368+    status pages.
13369+
13370+CLI
13371+
13372+    The main "bin/tahoe" executable includes subcommands for manipulating the
13373+    filesystem, uploading/downloading files, and creating/running Tahoe
13374+    nodes. See `<frontends/CLI.rst>`_ for details.
13375+
13376+FTP, SFTP
13377+
13378+    Tahoe can also run both FTP and SFTP servers, and map a username/password
13379+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
13380+    for instructions on configuring these services, and the ``[ftpd]`` and
13381+    ``[sftpd]`` sections of ``tahoe.cfg``.
13382+
13383)
13384)
13385hunk ./docs/configuration.rst 402
13386-shares.needed = (int, optional) aka "k", default 3
13387-shares.total = (int, optional) aka "N", N >= k, default 10
13388-shares.happy = (int, optional) 1 <= happy <= N, default 7
13389-
13390- These three values set the default encoding parameters. Each time a new file
13391- is uploaded, erasure-coding is used to break the ciphertext into separate
13392- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13393- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13394- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13395- Setting k to 1 is equivalent to simple replication (uploading N copies of
13396- the file).
13397-
13398- These values control the tradeoff between storage overhead, performance, and
13399- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13400- backend storage space (the actual value will be a bit more, because of other
13401- forms of overhead). Up to N-k shares can be lost before the file becomes
13402- unrecoverable, so assuming there are at least N servers, up to N-k servers
13403- can be offline without losing the file. So large N/k ratios are more
13404- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13405- smaller than N.
13406-
13407- Large values of N will slow down upload operations slightly, since more
13408- servers must be involved, and will slightly increase storage overhead due to
13409- the hash trees that are created. Large values of k will cause downloads to
13410- be marginally slower, because more servers must be involved. N cannot be
13411- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13412- uses.
13413-
13414- shares.happy allows you control over the distribution of your immutable file.
13415- For a successful upload, shares are guaranteed to be initially placed on
13416- at least 'shares.happy' distinct servers, the correct functioning of any
13417- k of which is sufficient to guarantee the availability of the uploaded file.
13418- This value should not be larger than the number of servers on your grid.
13419-
13420- A value of shares.happy <= k is allowed, but does not provide any redundancy
13421- if some servers fail or lose shares.
13422-
13423- (Mutable files use a different share placement algorithm that does not
13424-  consider this parameter.)
13425-
13426-
13427-== Storage Server Configuration ==
13428-
13429-[storage]
13430-enabled = (boolean, optional)
13431-
13432- If this is True, the node will run a storage server, offering space to other
13433- clients. If it is False, the node will not run a storage server, meaning
13434- that no shares will be stored on this node. Use False this for clients who
13435- do not wish to provide storage service. The default value is True.
13436-
13437-readonly = (boolean, optional)
13438-
13439- If True, the node will run a storage server but will not accept any shares,
13440- making it effectively read-only. Use this for storage servers which are
13441- being decommissioned: the storage/ directory could be mounted read-only,
13442- while shares are moved to other servers. Note that this currently only
13443- affects immutable shares. Mutable shares (used for directories) will be
13444- written and modified anyway. See ticket #390 for the current status of this
13445- bug. The default value is False.
13446-
13447-reserved_space = (str, optional)
13448-
13449- If provided, this value defines how much disk space is reserved: the storage
13450- server will not accept any share which causes the amount of free disk space
13451- to drop below this value. (The free space is measured by a call to statvfs(2)
13452- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13453- user account under which the storage server runs.)
13454-
13455- This string contains a number, with an optional case-insensitive scale
13456- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13457- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13458- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13459-
13460-expire.enabled =
13461-expire.mode =
13462-expire.override_lease_duration =
13463-expire.cutoff_date =
13464-expire.immutable =
13465-expire.mutable =
13466-
13467- These settings control garbage-collection, in which the server will delete
13468- shares that no longer have an up-to-date lease on them. Please see the
13469- neighboring "garbage-collection.txt" document for full details.
13470-
13471-
13472-== Running A Helper ==
13473+Running A Helper
13474+================
13475)
13476merger 0.0 (
13477merger 0.0 (
13478hunk ./docs/configuration.rst 402
13479-shares.needed = (int, optional) aka "k", default 3
13480-shares.total = (int, optional) aka "N", N >= k, default 10
13481-shares.happy = (int, optional) 1 <= happy <= N, default 7
13482-
13483- These three values set the default encoding parameters. Each time a new file
13484- is uploaded, erasure-coding is used to break the ciphertext into separate
13485- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13486- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13487- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13488- Setting k to 1 is equivalent to simple replication (uploading N copies of
13489- the file).
13490-
13491- These values control the tradeoff between storage overhead, performance, and
13492- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13493- backend storage space (the actual value will be a bit more, because of other
13494- forms of overhead). Up to N-k shares can be lost before the file becomes
13495- unrecoverable, so assuming there are at least N servers, up to N-k servers
13496- can be offline without losing the file. So large N/k ratios are more
13497- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13498- smaller than N.
13499-
13500- Large values of N will slow down upload operations slightly, since more
13501- servers must be involved, and will slightly increase storage overhead due to
13502- the hash trees that are created. Large values of k will cause downloads to
13503- be marginally slower, because more servers must be involved. N cannot be
13504- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13505- uses.
13506-
13507- shares.happy allows you control over the distribution of your immutable file.
13508- For a successful upload, shares are guaranteed to be initially placed on
13509- at least 'shares.happy' distinct servers, the correct functioning of any
13510- k of which is sufficient to guarantee the availability of the uploaded file.
13511- This value should not be larger than the number of servers on your grid.
13512-
13513- A value of shares.happy <= k is allowed, but does not provide any redundancy
13514- if some servers fail or lose shares.
13515-
13516- (Mutable files use a different share placement algorithm that does not
13517-  consider this parameter.)
13518-
13519-
13520-== Storage Server Configuration ==
13521-
13522-[storage]
13523-enabled = (boolean, optional)
13524-
13525- If this is True, the node will run a storage server, offering space to other
13526- clients. If it is False, the node will not run a storage server, meaning
13527- that no shares will be stored on this node. Use False this for clients who
13528- do not wish to provide storage service. The default value is True.
13529-
13530-readonly = (boolean, optional)
13531-
13532- If True, the node will run a storage server but will not accept any shares,
13533- making it effectively read-only. Use this for storage servers which are
13534- being decommissioned: the storage/ directory could be mounted read-only,
13535- while shares are moved to other servers. Note that this currently only
13536- affects immutable shares. Mutable shares (used for directories) will be
13537- written and modified anyway. See ticket #390 for the current status of this
13538- bug. The default value is False.
13539-
13540-reserved_space = (str, optional)
13541-
13542- If provided, this value defines how much disk space is reserved: the storage
13543- server will not accept any share which causes the amount of free disk space
13544- to drop below this value. (The free space is measured by a call to statvfs(2)
13545- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13546- user account under which the storage server runs.)
13547-
13548- This string contains a number, with an optional case-insensitive scale
13549- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13550- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13551- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13552-
13553-expire.enabled =
13554-expire.mode =
13555-expire.override_lease_duration =
13556-expire.cutoff_date =
13557-expire.immutable =
13558-expire.mutable =
13559-
13560- These settings control garbage-collection, in which the server will delete
13561- shares that no longer have an up-to-date lease on them. Please see the
13562- neighboring "garbage-collection.txt" document for full details.
13563-
13564-
13565-== Running A Helper ==
13566+Running A Helper
13567+================
13568merger 0.0 (
13569hunk ./docs/configuration.rst 324
13570+``mutable.format = sdmf or mdmf``
13571+
13572+    This value tells Tahoe what the default mutable file format should
13573+    be. If ``mutable.format=sdmf``, then newly created mutable files will be
13574+    in the old SDMF format. This is desirable for clients that operate on
13575+    grids where some peers run older versions of Tahoe, as these older
13576+    versions cannot read the new MDMF mutable file format. If
13577+    ``mutable.format`` is ``mdmf``, then newly created mutable files will use
13578+    the new MDMF format, which supports efficient in-place modification and
13579+    streaming downloads. You can overwrite this value using a special
13580+    mutable-type parameter in the webapi. If you do not specify a value here,
13581+    Tahoe will use SDMF for all newly-created mutable files.
13582+
13583+    Note that this parameter only applies to mutable files. Mutable
13584+    directories, which are stored as mutable files, are not controlled by
13585+    this parameter and will always use SDMF. We may revisit this decision
13586+    in future versions of Tahoe-LAFS.
13587+
13588merger 0.0 (
13589merger 0.0 (
13590replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
13591merger 0.0 (
13592hunk ./docs/configuration.rst 384
13593-shares.needed = (int, optional) aka "k", default 3
13594-shares.total = (int, optional) aka "N", N >= k, default 10
13595-shares.happy = (int, optional) 1 <= happy <= N, default 7
13596-
13597- These three values set the default encoding parameters. Each time a new file
13598- is uploaded, erasure-coding is used to break the ciphertext into separate
13599- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13600- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13601- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13602- Setting k to 1 is equivalent to simple replication (uploading N copies of
13603- the file).
13604-
13605- These values control the tradeoff between storage overhead, performance, and
13606- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13607- backend storage space (the actual value will be a bit more, because of other
13608- forms of overhead). Up to N-k shares can be lost before the file becomes
13609- unrecoverable, so assuming there are at least N servers, up to N-k servers
13610- can be offline without losing the file. So large N/k ratios are more
13611- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13612- smaller than N.
13613-
13614- Large values of N will slow down upload operations slightly, since more
13615- servers must be involved, and will slightly increase storage overhead due to
13616- the hash trees that are created. Large values of k will cause downloads to
13617- be marginally slower, because more servers must be involved. N cannot be
13618- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13619- uses.
13620-
13621- shares.happy allows you control over the distribution of your immutable file.
13622- For a successful upload, shares are guaranteed to be initially placed on
13623- at least 'shares.happy' distinct servers, the correct functioning of any
13624- k of which is sufficient to guarantee the availability of the uploaded file.
13625- This value should not be larger than the number of servers on your grid.
13626-
13627- A value of shares.happy <= k is allowed, but does not provide any redundancy
13628- if some servers fail or lose shares.
13629-
13630- (Mutable files use a different share placement algorithm that does not
13631-  consider this parameter.)
13632-
13633-
13634-== Storage Server Configuration ==
13635-
13636-[storage]
13637-enabled = (boolean, optional)
13638-
13639- If this is True, the node will run a storage server, offering space to other
13640- clients. If it is False, the node will not run a storage server, meaning
13641- that no shares will be stored on this node. Use False this for clients who
13642- do not wish to provide storage service. The default value is True.
13643-
13644-readonly = (boolean, optional)
13645-
13646- If True, the node will run a storage server but will not accept any shares,
13647- making it effectively read-only. Use this for storage servers which are
13648- being decommissioned: the storage/ directory could be mounted read-only,
13649- while shares are moved to other servers. Note that this currently only
13650- affects immutable shares. Mutable shares (used for directories) will be
13651- written and modified anyway. See ticket #390 for the current status of this
13652- bug. The default value is False.
13653-
13654-reserved_space = (str, optional)
13655-
13656- If provided, this value defines how much disk space is reserved: the storage
13657- server will not accept any share which causes the amount of free disk space
13658- to drop below this value. (The free space is measured by a call to statvfs(2)
13659- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13660- user account under which the storage server runs.)
13661-
13662- This string contains a number, with an optional case-insensitive scale
13663- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13664- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13665- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13666-
13667-expire.enabled =
13668-expire.mode =
13669-expire.override_lease_duration =
13670-expire.cutoff_date =
13671-expire.immutable =
13672-expire.mutable =
13673-
13674- These settings control garbage-collection, in which the server will delete
13675- shares that no longer have an up-to-date lease on them. Please see the
13676- neighboring "garbage-collection.txt" document for full details.
13677-
13678-
13679-== Running A Helper ==
13680+Running A Helper
13681+================
13682hunk ./docs/configuration.rst 424
13683+mutable.format = sdmf or mdmf
13684+
13685+ This value tells Tahoe-LAFS what the default mutable file format should
13686+ be. If mutable.format=sdmf, then newly created mutable files will be in
13687+ the old SDMF format. This is desirable for clients that operate on
13688+ grids where some peers run older versions of Tahoe-LAFS, as these older
13689+ versions cannot read the new MDMF mutable file format. If
13690+ mutable.format = mdmf, then newly created mutable files will use the
13691+ new MDMF format, which supports efficient in-place modification and
13692+ streaming downloads. You can overwrite this value using a special
13693+ mutable-type parameter in the webapi. If you do not specify a value
13694+ here, Tahoe-LAFS will use SDMF for all newly-created mutable files.
13695+
13696+ Note that this parameter only applies to mutable files. Mutable
13697+ directories, which are stored as mutable files, are not controlled by
13698+ this parameter and will always use SDMF. We may revisit this decision
13699+ in future versions of Tahoe-LAFS.
13700)
13701)
13702hunk ./docs/configuration.rst 324
13703+Frontend Configuration
13704+======================
13705+
13706+The Tahoe client process can run a variety of frontend file-access protocols.
13707+You will use these to create and retrieve files from the virtual filesystem.
13708+Configuration details for each are documented in the following
13709+protocol-specific guides:
13710+
13711+HTTP
13712+
13713+    Tahoe runs a webserver by default on port 3456. This interface provides a
13714+    human-oriented "WUI", with pages to create, modify, and browse
13715+    directories and files, as well as a number of pages to check on the
13716+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
13717+    with a REST-ful HTTP interface that can be used by other programs
13718+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
13719+    details, and the ``web.port`` and ``web.static`` config variables above.
13720+    The `<frontends/download-status.rst>`_ document also describes a few WUI
13721+    status pages.
13722+
13723+CLI
13724+
13725+    The main "bin/tahoe" executable includes subcommands for manipulating the
13726+    filesystem, uploading/downloading files, and creating/running Tahoe
13727+    nodes. See `<frontends/CLI.rst>`_ for details.
13728+
13729+FTP, SFTP
13730+
13731+    Tahoe can also run both FTP and SFTP servers, and map a username/password
13732+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
13733+    for instructions on configuring these services, and the ``[ftpd]`` and
13734+    ``[sftpd]`` sections of ``tahoe.cfg``.
13735+
13736)
13737)
13738)
13739replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
13740)
13741hunk ./src/allmydata/mutable/retrieve.py 7
13742 from zope.interface import implements
13743 from twisted.internet import defer
13744 from twisted.python import failure
13745-from foolscap.api import DeadReferenceError, eventually, fireEventually
13746-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
13747-from allmydata.util import hashutil, idlib, log
13748+from twisted.internet.interfaces import IPushProducer, IConsumer
13749+from foolscap.api import eventually, fireEventually
13750+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
13751+                                 MDMF_VERSION, SDMF_VERSION
13752+from allmydata.util import hashutil, log, mathutil
13753+from allmydata.util.dictutil import DictOfSets
13754 from allmydata import hashtree, codec
13755 from allmydata.storage.server import si_b2a
13756 from pycryptopp.cipher.aes import AES
13757hunk ./src/allmydata/mutable/retrieve.py 239
13758             # KiB, so we ask for that much.
13759             # TODO: Change the cache methods to allow us to fetch all of the
13760             # data that they have, then change this method to do that.
13761-            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
13762-                                                               shnum,
13763-                                                               0,
13764-                                                               1000)
13765+            any_cache = self._node._read_from_cache(self.verinfo, shnum,
13766+                                                    0, 1000)
13767             ss = self.servermap.connections[peerid]
13768             reader = MDMFSlotReadProxy(ss,
13769                                        self._storage_index,
13770hunk ./src/allmydata/mutable/retrieve.py 373
13771                  (k, n, self._num_segments, self._segment_size,
13772                   self._tail_segment_size))
13773 
13774-        # ask the cache first
13775-        got_from_cache = False
13776-        datavs = []
13777-        for (offset, length) in readv:
13778-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
13779-                                                            offset, length)
13780-            if data is not None:
13781-                datavs.append(data)
13782-        if len(datavs) == len(readv):
13783-            self.log("got data from cache")
13784-            got_from_cache = True
13785-            d = fireEventually({shnum: datavs})
13786-            # datavs is a dict mapping shnum to a pair of strings
13787+        for i in xrange(self._total_shares):
13788+            # So we don't have to do this later.
13789+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
13790+
13791+        # Our last task is to tell the downloader where to start and
13792+        # where to stop. We use three parameters for that:
13793+        #   - self._start_segment: the segment that we need to start
13794+        #     downloading from.
13795+        #   - self._current_segment: the next segment that we need to
13796+        #     download.
13797+        #   - self._last_segment: The last segment that we were asked to
13798+        #     download.
13799+        #
13800+        #  We say that the download is complete when
13801+        #  self._current_segment > self._last_segment. We use
13802+        #  self._start_segment and self._last_segment to know when to
13803+        #  strip things off of segments, and how much to strip.
13804+        if self._offset:
13805+            self.log("got offset: %d" % self._offset)
13806+            # our start segment is the first segment containing the
13807+            # offset we were given.
13808+            start = mathutil.div_ceil(self._offset,
13809+                                      self._segment_size)
13810+            # this gets us the first segment after self._offset. Then
13811+            # our start segment is the one before it.
13812+            start -= 1
13813+
13814+            assert start < self._num_segments
13815+            self._start_segment = start
13816+            self.log("got start segment: %d" % self._start_segment)
13817         else:
13818             self._start_segment = 0
13819 
13820hunk ./src/allmydata/mutable/servermap.py 7
13821 from itertools import count
13822 from twisted.internet import defer
13823 from twisted.python import failure
13824-from foolscap.api import DeadReferenceError, RemoteException, eventually
13825-from allmydata.util import base32, hashutil, idlib, log
13826+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
13827+                         fireEventually
13828+from allmydata.util import base32, hashutil, idlib, log, deferredutil
13829+from allmydata.util.dictutil import DictOfSets
13830 from allmydata.storage.server import si_b2a
13831 from allmydata.interfaces import IServermapUpdaterStatus
13832 from pycryptopp.publickey import rsa
13833hunk ./src/allmydata/mutable/servermap.py 16
13834 
13835 from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \
13836-     DictOfSets, CorruptShareError, NeedMoreDataError
13837-from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
13838-     SIGNED_PREFIX_LENGTH
13839+     CorruptShareError
13840+from allmydata.mutable.layout import SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
13841 
13842 class UpdateStatus:
13843     implements(IServermapUpdaterStatus)
13844hunk ./src/allmydata/mutable/servermap.py 391
13845         #  * if we need the encrypted private key, we want [-1216ish:]
13846         #   * but we can't read from negative offsets
13847         #   * the offset table tells us the 'ish', also the positive offset
13848-        # A future version of the SMDF slot format should consider using
13849-        # fixed-size slots so we can retrieve less data. For now, we'll just
13850-        # read 2000 bytes, which also happens to read enough actual data to
13851-        # pre-fetch a 9-entry dirnode.
13852+        # MDMF:
13853+        #  * Checkstring? [0:72]
13854+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
13855+        #    the offset table will tell us for sure.
13856+        #  * If we need the verification key, we have to consult the offset
13857+        #    table as well.
13858+        # At this point, we don't know which we are. Our filenode can
13859+        # tell us, but it might be lying -- in some cases, we're
13860+        # responsible for telling it which kind of file it is.
13861         self._read_size = 4000
13862         if mode == MODE_CHECK:
13863             # we use unpack_prefix_and_signature, so we need 1k
13864hunk ./src/allmydata/mutable/servermap.py 633
13865         updated.
13866         """
13867         if verinfo:
13868-            self._node._add_to_cache(verinfo, shnum, 0, data, now)
13869+            self._node._add_to_cache(verinfo, shnum, 0, data)
13870 
13871 
13872     def _got_results(self, datavs, peerid, readsize, stuff, started):
13873hunk ./src/allmydata/mutable/servermap.py 664
13874 
13875         for shnum,datav in datavs.items():
13876             data = datav[0]
13877-            try:
13878-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
13879-                last_verinfo = verinfo
13880-                last_shnum = shnum
13881-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
13882-            except CorruptShareError, e:
13883-                # log it and give the other shares a chance to be processed
13884-                f = failure.Failure()
13885-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
13886-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
13887-                self.notify_server_corruption(peerid, shnum, str(e))
13888-                self._bad_peers.add(peerid)
13889-                self._last_failure = f
13890-                checkstring = data[:SIGNED_PREFIX_LENGTH]
13891-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
13892-                self._servermap.problems.append(f)
13893-                pass
13894+            reader = MDMFSlotReadProxy(ss,
13895+                                       storage_index,
13896+                                       shnum,
13897+                                       data)
13898+            self._readers.setdefault(peerid, dict())[shnum] = reader
13899+            # our goal, with each response, is to validate the version
13900+            # information and share data as best we can at this point --
13901+            # we do this by validating the signature. To do this, we
13902+            # need to do the following:
13903+            #   - If we don't already have the public key, fetch the
13904+            #     public key. We use this to validate the signature.
13905+            if not self._node.get_pubkey():
13906+                # fetch and set the public key.
13907+                d = reader.get_verification_key(queue=True)
13908+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
13909+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
13910+                # XXX: Make self._pubkey_query_failed?
13911+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
13912+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
13913+            else:
13914+                # we already have the public key.
13915+                d = defer.succeed(None)
13916 
13917             # Neither of these two branches return anything of
13918             # consequence, so the first entry in our deferredlist will
13919hunk ./src/allmydata/test/test_storage.py 1
13920-import time, os.path, platform, stat, re, simplejson, struct
13921+import time, os.path, platform, stat, re, simplejson, struct, shutil
13922 
13923hunk ./src/allmydata/test/test_storage.py 3
13924-import time, os.path, stat, re, simplejson, struct
13925+import mock
13926 
13927 from twisted.trial import unittest
13928 
13929}
13930[mutable/filenode.py: fix create_mutable_file('string')
13931"Brian Warner <warner@lothar.com>"**20110221014659
13932 Ignore-this: dc6bdad761089f0199681eeb784f1001
13933] hunk ./src/allmydata/mutable/filenode.py 137
13934         if contents is None:
13935             return MutableData("")
13936 
13937+        if isinstance(contents, str):
13938+            return MutableData(contents)
13939+
13940         if IMutableUploadable.providedBy(contents):
13941             return contents
13942 
13943[resolve more conflicts with current trunk
13944"Brian Warner <warner@lothar.com>"**20110221055600
13945 Ignore-this: 77ad038a478dbf5d9b34f7a68159a3e0
13946] hunk ./src/allmydata/mutable/servermap.py 461
13947         self._queries_completed = 0
13948 
13949         sb = self._storage_broker
13950-        full_peerlist = sb.get_servers_for_index(self._storage_index)
13951+        # All of the peers, permuted by the storage index, as usual.
13952+        full_peerlist = [(s.get_serverid(), s.get_rref())
13953+                         for s in sb.get_servers_for_psi(self._storage_index)]
13954         self.full_peerlist = full_peerlist # for use later, immutable
13955         self.extra_peers = full_peerlist[:] # peers are removed as we use them
13956         self._good_peers = set() # peers who had some shares
13957[update MDMF code with StorageFarmBroker changes
13958"Brian Warner <warner@lothar.com>"**20110221061004
13959 Ignore-this: a693b201d31125b391cebe0412ddd027
13960] {
13961hunk ./src/allmydata/mutable/publish.py 203
13962         self._encprivkey = self._node.get_encprivkey()
13963 
13964         sb = self._storage_broker
13965-        full_peerlist = sb.get_servers_for_index(self._storage_index)
13966+        full_peerlist = [(s.get_serverid(), s.get_rref())
13967+                         for s in sb.get_servers_for_psi(self._storage_index)]
13968         self.full_peerlist = full_peerlist # for use later, immutable
13969         self.bad_peers = set() # peerids who have errbacked/refused requests
13970 
13971hunk ./src/allmydata/test/test_mutable.py 2538
13972             # for either a block and salt or for hashes, either of which
13973             # will exercise the error handling code.
13974             killer = FirstServerGetsKilled()
13975-            for (serverid, ss) in nm.storage_broker.get_all_servers():
13976-                ss.post_call_notifier = killer.notify
13977+            for s in nm.storage_broker.get_connected_servers():
13978+                s.get_rref().post_call_notifier = killer.notify
13979             ver = servermap.best_recoverable_version()
13980             assert ver
13981             return self._node.download_version(servermap, ver)
13982}
13983[mutable/filenode: Clean up servermap handling in MutableFileVersion
13984Kevan Carstensen <kevan@isnotajoke.com>**20110226010433
13985 Ignore-this: 2257c9f65502098789f5ea355b94f130
13986 
13987 We want to update the servermap before attempting to modify a file,
13988 which we now do. This introduced code duplication, which was addressed
13989 by refactoring the servermap update into its own method, and then
13990 eliminating duplicate servermap updates throughout the
13991 MutableFileVersion.
13992] {
13993hunk ./src/allmydata/mutable/filenode.py 19
13994 from allmydata.mutable.publish import Publish, MutableData,\
13995                                       DEFAULT_MAX_SEGMENT_SIZE, \
13996                                       TransformingUploadable
13997-from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
13998+from allmydata.mutable.common import MODE_READ, MODE_WRITE, MODE_CHECK, UnrecoverableFileError, \
13999      ResponseCache, UncoordinatedWriteError
14000 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
14001 from allmydata.mutable.retrieve import Retrieve
14002hunk ./src/allmydata/mutable/filenode.py 807
14003         a little bit.
14004         """
14005         log.msg("doing modify")
14006-        d = self._modify_once(modifier, first_time)
14007+        if first_time:
14008+            d = self._update_servermap()
14009+        else:
14010+            # We ran into trouble; do MODE_CHECK so we're a little more
14011+            # careful on subsequent tries.
14012+            d = self._update_servermap(mode=MODE_CHECK)
14013+
14014+        d.addCallback(lambda ignored:
14015+            self._modify_once(modifier, first_time))
14016         def _retry(f):
14017             f.trap(UncoordinatedWriteError)
14018hunk ./src/allmydata/mutable/filenode.py 818
14019+            # Uh oh, it broke. We're allowed to trust the servermap for our
14020+            # first try, but after that we need to update it. It's
14021+            # possible that we've failed due to a race with another
14022+            # uploader, and if the race is to converge correctly, we
14023+            # need to know about that upload.
14024             d2 = defer.maybeDeferred(backoffer, self, f)
14025             d2.addCallback(lambda ignored:
14026                            self._modify_and_retry(modifier,
14027hunk ./src/allmydata/mutable/filenode.py 837
14028         I attempt to apply a modifier to the contents of the mutable
14029         file.
14030         """
14031-        # XXX: This is wrong -- we could get more servers if we updated
14032-        # in MODE_ANYTHING and possibly MODE_CHECK. Probably we want to
14033-        # assert that the last update wasn't MODE_READ
14034-        assert self._servermap.last_update_mode == MODE_WRITE
14035+        assert self._servermap.last_update_mode != MODE_READ
14036 
14037         # download_to_data is serialized, so we have to call this to
14038         # avoid deadlock.
14039hunk ./src/allmydata/mutable/filenode.py 1076
14040 
14041         # Now ask for the servermap to be updated in MODE_WRITE with
14042         # this update range.
14043-        u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
14044-                             self._servermap,
14045-                             mode=MODE_WRITE,
14046-                             update_range=(start_segment, end_segment))
14047-        return u.update()
14048+        return self._update_servermap(update_range=(start_segment,
14049+                                                    end_segment))
14050 
14051 
14052     def _decode_and_decrypt_segments(self, ignored, data, offset):
14053hunk ./src/allmydata/mutable/filenode.py 1135
14054                                    segments_and_bht[1])
14055         p = Publish(self._node, self._storage_broker, self._servermap)
14056         return p.update(u, offset, segments_and_bht[2], self._version)
14057+
14058+
14059+    def _update_servermap(self, mode=MODE_WRITE, update_range=None):
14060+        """
14061+        I update the servermap. I return a Deferred that fires when the
14062+        servermap update is done.
14063+        """
14064+        if update_range:
14065+            u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
14066+                                 self._servermap,
14067+                                 mode=mode,
14068+                                 update_range=update_range)
14069+        else:
14070+            u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
14071+                                 self._servermap,
14072+                                 mode=mode)
14073+        return u.update()
14074}
14075[web: Use the string "replace" to trigger whole-file replacement when processing an offset parameter.
14076Kevan Carstensen <kevan@isnotajoke.com>**20110227231643
14077 Ignore-this: 5bbf0b90d68efe20d4c531bb98a8321a
14078] {
14079hunk ./docs/frontends/webapi.rst 360
14080  To use the /uri/$FILECAP form, $FILECAP must be a write-cap for a mutable file.
14081 
14082  In the /uri/$DIRCAP/[SUBDIRS../]FILENAME form, if the target file is a
14083- writeable mutable file, that file's contents will be overwritten in-place. If
14084- it is a read-cap for a mutable file, an error will occur. If it is an
14085- immutable file, the old file will be discarded, and a new one will be put in
14086- its place. If the target file is a writable mutable file, you may also
14087- specify an "offset" parameter -- a byte offset that determines where in
14088- the mutable file the data from the HTTP request body is placed. This
14089- operation is relatively efficient for MDMF mutable files, and is
14090- relatively inefficient (but still supported) for SDMF mutable files.
14091+ writeable mutable file, that file's contents will be overwritten
14092+ in-place. If it is a read-cap for a mutable file, an error will occur.
14093+ If it is an immutable file, the old file will be discarded, and a new
14094+ one will be put in its place. If the target file is a writable mutable
14095+ file, you may also specify an "offset" parameter -- a byte offset that
14096+ determines where in the mutable file the data from the HTTP request
14097+ body is placed. This operation is relatively efficient for MDMF mutable
14098+ files, and is relatively inefficient (but still supported) for SDMF
14099+ mutable files. If no offset parameter is specified, then the entire
14100+ file is replaced with the data from the HTTP request body. For an
14101+ immutable file, the "offset" parameter is not valid.
14102 
14103  When creating a new file, if "mutable=true" is in the query arguments, the
14104  operation will create a mutable file instead of an immutable one.
14105hunk ./src/allmydata/test/test_web.py 3206
14106             self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
14107         return d
14108 
14109+    def test_PUT_update_at_invalid_offset(self):
14110+        file_contents = "test file" * 100000 # about 900 KiB
14111+        d = self.PUT("/uri?mutable=true", file_contents)
14112+        def _then(filecap):
14113+            self.filecap = filecap
14114+        d.addCallback(_then)
14115+        # Negative offsets should cause an error.
14116+        d.addCallback(lambda ignored:
14117+            self.shouldHTTPError("test mutable invalid offset negative",
14118+                                 400, "Bad Request",
14119+                                 "Invalid offset",
14120+                                 self.PUT,
14121+                                 "/uri/%s?offset=-1" % self.filecap,
14122+                                 "foo"))
14123+        return d
14124 
14125     def test_PUT_update_at_offset_immutable(self):
14126         file_contents = "Test file" * 100000
14127hunk ./src/allmydata/web/common.py 55
14128     # message? Since this call is going to be used by programmers and
14129     # their tools rather than users (through the wui), it is not
14130     # inconsistent to return that, I guess.
14131-    offset = int(offset)
14132-    return offset
14133+    return int(offset)
14134 
14135 
14136 def get_root(ctx_or_req):
14137hunk ./src/allmydata/web/filenode.py 219
14138         req = IRequest(ctx)
14139         t = get_arg(req, "t", "").strip()
14140         replace = parse_replace_arg(get_arg(req, "replace", "true"))
14141-        offset = parse_offset_arg(get_arg(req, "offset", -1))
14142+        offset = parse_offset_arg(get_arg(req, "offset", False))
14143 
14144         if not t:
14145hunk ./src/allmydata/web/filenode.py 222
14146-            if self.node.is_mutable() and offset >= 0:
14147-                return self.update_my_contents(req, offset)
14148-
14149-            elif self.node.is_mutable():
14150-                return self.replace_my_contents(req)
14151             if not replace:
14152                 # this is the early trap: if someone else modifies the
14153                 # directory while we're uploading, the add_file(overwrite=)
14154hunk ./src/allmydata/web/filenode.py 227
14155                 # call in replace_me_with_a_child will do the late trap.
14156                 raise ExistingChildError()
14157-            if offset >= 0:
14158-                raise WebError("PUT to a file: append operation invoked "
14159-                               "on an immutable cap")
14160 
14161hunk ./src/allmydata/web/filenode.py 228
14162+            if self.node.is_mutable():
14163+                if offset == False:
14164+                    return self.replace_my_contents(req)
14165+
14166+                if offset >= 0:
14167+                    return self.update_my_contents(req, offset)
14168+
14169+                raise WebError("PUT to a mutable file: Invalid offset")
14170+
14171+            else:
14172+                if offset != False:
14173+                    raise WebError("PUT to a file: append operation invoked "
14174+                                   "on an immutable cap")
14175+
14176+                assert self.parentnode and self.name
14177+                return self.replace_me_with_a_child(req, self.client, replace)
14178 
14179hunk ./src/allmydata/web/filenode.py 245
14180-            assert self.parentnode and self.name
14181-            return self.replace_me_with_a_child(req, self.client, replace)
14182         if t == "uri":
14183             if not replace:
14184                 raise ExistingChildError()
14185}
14186[docs/configuration.rst: fix more conflicts between #393 and trunk
14187Kevan Carstensen <kevan@isnotajoke.com>**20110228003426
14188 Ignore-this: 7917effdeecab00d634a06f1df8fe2cf
14189] {
14190replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
14191hunk ./docs/configuration.rst 324
14192     (Mutable files use a different share placement algorithm that does not
14193     currently consider this parameter.)
14194 
14195+``mutable.format = sdmf or mdmf``
14196+
14197+    This value tells Tahoe-LAFS what the default mutable file format should
14198+    be. If ``mutable.format=sdmf``, then newly created mutable files will be
14199+    in the old SDMF format. This is desirable for clients that operate on
14200+    grids where some peers run older versions of Tahoe-LAFS, as these older
14201+    versions cannot read the new MDMF mutable file format. If
14202+    ``mutable.format`` is ``mdmf``, then newly created mutable files will use
14203+    the new MDMF format, which supports efficient in-place modification and
14204+    streaming downloads. You can overwrite this value using a special
14205+    mutable-type parameter in the webapi. If you do not specify a value here,
14206+    Tahoe-LAFS will use SDMF for all newly-created mutable files.
14207+
14208+    Note that this parameter only applies to mutable files. Mutable
14209+    directories, which are stored as mutable files, are not controlled by
14210+    this parameter and will always use SDMF. We may revisit this decision
14211+    in future versions of Tahoe-LAFS.
14212+
14213+
14214+Frontend Configuration
14215+======================
14216+
14217+The Tahoe client process can run a variety of frontend file-access protocols.
14218+You will use these to create and retrieve files from the virtual filesystem.
14219+Configuration details for each are documented in the following
14220+protocol-specific guides:
14221+
14222+HTTP
14223+
14224+    Tahoe runs a webserver by default on port 3456. This interface provides a
14225+    human-oriented "WUI", with pages to create, modify, and browse
14226+    directories and files, as well as a number of pages to check on the
14227+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
14228+    with a REST-ful HTTP interface that can be used by other programs
14229+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
14230+    details, and the ``web.port`` and ``web.static`` config variables above.
14231+    The `<frontends/download-status.rst>`_ document also describes a few WUI
14232+    status pages.
14233+
14234+CLI
14235+
14236+    The main "bin/tahoe" executable includes subcommands for manipulating the
14237+    filesystem, uploading/downloading files, and creating/running Tahoe
14238+    nodes. See `<frontends/CLI.rst>`_ for details.
14239+
14240+FTP, SFTP
14241+
14242+    Tahoe can also run both FTP and SFTP servers, and map a username/password
14243+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
14244+    for instructions on configuring these services, and the ``[ftpd]`` and
14245+    ``[sftpd]`` sections of ``tahoe.cfg``.
14246+
14247 
14248 Storage Server Configuration
14249 ============================
14250hunk ./docs/configuration.rst 436
14251     `<garbage-collection.rst>`_ for full details.
14252 
14253 
14254-shares.needed = (int, optional) aka "k", default 3
14255-shares.total = (int, optional) aka "N", N >= k, default 10
14256-shares.happy = (int, optional) 1 <= happy <= N, default 7
14257-
14258- These three values set the default encoding parameters. Each time a new file
14259- is uploaded, erasure-coding is used to break the ciphertext into separate
14260- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
14261- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
14262- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
14263- Setting k to 1 is equivalent to simple replication (uploading N copies of
14264- the file).
14265-
14266- These values control the tradeoff between storage overhead, performance, and
14267- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
14268- backend storage space (the actual value will be a bit more, because of other
14269- forms of overhead). Up to N-k shares can be lost before the file becomes
14270- unrecoverable, so assuming there are at least N servers, up to N-k servers
14271- can be offline without losing the file. So large N/k ratios are more
14272- reliable, and small N/k ratios use less disk space. Clearly, k must never be
14273- smaller than N.
14274-
14275- Large values of N will slow down upload operations slightly, since more
14276- servers must be involved, and will slightly increase storage overhead due to
14277- the hash trees that are created. Large values of k will cause downloads to
14278- be marginally slower, because more servers must be involved. N cannot be
14279- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe-LAFS
14280- uses.
14281-
14282- shares.happy allows you control over the distribution of your immutable file.
14283- For a successful upload, shares are guaranteed to be initially placed on
14284- at least 'shares.happy' distinct servers, the correct functioning of any
14285- k of which is sufficient to guarantee the availability of the uploaded file.
14286- This value should not be larger than the number of servers on your grid.
14287-
14288- A value of shares.happy <= k is allowed, but does not provide any redundancy
14289- if some servers fail or lose shares.
14290-
14291- (Mutable files use a different share placement algorithm that does not
14292-  consider this parameter.)
14293-
14294-
14295-== Storage Server Configuration ==
14296-
14297-[storage]
14298-enabled = (boolean, optional)
14299-
14300- If this is True, the node will run a storage server, offering space to other
14301- clients. If it is False, the node will not run a storage server, meaning
14302- that no shares will be stored on this node. Use False this for clients who
14303- do not wish to provide storage service. The default value is True.
14304-
14305-readonly = (boolean, optional)
14306-
14307- If True, the node will run a storage server but will not accept any shares,
14308- making it effectively read-only. Use this for storage servers which are
14309- being decommissioned: the storage/ directory could be mounted read-only,
14310- while shares are moved to other servers. Note that this currently only
14311- affects immutable shares. Mutable shares (used for directories) will be
14312- written and modified anyway. See ticket #390 for the current status of this
14313- bug. The default value is False.
14314-
14315-reserved_space = (str, optional)
14316-
14317- If provided, this value defines how much disk space is reserved: the storage
14318- server will not accept any share which causes the amount of free disk space
14319- to drop below this value. (The free space is measured by a call to statvfs(2)
14320- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
14321- user account under which the storage server runs.)
14322-
14323- This string contains a number, with an optional case-insensitive scale
14324- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
14325- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
14326- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
14327-
14328-expire.enabled =
14329-expire.mode =
14330-expire.override_lease_duration =
14331-expire.cutoff_date =
14332-expire.immutable =
14333-expire.mutable =
14334-
14335- These settings control garbage-collection, in which the server will delete
14336- shares that no longer have an up-to-date lease on them. Please see the
14337- neighboring "garbage-collection.txt" document for full details.
14338-
14339-
14340-== Running A Helper ==
14341+Running A Helper
14342+================
14343 
14344 A "helper" is a regular client node that also offers the "upload helper"
14345 service.
14346}
14347[mutable/layout: remove references to the salt hash tree.
14348Kevan Carstensen <kevan@isnotajoke.com>**20110228010637
14349 Ignore-this: b3b2963ba4d0b42c78b6bba219d4deb5
14350] {
14351hunk ./src/allmydata/mutable/layout.py 577
14352     # 99          8           The offset of the EOF
14353     #
14354     # followed by salts and share data, the encrypted private key, the
14355-    # block hash tree, the salt hash tree, the share hash chain, a
14356-    # signature over the first eight fields, and a verification key.
14357+    # block hash tree, the share hash chain, a signature over the first
14358+    # eight fields, and a verification key.
14359     #
14360     # The checkstring is the first three fields -- the version number,
14361     # sequence number, root hash and root salt hash. This is consistent
14362hunk ./src/allmydata/mutable/layout.py 628
14363     #      calculate the offset for the share hash chain, and fill that
14364     #      into the offsets table.
14365     #
14366-    #   4: At the same time, we're in a position to upload the salt hash
14367-    #      tree. This is a Merkle tree over all of the salts. We use a
14368-    #      Merkle tree so that we can validate each block,salt pair as
14369-    #      we download them later. We do this using
14370-    #
14371-    #        put_salthashes(salt_hash_tree)
14372-    #
14373-    #      When you do this, I automatically put the root of the tree
14374-    #      (the hash at index 0 of the list) in its appropriate slot in
14375-    #      the signed prefix of the share.
14376-    #
14377-    #   5: We're now in a position to upload the share hash chain for
14378+    #   4: We're now in a position to upload the share hash chain for
14379     #      a share. Do that with something like:
14380     #     
14381     #        put_sharehashes(share_hash_chain)
14382hunk ./src/allmydata/mutable/layout.py 639
14383     #      The root of this tree will be put explicitly in the next
14384     #      step.
14385     #
14386-    #      TODO: Why? Why not just include it in the tree here?
14387-    #
14388-    #   6: Before putting the signature, we must first put the
14389+    #   5: Before putting the signature, we must first put the
14390     #      root_hash. Do this with:
14391     #
14392     #        put_root_hash(root_hash).
14393hunk ./src/allmydata/mutable/layout.py 872
14394             raise LayoutInvalid("I was given the wrong size block to write")
14395 
14396         # We want to write at len(MDMFHEADER) + segnum * block_size.
14397-
14398         offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
14399         data = salt + data
14400 
14401hunk ./src/allmydata/mutable/layout.py 889
14402         # tree is written, since that could cause the private key to run
14403         # into the block hash tree. Before it writes the block hash
14404         # tree, the block hash tree writing method writes the offset of
14405-        # the salt hash tree. So that's a good indicator of whether or
14406+        # the share hash chain. So that's a good indicator of whether or
14407         # not the block hash tree has been written.
14408         if "share_hash_chain" in self._offsets:
14409             raise LayoutInvalid("You must write this before the block hash tree")
14410hunk ./src/allmydata/mutable/layout.py 907
14411         The encrypted private key must be queued before the block hash
14412         tree, since we need to know how large it is to know where the
14413         block hash tree should go. The block hash tree must be put
14414-        before the salt hash tree, since its size determines the
14415+        before the share hash chain, since its size determines the
14416         offset of the share hash chain.
14417         """
14418         assert self._offsets
14419hunk ./src/allmydata/mutable/layout.py 932
14420         I queue a write vector to put the share hash chain in my
14421         argument onto the remote server.
14422 
14423-        The salt hash tree must be queued before the share hash chain,
14424-        since we need to know where the salt hash tree ends before we
14425+        The block hash tree must be queued before the share hash chain,
14426+        since we need to know where the block hash tree ends before we
14427         can know where the share hash chain starts. The share hash chain
14428         must be put before the signature, since the length of the packed
14429         share hash chain determines the offset of the signature. Also,
14430hunk ./src/allmydata/mutable/layout.py 937
14431-        semantically, you must know what the root of the salt hash tree
14432+        semantically, you must know what the root of the block hash tree
14433         is before you can generate a valid signature.
14434         """
14435         assert isinstance(sharehashes, dict)
14436hunk ./src/allmydata/mutable/layout.py 942
14437         if "share_hash_chain" not in self._offsets:
14438-            raise LayoutInvalid("You need to put the salt hash tree before "
14439+            raise LayoutInvalid("You need to put the block hash tree before "
14440                                 "you can put the share hash chain")
14441         # The signature comes after the share hash chain. If the
14442         # signature has already been written, we must not write another
14443}
14444[test_mutable.py: add test to exercise fencepost bug
14445warner@lothar.com**20110228021056
14446 Ignore-this: d2f9cf237ce6db42fb250c8ad71a4fc3
14447] {
14448hunk ./src/allmydata/test/test_mutable.py 2
14449 
14450-import os
14451+import os, re
14452 from cStringIO import StringIO
14453 from twisted.trial import unittest
14454 from twisted.internet import defer, reactor
14455hunk ./src/allmydata/test/test_mutable.py 2931
14456         self.set_up_grid()
14457         self.c = self.g.clients[0]
14458         self.nm = self.c.nodemaker
14459-        self.data = "test data" * 100000 # about 900 KiB; MDMF
14460+        self.data = "testdata " * 100000 # about 900 KiB; MDMF
14461         self.small_data = "test data" * 10 # about 90 B; SDMF
14462         return self.do_upload()
14463 
14464hunk ./src/allmydata/test/test_mutable.py 2981
14465             self.failUnlessEqual(results, new_data))
14466         return d
14467 
14468+    def test_replace_segstart1(self):
14469+        offset = 128*1024+1
14470+        new_data = "NNNN"
14471+        expected = self.data[:offset]+new_data+self.data[offset+4:]
14472+        d = self.mdmf_node.get_best_mutable_version()
14473+        d.addCallback(lambda mv:
14474+            mv.update(MutableData(new_data), offset))
14475+        d.addCallback(lambda ignored:
14476+            self.mdmf_node.download_best_version())
14477+        def _check(results):
14478+            if results != expected:
14479+                print
14480+                print "got: %s ... %s" % (results[:20], results[-20:])
14481+                print "exp: %s ... %s" % (expected[:20], expected[-20:])
14482+                self.fail("results != expected")
14483+        d.addCallback(_check)
14484+        return d
14485+
14486+    def _check_differences(self, got, expected):
14487+        # displaying arbitrary file corruption is tricky for a
14488+        # 1MB file of repeating data,, so look for likely places
14489+        # with problems and display them separately
14490+        gotmods = [mo.span() for mo in re.finditer('([A-Z]+)', got)]
14491+        expmods = [mo.span() for mo in re.finditer('([A-Z]+)', expected)]
14492+        gotspans = ["%d:%d=%s" % (start,end,got[start:end])
14493+                    for (start,end) in gotmods]
14494+        expspans = ["%d:%d=%s" % (start,end,expected[start:end])
14495+                    for (start,end) in expmods]
14496+        #print "expecting: %s" % expspans
14497+
14498+        SEGSIZE = 128*1024
14499+        if got != expected:
14500+            print "differences:"
14501+            for segnum in range(len(expected)//SEGSIZE):
14502+                start = segnum * SEGSIZE
14503+                end = (segnum+1) * SEGSIZE
14504+                got_ends = "%s .. %s" % (got[start:start+20], got[end-20:end])
14505+                exp_ends = "%s .. %s" % (expected[start:start+20], expected[end-20:end])
14506+                if got_ends != exp_ends:
14507+                    print "expected[%d]: %s" % (start, exp_ends)
14508+                    print "got     [%d]: %s" % (start, got_ends)
14509+            if expspans != gotspans:
14510+                print "expected: %s" % expspans
14511+                print "got     : %s" % gotspans
14512+            open("EXPECTED","wb").write(expected)
14513+            open("GOT","wb").write(got)
14514+            print "wrote data to EXPECTED and GOT"
14515+            self.fail("didn't get expected data")
14516+
14517+
14518+    def test_replace_locations(self):
14519+        # exercise fencepost conditions
14520+        expected = self.data
14521+        SEGSIZE = 128*1024
14522+        suspects = range(SEGSIZE-3, SEGSIZE+1)+range(2*SEGSIZE-3, 2*SEGSIZE+1)
14523+        letters = iter("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
14524+        d = defer.succeed(None)
14525+        for offset in suspects:
14526+            new_data = letters.next()*2 # "AA", then "BB", etc
14527+            expected = expected[:offset]+new_data+expected[offset+2:]
14528+            d.addCallback(lambda ign:
14529+                          self.mdmf_node.get_best_mutable_version())
14530+            def _modify(mv, offset=offset, new_data=new_data):
14531+                # close over 'offset','new_data'
14532+                md = MutableData(new_data)
14533+                return mv.update(md, offset)
14534+            d.addCallback(_modify)
14535+            d.addCallback(lambda ignored:
14536+                          self.mdmf_node.download_best_version())
14537+            d.addCallback(self._check_differences, expected)
14538+        return d
14539+
14540 
14541     def test_replace_and_extend(self):
14542         # We should be able to replace data in the middle of a mutable
14543}
14544[mutable/publish: account for offsets on segment boundaries.
14545Kevan Carstensen <kevan@isnotajoke.com>**20110228083327
14546 Ignore-this: c8758a0580fcc15a22c2f8582d758a6b
14547] {
14548hunk ./src/allmydata/mutable/filenode.py 17
14549 from pycryptopp.cipher.aes import AES
14550 
14551 from allmydata.mutable.publish import Publish, MutableData,\
14552-                                      DEFAULT_MAX_SEGMENT_SIZE, \
14553                                       TransformingUploadable
14554 from allmydata.mutable.common import MODE_READ, MODE_WRITE, MODE_CHECK, UnrecoverableFileError, \
14555      ResponseCache, UncoordinatedWriteError
14556hunk ./src/allmydata/mutable/filenode.py 1058
14557         # appending data to the file.
14558         assert offset <= self.get_size()
14559 
14560+        segsize = self._version[3]
14561         # We'll need the segment that the data starts in, regardless of
14562         # what we'll do later.
14563hunk ./src/allmydata/mutable/filenode.py 1061
14564-        start_segment = mathutil.div_ceil(offset, DEFAULT_MAX_SEGMENT_SIZE)
14565+        start_segment = mathutil.div_ceil(offset, segsize)
14566         start_segment -= 1
14567 
14568         # We only need the end segment if the data we append does not go
14569hunk ./src/allmydata/mutable/filenode.py 1069
14570         end_segment = start_segment
14571         if offset + data.get_size() < self.get_size():
14572             end_data = offset + data.get_size()
14573-            end_segment = mathutil.div_ceil(end_data, DEFAULT_MAX_SEGMENT_SIZE)
14574+            end_segment = mathutil.div_ceil(end_data, segsize)
14575             end_segment -= 1
14576         self._start_segment = start_segment
14577         self._end_segment = end_segment
14578hunk ./src/allmydata/mutable/publish.py 551
14579                                                   segment_size)
14580             self.starting_segment = mathutil.div_ceil(offset,
14581                                                       segment_size)
14582-            self.starting_segment -= 1
14583+            if offset % segment_size != 0:
14584+                self.starting_segment -= 1
14585             if offset == 0:
14586                 self.starting_segment = 0
14587 
14588}
14589[tahoe-put: raise UsageError when given a nonsensical mutable type, move option validation code to the option parser.
14590Kevan Carstensen <kevan@isnotajoke.com>**20110301030807
14591 Ignore-this: 2dc19d8bd741842eff458ca553d0bf2a
14592] {
14593hunk ./src/allmydata/scripts/cli.py 186
14594         if self.from_file == u"-":
14595             self.from_file = None
14596 
14597+        if self['mutable-type'] and self['mutable-type'] not in ("sdmf", "mdmf"):
14598+            raise usage.UsageError("%s is an invalid format" % self['mutable-type'])
14599+
14600+
14601     def getSynopsis(self):
14602         return "Usage:  %s put [options] LOCAL_FILE REMOTE_FILE" % (self.command_name,)
14603 
14604hunk ./src/allmydata/scripts/tahoe_put.py 33
14605     stdout = options.stdout
14606     stderr = options.stderr
14607 
14608-    if mutable_type and mutable_type not in ('sdmf', 'mdmf'):
14609-        # Don't try to pass unsupported types to the webapi
14610-        print >>stderr, "error: %s is an invalid format" % mutable_type
14611-        return 1
14612-
14613     if nodeurl[-1] != "/":
14614         nodeurl += "/"
14615     if to_file:
14616hunk ./src/allmydata/test/test_cli.py 1053
14617         return d
14618 
14619     def test_mutable_type_invalid_format(self):
14620-        self.basedir = "cli/Put/mutable_type_invalid_format"
14621-        self.set_up_grid()
14622-        data = "data" * 100000
14623-        fn1 = os.path.join(self.basedir, "data")
14624-        fileutil.write(fn1, data)
14625-        d = self.do_cli("put", "--mutable", "--mutable-type=ldmf", fn1)
14626-        def _check_failure((rc, out, err)):
14627-            self.failIfEqual(rc, 0)
14628-            self.failUnlessIn("invalid", err)
14629-        d.addCallback(_check_failure)
14630-        return d
14631+        o = cli.PutOptions()
14632+        self.failUnlessRaises(usage.UsageError,
14633+                              o.parseOptions,
14634+                              ["--mutable", "--mutable-type=ldmf"])
14635 
14636     def test_put_with_nonexistent_alias(self):
14637         # when invoked with an alias that doesn't exist, 'tahoe put'
14638}
14639[web: use None instead of False in the case of no offset, use object identity comparison to check whether or not an offset was specified.
14640Kevan Carstensen <kevan@isnotajoke.com>**20110305010858
14641 Ignore-this: 14b7550ca95ce423c9b0b7f6f14ffd2f
14642] {
14643hunk ./src/allmydata/test/test_mutable.py 2981
14644             self.failUnlessEqual(results, new_data))
14645         return d
14646 
14647+    def test_replace_beginning(self):
14648+        # We should be able to replace data at the beginning of the file
14649+        # without truncating the file
14650+        B = "beginning"
14651+        new_data = B + self.data[len(B):]
14652+        d = self.mdmf_node.get_best_mutable_version()
14653+        d.addCallback(lambda mv: mv.update(MutableData(B), 0))
14654+        d.addCallback(lambda ignored: self.mdmf_node.download_best_version())
14655+        d.addCallback(lambda results: self.failUnlessEqual(results, new_data))
14656+        return d
14657+
14658     def test_replace_segstart1(self):
14659         offset = 128*1024+1
14660         new_data = "NNNN"
14661hunk ./src/allmydata/test/test_web.py 3204
14662         d.addCallback(_get_data)
14663         d.addCallback(lambda results:
14664             self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
14665+        # and try replacing the beginning of the file
14666+        d.addCallback(lambda ignored:
14667+            self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
14668+        d.addCallback(_get_data)
14669+        d.addCallback(lambda results:
14670+            self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
14671         return d
14672 
14673     def test_PUT_update_at_invalid_offset(self):
14674hunk ./src/allmydata/web/common.py 55
14675     # message? Since this call is going to be used by programmers and
14676     # their tools rather than users (through the wui), it is not
14677     # inconsistent to return that, I guess.
14678-    return int(offset)
14679+    if offset is not None:
14680+        offset = int(offset)
14681+
14682+    return offset
14683 
14684 
14685 def get_root(ctx_or_req):
14686hunk ./src/allmydata/web/filenode.py 219
14687         req = IRequest(ctx)
14688         t = get_arg(req, "t", "").strip()
14689         replace = parse_replace_arg(get_arg(req, "replace", "true"))
14690-        offset = parse_offset_arg(get_arg(req, "offset", False))
14691+        offset = parse_offset_arg(get_arg(req, "offset", None))
14692 
14693         if not t:
14694             if not replace:
14695hunk ./src/allmydata/web/filenode.py 229
14696                 raise ExistingChildError()
14697 
14698             if self.node.is_mutable():
14699-                if offset == False:
14700+                if offset is None:
14701                     return self.replace_my_contents(req)
14702 
14703                 if offset >= 0:
14704hunk ./src/allmydata/web/filenode.py 238
14705                 raise WebError("PUT to a mutable file: Invalid offset")
14706 
14707             else:
14708-                if offset != False:
14709+                if offset is not None:
14710                     raise WebError("PUT to a file: append operation invoked "
14711                                    "on an immutable cap")
14712 
14713}
14714[mutable/filenode: remove incorrect comments about segment boundaries
14715Kevan Carstensen <kevan@isnotajoke.com>**20110307081713
14716 Ignore-this: 7008644c3d9588815000a86edbf9c568
14717] {
14718hunk ./src/allmydata/mutable/filenode.py 1001
14719         offset. I return a Deferred that fires when this has been
14720         completed.
14721         """
14722-        # We have two cases here:
14723-        # 1. The new data will add few enough segments so that it does
14724-        #    not cross into the next power-of-two boundary.
14725-        # 2. It doesn't.
14726-        #
14727-        # In the former case, we can modify the file in place. In the
14728-        # latter case, we need to re-encode the file.
14729         new_size = data.get_size() + offset
14730         old_size = self.get_size()
14731         segment_size = self._version[3]
14732hunk ./src/allmydata/mutable/filenode.py 1011
14733         log.msg("got %d old segments, %d new segments" % \
14734                         (num_old_segments, num_new_segments))
14735 
14736-        # We also do a whole file re-encode if the file is an SDMF file.
14737+        # We do a whole file re-encode if the file is an SDMF file.
14738         if self._version[2]: # version[2] == SDMF salt, which MDMF lacks
14739             log.msg("doing re-encode instead of in-place update")
14740             return self._do_modify_update(data, offset)
14741hunk ./src/allmydata/mutable/filenode.py 1016
14742 
14743+        # Otherwise, we can replace just the parts that are changing.
14744         log.msg("updating in place")
14745         d = self._do_update_update(data, offset)
14746         d.addCallback(self._decode_and_decrypt_segments, data, offset)
14747}
14748[mutable: use integer division where appropriate
14749Kevan Carstensen <kevan@isnotajoke.com>**20110307082229
14750 Ignore-this: a8767e89d919c9f2a5d5fef3953d53f9
14751] {
14752hunk ./src/allmydata/mutable/filenode.py 1055
14753         segsize = self._version[3]
14754         # We'll need the segment that the data starts in, regardless of
14755         # what we'll do later.
14756-        start_segment = mathutil.div_ceil(offset, segsize)
14757-        start_segment -= 1
14758+        start_segment = offset // segsize
14759 
14760         # We only need the end segment if the data we append does not go
14761         # beyond the current end-of-file.
14762hunk ./src/allmydata/mutable/filenode.py 1062
14763         end_segment = start_segment
14764         if offset + data.get_size() < self.get_size():
14765             end_data = offset + data.get_size()
14766-            end_segment = mathutil.div_ceil(end_data, segsize)
14767-            end_segment -= 1
14768+            end_segment = end_data // segsize
14769+
14770         self._start_segment = start_segment
14771         self._end_segment = end_segment
14772 
14773hunk ./src/allmydata/mutable/publish.py 547
14774 
14775         # Calculate the starting segment for the upload.
14776         if segment_size:
14777+            # We use div_ceil instead of integer division here because
14778+            # it is semantically correct.
14779+            # If datalength isn't an even multiple of segment_size, but
14780+            # is larger than segment_size, datalength // segment_size
14781+            # will be the largest number such that num <= datalength and
14782+            # num % segment_size == 0. But that's not what we want,
14783+            # because it ignores the extra data. div_ceil will give us
14784+            # the right number of segments for the data that we're
14785+            # given.
14786             self.num_segments = mathutil.div_ceil(self.datalength,
14787                                                   segment_size)
14788hunk ./src/allmydata/mutable/publish.py 558
14789-            self.starting_segment = mathutil.div_ceil(offset,
14790-                                                      segment_size)
14791-            if offset % segment_size != 0:
14792-                self.starting_segment -= 1
14793-            if offset == 0:
14794-                self.starting_segment = 0
14795+
14796+            self.starting_segment = offset // segment_size
14797 
14798         else:
14799             self.num_segments = 0
14800hunk ./src/allmydata/mutable/publish.py 604
14801         self.end_segment = self.num_segments - 1
14802         # Now figure out where the last segment should be.
14803         if self.data.get_size() != self.datalength:
14804+            # We're updating a few segments in the middle of a mutable
14805+            # file, so we don't want to republish the whole thing.
14806+            # (we don't have enough data to do that even if we wanted
14807+            # to)
14808             end = self.data.get_size()
14809hunk ./src/allmydata/mutable/publish.py 609
14810-            self.end_segment = mathutil.div_ceil(end,
14811-                                                 segment_size)
14812-            self.end_segment -= 1
14813+            self.end_segment = end // segment_size
14814+            if end % segment_size == 0:
14815+                self.end_segment -= 1
14816+
14817         self.log("got start segment %d" % self.starting_segment)
14818         self.log("got end segment %d" % self.end_segment)
14819 
14820}
14821[mutable/layout.py: reorder on-disk format to aput variable-length fields at the end of the share, after a predictably long preamble
14822Kevan Carstensen <kevan@isnotajoke.com>**20110501224125
14823 Ignore-this: 8b2c5d29b8984dfe675c1a2ada5205cf
14824] {
14825hunk ./src/allmydata/mutable/layout.py 539
14826                                      self._readvs)
14827 
14828 
14829-MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
14830+MDMFHEADER = ">BQ32sBBQQ QQQQQQQQ"
14831 MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
14832 MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
14833 MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
14834hunk ./src/allmydata/mutable/layout.py 545
14835 MDMFCHECKSTRING = ">BQ32s"
14836 MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
14837-MDMFOFFSETS = ">QQQQQQ"
14838+MDMFOFFSETS = ">QQQQQQQQ"
14839 MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
14840hunk ./src/allmydata/mutable/layout.py 547
14841+# XXX Fix this.
14842+PRIVATE_KEY_SIZE = 2000
14843+SIGNATURE_SIZE = 10000
14844+VERIFICATION_KEY_SIZE = 2000
14845+# We know we won't ever have more than 256 shares.
14846+# XXX: This, too, can be
14847+SHARE_HASH_CHAIN_SIZE = HASH_SIZE * 256
14848 
14849 class MDMFSlotWriteProxy:
14850     implements(IMutableSlotWriter)
14851hunk ./src/allmydata/mutable/layout.py 577
14852     # 51          8           The data length of the original plaintext
14853     #-- end signed part --
14854     # 59          8           The offset of the encrypted private key
14855-    # 83          8           The offset of the signature
14856-    # 91          8           The offset of the verification key
14857-    # 67          8           The offset of the block hash tree
14858-    # 75          8           The offset of the share hash chain
14859-    # 99          8           The offset of the EOF
14860-    #
14861-    # followed by salts and share data, the encrypted private key, the
14862-    # block hash tree, the share hash chain, a signature over the first
14863-    # eight fields, and a verification key.
14864+    # 67          8           The offset of the signature
14865+    # 75          8           The offset of the verification key
14866+    # 83          8           The offset of the end of the v. key.
14867+    # 92          8           The offset of the share data
14868+    # 100         8           The offset of the block hash tree
14869+    # 108         8           The offset of the share hash chain
14870+    # 116         8           The offset of EOF
14871     #
14872hunk ./src/allmydata/mutable/layout.py 585
14873+    # followed by the encrypted private key, signature, verification
14874+    # key, share hash chain, data, and block hash tree. We order the
14875+    # fields that way to make smart downloaders -- downloaders which
14876+    # prempetively read a big part of the share -- possible.
14877+    #
14878     # The checkstring is the first three fields -- the version number,
14879     # sequence number, root hash and root salt hash. This is consistent
14880     # in meaning to what we have with SDMF files, except now instead of
14881hunk ./src/allmydata/mutable/layout.py 792
14882         data_size += self._tail_block_size
14883         data_size += SALT_SIZE
14884         self._offsets['enc_privkey'] = MDMFHEADERSIZE
14885-        self._offsets['enc_privkey'] += data_size
14886-        # We'll wait for the rest. Callers can now call my "put_block" and
14887-        # "set_checkstring" methods.
14888+
14889+        # We don't define offsets for these because we want them to be
14890+        # tightly packed -- this allows us to ignore the responsibility
14891+        # of padding individual values, and of removing that padding
14892+        # later. So nonconstant_start is where we start writing
14893+        # nonconstant data.
14894+        nonconstant_start = self._offsets['enc_privkey']
14895+        nonconstant_start += PRIVATE_KEY_SIZE
14896+        nonconstant_start += SIGNATURE_SIZE
14897+        nonconstant_start += VERIFICATION_KEY_SIZE
14898+        nonconstant_start += SHARE_HASH_CHAIN_SIZE
14899+
14900+        self._offsets['share_data'] = nonconstant_start
14901+
14902+        # Finally, we know how big the share data will be, so we can
14903+        # figure out where the block hash tree needs to go.
14904+        # XXX: But this will go away if Zooko wants to make it so that
14905+        # you don't need to know the size of the file before you start
14906+        # uploading it.
14907+        self._offsets['block_hash_tree'] = self._offsets['share_data'] + \
14908+                    data_size
14909+
14910+        # Done. We can snow start writing.
14911 
14912 
14913     def set_checkstring(self,
14914hunk ./src/allmydata/mutable/layout.py 891
14915         anything to be written yet.
14916         """
14917         if segnum >= self._num_segments:
14918-            raise LayoutInvalid("I won't overwrite the private key")
14919+            raise LayoutInvalid("I won't overwrite the block hash tree")
14920         if len(salt) != SALT_SIZE:
14921             raise LayoutInvalid("I was given a salt of size %d, but "
14922                                 "I wanted a salt of size %d")
14923hunk ./src/allmydata/mutable/layout.py 902
14924             raise LayoutInvalid("I was given the wrong size block to write")
14925 
14926         # We want to write at len(MDMFHEADER) + segnum * block_size.
14927-        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
14928+        offset = self._offsets['share_data'] + \
14929+            (self._actual_block_size * segnum)
14930         data = salt + data
14931 
14932         self._writevs.append(tuple([offset, data]))
14933hunk ./src/allmydata/mutable/layout.py 922
14934         # tree, the block hash tree writing method writes the offset of
14935         # the share hash chain. So that's a good indicator of whether or
14936         # not the block hash tree has been written.
14937-        if "share_hash_chain" in self._offsets:
14938-            raise LayoutInvalid("You must write this before the block hash tree")
14939+        if "signature" in self._offsets:
14940+            raise LayoutInvalid("You can't put the encrypted private key "
14941+                                "after putting the share hash chain")
14942+
14943+        self._offsets['share_hash_chain'] = self._offsets['enc_privkey'] + \
14944+                len(encprivkey)
14945 
14946hunk ./src/allmydata/mutable/layout.py 929
14947-        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + \
14948-            len(encprivkey)
14949         self._writevs.append(tuple([self._offsets['enc_privkey'], encprivkey]))
14950 
14951 
14952hunk ./src/allmydata/mutable/layout.py 944
14953         offset of the share hash chain.
14954         """
14955         assert self._offsets
14956+        assert "block_hash_tree" in self._offsets
14957+
14958         assert isinstance(blockhashes, list)
14959hunk ./src/allmydata/mutable/layout.py 947
14960-        if "block_hash_tree" not in self._offsets:
14961-            raise LayoutInvalid("You must put the encrypted private key "
14962-                                "before you put the block hash tree")
14963-        # If written, the share hash chain causes the signature offset
14964-        # to be defined.
14965-        if "signature" in self._offsets:
14966-            raise LayoutInvalid("You must put the block hash tree before "
14967-                                "you put the share hash chain")
14968+
14969         blockhashes_s = "".join(blockhashes)
14970hunk ./src/allmydata/mutable/layout.py 949
14971-        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
14972+        self._offsets['EOF'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
14973 
14974         self._writevs.append(tuple([self._offsets['block_hash_tree'],
14975                                   blockhashes_s]))
14976hunk ./src/allmydata/mutable/layout.py 969
14977         is before you can generate a valid signature.
14978         """
14979         assert isinstance(sharehashes, dict)
14980+        assert self._offsets
14981         if "share_hash_chain" not in self._offsets:
14982hunk ./src/allmydata/mutable/layout.py 971
14983-            raise LayoutInvalid("You need to put the block hash tree before "
14984-                                "you can put the share hash chain")
14985+            raise LayoutInvalid("You must put the block hash tree before "
14986+                                "putting the share hash chain")
14987+
14988         # The signature comes after the share hash chain. If the
14989         # signature has already been written, we must not write another
14990         # share hash chain. The signature writes the verification key
14991hunk ./src/allmydata/mutable/layout.py 984
14992                                 "before you write the signature")
14993         sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
14994                                   for i in sorted(sharehashes.keys())])
14995-        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
14996+        self._offsets['signature'] = self._offsets['share_hash_chain'] + \
14997+            len(sharehashes_s)
14998         self._writevs.append(tuple([self._offsets['share_hash_chain'],
14999                             sharehashes_s]))
15000 
15001hunk ./src/allmydata/mutable/layout.py 1002
15002         # Signature is defined by the routine that places the share hash
15003         # chain, so it's a good thing to look for in finding out whether
15004         # or not the share hash chain exists on the remote server.
15005-        if "signature" not in self._offsets:
15006-            raise LayoutInvalid("You need to put the share hash chain "
15007-                                "before you can put the root share hash")
15008         if len(roothash) != HASH_SIZE:
15009             raise LayoutInvalid("hashes and salts must be exactly %d bytes"
15010                                  % HASH_SIZE)
15011hunk ./src/allmydata/mutable/layout.py 1053
15012         # If we put the signature after we put the verification key, we
15013         # could end up running into the verification key, and will
15014         # probably screw up the offsets as well. So we don't allow that.
15015+        if "verification_key_end" in self._offsets:
15016+            raise LayoutInvalid("You can't put the signature after the "
15017+                                "verification key")
15018         # The method that writes the verification key defines the EOF
15019         # offset before writing the verification key, so look for that.
15020hunk ./src/allmydata/mutable/layout.py 1058
15021-        if "EOF" in self._offsets:
15022-            raise LayoutInvalid("You must write the signature before the verification key")
15023-
15024-        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
15025+        self._offsets['verification_key'] = self._offsets['signature'] +\
15026+            len(signature)
15027         self._writevs.append(tuple([self._offsets['signature'], signature]))
15028 
15029 
15030hunk ./src/allmydata/mutable/layout.py 1074
15031         if "verification_key" not in self._offsets:
15032             raise LayoutInvalid("You must put the signature before you "
15033                                 "can put the verification key")
15034-        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
15035+
15036+        self._offsets['verification_key_end'] = \
15037+            self._offsets['verification_key'] + len(verification_key)
15038         self._writevs.append(tuple([self._offsets['verification_key'],
15039                             verification_key]))
15040 
15041hunk ./src/allmydata/mutable/layout.py 1102
15042         of the write vectors that I've dealt with so far to be published
15043         to the remote server, ending the write process.
15044         """
15045-        if "EOF" not in self._offsets:
15046+        if "verification_key_end" not in self._offsets:
15047             raise LayoutInvalid("You must put the verification key before "
15048                                 "you can publish the offsets")
15049         offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
15050hunk ./src/allmydata/mutable/layout.py 1108
15051         offsets = struct.pack(MDMFOFFSETS,
15052                               self._offsets['enc_privkey'],
15053-                              self._offsets['block_hash_tree'],
15054                               self._offsets['share_hash_chain'],
15055                               self._offsets['signature'],
15056                               self._offsets['verification_key'],
15057hunk ./src/allmydata/mutable/layout.py 1111
15058+                              self._offsets['verification_key_end'],
15059+                              self._offsets['share_data'],
15060+                              self._offsets['block_hash_tree'],
15061                               self._offsets['EOF'])
15062         self._writevs.append(tuple([offsets_offset, offsets]))
15063         encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
15064hunk ./src/allmydata/mutable/layout.py 1227
15065         # MDMF, though we'll be left with 4 more bytes than we
15066         # need if this ends up being MDMF. This is probably less
15067         # expensive than the cost of a second roundtrip.
15068-        readvs = [(0, 107)]
15069+        readvs = [(0, 123)]
15070         d = self._read(readvs, force_remote)
15071         d.addCallback(self._process_encoding_parameters)
15072         d.addCallback(self._process_offsets)
15073hunk ./src/allmydata/mutable/layout.py 1330
15074             read_length = MDMFOFFSETS_LENGTH
15075             end = read_offset + read_length
15076             (encprivkey,
15077-             blockhashes,
15078              sharehashes,
15079              signature,
15080              verification_key,
15081hunk ./src/allmydata/mutable/layout.py 1333
15082+             verification_key_end,
15083+             sharedata,
15084+             blockhashes,
15085              eof) = struct.unpack(MDMFOFFSETS,
15086                                   offsets[read_offset:end])
15087             self._offsets = {}
15088hunk ./src/allmydata/mutable/layout.py 1344
15089             self._offsets['share_hash_chain'] = sharehashes
15090             self._offsets['signature'] = signature
15091             self._offsets['verification_key'] = verification_key
15092+            self._offsets['verification_key_end']= \
15093+                verification_key_end
15094             self._offsets['EOF'] = eof
15095hunk ./src/allmydata/mutable/layout.py 1347
15096+            self._offsets['share_data'] = sharedata
15097 
15098 
15099     def get_block_and_salt(self, segnum, queue=False):
15100hunk ./src/allmydata/mutable/layout.py 1357
15101         """
15102         d = self._maybe_fetch_offsets_and_header()
15103         def _then(ignored):
15104-            if self._version_number == 1:
15105-                base_share_offset = MDMFHEADERSIZE
15106-            else:
15107-                base_share_offset = self._offsets['share_data']
15108+            base_share_offset = self._offsets['share_data']
15109 
15110             if segnum + 1 > self._num_segments:
15111                 raise LayoutInvalid("Not a valid segment number")
15112hunk ./src/allmydata/mutable/layout.py 1430
15113         def _then(ignored):
15114             blockhashes_offset = self._offsets['block_hash_tree']
15115             if self._version_number == 1:
15116-                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
15117+                blockhashes_length = self._offsets['EOF'] - blockhashes_offset
15118             else:
15119                 blockhashes_length = self._offsets['share_data'] - blockhashes_offset
15120             readvs = [(blockhashes_offset, blockhashes_length)]
15121hunk ./src/allmydata/mutable/layout.py 1501
15122             if self._version_number == 0:
15123                 privkey_length = self._offsets['EOF'] - privkey_offset
15124             else:
15125-                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
15126+                privkey_length = self._offsets['share_hash_chain'] - privkey_offset
15127             readvs = [(privkey_offset, privkey_length)]
15128             return readvs
15129         d.addCallback(_make_readvs)
15130hunk ./src/allmydata/mutable/layout.py 1549
15131         def _make_readvs(ignored):
15132             if self._version_number == 1:
15133                 vk_offset = self._offsets['verification_key']
15134-                vk_length = self._offsets['EOF'] - vk_offset
15135+                vk_length = self._offsets['verification_key_end'] - vk_offset
15136             else:
15137                 vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
15138                 vk_length = self._offsets['signature'] - vk_offset
15139hunk ./src/allmydata/test/test_storage.py 26
15140 from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
15141                                      LayoutInvalid, MDMFSIGNABLEHEADER, \
15142                                      SIGNED_PREFIX, MDMFHEADER, \
15143-                                     MDMFOFFSETS, SDMFSlotWriteProxy
15144+                                     MDMFOFFSETS, SDMFSlotWriteProxy, \
15145+                                     PRIVATE_KEY_SIZE, \
15146+                                     SIGNATURE_SIZE, \
15147+                                     VERIFICATION_KEY_SIZE, \
15148+                                     SHARE_HASH_CHAIN_SIZE
15149 from allmydata.interfaces import BadWriteEnablerError
15150 from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
15151 from allmydata.test.common_web import WebRenderingMixin
15152hunk ./src/allmydata/test/test_storage.py 1408
15153 
15154         # The encrypted private key comes after the shares + salts
15155         offset_size = struct.calcsize(MDMFOFFSETS)
15156-        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
15157-        # The blockhashes come after the private key
15158-        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
15159-        # The sharehashes come after the salt hashes
15160-        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
15161-        # The signature comes after the share hash chain
15162+        encrypted_private_key_offset = len(data) + offset_size
15163+        # The share has chain comes after the private key
15164+        sharehashes_offset = encrypted_private_key_offset + \
15165+            len(self.encprivkey)
15166+
15167+        # The signature comes after the share hash chain.
15168         signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
15169hunk ./src/allmydata/test/test_storage.py 1415
15170-        # The verification key comes after the signature
15171-        verification_offset = signature_offset + len(self.signature)
15172-        # The EOF comes after the verification key
15173-        eof_offset = verification_offset + len(self.verification_key)
15174+
15175+        verification_key_offset = signature_offset + len(self.signature)
15176+        verification_key_end = verification_key_offset + \
15177+            len(self.verification_key)
15178+
15179+        share_data_offset = offset_size
15180+        share_data_offset += PRIVATE_KEY_SIZE
15181+        share_data_offset += SIGNATURE_SIZE
15182+        share_data_offset += VERIFICATION_KEY_SIZE
15183+        share_data_offset += SHARE_HASH_CHAIN_SIZE
15184+
15185+        blockhashes_offset = share_data_offset + len(sharedata)
15186+        eof_offset = blockhashes_offset + len(self.block_hash_tree_s)
15187+
15188         data += struct.pack(MDMFOFFSETS,
15189                             encrypted_private_key_offset,
15190hunk ./src/allmydata/test/test_storage.py 1431
15191-                            blockhashes_offset,
15192                             sharehashes_offset,
15193                             signature_offset,
15194hunk ./src/allmydata/test/test_storage.py 1433
15195-                            verification_offset,
15196+                            verification_key_offset,
15197+                            verification_key_end,
15198+                            share_data_offset,
15199+                            blockhashes_offset,
15200                             eof_offset)
15201hunk ./src/allmydata/test/test_storage.py 1438
15202+
15203         self.offsets = {}
15204         self.offsets['enc_privkey'] = encrypted_private_key_offset
15205         self.offsets['block_hash_tree'] = blockhashes_offset
15206hunk ./src/allmydata/test/test_storage.py 1444
15207         self.offsets['share_hash_chain'] = sharehashes_offset
15208         self.offsets['signature'] = signature_offset
15209-        self.offsets['verification_key'] = verification_offset
15210+        self.offsets['verification_key'] = verification_key_offset
15211+        self.offsets['share_data'] = share_data_offset
15212+        self.offsets['verification_key_end'] = verification_key_end
15213         self.offsets['EOF'] = eof_offset
15214hunk ./src/allmydata/test/test_storage.py 1448
15215-        # Next, we'll add in the salts and share data,
15216-        data += sharedata
15217+
15218         # the private key,
15219         data += self.encprivkey
15220hunk ./src/allmydata/test/test_storage.py 1451
15221-        # the block hash tree,
15222-        data += self.block_hash_tree_s
15223-        # the share hash chain,
15224+        # the sharehashes
15225         data += self.share_hash_chain_s
15226         # the signature,
15227         data += self.signature
15228hunk ./src/allmydata/test/test_storage.py 1457
15229         # and the verification key
15230         data += self.verification_key
15231+        # Then we'll add in gibberish until we get to the right point.
15232+        nulls = "".join([" " for i in xrange(len(data), share_data_offset)])
15233+        data += nulls
15234+
15235+        # Then the share data
15236+        data += sharedata
15237+        # the blockhashes
15238+        data += self.block_hash_tree_s
15239         return data
15240 
15241 
15242hunk ./src/allmydata/test/test_storage.py 1729
15243         return d
15244 
15245 
15246-    def test_blockhashes_after_share_hash_chain(self):
15247+    def test_private_key_after_share_hash_chain(self):
15248         mw = self._make_new_mw("si1", 0)
15249         d = defer.succeed(None)
15250hunk ./src/allmydata/test/test_storage.py 1732
15251-        # Put everything up to and including the share hash chain
15252         for i in xrange(6):
15253             d.addCallback(lambda ignored, i=i:
15254                 mw.put_block(self.block, i, self.salt))
15255hunk ./src/allmydata/test/test_storage.py 1738
15256         d.addCallback(lambda ignored:
15257             mw.put_encprivkey(self.encprivkey))
15258         d.addCallback(lambda ignored:
15259-            mw.put_blockhashes(self.block_hash_tree))
15260-        d.addCallback(lambda ignored:
15261             mw.put_sharehashes(self.share_hash_chain))
15262 
15263hunk ./src/allmydata/test/test_storage.py 1740
15264-        # Now try to put the block hash tree again.
15265+        # Now try to put the private key again.
15266         d.addCallback(lambda ignored:
15267hunk ./src/allmydata/test/test_storage.py 1742
15268-            self.shouldFail(LayoutInvalid, "test repeat salthashes",
15269-                            None,
15270-                            mw.put_blockhashes, self.block_hash_tree))
15271-        return d
15272-
15273-
15274-    def test_encprivkey_after_blockhashes(self):
15275-        mw = self._make_new_mw("si1", 0)
15276-        d = defer.succeed(None)
15277-        # Put everything up to and including the block hash tree
15278-        for i in xrange(6):
15279-            d.addCallback(lambda ignored, i=i:
15280-                mw.put_block(self.block, i, self.salt))
15281-        d.addCallback(lambda ignored:
15282-            mw.put_encprivkey(self.encprivkey))
15283-        d.addCallback(lambda ignored:
15284-            mw.put_blockhashes(self.block_hash_tree))
15285-        d.addCallback(lambda ignored:
15286-            self.shouldFail(LayoutInvalid, "out of order private key",
15287+            self.shouldFail(LayoutInvalid, "test repeat private key",
15288                             None,
15289                             mw.put_encprivkey, self.encprivkey))
15290         return d
15291hunk ./src/allmydata/test/test_storage.py 1748
15292 
15293 
15294-    def test_share_hash_chain_after_signature(self):
15295-        mw = self._make_new_mw("si1", 0)
15296-        d = defer.succeed(None)
15297-        # Put everything up to and including the signature
15298-        for i in xrange(6):
15299-            d.addCallback(lambda ignored, i=i:
15300-                mw.put_block(self.block, i, self.salt))
15301-        d.addCallback(lambda ignored:
15302-            mw.put_encprivkey(self.encprivkey))
15303-        d.addCallback(lambda ignored:
15304-            mw.put_blockhashes(self.block_hash_tree))
15305-        d.addCallback(lambda ignored:
15306-            mw.put_sharehashes(self.share_hash_chain))
15307-        d.addCallback(lambda ignored:
15308-            mw.put_root_hash(self.root_hash))
15309-        d.addCallback(lambda ignored:
15310-            mw.put_signature(self.signature))
15311-        # Now try to put the share hash chain again. This should fail
15312-        d.addCallback(lambda ignored:
15313-            self.shouldFail(LayoutInvalid, "out of order share hash chain",
15314-                            None,
15315-                            mw.put_sharehashes, self.share_hash_chain))
15316-        return d
15317-
15318-
15319     def test_signature_after_verification_key(self):
15320         mw = self._make_new_mw("si1", 0)
15321         d = defer.succeed(None)
15322hunk ./src/allmydata/test/test_storage.py 1877
15323         mw = self._make_new_mw("si1", 0)
15324         # Test writing some blocks.
15325         read = self.ss.remote_slot_readv
15326-        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
15327+        expected_private_key_offset = struct.calcsize(MDMFHEADER)
15328+        expected_sharedata_offset = struct.calcsize(MDMFHEADER) + \
15329+                                    PRIVATE_KEY_SIZE + \
15330+                                    SIGNATURE_SIZE + \
15331+                                    VERIFICATION_KEY_SIZE + \
15332+                                    SHARE_HASH_CHAIN_SIZE
15333         written_block_size = 2 + len(self.salt)
15334         written_block = self.block + self.salt
15335         for i in xrange(6):
15336hunk ./src/allmydata/test/test_storage.py 1903
15337                 self.failUnlessEqual(read("si1", [0], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
15338                                 {0: [written_block]})
15339 
15340-            expected_private_key_offset = expected_sharedata_offset + \
15341-                                      len(written_block) * 6
15342             self.failUnlessEqual(len(self.encprivkey), 7)
15343             self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
15344                                  {0: [self.encprivkey]})
15345hunk ./src/allmydata/test/test_storage.py 1907
15346 
15347-            expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
15348+            expected_block_hash_offset = expected_sharedata_offset + \
15349+                        (6 * written_block_size)
15350             self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
15351             self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
15352                                  {0: [self.block_hash_tree_s]})
15353hunk ./src/allmydata/test/test_storage.py 1913
15354 
15355-            expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
15356+            expected_share_hash_offset = expected_private_key_offset + len(self.encprivkey)
15357             self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
15358                                  {0: [self.share_hash_chain_s]})
15359 
15360hunk ./src/allmydata/test/test_storage.py 1919
15361             self.failUnlessEqual(read("si1", [0], [(9, 32)]),
15362                                  {0: [self.root_hash]})
15363-            expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
15364+            expected_signature_offset = expected_share_hash_offset + \
15365+                len(self.share_hash_chain_s)
15366             self.failUnlessEqual(len(self.signature), 9)
15367             self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
15368                                  {0: [self.signature]})
15369hunk ./src/allmydata/test/test_storage.py 1941
15370             self.failUnlessEqual(n, 10)
15371             self.failUnlessEqual(segsize, 6)
15372             self.failUnlessEqual(datalen, 36)
15373-            expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
15374+            expected_eof_offset = expected_block_hash_offset + \
15375+                len(self.block_hash_tree_s)
15376 
15377             # Check the version number to make sure that it is correct.
15378             expected_version_number = struct.pack(">B", 1)
15379hunk ./src/allmydata/test/test_storage.py 1969
15380             expected_offset = struct.pack(">Q", expected_private_key_offset)
15381             self.failUnlessEqual(read("si1", [0], [(59, 8)]),
15382                                  {0: [expected_offset]})
15383-            expected_offset = struct.pack(">Q", expected_block_hash_offset)
15384+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
15385             self.failUnlessEqual(read("si1", [0], [(67, 8)]),
15386                                  {0: [expected_offset]})
15387hunk ./src/allmydata/test/test_storage.py 1972
15388-            expected_offset = struct.pack(">Q", expected_share_hash_offset)
15389+            expected_offset = struct.pack(">Q", expected_signature_offset)
15390             self.failUnlessEqual(read("si1", [0], [(75, 8)]),
15391                                  {0: [expected_offset]})
15392hunk ./src/allmydata/test/test_storage.py 1975
15393-            expected_offset = struct.pack(">Q", expected_signature_offset)
15394+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
15395             self.failUnlessEqual(read("si1", [0], [(83, 8)]),
15396                                  {0: [expected_offset]})
15397hunk ./src/allmydata/test/test_storage.py 1978
15398-            expected_offset = struct.pack(">Q", expected_verification_key_offset)
15399+            expected_offset = struct.pack(">Q", expected_verification_key_offset + len(self.verification_key))
15400             self.failUnlessEqual(read("si1", [0], [(91, 8)]),
15401                                  {0: [expected_offset]})
15402hunk ./src/allmydata/test/test_storage.py 1981
15403-            expected_offset = struct.pack(">Q", expected_eof_offset)
15404+            expected_offset = struct.pack(">Q", expected_sharedata_offset)
15405             self.failUnlessEqual(read("si1", [0], [(99, 8)]),
15406                                  {0: [expected_offset]})
15407hunk ./src/allmydata/test/test_storage.py 1984
15408+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
15409+            self.failUnlessEqual(read("si1", [0], [(107, 8)]),
15410+                                 {0: [expected_offset]})
15411+            expected_offset = struct.pack(">Q", expected_eof_offset)
15412+            self.failUnlessEqual(read("si1", [0], [(115, 8)]),
15413+                                 {0: [expected_offset]})
15414         d.addCallback(_check_publish)
15415         return d
15416 
15417hunk ./src/allmydata/test/test_storage.py 2117
15418         for i in xrange(6):
15419             d.addCallback(lambda ignored, i=i:
15420                 mw0.put_block(self.block, i, self.salt))
15421-        # Try to write the block hashes before writing the encrypted
15422-        # private key
15423-        d.addCallback(lambda ignored:
15424-            self.shouldFail(LayoutInvalid, "block hashes before key",
15425-                            None, mw0.put_blockhashes,
15426-                            self.block_hash_tree))
15427-
15428-        # Write the private key.
15429-        d.addCallback(lambda ignored:
15430-            mw0.put_encprivkey(self.encprivkey))
15431-
15432 
15433hunk ./src/allmydata/test/test_storage.py 2118
15434-        # Try to write the share hash chain without writing the block
15435-        # hash tree
15436+        # Try to write the share hash chain without writing the
15437+        # encrypted private key
15438         d.addCallback(lambda ignored:
15439             self.shouldFail(LayoutInvalid, "share hash chain before "
15440hunk ./src/allmydata/test/test_storage.py 2122
15441-                                           "salt hash tree",
15442+                                           "private key",
15443                             None,
15444                             mw0.put_sharehashes, self.share_hash_chain))
15445hunk ./src/allmydata/test/test_storage.py 2125
15446-
15447-        # Try to write the root hash and without writing either the
15448-        # block hashes or the or the share hashes
15449+        # Write the private key.
15450         d.addCallback(lambda ignored:
15451hunk ./src/allmydata/test/test_storage.py 2127
15452-            self.shouldFail(LayoutInvalid, "root hash before share hashes",
15453-                            None,
15454-                            mw0.put_root_hash, self.root_hash))
15455+            mw0.put_encprivkey(self.encprivkey))
15456 
15457         # Now write the block hashes and try again
15458         d.addCallback(lambda ignored:
15459hunk ./src/allmydata/test/test_storage.py 2133
15460             mw0.put_blockhashes(self.block_hash_tree))
15461 
15462-        d.addCallback(lambda ignored:
15463-            self.shouldFail(LayoutInvalid, "root hash before share hashes",
15464-                            None, mw0.put_root_hash, self.root_hash))
15465-
15466         # We haven't yet put the root hash on the share, so we shouldn't
15467         # be able to sign it.
15468         d.addCallback(lambda ignored:
15469hunk ./src/allmydata/test/test_storage.py 2378
15470         # This should be enough to fill in both the encoding parameters
15471         # and the table of offsets, which will complete the version
15472         # information tuple.
15473-        d.addCallback(_make_mr, 107)
15474+        d.addCallback(_make_mr, 123)
15475         d.addCallback(lambda mr:
15476             mr.get_verinfo())
15477         def _check_verinfo(verinfo):
15478hunk ./src/allmydata/test/test_storage.py 2412
15479         d.addCallback(_check_verinfo)
15480         # This is not enough data to read a block and a share, so the
15481         # wrapper should attempt to read this from the remote server.
15482-        d.addCallback(_make_mr, 107)
15483+        d.addCallback(_make_mr, 123)
15484         d.addCallback(lambda mr:
15485             mr.get_block_and_salt(0))
15486         def _check_block_and_salt((block, salt)):
15487hunk ./src/allmydata/test/test_storage.py 2420
15488             self.failUnlessEqual(salt, self.salt)
15489             self.failUnlessEqual(self.rref.read_count, 1)
15490         # This should be enough data to read one block.
15491-        d.addCallback(_make_mr, 249)
15492+        d.addCallback(_make_mr, 123 + PRIVATE_KEY_SIZE + SIGNATURE_SIZE + VERIFICATION_KEY_SIZE + SHARE_HASH_CHAIN_SIZE + 140)
15493         d.addCallback(lambda mr:
15494             mr.get_block_and_salt(0))
15495         d.addCallback(_check_block_and_salt)
15496hunk ./src/allmydata/test/test_storage.py 2438
15497         # This should be enough to get us the encoding parameters,
15498         # offset table, and everything else we need to build a verinfo
15499         # string.
15500-        d.addCallback(_make_mr, 107)
15501+        d.addCallback(_make_mr, 123)
15502         d.addCallback(lambda mr:
15503             mr.get_verinfo())
15504         def _check_verinfo(verinfo):
15505hunk ./src/allmydata/test/test_storage.py 2473
15506             self.failUnlessEqual(self.rref.read_count, 0)
15507         d.addCallback(_check_verinfo)
15508         # This shouldn't be enough to read any share data.
15509-        d.addCallback(_make_mr, 107)
15510+        d.addCallback(_make_mr, 123)
15511         d.addCallback(lambda mr:
15512             mr.get_block_and_salt(0))
15513         def _check_block_and_salt((block, salt)):
15514}
15515[uri.py: Add MDMF cap
15516Kevan Carstensen <kevan@isnotajoke.com>**20110501224249
15517 Ignore-this: a6d1046d33f5cc811c5e8b10af925f33
15518] {
15519hunk ./src/allmydata/interfaces.py 546
15520 
15521 class IMutableFileURI(Interface):
15522     """I am a URI which represents a mutable filenode."""
15523+    def get_extension_params():
15524+        """Return the extension parameters in the URI"""
15525 
15526 class IDirectoryURI(Interface):
15527     pass
15528hunk ./src/allmydata/test/test_uri.py 2
15529 
15530+import re
15531 from twisted.trial import unittest
15532 from allmydata import uri
15533 from allmydata.util import hashutil, base32
15534hunk ./src/allmydata/test/test_uri.py 259
15535         uri.CHKFileURI.init_from_string(fileURI)
15536 
15537 class Mutable(testutil.ReallyEqualMixin, unittest.TestCase):
15538-    def test_pack(self):
15539-        writekey = "\x01" * 16
15540-        fingerprint = "\x02" * 32
15541+    def setUp(self):
15542+        self.writekey = "\x01" * 16
15543+        self.fingerprint = "\x02" * 32
15544+        self.readkey = hashutil.ssk_readkey_hash(self.writekey)
15545+        self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
15546 
15547hunk ./src/allmydata/test/test_uri.py 265
15548-        u = uri.WriteableSSKFileURI(writekey, fingerprint)
15549-        self.failUnlessReallyEqual(u.writekey, writekey)
15550-        self.failUnlessReallyEqual(u.fingerprint, fingerprint)
15551+    def test_pack(self):
15552+        u = uri.WriteableSSKFileURI(self.writekey, self.fingerprint)
15553+        self.failUnlessReallyEqual(u.writekey, self.writekey)
15554+        self.failUnlessReallyEqual(u.fingerprint, self.fingerprint)
15555         self.failIf(u.is_readonly())
15556         self.failUnless(u.is_mutable())
15557         self.failUnless(IURI.providedBy(u))
15558hunk ./src/allmydata/test/test_uri.py 281
15559         self.failUnlessReallyEqual(u, u_h)
15560 
15561         u2 = uri.from_string(u.to_string())
15562-        self.failUnlessReallyEqual(u2.writekey, writekey)
15563-        self.failUnlessReallyEqual(u2.fingerprint, fingerprint)
15564+        self.failUnlessReallyEqual(u2.writekey, self.writekey)
15565+        self.failUnlessReallyEqual(u2.fingerprint, self.fingerprint)
15566         self.failIf(u2.is_readonly())
15567         self.failUnless(u2.is_mutable())
15568         self.failUnless(IURI.providedBy(u2))
15569hunk ./src/allmydata/test/test_uri.py 297
15570         self.failUnless(isinstance(u2imm, uri.UnknownURI), u2imm)
15571 
15572         u3 = u2.get_readonly()
15573-        readkey = hashutil.ssk_readkey_hash(writekey)
15574-        self.failUnlessReallyEqual(u3.fingerprint, fingerprint)
15575+        readkey = hashutil.ssk_readkey_hash(self.writekey)
15576+        self.failUnlessReallyEqual(u3.fingerprint, self.fingerprint)
15577         self.failUnlessReallyEqual(u3.readkey, readkey)
15578         self.failUnless(u3.is_readonly())
15579         self.failUnless(u3.is_mutable())
15580hunk ./src/allmydata/test/test_uri.py 317
15581         u3_h = uri.ReadonlySSKFileURI.init_from_human_encoding(he)
15582         self.failUnlessReallyEqual(u3, u3_h)
15583 
15584-        u4 = uri.ReadonlySSKFileURI(readkey, fingerprint)
15585-        self.failUnlessReallyEqual(u4.fingerprint, fingerprint)
15586+        u4 = uri.ReadonlySSKFileURI(readkey, self.fingerprint)
15587+        self.failUnlessReallyEqual(u4.fingerprint, self.fingerprint)
15588         self.failUnlessReallyEqual(u4.readkey, readkey)
15589         self.failUnless(u4.is_readonly())
15590         self.failUnless(u4.is_mutable())
15591hunk ./src/allmydata/test/test_uri.py 350
15592         self.failUnlessReallyEqual(u5, u5_h)
15593 
15594 
15595+    def test_writable_mdmf_cap(self):
15596+        u1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15597+        cap = u1.to_string()
15598+        u = uri.WritableMDMFFileURI.init_from_string(cap)
15599+
15600+        self.failUnless(IMutableFileURI.providedBy(u))
15601+        self.failUnlessReallyEqual(u.fingerprint, self.fingerprint)
15602+        self.failUnlessReallyEqual(u.writekey, self.writekey)
15603+        self.failUnless(u.is_mutable())
15604+        self.failIf(u.is_readonly())
15605+        self.failUnlessEqual(cap, u.to_string())
15606+
15607+        # Now get a readonly cap from the writable cap, and test that it
15608+        # degrades gracefully.
15609+        ru = u.get_readonly()
15610+        self.failUnlessReallyEqual(self.readkey, ru.readkey)
15611+        self.failUnlessReallyEqual(self.fingerprint, ru.fingerprint)
15612+        self.failUnless(ru.is_mutable())
15613+        self.failUnless(ru.is_readonly())
15614+
15615+        # Now get a verifier cap.
15616+        vu = ru.get_verify_cap()
15617+        self.failUnlessReallyEqual(self.storage_index, vu.storage_index)
15618+        self.failUnlessReallyEqual(self.fingerprint, vu.fingerprint)
15619+        self.failUnless(IVerifierURI.providedBy(vu))
15620+
15621+    def test_readonly_mdmf_cap(self):
15622+        u1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15623+        cap = u1.to_string()
15624+        u2 = uri.ReadonlyMDMFFileURI.init_from_string(cap)
15625+
15626+        self.failUnlessReallyEqual(u2.fingerprint, self.fingerprint)
15627+        self.failUnlessReallyEqual(u2.readkey, self.readkey)
15628+        self.failUnless(u2.is_readonly())
15629+        self.failUnless(u2.is_mutable())
15630+
15631+        vu = u2.get_verify_cap()
15632+        self.failUnlessEqual(u2.storage_index, self.storage_index)
15633+        self.failUnlessEqual(u2.fingerprint, self.fingerprint)
15634+
15635+    def test_create_writable_mdmf_cap_from_readcap(self):
15636+        # we shouldn't be able to create a writable MDMF cap given only a
15637+        # readcap.
15638+        u1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15639+        cap = u1.to_string()
15640+        self.failUnlessRaises(uri.BadURIError,
15641+                              uri.WritableMDMFFileURI.init_from_string,
15642+                              cap)
15643+
15644+    def test_create_writable_mdmf_cap_from_verifycap(self):
15645+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15646+        cap = u1.to_string()
15647+        self.failUnlessRaises(uri.BadURIError,
15648+                              uri.WritableMDMFFileURI.init_from_string,
15649+                              cap)
15650+
15651+    def test_create_readonly_mdmf_cap_from_verifycap(self):
15652+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15653+        cap = u1.to_string()
15654+        self.failUnlessRaises(uri.BadURIError,
15655+                              uri.ReadonlyMDMFFileURI.init_from_string,
15656+                              cap)
15657+
15658+    def test_mdmf_verifier_cap(self):
15659+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15660+        self.failUnless(u1.is_readonly())
15661+        self.failIf(u1.is_mutable())
15662+        self.failUnlessReallyEqual(self.storage_index, u1.storage_index)
15663+        self.failUnlessReallyEqual(self.fingerprint, u1.fingerprint)
15664+
15665+        cap = u1.to_string()
15666+        u2 = uri.MDMFVerifierURI.init_from_string(cap)
15667+        self.failUnless(u2.is_readonly())
15668+        self.failIf(u2.is_mutable())
15669+        self.failUnlessReallyEqual(self.storage_index, u2.storage_index)
15670+        self.failUnlessReallyEqual(self.fingerprint, u2.fingerprint)
15671+
15672+        u3 = u2.get_readonly()
15673+        self.failUnlessReallyEqual(u3, u2)
15674+
15675+        u4 = u2.get_verify_cap()
15676+        self.failUnlessReallyEqual(u4, u2)
15677+
15678+    def test_mdmf_cap_extra_information(self):
15679+        # MDMF caps can be arbitrarily extended after the fingerprint
15680+        # and key/storage index fields.
15681+        u1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15682+        self.failUnlessEqual([], u1.get_extension_params())
15683+
15684+        cap = u1.to_string()
15685+        # Now let's append some fields. Say, 131073 (the segment size)
15686+        # and 3 (the "k" encoding parameter).
15687+        expected_extensions = []
15688+        for e in ('131073', '3'):
15689+            cap += (":%s" % e)
15690+            expected_extensions.append(e)
15691+
15692+            u2 = uri.WritableMDMFFileURI.init_from_string(cap)
15693+            self.failUnlessReallyEqual(self.writekey, u2.writekey)
15694+            self.failUnlessReallyEqual(self.fingerprint, u2.fingerprint)
15695+            self.failIf(u2.is_readonly())
15696+            self.failUnless(u2.is_mutable())
15697+
15698+            c2 = u2.to_string()
15699+            u2n = uri.WritableMDMFFileURI.init_from_string(c2)
15700+            self.failUnlessReallyEqual(u2, u2n)
15701+
15702+            # We should get the extra back when we ask for it.
15703+            self.failUnlessEqual(expected_extensions, u2.get_extension_params())
15704+
15705+            # These should be preserved through cap attenuation, too.
15706+            u3 = u2.get_readonly()
15707+            self.failUnlessReallyEqual(self.readkey, u3.readkey)
15708+            self.failUnlessReallyEqual(self.fingerprint, u3.fingerprint)
15709+            self.failUnless(u3.is_readonly())
15710+            self.failUnless(u3.is_mutable())
15711+            self.failUnlessEqual(expected_extensions, u3.get_extension_params())
15712+
15713+            c3 = u3.to_string()
15714+            u3n = uri.ReadonlyMDMFFileURI.init_from_string(c3)
15715+            self.failUnlessReallyEqual(u3, u3n)
15716+
15717+            u4 = u3.get_verify_cap()
15718+            self.failUnlessReallyEqual(self.storage_index, u4.storage_index)
15719+            self.failUnlessReallyEqual(self.fingerprint, u4.fingerprint)
15720+            self.failUnless(u4.is_readonly())
15721+            self.failIf(u4.is_mutable())
15722+
15723+            c4 = u4.to_string()
15724+            u4n = uri.MDMFVerifierURI.init_from_string(c4)
15725+            self.failUnlessReallyEqual(u4n, u4)
15726+
15727+            self.failUnlessEqual(expected_extensions, u4.get_extension_params())
15728+
15729+
15730+    def test_sdmf_cap_extra_information(self):
15731+        # For interface consistency, we define a method to get
15732+        # extensions for SDMF files as well. This method must always
15733+        # return no extensions, since SDMF files were not created with
15734+        # extensions and cannot be modified to include extensions
15735+        # without breaking older clients.
15736+        u1 = uri.WriteableSSKFileURI(self.writekey, self.fingerprint)
15737+        cap = u1.to_string()
15738+        u2 = uri.WriteableSSKFileURI.init_from_string(cap)
15739+        self.failUnlessEqual([], u2.get_extension_params())
15740+
15741+    def test_extension_character_range(self):
15742+        # As written now, we shouldn't put things other than numbers in
15743+        # the extension fields.
15744+        writecap = uri.WritableMDMFFileURI(self.writekey, self.fingerprint).to_string()
15745+        readcap  = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint).to_string()
15746+        vcap     = uri.MDMFVerifierURI(self.storage_index, self.fingerprint).to_string()
15747+        self.failUnlessRaises(uri.BadURIError,
15748+                              uri.WritableMDMFFileURI.init_from_string,
15749+                              ("%s:invalid" % writecap))
15750+        self.failUnlessRaises(uri.BadURIError,
15751+                              uri.ReadonlyMDMFFileURI.init_from_string,
15752+                              ("%s:invalid" % readcap))
15753+        self.failUnlessRaises(uri.BadURIError,
15754+                              uri.MDMFVerifierURI.init_from_string,
15755+                              ("%s:invalid" % vcap))
15756+
15757+
15758+    def test_mdmf_valid_human_encoding(self):
15759+        # What's a human encoding? Well, it's of the form:
15760+        base = "https://127.0.0.1:3456/uri/"
15761+        # With a cap on the end. For each of the cap types, we need to
15762+        # test that a valid cap (with and without the traditional
15763+        # separators) is recognized and accepted by the classes.
15764+        w1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15765+        w2 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint,
15766+                                     ['131073', '3'])
15767+        r1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15768+        r2 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint,
15769+                                     ['131073', '3'])
15770+        v1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15771+        v2 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint,
15772+                                 ['131073', '3'])
15773+
15774+        # These will yield six different caps.
15775+        for o in (w1, w2, r1 , r2, v1, v2):
15776+            url = base + o.to_string()
15777+            o1 = o.__class__.init_from_human_encoding(url)
15778+            self.failUnlessReallyEqual(o1, o)
15779+
15780+            # Note that our cap will, by default, have : as separators.
15781+            # But it's expected that users from, e.g., the WUI, will
15782+            # have %3A as a separator. We need to make sure that the
15783+            # initialization routine handles that, too.
15784+            cap = o.to_string()
15785+            cap = re.sub(":", "%3A", cap)
15786+            url = base + cap
15787+            o2 = o.__class__.init_from_human_encoding(url)
15788+            self.failUnlessReallyEqual(o2, o)
15789+
15790+
15791+    def test_mdmf_human_encoding_invalid_base(self):
15792+        # What's a human encoding? Well, it's of the form:
15793+        base = "https://127.0.0.1:3456/foo/bar/bazuri/"
15794+        # With a cap on the end. For each of the cap types, we need to
15795+        # test that a valid cap (with and without the traditional
15796+        # separators) is recognized and accepted by the classes.
15797+        w1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15798+        w2 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint,
15799+                                     ['131073', '3'])
15800+        r1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15801+        r2 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint,
15802+                                     ['131073', '3'])
15803+        v1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15804+        v2 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint,
15805+                                 ['131073', '3'])
15806+
15807+        # These will yield six different caps.
15808+        for o in (w1, w2, r1 , r2, v1, v2):
15809+            url = base + o.to_string()
15810+            self.failUnlessRaises(uri.BadURIError,
15811+                                  o.__class__.init_from_human_encoding,
15812+                                  url)
15813+
15814+    def test_mdmf_human_encoding_invalid_cap(self):
15815+        base = "https://127.0.0.1:3456/uri/"
15816+        # With a cap on the end. For each of the cap types, we need to
15817+        # test that a valid cap (with and without the traditional
15818+        # separators) is recognized and accepted by the classes.
15819+        w1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15820+        w2 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint,
15821+                                     ['131073', '3'])
15822+        r1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15823+        r2 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint,
15824+                                     ['131073', '3'])
15825+        v1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15826+        v2 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint,
15827+                                 ['131073', '3'])
15828+
15829+        # These will yield six different caps.
15830+        for o in (w1, w2, r1 , r2, v1, v2):
15831+            # not exhaustive, obviously...
15832+            url = base + o.to_string() + "foobarbaz"
15833+            url2 = base + "foobarbaz" + o.to_string()
15834+            url3 = base + o.to_string()[:25] + "foo" + o.to_string()[:25]
15835+            for u in (url, url2, url3):
15836+                self.failUnlessRaises(uri.BadURIError,
15837+                                      o.__class__.init_from_human_encoding,
15838+                                      u)
15839+
15840+    def test_mdmf_from_string(self):
15841+        # Make sure that the from_string utility function works with
15842+        # MDMF caps.
15843+        u1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15844+        cap = u1.to_string()
15845+        self.failUnless(uri.is_uri(cap))
15846+        u2 = uri.from_string(cap)
15847+        self.failUnlessReallyEqual(u1, u2)
15848+        u3 = uri.from_string_mutable_filenode(cap)
15849+        self.failUnlessEqual(u3, u1)
15850+
15851+        # XXX: We should refactor the extension field into setUp
15852+        u1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint,
15853+                                     ['131073', '3'])
15854+        cap = u1.to_string()
15855+        self.failUnless(uri.is_uri(cap))
15856+        u2 = uri.from_string(cap)
15857+        self.failUnlessReallyEqual(u1, u2)
15858+        u3 = uri.from_string_mutable_filenode(cap)
15859+        self.failUnlessEqual(u3, u1)
15860+
15861+        u1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15862+        cap = u1.to_string()
15863+        self.failUnless(uri.is_uri(cap))
15864+        u2 = uri.from_string(cap)
15865+        self.failUnlessReallyEqual(u1, u2)
15866+        u3 = uri.from_string_mutable_filenode(cap)
15867+        self.failUnlessEqual(u3, u1)
15868+
15869+        u1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint,
15870+                                     ['131073', '3'])
15871+        cap = u1.to_string()
15872+        self.failUnless(uri.is_uri(cap))
15873+        u2 = uri.from_string(cap)
15874+        self.failUnlessReallyEqual(u1, u2)
15875+        u3 = uri.from_string_mutable_filenode(cap)
15876+        self.failUnlessEqual(u3, u1)
15877+
15878+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15879+        cap = u1.to_string()
15880+        self.failUnless(uri.is_uri(cap))
15881+        u2 = uri.from_string(cap)
15882+        self.failUnlessReallyEqual(u1, u2)
15883+        u3 = uri.from_string_verifier(cap)
15884+        self.failUnlessEqual(u3, u1)
15885+
15886+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint,
15887+                                 ['131073', '3'])
15888+        cap = u1.to_string()
15889+        self.failUnless(uri.is_uri(cap))
15890+        u2 = uri.from_string(cap)
15891+        self.failUnlessReallyEqual(u1, u2)
15892+        u3 = uri.from_string_verifier(cap)
15893+        self.failUnlessEqual(u3, u1)
15894+
15895+
15896 class Dirnode(testutil.ReallyEqualMixin, unittest.TestCase):
15897     def test_pack(self):
15898         writekey = "\x01" * 16
15899hunk ./src/allmydata/uri.py 31
15900 SEP='(?::|%3A)'
15901 NUMBER='([0-9]+)'
15902 NUMBER_IGNORE='(?:[0-9]+)'
15903+OPTIONAL_EXTENSION_FIELD = '(' + SEP + '[0-9' + SEP + ']+|)'
15904 
15905 # "human-encoded" URIs are allowed to come with a leading
15906 # 'http://127.0.0.1:(8123|3456)/uri/' that will be ignored.
15907hunk ./src/allmydata/uri.py 297
15908     def get_verify_cap(self):
15909         return SSKVerifierURI(self.storage_index, self.fingerprint)
15910 
15911+    def get_extension_params(self):
15912+        return []
15913 
15914 class ReadonlySSKFileURI(_BaseURI):
15915     implements(IURI, IMutableFileURI)
15916hunk ./src/allmydata/uri.py 354
15917     def get_verify_cap(self):
15918         return SSKVerifierURI(self.storage_index, self.fingerprint)
15919 
15920+    def get_extension_params(self):
15921+        return []
15922 
15923 class SSKVerifierURI(_BaseURI):
15924     implements(IVerifierURI)
15925hunk ./src/allmydata/uri.py 401
15926     def get_verify_cap(self):
15927         return self
15928 
15929+    def get_extension_params(self):
15930+        return []
15931+
15932+class WritableMDMFFileURI(_BaseURI):
15933+    implements(IURI, IMutableFileURI)
15934+
15935+    BASE_STRING='URI:MDMF:'
15936+    STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
15937+    HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'MDMF'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
15938+
15939+    def __init__(self, writekey, fingerprint, params=[]):
15940+        self.writekey = writekey
15941+        self.readkey = hashutil.ssk_readkey_hash(writekey)
15942+        self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
15943+        assert len(self.storage_index) == 16
15944+        self.fingerprint = fingerprint
15945+        self.extension = params
15946+
15947+    @classmethod
15948+    def init_from_human_encoding(cls, uri):
15949+        mo = cls.HUMAN_RE.search(uri)
15950+        if not mo:
15951+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
15952+        params = filter(lambda x: x != '', re.split(SEP, mo.group(3)))
15953+        return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
15954+
15955+    @classmethod
15956+    def init_from_string(cls, uri):
15957+        mo = cls.STRING_RE.search(uri)
15958+        if not mo:
15959+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
15960+        params = mo.group(3)
15961+        params = filter(lambda x: x != '', params.split(":"))
15962+        return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
15963+
15964+    def to_string(self):
15965+        assert isinstance(self.writekey, str)
15966+        assert isinstance(self.fingerprint, str)
15967+        ret = 'URI:MDMF:%s:%s' % (base32.b2a(self.writekey),
15968+                                  base32.b2a(self.fingerprint))
15969+        if self.extension:
15970+            ret += ":"
15971+            ret += ":".join(self.extension)
15972+
15973+        return ret
15974+
15975+    def __repr__(self):
15976+        return "<%s %s>" % (self.__class__.__name__, self.abbrev())
15977+
15978+    def abbrev(self):
15979+        return base32.b2a(self.writekey[:5])
15980+
15981+    def abbrev_si(self):
15982+        return base32.b2a(self.storage_index)[:5]
15983+
15984+    def is_readonly(self):
15985+        return False
15986+
15987+    def is_mutable(self):
15988+        return True
15989+
15990+    def get_readonly(self):
15991+        return ReadonlyMDMFFileURI(self.readkey, self.fingerprint, self.extension)
15992+
15993+    def get_verify_cap(self):
15994+        return MDMFVerifierURI(self.storage_index, self.fingerprint, self.extension)
15995+
15996+    def get_extension_params(self):
15997+        return self.extension
15998+
15999+class ReadonlyMDMFFileURI(_BaseURI):
16000+    implements(IURI, IMutableFileURI)
16001+
16002+    BASE_STRING='URI:MDMF-RO:'
16003+    STRING_RE=re.compile('^' +BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
16004+    HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'MDMF-RO'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
16005+
16006+    def __init__(self, readkey, fingerprint, params=[]):
16007+        self.readkey = readkey
16008+        self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
16009+        assert len(self.storage_index) == 16
16010+        self.fingerprint = fingerprint
16011+        self.extension = params
16012+
16013+    @classmethod
16014+    def init_from_human_encoding(cls, uri):
16015+        mo = cls.HUMAN_RE.search(uri)
16016+        if not mo:
16017+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
16018+        params = mo.group(3)
16019+        params = filter(lambda x: x!= '', re.split(SEP, params))
16020+        return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
16021+
16022+    @classmethod
16023+    def init_from_string(cls, uri):
16024+        mo = cls.STRING_RE.search(uri)
16025+        if not mo:
16026+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
16027+
16028+        params = mo.group(3)
16029+        params = filter(lambda x: x != '', params.split(":"))
16030+        return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
16031+
16032+    def to_string(self):
16033+        assert isinstance(self.readkey, str)
16034+        assert isinstance(self.fingerprint, str)
16035+        ret = 'URI:MDMF-RO:%s:%s' % (base32.b2a(self.readkey),
16036+                                     base32.b2a(self.fingerprint))
16037+        if self.extension:
16038+            ret += ":"
16039+            ret += ":".join(self.extension)
16040+
16041+        return ret
16042+
16043+    def __repr__(self):
16044+        return "<%s %s>" % (self.__class__.__name__, self.abbrev())
16045+
16046+    def abbrev(self):
16047+        return base32.b2a(self.readkey[:5])
16048+
16049+    def abbrev_si(self):
16050+        return base32.b2a(self.storage_index)[:5]
16051+
16052+    def is_readonly(self):
16053+        return True
16054+
16055+    def is_mutable(self):
16056+        return True
16057+
16058+    def get_readonly(self):
16059+        return self
16060+
16061+    def get_verify_cap(self):
16062+        return MDMFVerifierURI(self.storage_index, self.fingerprint, self.extension)
16063+
16064+    def get_extension_params(self):
16065+        return self.extension
16066+
16067+class MDMFVerifierURI(_BaseURI):
16068+    implements(IVerifierURI)
16069+
16070+    BASE_STRING='URI:MDMF-Verifier:'
16071+    STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
16072+    HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'MDMF-Verifier'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
16073+
16074+    def __init__(self, storage_index, fingerprint, params=[]):
16075+        assert len(storage_index) == 16
16076+        self.storage_index = storage_index
16077+        self.fingerprint = fingerprint
16078+        self.extension = params
16079+
16080+    @classmethod
16081+    def init_from_human_encoding(cls, uri):
16082+        mo = cls.HUMAN_RE.search(uri)
16083+        if not mo:
16084+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
16085+        params = mo.group(3)
16086+        params = filter(lambda x: x != '', re.split(SEP, params))
16087+        return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
16088+
16089+    @classmethod
16090+    def init_from_string(cls, uri):
16091+        mo = cls.STRING_RE.search(uri)
16092+        if not mo:
16093+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
16094+        params = mo.group(3)
16095+        params = filter(lambda x: x != '', params.split(":"))
16096+        return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
16097+
16098+    def to_string(self):
16099+        assert isinstance(self.storage_index, str)
16100+        assert isinstance(self.fingerprint, str)
16101+        ret = 'URI:MDMF-Verifier:%s:%s' % (si_b2a(self.storage_index),
16102+                                           base32.b2a(self.fingerprint))
16103+        if self.extension:
16104+            ret += ':'
16105+            ret += ":".join(self.extension)
16106+
16107+        return ret
16108+
16109+    def is_readonly(self):
16110+        return True
16111+
16112+    def is_mutable(self):
16113+        return False
16114+
16115+    def get_readonly(self):
16116+        return self
16117+
16118+    def get_verify_cap(self):
16119+        return self
16120+
16121+    def get_extension_params(self):
16122+        return self.extension
16123+
16124 class _DirectoryBaseURI(_BaseURI):
16125     implements(IURI, IDirnodeURI)
16126     def __init__(self, filenode_uri=None):
16127hunk ./src/allmydata/uri.py 831
16128             kind = "URI:SSK-RO readcap to a mutable file"
16129         elif s.startswith('URI:SSK-Verifier:'):
16130             return SSKVerifierURI.init_from_string(s)
16131+        elif s.startswith('URI:MDMF:'):
16132+            return WritableMDMFFileURI.init_from_string(s)
16133+        elif s.startswith('URI:MDMF-RO:'):
16134+            return ReadonlyMDMFFileURI.init_from_string(s)
16135+        elif s.startswith('URI:MDMF-Verifier:'):
16136+            return MDMFVerifierURI.init_from_string(s)
16137         elif s.startswith('URI:DIR2:'):
16138             if can_be_writeable:
16139                 return DirectoryURI.init_from_string(s)
16140}
16141[nodemaker, mutable/filenode: train nodemaker and filenode to handle MDMF caps
16142Kevan Carstensen <kevan@isnotajoke.com>**20110501224523
16143 Ignore-this: 1f3b4581eb583e7bb93d234182bda395
16144] {
16145hunk ./src/allmydata/mutable/filenode.py 12
16146      IMutableFileVersion, IWritable
16147 from allmydata.util import hashutil, log, consumer, deferredutil, mathutil
16148 from allmydata.util.assertutil import precondition
16149-from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
16150+from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI, \
16151+                          WritableMDMFFileURI, ReadonlyMDMFFileURI
16152 from allmydata.monitor import Monitor
16153 from pycryptopp.cipher.aes import AES
16154 
16155hunk ./src/allmydata/mutable/filenode.py 75
16156         # set to this default value in case neither of those things happen,
16157         # or in case the servermap can't find any shares to tell us what
16158         # to publish as.
16159-        # TODO: Set this back to None, and find out why the tests fail
16160-        #       with it set to None.
16161+        # XXX: Version should come in via the constructor.
16162         self._protocol_version = None
16163 
16164         # all users of this MutableFileNode go through the serializer. This
16165hunk ./src/allmydata/mutable/filenode.py 95
16166         # verification key, nor things like 'k' or 'N'. If and when someone
16167         # wants to get our contents, we'll pull from shares and fill those
16168         # in.
16169-        assert isinstance(filecap, (ReadonlySSKFileURI, WriteableSSKFileURI))
16170+        if isinstance(filecap, (WritableMDMFFileURI, ReadonlyMDMFFileURI)):
16171+            self._protocol_version = MDMF_VERSION
16172+        elif isinstance(filecap, (ReadonlySSKFileURI, WriteableSSKFileURI)):
16173+            self._protocol_version = SDMF_VERSION
16174+
16175         self._uri = filecap
16176         self._writekey = None
16177hunk ./src/allmydata/mutable/filenode.py 102
16178-        if isinstance(filecap, WriteableSSKFileURI):
16179+
16180+        if not filecap.is_readonly() and filecap.is_mutable():
16181             self._writekey = self._uri.writekey
16182         self._readkey = self._uri.readkey
16183         self._storage_index = self._uri.storage_index
16184hunk ./src/allmydata/mutable/filenode.py 131
16185         self._writekey = hashutil.ssk_writekey_hash(privkey_s)
16186         self._encprivkey = self._encrypt_privkey(self._writekey, privkey_s)
16187         self._fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
16188-        self._uri = WriteableSSKFileURI(self._writekey, self._fingerprint)
16189+        if self._protocol_version == MDMF_VERSION:
16190+            self._uri = WritableMDMFFileURI(self._writekey, self._fingerprint)
16191+        else:
16192+            self._uri = WriteableSSKFileURI(self._writekey, self._fingerprint)
16193         self._readkey = self._uri.readkey
16194         self._storage_index = self._uri.storage_index
16195         initial_contents = self._get_initial_contents(contents)
16196hunk ./src/allmydata/nodemaker.py 82
16197             return self._create_immutable(cap)
16198         if isinstance(cap, uri.CHKFileVerifierURI):
16199             return self._create_immutable_verifier(cap)
16200-        if isinstance(cap, (uri.ReadonlySSKFileURI, uri.WriteableSSKFileURI)):
16201+        if isinstance(cap, (uri.ReadonlySSKFileURI, uri.WriteableSSKFileURI,
16202+                            uri.WritableMDMFFileURI, uri.ReadonlyMDMFFileURI)):
16203             return self._create_mutable(cap)
16204         if isinstance(cap, (uri.DirectoryURI,
16205                             uri.ReadonlyDirectoryURI,
16206hunk ./src/allmydata/test/test_mutable.py 196
16207                     offset2 = 0
16208                 if offset1 == "pubkey" and IV:
16209                     real_offset = 107
16210-                elif offset1 == "share_data" and not IV:
16211-                    real_offset = 107
16212                 elif offset1 in o:
16213                     real_offset = o[offset1]
16214                 else:
16215hunk ./src/allmydata/test/test_mutable.py 270
16216         return d
16217 
16218 
16219+    def test_mdmf_filenode_cap(self):
16220+        # Test that an MDMF filenode, once created, returns an MDMF URI.
16221+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16222+        def _created(n):
16223+            self.failUnless(isinstance(n, MutableFileNode))
16224+            cap = n.get_cap()
16225+            self.failUnless(isinstance(cap, uri.WritableMDMFFileURI))
16226+            rcap = n.get_readcap()
16227+            self.failUnless(isinstance(rcap, uri.ReadonlyMDMFFileURI))
16228+            vcap = n.get_verify_cap()
16229+            self.failUnless(isinstance(vcap, uri.MDMFVerifierURI))
16230+        d.addCallback(_created)
16231+        return d
16232+
16233+
16234+    def test_create_from_mdmf_writecap(self):
16235+        # Test that the nodemaker is capable of creating an MDMF
16236+        # filenode given an MDMF cap.
16237+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16238+        def _created(n):
16239+            self.failUnless(isinstance(n, MutableFileNode))
16240+            s = n.get_uri()
16241+            self.failUnless(s.startswith("URI:MDMF"))
16242+            n2 = self.nodemaker.create_from_cap(s)
16243+            self.failUnless(isinstance(n2, MutableFileNode))
16244+            self.failUnlessEqual(n.get_storage_index(), n2.get_storage_index())
16245+            self.failUnlessEqual(n.get_uri(), n2.get_uri())
16246+        d.addCallback(_created)
16247+        return d
16248+
16249+
16250+    def test_create_from_mdmf_writecap_with_extensions(self):
16251+        # Test that the nodemaker is capable of creating an MDMF
16252+        # filenode when given a writecap with extension parameters in
16253+        # them.
16254+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16255+        def _created(n):
16256+            self.failUnless(isinstance(n, MutableFileNode))
16257+            s = n.get_uri()
16258+            s2 = "%s:3:131073" % s
16259+            n2 = self.nodemaker.create_from_cap(s2)
16260+
16261+            self.failUnlessEqual(n2.get_storage_index(), n.get_storage_index())
16262+            self.failUnlessEqual(n.get_writekey(), n2.get_writekey())
16263+        d.addCallback(_created)
16264+        return d
16265+
16266+
16267+    def test_create_from_mdmf_readcap(self):
16268+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16269+        def _created(n):
16270+            self.failUnless(isinstance(n, MutableFileNode))
16271+            s = n.get_readonly_uri()
16272+            n2 = self.nodemaker.create_from_cap(s)
16273+            self.failUnless(isinstance(n2, MutableFileNode))
16274+
16275+            # Check that it's a readonly node
16276+            self.failUnless(n2.is_readonly())
16277+        d.addCallback(_created)
16278+        return d
16279+
16280+
16281+    def test_create_from_mdmf_readcap_with_extensions(self):
16282+        # We should be able to create an MDMF filenode with the
16283+        # extension parameters without it breaking.
16284+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16285+        def _created(n):
16286+            self.failUnless(isinstance(n, MutableFileNode))
16287+            s = n.get_readonly_uri()
16288+            s = "%s:3:131073" % s
16289+
16290+            n2 = self.nodemaker.create_from_cap(s)
16291+            self.failUnless(isinstance(n2, MutableFileNode))
16292+            self.failUnless(n2.is_readonly())
16293+            self.failUnlessEqual(n.get_storage_index(), n2.get_storage_index())
16294+        d.addCallback(_created)
16295+        return d
16296+
16297+
16298+    def test_internal_version_from_cap(self):
16299+        # MutableFileNodes and MutableFileVersions have an internal
16300+        # switch that tells them whether they're dealing with an SDMF or
16301+        # MDMF mutable file when they start doing stuff. We want to make
16302+        # sure that this is set appropriately given an MDMF cap.
16303+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16304+        def _created(n):
16305+            self.uri = n.get_uri()
16306+            self.failUnlessEqual(n._protocol_version, MDMF_VERSION)
16307+
16308+            n2 = self.nodemaker.create_from_cap(self.uri)
16309+            self.failUnlessEqual(n2._protocol_version, MDMF_VERSION)
16310+        d.addCallback(_created)
16311+        return d
16312+
16313+
16314     def test_serialize(self):
16315         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
16316         calls = []
16317hunk ./src/allmydata/test/test_mutable.py 464
16318         return d
16319 
16320 
16321+    def test_download_from_mdmf_cap(self):
16322+        # We should be able to download an MDMF file given its cap
16323+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16324+        def _created(node):
16325+            self.uri = node.get_uri()
16326+
16327+            return node.overwrite(MutableData("contents1" * 100000))
16328+        def _then(ignored):
16329+            node = self.nodemaker.create_from_cap(self.uri)
16330+            return node.download_best_version()
16331+        def _downloaded(data):
16332+            self.failUnlessEqual(data, "contents1" * 100000)
16333+        d.addCallback(_created)
16334+        d.addCallback(_then)
16335+        d.addCallback(_downloaded)
16336+        return d
16337+
16338+
16339     def test_mdmf_write_count(self):
16340         # Publishing an MDMF file should only cause one write for each
16341         # share that is to be published. Otherwise, we introduce
16342hunk ./src/allmydata/test/test_mutable.py 1735
16343     def test_verify_mdmf_bad_encprivkey(self):
16344         d = self.publish_mdmf()
16345         d.addCallback(lambda ignored:
16346-            corrupt(None, self._storage, "enc_privkey", [1]))
16347+            corrupt(None, self._storage, "enc_privkey", [0]))
16348         d.addCallback(lambda ignored:
16349             self._fn.check(Monitor(), verify=True))
16350         d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
16351hunk ./src/allmydata/test/test_mutable.py 2843
16352         return d
16353 
16354 
16355+    def test_version_extension_api(self):
16356+        # We need to define an API by which an uploader can set the
16357+        # extension parameters, and by which a downloader can retrieve
16358+        # extensions.
16359+        self.failUnless(False)
16360+
16361+
16362+    def test_extensions_from_cap(self):
16363+        self.failUnless(False)
16364+
16365+
16366+    def test_extensions_from_upload(self):
16367+        self.failUnless(False)
16368+
16369+
16370+    def test_cap_after_upload(self):
16371+        self.failUnless(False)
16372+
16373+
16374     def test_get_writekey(self):
16375         d = self.mdmf_node.get_best_mutable_version()
16376         d.addCallback(lambda bv:
16377}
16378[mutable/retrieve: fix typo in paused check
16379Kevan Carstensen <kevan@isnotajoke.com>**20110515225946
16380 Ignore-this: a9c7f3bdbab2f8248f8b6a64f574e7c4
16381] hunk ./src/allmydata/mutable/retrieve.py 207
16382         """
16383         if self._paused:
16384             d = defer.Deferred()
16385-            self._pause_defered.addCallback(lambda ignored: d.callback(res))
16386+            self._pause_deferred.addCallback(lambda ignored: d.callback(res))
16387             return d
16388         return defer.succeed(res)
16389 
16390[scripts/tahoe_put.py: teach tahoe put about MDMF caps
16391Kevan Carstensen <kevan@isnotajoke.com>**20110515230008
16392 Ignore-this: 1522f434f651683c924e37251a3c1bfd
16393] hunk ./src/allmydata/scripts/tahoe_put.py 49
16394         #  DIRCAP:./subdir/foo : DIRCAP/subdir/foo
16395         #  MUTABLE-FILE-WRITECAP : filecap
16396 
16397-        # FIXME: this shouldn't rely on a particular prefix.
16398-        if to_file.startswith("URI:SSK:"):
16399+        # FIXME: don't hardcode cap format.
16400+        if to_file.startswith("URI:MDMF:") or to_file.startswith("URI:SSK:"):
16401             url = nodeurl + "uri/%s" % urllib.quote(to_file)
16402         else:
16403             try:
16404[test/common.py: fix some MDMF-related bugs in common test fixtures
16405Kevan Carstensen <kevan@isnotajoke.com>**20110515230038
16406 Ignore-this: ab5ffe4789bb5e6ed5f54b91b760bac9
16407] {
16408hunk ./src/allmydata/test/common.py 199
16409                  default_encoding_parameters, history):
16410         self.init_from_cap(make_mutable_file_cap())
16411     def create(self, contents, key_generator=None, keysize=None):
16412+        if self.file_types[self.storage_index] == MDMF_VERSION and \
16413+            isinstance(self.my_uri, (uri.ReadonlySSKFileURI,
16414+                                 uri.WriteableSSKFileURI)):
16415+            self.init_from_cap(make_mdmf_mutable_file_cap())
16416         initial_contents = self._get_initial_contents(contents)
16417         data = initial_contents.read(initial_contents.get_size())
16418         data = "".join(data)
16419hunk ./src/allmydata/test/common.py 220
16420         return contents(self)
16421     def init_from_cap(self, filecap):
16422         assert isinstance(filecap, (uri.WriteableSSKFileURI,
16423-                                    uri.ReadonlySSKFileURI))
16424+                                    uri.ReadonlySSKFileURI,
16425+                                    uri.WritableMDMFFileURI,
16426+                                    uri.ReadonlyMDMFFileURI))
16427         self.my_uri = filecap
16428         self.storage_index = self.my_uri.get_storage_index()
16429hunk ./src/allmydata/test/common.py 225
16430+        if isinstance(filecap, (uri.WritableMDMFFileURI,
16431+                                uri.ReadonlyMDMFFileURI)):
16432+            self.file_types[self.storage_index] = MDMF_VERSION
16433+
16434+        else:
16435+            self.file_types[self.storage_index] = SDMF_VERSION
16436+
16437         return self
16438     def get_cap(self):
16439         return self.my_uri
16440hunk ./src/allmydata/test/common.py 249
16441         return self.my_uri.get_readonly().to_string()
16442     def get_verify_cap(self):
16443         return self.my_uri.get_verify_cap()
16444+    def get_repair_cap(self):
16445+        if self.my_uri.is_readonly():
16446+            return None
16447+        return self.my_uri
16448     def is_readonly(self):
16449         return self.my_uri.is_readonly()
16450     def is_mutable(self):
16451hunk ./src/allmydata/test/common.py 406
16452 def make_mutable_file_cap():
16453     return uri.WriteableSSKFileURI(writekey=os.urandom(16),
16454                                    fingerprint=os.urandom(32))
16455-def make_mutable_file_uri():
16456-    return make_mutable_file_cap().to_string()
16457+
16458+def make_mdmf_mutable_file_cap():
16459+    return uri.WritableMDMFFileURI(writekey=os.urandom(16),
16460+                                   fingerprint=os.urandom(32))
16461+
16462+def make_mutable_file_uri(mdmf=False):
16463+    if mdmf:
16464+        uri = make_mdmf_mutable_file_cap()
16465+    else:
16466+        uri = make_mutable_file_cap()
16467+
16468+    return uri.to_string()
16469 
16470 def make_verifier_uri():
16471     return uri.SSKVerifierURI(storage_index=os.urandom(16),
16472hunk ./src/allmydata/test/common.py 423
16473                               fingerprint=os.urandom(32)).to_string()
16474 
16475+def create_mutable_filenode(contents, mdmf=False):
16476+    # XXX: All of these arguments are kind of stupid.
16477+    if mdmf:
16478+        cap = make_mdmf_mutable_file_cap()
16479+    else:
16480+        cap = make_mutable_file_cap()
16481+
16482+    filenode = FakeMutableFileNode(None, None, None, None)
16483+    filenode.init_from_cap(cap)
16484+    FakeMutableFileNode.all_contents[filenode.storage_index] = contents
16485+    return filenode
16486+
16487+
16488 class FakeDirectoryNode(dirnode.DirectoryNode):
16489     """This offers IDirectoryNode, but uses a FakeMutableFileNode for the
16490     backing store, so it doesn't go to the grid. The child data is still
16491}
16492[test/test_cli: Alter existing MDMF tests to test for MDMF caps
16493Kevan Carstensen <kevan@isnotajoke.com>**20110515230054
16494 Ignore-this: a90d089e1afb0f261710083c2be6b2fa
16495] {
16496hunk ./src/allmydata/test/test_cli.py 13
16497 from allmydata.util import fileutil, hashutil, base32
16498 from allmydata import uri
16499 from allmydata.immutable import upload
16500+from allmydata.interfaces import MDMF_VERSION, SDMF_VERSION
16501 from allmydata.mutable.publish import MutableData
16502 from allmydata.dirnode import normalize
16503 
16504hunk ./src/allmydata/test/test_cli.py 33
16505 from allmydata.test.common_util import StallMixin, ReallyEqualMixin
16506 from allmydata.test.no_network import GridTestMixin
16507 from twisted.internet import threads # CLI tests use deferToThread
16508+from twisted.internet import defer # List uses a DeferredList in one place.
16509 from twisted.python import usage
16510 
16511 from allmydata.util.assertutil import precondition
16512hunk ./src/allmydata/test/test_cli.py 1014
16513         d.addCallback(lambda (rc,out,err): self.failUnlessReallyEqual(out, DATA2))
16514         return d
16515 
16516+    def _check_mdmf_json(self, (rc, json, err)):
16517+         self.failUnlessEqual(rc, 0)
16518+         self.failUnlessEqual(err, "")
16519+         self.failUnlessIn('"mutable-type": "mdmf"', json)
16520+         # We also want a valid MDMF cap to be in the json.
16521+         self.failUnlessIn("URI:MDMF", json)
16522+         self.failUnlessIn("URI:MDMF-RO", json)
16523+         self.failUnlessIn("URI:MDMF-Verifier", json)
16524+
16525+    def _check_sdmf_json(self, (rc, json, err)):
16526+        self.failUnlessEqual(rc, 0)
16527+        self.failUnlessEqual(err, "")
16528+        self.failUnlessIn('"mutable-type": "sdmf"', json)
16529+        # We also want to see the appropriate SDMF caps.
16530+        self.failUnlessIn("URI:SSK", json)
16531+        self.failUnlessIn("URI:SSK-RO", json)
16532+        self.failUnlessIn("URI:SSK-Verifier", json)
16533+
16534     def test_mutable_type(self):
16535         self.basedir = "cli/Put/mutable_type"
16536         self.set_up_grid()
16537hunk ./src/allmydata/test/test_cli.py 1044
16538                         fn1, "tahoe:uploaded.txt"))
16539         d.addCallback(lambda ignored:
16540             self.do_cli("ls", "--json", "tahoe:uploaded.txt"))
16541-        d.addCallback(lambda (rc, json, err): self.failUnlessIn("mdmf", json))
16542+        d.addCallback(self._check_mdmf_json)
16543         d.addCallback(lambda ignored:
16544             self.do_cli("put", "--mutable", "--mutable-type=sdmf",
16545                         fn1, "tahoe:uploaded2.txt"))
16546hunk ./src/allmydata/test/test_cli.py 1050
16547         d.addCallback(lambda ignored:
16548             self.do_cli("ls", "--json", "tahoe:uploaded2.txt"))
16549-        d.addCallback(lambda (rc, json, err):
16550-            self.failUnlessIn("sdmf", json))
16551+        d.addCallback(self._check_sdmf_json)
16552         return d
16553 
16554     def test_mutable_type_unlinked(self):
16555hunk ./src/allmydata/test/test_cli.py 1062
16556         d = self.do_cli("put", "--mutable", "--mutable-type=mdmf", fn1)
16557         d.addCallback(lambda (rc, cap, err):
16558             self.do_cli("ls", "--json", cap))
16559-        d.addCallback(lambda (rc, json, err): self.failUnlessIn("mdmf", json))
16560+        d.addCallback(self._check_mdmf_json)
16561         d.addCallback(lambda ignored:
16562             self.do_cli("put", "--mutable", "--mutable-type=sdmf", fn1))
16563         d.addCallback(lambda (rc, cap, err):
16564hunk ./src/allmydata/test/test_cli.py 1067
16565             self.do_cli("ls", "--json", cap))
16566-        d.addCallback(lambda (rc, json, err):
16567-            self.failUnlessIn("sdmf", json))
16568+        d.addCallback(self._check_sdmf_json)
16569         return d
16570 
16571hunk ./src/allmydata/test/test_cli.py 1070
16572+    def test_put_to_mdmf_cap(self):
16573+        self.basedir = "cli/Put/put_to_mdmf_cap"
16574+        self.set_up_grid()
16575+        data = "data" * 100000
16576+        fn1 = os.path.join(self.basedir, "data")
16577+        fileutil.write(fn1, data)
16578+        d = self.do_cli("put", "--mutable", "--mutable-type=mdmf", fn1)
16579+        def _got_cap((rc, out, err)):
16580+            self.failUnlessEqual(rc, 0)
16581+            self.cap = out
16582+        d.addCallback(_got_cap)
16583+        # Now try to write something to the cap using put.
16584+        data2 = "data2" * 100000
16585+        fn2 = os.path.join(self.basedir, "data2")
16586+        fileutil.write(fn2, data2)
16587+        d.addCallback(lambda ignored:
16588+            self.do_cli("put", fn2, self.cap))
16589+        def _got_put((rc, out, err)):
16590+            self.failUnlessEqual(rc, 0)
16591+            self.failUnlessIn(self.cap, out)
16592+        d.addCallback(_got_put)
16593+        # Now get the cap. We should see the data we just put there.
16594+        d.addCallback(lambda ignored:
16595+            self.do_cli("get", self.cap))
16596+        def _got_data((rc, out, err)):
16597+            self.failUnlessEqual(rc, 0)
16598+            self.failUnlessEqual(out, data2)
16599+        d.addCallback(_got_data)
16600+        return d
16601+
16602+    def test_put_to_sdmf_cap(self):
16603+        self.basedir = "cli/Put/put_to_sdmf_cap"
16604+        self.set_up_grid()
16605+        data = "data" * 100000
16606+        fn1 = os.path.join(self.basedir, "data")
16607+        fileutil.write(fn1, data)
16608+        d = self.do_cli("put", "--mutable", "--mutable-type=sdmf", fn1)
16609+        def _got_cap((rc, out, err)):
16610+            self.failUnlessEqual(rc, 0)
16611+            self.cap = out
16612+        d.addCallback(_got_cap)
16613+        # Now try to write something to the cap using put.
16614+        data2 = "data2" * 100000
16615+        fn2 = os.path.join(self.basedir, "data2")
16616+        fileutil.write(fn2, data2)
16617+        d.addCallback(lambda ignored:
16618+            self.do_cli("put", fn2, self.cap))
16619+        def _got_put((rc, out, err)):
16620+            self.failUnlessEqual(rc, 0)
16621+            self.failUnlessIn(self.cap, out)
16622+        d.addCallback(_got_put)
16623+        # Now get the cap. We should see the data we just put there.
16624+        d.addCallback(lambda ignored:
16625+            self.do_cli("get", self.cap))
16626+        def _got_data((rc, out, err)):
16627+            self.failUnlessEqual(rc, 0)
16628+            self.failUnlessEqual(out, data2)
16629+        d.addCallback(_got_data)
16630+        return d
16631+
16632     def test_mutable_type_invalid_format(self):
16633         o = cli.PutOptions()
16634         self.failUnlessRaises(usage.UsageError,
16635hunk ./src/allmydata/test/test_cli.py 1363
16636         d.addCallback(_check)
16637         return d
16638 
16639+    def _create_directory_structure(self):
16640+        # Create a simple directory structure that we can use for MDMF,
16641+        # SDMF, and immutable testing.
16642+        assert self.g
16643+
16644+        client = self.g.clients[0]
16645+        # Create a dirnode
16646+        d = client.create_dirnode()
16647+        def _got_rootnode(n):
16648+            # Add a few nodes.
16649+            self._dircap = n.get_uri()
16650+            nm = n._nodemaker
16651+            # The uploaders may run at the same time, so we need two
16652+            # MutableData instances or they'll fight over offsets &c and
16653+            # break.
16654+            mutable_data = MutableData("data" * 100000)
16655+            mutable_data2 = MutableData("data" * 100000)
16656+            # Add both kinds of mutable node.
16657+            d1 = nm.create_mutable_file(mutable_data,
16658+                                        version=MDMF_VERSION)
16659+            d2 = nm.create_mutable_file(mutable_data2,
16660+                                        version=SDMF_VERSION)
16661+            # Add an immutable node. We do this through the directory,
16662+            # with add_file.
16663+            immutable_data = upload.Data("immutable data" * 100000,
16664+                                         convergence="")
16665+            d3 = n.add_file(u"immutable", immutable_data)
16666+            ds = [d1, d2, d3]
16667+            dl = defer.DeferredList(ds)
16668+            def _made_files((r1, r2, r3)):
16669+                self.failUnless(r1[0])
16670+                self.failUnless(r2[0])
16671+                self.failUnless(r3[0])
16672+
16673+                # r1, r2, and r3 contain nodes.
16674+                mdmf_node = r1[1]
16675+                sdmf_node = r2[1]
16676+                imm_node = r3[1]
16677+
16678+                self._mdmf_uri = mdmf_node.get_uri()
16679+                self._mdmf_readonly_uri = mdmf_node.get_readonly_uri()
16680+                self._sdmf_uri = mdmf_node.get_uri()
16681+                self._sdmf_readonly_uri = sdmf_node.get_readonly_uri()
16682+                self._imm_uri = imm_node.get_uri()
16683+
16684+                d1 = n.set_node(u"mdmf", mdmf_node)
16685+                d2 = n.set_node(u"sdmf", sdmf_node)
16686+                return defer.DeferredList([d1, d2])
16687+            # We can now list the directory by listing self._dircap.
16688+            dl.addCallback(_made_files)
16689+            return dl
16690+        d.addCallback(_got_rootnode)
16691+        return d
16692+
16693+    def test_list_mdmf(self):
16694+        # 'tahoe ls' should include MDMF files.
16695+        self.basedir = "cli/List/list_mdmf"
16696+        self.set_up_grid()
16697+        d = self._create_directory_structure()
16698+        d.addCallback(lambda ignored:
16699+            self.do_cli("ls", self._dircap))
16700+        def _got_ls((rc, out, err)):
16701+            self.failUnlessEqual(rc, 0)
16702+            self.failUnlessEqual(err, "")
16703+            self.failUnlessIn("immutable", out)
16704+            self.failUnlessIn("mdmf", out)
16705+            self.failUnlessIn("sdmf", out)
16706+        d.addCallback(_got_ls)
16707+        return d
16708+
16709+    def test_list_mdmf_json(self):
16710+        # 'tahoe ls' should include MDMF caps when invoked with MDMF
16711+        # caps.
16712+        self.basedir = "cli/List/list_mdmf_json"
16713+        self.set_up_grid()
16714+        d = self._create_directory_structure()
16715+        d.addCallback(lambda ignored:
16716+            self.do_cli("ls", "--json", self._dircap))
16717+        def _got_json((rc, out, err)):
16718+            self.failUnlessEqual(rc, 0)
16719+            self.failUnlessEqual(err, "")
16720+            self.failUnlessIn(self._mdmf_uri, out)
16721+            self.failUnlessIn(self._mdmf_readonly_uri, out)
16722+            self.failUnlessIn(self._sdmf_uri, out)
16723+            self.failUnlessIn(self._sdmf_readonly_uri, out)
16724+            self.failUnlessIn(self._imm_uri, out)
16725+            self.failUnlessIn('"mutable-type": "sdmf"', out)
16726+            self.failUnlessIn('"mutable-type": "mdmf"', out)
16727+        d.addCallback(_got_json)
16728+        return d
16729+
16730 
16731 class Mv(GridTestMixin, CLITestMixin, unittest.TestCase):
16732     def test_mv_behavior(self):
16733}
16734[test/test_mutable.py: write a test for pausing during retrieval, write support structure for that test
16735Kevan Carstensen <kevan@isnotajoke.com>**20110515230207
16736 Ignore-this: 8884ef3ad5be59dbc870ed14002ac45
16737] {
16738hunk ./src/allmydata/test/test_mutable.py 6
16739 from cStringIO import StringIO
16740 from twisted.trial import unittest
16741 from twisted.internet import defer, reactor
16742+from twisted.internet.interfaces import IConsumer
16743+from zope.interface import implements
16744 from allmydata import uri, client
16745 from allmydata.nodemaker import NodeMaker
16746 from allmydata.util import base32, consumer
16747hunk ./src/allmydata/test/test_mutable.py 466
16748         return d
16749 
16750 
16751+    def test_retrieve_pause(self):
16752+        # We should make sure that the retriever is able to pause
16753+        # correctly.
16754+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16755+        def _created(node):
16756+            self.node = node
16757+
16758+            return node.overwrite(MutableData("contents1" * 100000))
16759+        d.addCallback(_created)
16760+        # Now we'll retrieve it into a pausing consumer.
16761+        d.addCallback(lambda ignored:
16762+            self.node.get_best_mutable_version())
16763+        def _got_version(version):
16764+            self.c = PausingConsumer()
16765+            return version.read(self.c)
16766+        d.addCallback(_got_version)
16767+        d.addCallback(lambda ignored:
16768+            self.failUnlessEqual(self.c.data, "contents1" * 100000))
16769+        return d
16770+    test_retrieve_pause.timeout = 25
16771+
16772+
16773     def test_download_from_mdmf_cap(self):
16774         # We should be able to download an MDMF file given its cap
16775         d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16776hunk ./src/allmydata/test/test_mutable.py 944
16777                     index = versionmap[shnum]
16778                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
16779 
16780+class PausingConsumer:
16781+    implements(IConsumer)
16782+    def __init__(self):
16783+        self.data = ""
16784+        self.already_paused = False
16785+
16786+    def registerProducer(self, producer, streaming):
16787+        self.producer = producer
16788+        self.producer.resumeProducing()
16789 
16790hunk ./src/allmydata/test/test_mutable.py 954
16791+    def unregisterProducer(self):
16792+        self.producer = None
16793+
16794+    def _unpause(self, ignored):
16795+        self.producer.resumeProducing()
16796+
16797+    def write(self, data):
16798+        self.data += data
16799+        if not self.already_paused:
16800+           self.producer.pauseProducing()
16801+           self.already_paused = True
16802+           reactor.callLater(15, self._unpause, None)
16803 
16804 
16805 class Servermap(unittest.TestCase, PublishMixin):
16806}
16807[test/test_mutable.py: implement cap type checking
16808Kevan Carstensen <kevan@isnotajoke.com>**20110515230326
16809 Ignore-this: 64cf51b809605061047c8a1b02f5e212
16810] hunk ./src/allmydata/test/test_mutable.py 2904
16811 
16812 
16813     def test_cap_after_upload(self):
16814-        self.failUnless(False)
16815+        # If we create a new mutable file and upload things to it, and
16816+        # it's an MDMF file, we should get an MDMF cap back from that
16817+        # file and should be able to use that.
16818+        # That's essentially what MDMF node is, so just check that.
16819+        mdmf_uri = self.mdmf_node.get_uri()
16820+        cap = uri.from_string(mdmf_uri)
16821+        self.failUnless(isinstance(cap, uri.WritableMDMFFileURI))
16822+        readonly_mdmf_uri = self.mdmf_node.get_readonly_uri()
16823+        cap = uri.from_string(readonly_mdmf_uri)
16824+        self.failUnless(isinstance(cap, uri.ReadonlyMDMFFileURI))
16825 
16826 
16827     def test_get_writekey(self):
16828[test/test_web: add MDMF cap tests
16829Kevan Carstensen <kevan@isnotajoke.com>**20110515230358
16830 Ignore-this: ace5af3bdc9b65c3f6964c8fe056816
16831] {
16832hunk ./src/allmydata/test/test_web.py 27
16833 from allmydata.util.netstring import split_netstring
16834 from allmydata.util.encodingutil import to_str
16835 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
16836-     create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
16837+     create_chk_filenode, WebErrorMixin, ShouldFailMixin, \
16838+     make_mutable_file_uri, create_mutable_filenode
16839 from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
16840 from allmydata.mutable import servermap, publish, retrieve
16841 import allmydata.test.common_util as testutil
16842hunk ./src/allmydata/test/test_web.py 208
16843             foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
16844             self._bar_txt_verifycap = n.get_verify_cap().to_string()
16845 
16846+            # sdmf
16847+            # XXX: Do we ever use this?
16848+            self.BAZ_CONTENTS, n, self._baz_txt_uri, self._baz_txt_readonly_uri = self.makefile_mutable(0)
16849+
16850+            foo.set_uri(u"baz.txt", self._baz_txt_uri, self._baz_txt_readonly_uri)
16851+
16852+            # mdmf
16853+            self.QUUX_CONTENTS, n, self._quux_txt_uri, self._quux_txt_readonly_uri = self.makefile_mutable(0, mdmf=True)
16854+            assert self._quux_txt_uri.startswith("URI:MDMF")
16855+            foo.set_uri(u"quux.txt", self._quux_txt_uri, self._quux_txt_readonly_uri)
16856+
16857             foo.set_uri(u"empty", res[3][1].get_uri(),
16858                         res[3][1].get_readonly_uri())
16859             sub_uri = res[4][1].get_uri()
16860hunk ./src/allmydata/test/test_web.py 250
16861             # public/
16862             # public/foo/
16863             # public/foo/bar.txt
16864+            # public/foo/baz.txt
16865+            # public/foo/quux.txt
16866             # public/foo/blockingfile
16867             # public/foo/empty/
16868             # public/foo/sub/
16869hunk ./src/allmydata/test/test_web.py 272
16870         n = create_chk_filenode(contents)
16871         return contents, n, n.get_uri()
16872 
16873+    def makefile_mutable(self, number, mdmf=False):
16874+        contents = "contents of mutable file %s\n" % number
16875+        n = create_mutable_filenode(contents, mdmf)
16876+        return contents, n, n.get_uri(), n.get_readonly_uri()
16877+
16878     def tearDown(self):
16879         return self.s.stopService()
16880 
16881hunk ./src/allmydata/test/test_web.py 283
16882     def failUnlessIsBarDotTxt(self, res):
16883         self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
16884 
16885+    def failUnlessIsQuuxDotTxt(self, res):
16886+        self.failUnlessReallyEqual(res, self.QUUX_CONTENTS, res)
16887+
16888+    def failUnlessIsBazDotTxt(self, res):
16889+        self.failUnlessReallyEqual(res, self.BAZ_CONTENTS, res)
16890+
16891     def failUnlessIsBarJSON(self, res):
16892         data = simplejson.loads(res)
16893         self.failUnless(isinstance(data, list))
16894hunk ./src/allmydata/test/test_web.py 300
16895         self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
16896         self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
16897 
16898+    def failUnlessIsQuuxJSON(self, res):
16899+        data = simplejson.loads(res)
16900+        self.failUnless(isinstance(data, list))
16901+        self.failUnlessEqual(data[0], "filenode")
16902+        self.failUnless(isinstance(data[1], dict))
16903+        metadata = data[1]
16904+        return self.failUnlessIsQuuxDotTxtMetadata(metadata)
16905+
16906+    def failUnlessIsQuuxDotTxtMetadata(self, metadata):
16907+        self.failUnless(metadata['mutable'])
16908+        self.failUnless("rw_uri" in metadata)
16909+        self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
16910+        self.failUnless("ro_uri" in metadata)
16911+        self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
16912+        self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
16913+
16914     def failUnlessIsFooJSON(self, res):
16915         data = simplejson.loads(res)
16916         self.failUnless(isinstance(data, list))
16917hunk ./src/allmydata/test/test_web.py 329
16918 
16919         kidnames = sorted([unicode(n) for n in data[1]["children"]])
16920         self.failUnlessEqual(kidnames,
16921-                             [u"bar.txt", u"blockingfile", u"empty",
16922-                              u"n\u00fc.txt", u"sub"])
16923+                             [u"bar.txt", u"baz.txt", u"blockingfile",
16924+                              u"empty", u"n\u00fc.txt", u"quux.txt", u"sub"])
16925         kids = dict( [(unicode(name),value)
16926                       for (name,value)
16927                       in data[1]["children"].iteritems()] )
16928hunk ./src/allmydata/test/test_web.py 351
16929                                    self._bar_txt_metadata["tahoe"]["linkcrtime"])
16930         self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
16931                                    self._bar_txt_uri)
16932+        self.failUnlessIn("quux.txt", kids)
16933+        self.failUnlessReallyEqual(kids[u"quux.txt"][1]["rw_uri"],
16934+                                   self._quux_txt_uri)
16935+        self.failUnlessReallyEqual(kids[u"quux.txt"][1]["ro_uri"],
16936+                                   self._quux_txt_readonly_uri)
16937 
16938     def GET(self, urlpath, followRedirect=False, return_response=False,
16939             **kwargs):
16940hunk ./src/allmydata/test/test_web.py 870
16941         d.addCallback(self.failUnlessIsBarDotTxt)
16942         return d
16943 
16944+    def test_GET_FILE_URI_mdmf(self):
16945+        base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
16946+        d = self.GET(base)
16947+        d.addCallback(self.failUnlessIsQuuxDotTxt)
16948+        return d
16949+
16950+    def test_GET_FILE_URI_mdmf_extensions(self):
16951+        base = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
16952+        d = self.GET(base)
16953+        d.addCallback(self.failUnlessIsQuuxDotTxt)
16954+        return d
16955+
16956+    def test_GET_FILE_URI_mdmf_readonly(self):
16957+        base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
16958+        d = self.GET(base)
16959+        d.addCallback(self.failUnlessIsQuuxDotTxt)
16960+        return d
16961+
16962     def test_GET_FILE_URI_badchild(self):
16963         base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
16964         errmsg = "Files have no children, certainly not named 'boguschild'"
16965hunk ./src/allmydata/test/test_web.py 904
16966                              self.PUT, base, "")
16967         return d
16968 
16969+    def test_PUT_FILE_URI_mdmf(self):
16970+        base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
16971+        self._quux_new_contents = "new_contents"
16972+        d = self.GET(base)
16973+        d.addCallback(lambda res:
16974+            self.failUnlessIsQuuxDotTxt(res))
16975+        d.addCallback(lambda ignored:
16976+            self.PUT(base, self._quux_new_contents))
16977+        d.addCallback(lambda ignored:
16978+            self.GET(base))
16979+        d.addCallback(lambda res:
16980+            self.failUnlessReallyEqual(res, self._quux_new_contents))
16981+        return d
16982+
16983+    def test_PUT_FILE_URI_mdmf_extensions(self):
16984+        base = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
16985+        self._quux_new_contents = "new_contents"
16986+        d = self.GET(base)
16987+        d.addCallback(lambda res: self.failUnlessIsQuuxDotTxt(res))
16988+        d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
16989+        d.addCallback(lambda ignored: self.GET(base))
16990+        d.addCallback(lambda res: self.failUnlessEqual(self._quux_new_contents,
16991+                                                       res))
16992+        return d
16993+
16994+    def test_PUT_FILE_URI_mdmf_readonly(self):
16995+        # We're not allowed to PUT things to a readonly cap.
16996+        base = "/uri/%s" % self._quux_txt_readonly_uri
16997+        d = self.GET(base)
16998+        d.addCallback(lambda res:
16999+            self.failUnlessIsQuuxDotTxt(res))
17000+        # What should we get here? We get a 500 error now; that's not right.
17001+        d.addCallback(lambda ignored:
17002+            self.shouldFail2(error.Error, "test_PUT_FILE_URI_mdmf_readonly",
17003+                             "400 Bad Request", "read-only cap",
17004+                             self.PUT, base, "new data"))
17005+        return d
17006+
17007+    def test_PUT_FILE_URI_sdmf_readonly(self):
17008+        # We're not allowed to put things to a readonly cap.
17009+        base = "/uri/%s" % self._baz_txt_readonly_uri
17010+        d = self.GET(base)
17011+        d.addCallback(lambda res:
17012+            self.failUnlessIsBazDotTxt(res))
17013+        d.addCallback(lambda ignored:
17014+            self.shouldFail2(error.Error, "test_PUT_FILE_URI_sdmf_readonly",
17015+                             "400 Bad Request", "read-only cap",
17016+                             self.PUT, base, "new_data"))
17017+        return d
17018+
17019     # TODO: version of this with a Unicode filename
17020     def test_GET_FILEURL_save(self):
17021         d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
17022hunk ./src/allmydata/test/test_web.py 970
17023         d.addBoth(self.should404, "test_GET_FILEURL_missing")
17024         return d
17025 
17026+    def test_GET_FILEURL_info_mdmf(self):
17027+        d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
17028+        def _got(res):
17029+            self.failUnlessIn("mutable file (mdmf)", res)
17030+            self.failUnlessIn(self._quux_txt_uri, res)
17031+            self.failUnlessIn(self._quux_txt_readonly_uri, res)
17032+        d.addCallback(_got)
17033+        return d
17034+
17035+    def test_GET_FILEURL_info_mdmf_readonly(self):
17036+        d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
17037+        def _got(res):
17038+            self.failUnlessIn("mutable file (mdmf)", res)
17039+            self.failIfIn(self._quux_txt_uri, res)
17040+            self.failUnlessIn(self._quux_txt_readonly_uri, res)
17041+        d.addCallback(_got)
17042+        return d
17043+
17044+    def test_GET_FILEURL_info_sdmf(self):
17045+        d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
17046+        def _got(res):
17047+            self.failUnlessIn("mutable file (sdmf)", res)
17048+            self.failUnlessIn(self._baz_txt_uri, res)
17049+        d.addCallback(_got)
17050+        return d
17051+
17052+    def test_GET_FILEURL_info_mdmf_extensions(self):
17053+        d = self.GET("/uri/%s:3:131073?t=info" % self._quux_txt_uri)
17054+        def _got(res):
17055+            self.failUnlessIn("mutable file (mdmf)", res)
17056+            self.failUnlessIn(self._quux_txt_uri, res)
17057+            self.failUnlessIn(self._quux_txt_readonly_uri, res)
17058+        d.addCallback(_got)
17059+        return d
17060+
17061     def test_PUT_overwrite_only_files(self):
17062         # create a directory, put a file in that directory.
17063         contents, n, filecap = self.makefile(8)
17064hunk ./src/allmydata/test/test_web.py 1052
17065         contents = self.NEWFILE_CONTENTS * 300000
17066         d = self.PUT("/uri?mutable=true&mutable-type=mdmf",
17067                      contents)
17068+        def _got_filecap(filecap):
17069+            self.failUnless(filecap.startswith("URI:MDMF"))
17070+            return filecap
17071+        d.addCallback(_got_filecap)
17072         d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
17073         d.addCallback(lambda json: self.failUnlessIn("mdmf", json))
17074         return d
17075hunk ./src/allmydata/test/test_web.py 1222
17076         d.addCallback(_got_json, "sdmf")
17077         return d
17078 
17079+    def test_GET_FILEURL_json_mdmf_extensions(self):
17080+        # A GET invoked against a URL that includes an MDMF cap with
17081+        # extensions should fetch the same JSON information as a GET
17082+        # invoked against a bare cap.
17083+        self._quux_txt_uri = "%s:3:131073" % self._quux_txt_uri
17084+        self._quux_txt_readonly_uri = "%s:3:131073" % self._quux_txt_readonly_uri
17085+        d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
17086+        d.addCallback(self.failUnlessIsQuuxJSON)
17087+        return d
17088+
17089+    def test_GET_FILEURL_json_mdmf(self):
17090+        d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
17091+        d.addCallback(self.failUnlessIsQuuxJSON)
17092+        return d
17093+
17094     def test_GET_FILEURL_json_missing(self):
17095         d = self.GET(self.public_url + "/foo/missing?json")
17096         d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
17097hunk ./src/allmydata/test/test_web.py 1281
17098             self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>',res)
17099             self.failUnlessIn("mutable-type-mdmf", res)
17100             self.failUnlessIn("mutable-type-sdmf", res)
17101+            self.failUnlessIn("quux", res)
17102         d.addCallback(_check)
17103         return d
17104 
17105hunk ./src/allmydata/test/test_web.py 1539
17106         d.addCallback(self.get_operation_results, "127", "json")
17107         def _got_json(stats):
17108             expected = {"count-immutable-files": 3,
17109-                        "count-mutable-files": 0,
17110+                        "count-mutable-files": 2,
17111                         "count-literal-files": 0,
17112hunk ./src/allmydata/test/test_web.py 1541
17113-                        "count-files": 3,
17114+                        "count-files": 5,
17115                         "count-directories": 3,
17116                         "size-immutable-files": 57,
17117                         "size-literal-files": 0,
17118hunk ./src/allmydata/test/test_web.py 1547
17119                         #"size-directories": 1912, # varies
17120                         #"largest-directory": 1590,
17121-                        "largest-directory-children": 5,
17122+                        "largest-directory-children": 7,
17123                         "largest-immutable-file": 19,
17124                         }
17125             for k,v in expected.iteritems():
17126hunk ./src/allmydata/test/test_web.py 1564
17127         def _check(res):
17128             self.failUnless(res.endswith("\n"))
17129             units = [simplejson.loads(t) for t in res[:-1].split("\n")]
17130-            self.failUnlessReallyEqual(len(units), 7)
17131+            self.failUnlessReallyEqual(len(units), 9)
17132             self.failUnlessEqual(units[-1]["type"], "stats")
17133             first = units[0]
17134             self.failUnlessEqual(first["path"], [])
17135hunk ./src/allmydata/test/test_web.py 1575
17136             self.failIfEqual(baz["storage-index"], None)
17137             self.failIfEqual(baz["verifycap"], None)
17138             self.failIfEqual(baz["repaircap"], None)
17139+            # XXX: Add quux and baz to this test.
17140             return
17141         d.addCallback(_check)
17142         return d
17143hunk ./src/allmydata/test/test_web.py 2021
17144         d.addCallback(lambda ignored:
17145             self.POST("/uri?t=upload&mutable=true&mutable-type=mdmf",
17146                       file=('mdmf.txt', self.NEWFILE_CONTENTS * 300000)))
17147+        def _got_filecap(filecap):
17148+            self.failUnless(filecap.startswith("URI:MDMF"))
17149+            return filecap
17150+        d.addCallback(_got_filecap)
17151         d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
17152         d.addCallback(_got_json, "mdmf")
17153         return d
17154hunk ./src/allmydata/test/test_web.py 2038
17155             filenameu = unicode(filename)
17156             self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
17157             return self.GET(self.public_url + "/foo/%s?t=json" % filename)
17158+        def _got_mdmf_cap(filecap):
17159+            self.failUnless(filecap.startswith("URI:MDMF"))
17160+            return filecap
17161         d.addCallback(_got_cap, "sdmf.txt")
17162         def _got_json(json, version):
17163             data = simplejson.loads(json)
17164hunk ./src/allmydata/test/test_web.py 2053
17165             self.POST(self.public_url + \
17166                       "/foo?t=upload&mutable=true&mutable-type=mdmf",
17167                       file=("mdmf.txt", self.NEWFILE_CONTENTS * 300000)))
17168+        d.addCallback(_got_mdmf_cap)
17169         d.addCallback(_got_cap, "mdmf.txt")
17170         d.addCallback(_got_json, "mdmf")
17171         return d
17172hunk ./src/allmydata/test/test_web.py 2287
17173         # make sure that nothing was added
17174         d.addCallback(lambda res:
17175                       self.failUnlessNodeKeysAre(self._foo_node,
17176-                                                 [u"bar.txt", u"blockingfile",
17177-                                                  u"empty", u"n\u00fc.txt",
17178+                                                 [u"bar.txt", u"baz.txt", u"blockingfile",
17179+                                                  u"empty", u"n\u00fc.txt", u"quux.txt",
17180                                                   u"sub"]))
17181         return d
17182 
17183hunk ./src/allmydata/test/test_web.py 2410
17184         d.addCallback(_check3)
17185         return d
17186 
17187+    def test_POST_FILEURL_mdmf_check(self):
17188+        quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
17189+        d = self.POST(quux_url, t="check")
17190+        def _check(res):
17191+            self.failUnlessIn("Healthy", res)
17192+        d.addCallback(_check)
17193+        quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
17194+        d.addCallback(lambda ignored:
17195+            self.POST(quux_extension_url, t="check"))
17196+        d.addCallback(_check)
17197+        return d
17198+
17199+    def test_POST_FILEURL_mdmf_check_and_repair(self):
17200+        quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
17201+        d = self.POST(quux_url, t="check", repair="true")
17202+        def _check(res):
17203+            self.failUnlessIn("Healthy", res)
17204+        d.addCallback(_check)
17205+        quux_extension_url = "/uri/%s" %\
17206+            urllib.quote("%s:3:131073" % self._quux_txt_uri)
17207+        d.addCallback(lambda ignored:
17208+            self.POST(quux_extension_url, t="check", repair="true"))
17209+        d.addCallback(_check)
17210+        return d
17211+
17212     def wait_for_operation(self, ignored, ophandle):
17213         url = "/operations/" + ophandle
17214         url += "?t=status&output=JSON"
17215hunk ./src/allmydata/test/test_web.py 2480
17216         d.addCallback(self.wait_for_operation, "123")
17217         def _check_json(data):
17218             self.failUnlessReallyEqual(data["finished"], True)
17219-            self.failUnlessReallyEqual(data["count-objects-checked"], 8)
17220-            self.failUnlessReallyEqual(data["count-objects-healthy"], 8)
17221+            self.failUnlessReallyEqual(data["count-objects-checked"], 10)
17222+            self.failUnlessReallyEqual(data["count-objects-healthy"], 10)
17223         d.addCallback(_check_json)
17224         d.addCallback(self.get_operation_results, "123", "html")
17225         def _check_html(res):
17226hunk ./src/allmydata/test/test_web.py 2485
17227-            self.failUnless("Objects Checked: <span>8</span>" in res)
17228-            self.failUnless("Objects Healthy: <span>8</span>" in res)
17229+            self.failUnless("Objects Checked: <span>10</span>" in res)
17230+            self.failUnless("Objects Healthy: <span>10</span>" in res)
17231         d.addCallback(_check_html)
17232 
17233         d.addCallback(lambda res:
17234hunk ./src/allmydata/test/test_web.py 2515
17235         d.addCallback(self.wait_for_operation, "124")
17236         def _check_json(data):
17237             self.failUnlessReallyEqual(data["finished"], True)
17238-            self.failUnlessReallyEqual(data["count-objects-checked"], 8)
17239-            self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 8)
17240+            self.failUnlessReallyEqual(data["count-objects-checked"], 10)
17241+            self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 10)
17242             self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
17243             self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
17244             self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
17245hunk ./src/allmydata/test/test_web.py 2522
17246             self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
17247             self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
17248-            self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 8)
17249+            self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 10)
17250             self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
17251             self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
17252         d.addCallback(_check_json)
17253hunk ./src/allmydata/test/test_web.py 2528
17254         d.addCallback(self.get_operation_results, "124", "html")
17255         def _check_html(res):
17256-            self.failUnless("Objects Checked: <span>8</span>" in res)
17257+            self.failUnless("Objects Checked: <span>10</span>" in res)
17258 
17259hunk ./src/allmydata/test/test_web.py 2530
17260-            self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
17261+            self.failUnless("Objects Healthy (before repair): <span>10</span>" in res)
17262             self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
17263             self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
17264 
17265hunk ./src/allmydata/test/test_web.py 2538
17266             self.failUnless("Repairs Successful: <span>0</span>" in res)
17267             self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
17268 
17269-            self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
17270+            self.failUnless("Objects Healthy (after repair): <span>10</span>" in res)
17271             self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
17272             self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
17273         d.addCallback(_check_html)
17274hunk ./src/allmydata/test/test_web.py 2668
17275         filecap3 = node3.get_readonly_uri()
17276         node4 = self.s.create_node_from_uri(make_mutable_file_uri())
17277         dircap = DirectoryNode(node4, None, None).get_uri()
17278+        mdmfcap = make_mutable_file_uri(mdmf=True)
17279         litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
17280         emptydircap = "URI:DIR2-LIT:"
17281         newkids = {u"child-imm":        ["filenode", {"rw_uri": filecap1,
17282hunk ./src/allmydata/test/test_web.py 2685
17283                                                       "ro_uri": self._make_readonly(dircap)}],
17284                    u"dirchild-lit":     ["dirnode",  {"ro_uri": litdircap}],
17285                    u"dirchild-empty":   ["dirnode",  {"ro_uri": emptydircap}],
17286+                   u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
17287+                                                        "ro_uri": self._make_readonly(mdmfcap)}],
17288                    }
17289         return newkids, {'filecap1': filecap1,
17290                          'filecap2': filecap2,
17291hunk ./src/allmydata/test/test_web.py 2696
17292                          'unknown_immcap': unknown_immcap,
17293                          'dircap': dircap,
17294                          'litdircap': litdircap,
17295-                         'emptydircap': emptydircap}
17296+                         'emptydircap': emptydircap,
17297+                         'mdmfcap': mdmfcap}
17298 
17299     def _create_immutable_children(self):
17300         contents, n, filecap1 = self.makefile(12)
17301hunk ./src/allmydata/test/test_web.py 3243
17302             data = data[1]
17303             self.failUnlessIn("mutable-type", data)
17304             self.failUnlessEqual(data['mutable-type'], "mdmf")
17305+            self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
17306+            self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
17307         d.addCallback(_got_json)
17308         return d
17309 
17310}
17311[web/filenode.py: complain if a PUT is requested with a readonly cap
17312Kevan Carstensen <kevan@isnotajoke.com>**20110515230421
17313 Ignore-this: e2f05201f3b008e157062ed187eacbb9
17314] hunk ./src/allmydata/web/filenode.py 229
17315                 raise ExistingChildError()
17316 
17317             if self.node.is_mutable():
17318+                # Are we a readonly filenode? We shouldn't allow callers
17319+                # to try to replace us if we are.
17320+                if self.node.is_readonly():
17321+                    raise WebError("PUT to a mutable file: replace or update"
17322+                                   " requested with read-only cap")
17323                 if offset is None:
17324                     return self.replace_my_contents(req)
17325 
17326[web/info.py: Display mutable type information when describing a mutable file
17327Kevan Carstensen <kevan@isnotajoke.com>**20110515230444
17328 Ignore-this: ce5ad22b494effe6c15e49471fae0d99
17329] {
17330hunk ./src/allmydata/web/info.py 8
17331 from nevow.inevow import IRequest
17332 
17333 from allmydata.util import base32
17334-from allmydata.interfaces import IDirectoryNode, IFileNode
17335+from allmydata.interfaces import IDirectoryNode, IFileNode, MDMF_VERSION, SDMF_VERSION
17336 from allmydata.web.common import getxmlfile
17337 from allmydata.mutable.common import UnrecoverableFileError # TODO: move
17338 
17339hunk ./src/allmydata/web/info.py 31
17340             si = node.get_storage_index()
17341             if si:
17342                 if node.is_mutable():
17343-                    return "mutable file"
17344+                    ret = "mutable file"
17345+                    if node.get_version() == MDMF_VERSION:
17346+                        ret += " (mdmf)"
17347+                    else:
17348+                        ret += " (sdmf)"
17349+                    return ret
17350                 return "immutable file"
17351             return "immutable LIT file"
17352         return "unknown"
17353}
17354[uri: teach mutable URI objects how to allow other objects to give them extension parameters
17355Kevan Carstensen <kevan@isnotajoke.com>**20110531012036
17356 Ignore-this: 96c06cee1efe5a92a5ed8d87ca09a7dd
17357] {
17358hunk ./src/allmydata/uri.py 300
17359     def get_extension_params(self):
17360         return []
17361 
17362+    def set_extension_params(self, params):
17363+        pass
17364+
17365 class ReadonlySSKFileURI(_BaseURI):
17366     implements(IURI, IMutableFileURI)
17367 
17368hunk ./src/allmydata/uri.py 360
17369     def get_extension_params(self):
17370         return []
17371 
17372+    def set_extension_params(self, params):
17373+        pass
17374+
17375 class SSKVerifierURI(_BaseURI):
17376     implements(IVerifierURI)
17377 
17378hunk ./src/allmydata/uri.py 410
17379     def get_extension_params(self):
17380         return []
17381 
17382+    def set_extension_params(self, params):
17383+        pass
17384+
17385 class WritableMDMFFileURI(_BaseURI):
17386     implements(IURI, IMutableFileURI)
17387 
17388hunk ./src/allmydata/uri.py 480
17389     def get_extension_params(self):
17390         return self.extension
17391 
17392+    def set_extension_params(self, params):
17393+        params = map(str, params)
17394+        self.extension = params
17395+
17396 class ReadonlyMDMFFileURI(_BaseURI):
17397     implements(IURI, IMutableFileURI)
17398 
17399hunk ./src/allmydata/uri.py 552
17400     def get_extension_params(self):
17401         return self.extension
17402 
17403+    def set_extension_params(self, params):
17404+        params = map(str, params)
17405+        self.extension = params
17406+
17407 class MDMFVerifierURI(_BaseURI):
17408     implements(IVerifierURI)
17409 
17410}
17411[interfaces: working update to interfaces.py for extension handling
17412Kevan Carstensen <kevan@isnotajoke.com>**20110531012201
17413 Ignore-this: 559c43cbf14eec7ac163ebd00c0b7a36
17414] {
17415hunk ./src/allmydata/interfaces.py 549
17416     def get_extension_params():
17417         """Return the extension parameters in the URI"""
17418 
17419+    def set_extension_params():
17420+        """Set the extension parameters that should be in the URI"""
17421+
17422 class IDirectoryURI(Interface):
17423     pass
17424 
17425hunk ./src/allmydata/interfaces.py 1049
17426         writer-visible data using this writekey.
17427         """
17428 
17429-    def set_version(version):
17430-        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
17431-        we upload in SDMF for reasons of compatibility. If you want to
17432-        change this, set_version will let you do that.
17433-
17434-        To say that this file should be uploaded in SDMF, pass in a 0. To
17435-        say that the file should be uploaded as MDMF, pass in a 1.
17436-        """
17437-
17438     def get_version():
17439         """Returns the mutable file protocol version."""
17440 
17441}
17442[mutable/publish: tell filenodes about encoding parameters so they can be put in the cap
17443Kevan Carstensen <kevan@isnotajoke.com>**20110531012447
17444 Ignore-this: cf19f07a6913208a327604457466f2f2
17445] hunk ./src/allmydata/mutable/publish.py 1146
17446         self.log("Publish done, success")
17447         self._status.set_status("Finished")
17448         self._status.set_progress(1.0)
17449+        # Get k and segsize, then give them to the caller.
17450+        hints = {}
17451+        hints['segsize'] = self.segment_size
17452+        hints['k'] = self.required_shares
17453+        self._node.set_downloader_hints(hints)
17454         eventually(self.done_deferred.callback, res)
17455 
17456     def _failure(self):
17457[mutable/servermap: caps imply protocol version, so the servermap doesn't need to tell the filenode what it is anymore.
17458Kevan Carstensen <kevan@isnotajoke.com>**20110531012557
17459 Ignore-this: 9925f5dde5452db92cdbc4a7d6adf1c1
17460] hunk ./src/allmydata/mutable/servermap.py 877
17461         # and the versionmap
17462         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
17463 
17464-        # It's our job to set the protocol version of our parent
17465-        # filenode if it isn't already set.
17466-        if not self._node.get_version():
17467-            # The first byte of the prefix is the version.
17468-            v = struct.unpack(">B", prefix[:1])[0]
17469-            self.log("got version %d" % v)
17470-            self._node.set_version(v)
17471-
17472         return verinfo
17473 
17474 
17475[mutable/filenode: pass downloader hints between publisher, MutableFileNode, and MutableFileVersion as convenient
17476Kevan Carstensen <kevan@isnotajoke.com>**20110531012641
17477 Ignore-this: 672c586891abfa38397bcdf90b64ca72
17478 
17479 We still need to work on making this more thorough; i.e., passing hints
17480 when other operations change encoding parameters.
17481] {
17482hunk ./src/allmydata/mutable/filenode.py 75
17483         # set to this default value in case neither of those things happen,
17484         # or in case the servermap can't find any shares to tell us what
17485         # to publish as.
17486-        # XXX: Version should come in via the constructor.
17487         self._protocol_version = None
17488 
17489         # all users of this MutableFileNode go through the serializer. This
17490hunk ./src/allmydata/mutable/filenode.py 83
17491         # forever without consuming more and more memory.
17492         self._serializer = defer.succeed(None)
17493 
17494+        # Starting with MDMF, we can get these from caps if they're
17495+        # there. Leave them alone for now; they'll be filled in by my
17496+        # init_from_cap method if necessary.
17497+        self._downloader_hints = {}
17498+
17499     def __repr__(self):
17500         if hasattr(self, '_uri'):
17501             return "<%s %x %s %s>" % (self.__class__.__name__, id(self), self.is_readonly() and 'RO' or 'RW', self._uri.abbrev())
17502hunk ./src/allmydata/mutable/filenode.py 120
17503         # if possible, otherwise by the first peer that Publish talks to.
17504         self._privkey = None
17505         self._encprivkey = None
17506+
17507+        # Starting with MDMF caps, we allowed arbitrary extensions in
17508+        # caps. If we were initialized with a cap that had extensions,
17509+        # we want to remember them so we can tell MutableFileVersions
17510+        # about them.
17511+        extensions = self._uri.get_extension_params()
17512+        if extensions:
17513+            extensions = map(int, extensions)
17514+            suspected_k, suspected_segsize = extensions
17515+            self._downloader_hints['k'] = suspected_k
17516+            self._downloader_hints['segsize'] = suspected_segsize
17517+
17518         return self
17519 
17520hunk ./src/allmydata/mutable/filenode.py 134
17521-    def create_with_keys(self, (pubkey, privkey), contents):
17522+    def create_with_keys(self, (pubkey, privkey), contents,
17523+                         version=SDMF_VERSION):
17524         """Call this to create a brand-new mutable file. It will create the
17525         shares, find homes for them, and upload the initial contents (created
17526         with the same rules as IClient.create_mutable_file() ). Returns a
17527hunk ./src/allmydata/mutable/filenode.py 148
17528         self._writekey = hashutil.ssk_writekey_hash(privkey_s)
17529         self._encprivkey = self._encrypt_privkey(self._writekey, privkey_s)
17530         self._fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
17531-        if self._protocol_version == MDMF_VERSION:
17532+        if version == MDMF_VERSION:
17533             self._uri = WritableMDMFFileURI(self._writekey, self._fingerprint)
17534hunk ./src/allmydata/mutable/filenode.py 150
17535-        else:
17536+            self._protocol_version = version
17537+        elif version == SDMF_VERSION:
17538             self._uri = WriteableSSKFileURI(self._writekey, self._fingerprint)
17539hunk ./src/allmydata/mutable/filenode.py 153
17540+            self._protocol_version = version
17541         self._readkey = self._uri.readkey
17542         self._storage_index = self._uri.storage_index
17543         initial_contents = self._get_initial_contents(contents)
17544hunk ./src/allmydata/mutable/filenode.py 365
17545                                      self._readkey,
17546                                      history=self._history)
17547             assert mfv.is_readonly()
17548+            mfv.set_downloader_hints(self._downloader_hints)
17549             # our caller can use this to download the contents of the
17550             # mutable file.
17551             return mfv
17552hunk ./src/allmydata/mutable/filenode.py 520
17553                                      self._secret_holder,
17554                                      history=self._history)
17555             assert not mfv.is_readonly()
17556+            mfv.set_downloader_hints(self._downloader_hints)
17557             return mfv
17558 
17559         return d.addCallback(_build_version)
17560hunk ./src/allmydata/mutable/filenode.py 549
17561         new_contents as an argument. I return a Deferred that eventually
17562         fires with the results of my replacement process.
17563         """
17564+        # TODO: Update downloader hints.
17565         return self._do_serialized(self._overwrite, new_contents)
17566 
17567 
17568hunk ./src/allmydata/mutable/filenode.py 563
17569         return d
17570 
17571 
17572-
17573     def upload(self, new_contents, servermap):
17574         """
17575         I overwrite the contents of the best recoverable version of this
17576hunk ./src/allmydata/mutable/filenode.py 570
17577         creating/updating our own servermap. I return a Deferred that
17578         fires with the results of my upload.
17579         """
17580+        # TODO: Update downloader hints
17581         return self._do_serialized(self._upload, new_contents, servermap)
17582 
17583 
17584hunk ./src/allmydata/mutable/filenode.py 582
17585         Deferred that eventually fires with an UploadResults instance
17586         describing this process.
17587         """
17588+        # TODO: Update downloader hints.
17589         return self._do_serialized(self._modify, modifier, backoffer)
17590 
17591 
17592hunk ./src/allmydata/mutable/filenode.py 650
17593         return u.update()
17594 
17595 
17596-    def set_version(self, version):
17597+    #def set_version(self, version):
17598         # I can be set in two ways:
17599         #  1. When the node is created.
17600         #  2. (for an existing share) when the Servermap is updated
17601hunk ./src/allmydata/mutable/filenode.py 655
17602         #     before I am read.
17603-        assert version in (MDMF_VERSION, SDMF_VERSION)
17604-        self._protocol_version = version
17605+    #    assert version in (MDMF_VERSION, SDMF_VERSION)
17606+    #    self._protocol_version = version
17607 
17608 
17609     def get_version(self):
17610hunk ./src/allmydata/mutable/filenode.py 691
17611         """
17612         assert self._pubkey, "update_servermap must be called before publish"
17613 
17614+        # Define IPublishInvoker with a set_downloader_hints method?
17615+        # Then have the publisher call that method when it's done publishing?
17616         p = Publish(self, self._storage_broker, servermap)
17617         if self._history:
17618             self._history.notify_publish(p.get_status(),
17619hunk ./src/allmydata/mutable/filenode.py 702
17620         return d
17621 
17622 
17623+    def set_downloader_hints(self, hints):
17624+        self._downloader_hints = hints
17625+        extensions = hints.values()
17626+        self._uri.set_extension_params(extensions)
17627+
17628+
17629     def _did_upload(self, res, size):
17630         self._most_recent_size = size
17631         return res
17632hunk ./src/allmydata/mutable/filenode.py 769
17633         return self._writekey
17634 
17635 
17636+    def set_downloader_hints(self, hints):
17637+        """
17638+        I set the downloader hints.
17639+        """
17640+        assert isinstance(hints, dict)
17641+
17642+        self._downloader_hints = hints
17643+
17644+
17645+    def get_downloader_hints(self):
17646+        """
17647+        I return the downloader hints.
17648+        """
17649+        return self._downloader_hints
17650+
17651+
17652     def overwrite(self, new_contents):
17653         """
17654         I overwrite the contents of this mutable file version with the
17655hunk ./src/allmydata/nodemaker.py 97
17656                             version=SDMF_VERSION):
17657         n = MutableFileNode(self.storage_broker, self.secret_holder,
17658                             self.default_encoding_parameters, self.history)
17659-        n.set_version(version)
17660         d = self.key_generator.generate(keysize)
17661hunk ./src/allmydata/nodemaker.py 98
17662-        d.addCallback(n.create_with_keys, contents)
17663+        d.addCallback(n.create_with_keys, contents, version=version)
17664         d.addCallback(lambda res: n)
17665         return d
17666 
17667}
17668[test: change test fixtures to work with our new extension passing API; add, change, and delete tests as appropriate to reflect the fact that caps without hints are now the exception rather than the norm
17669Kevan Carstensen <kevan@isnotajoke.com>**20110531012739
17670 Ignore-this: 30ebf79b5f6c17f40fa4385de12070a0
17671] {
17672hunk ./src/allmydata/test/common.py 198
17673     def __init__(self, storage_broker, secret_holder,
17674                  default_encoding_parameters, history):
17675         self.init_from_cap(make_mutable_file_cap())
17676-    def create(self, contents, key_generator=None, keysize=None):
17677-        if self.file_types[self.storage_index] == MDMF_VERSION and \
17678+        self._k = default_encoding_parameters['k']
17679+        self._segsize = default_encoding_parameters['max_segment_size']
17680+    def create(self, contents, key_generator=None, keysize=None,
17681+               version=SDMF_VERSION):
17682+        if version == MDMF_VERSION and \
17683             isinstance(self.my_uri, (uri.ReadonlySSKFileURI,
17684                                  uri.WriteableSSKFileURI)):
17685             self.init_from_cap(make_mdmf_mutable_file_cap())
17686hunk ./src/allmydata/test/common.py 206
17687+        self.file_types[self.storage_index] = version
17688         initial_contents = self._get_initial_contents(contents)
17689         data = initial_contents.read(initial_contents.get_size())
17690         data = "".join(data)
17691hunk ./src/allmydata/test/common.py 211
17692         self.all_contents[self.storage_index] = data
17693+        self.my_uri.set_extension_params([self._k, self._segsize])
17694         return defer.succeed(self)
17695     def _get_initial_contents(self, contents):
17696         if contents is None:
17697hunk ./src/allmydata/test/common.py 283
17698     def get_servermap(self, mode):
17699         return defer.succeed(None)
17700 
17701-    def set_version(self, version):
17702-        assert version in (SDMF_VERSION, MDMF_VERSION)
17703-        self.file_types[self.storage_index] = version
17704-
17705     def get_version(self):
17706         assert self.storage_index in self.file_types
17707         return self.file_types[self.storage_index]
17708hunk ./src/allmydata/test/common.py 361
17709         new_data = new_contents.read(new_contents.get_size())
17710         new_data = "".join(new_data)
17711         self.all_contents[self.storage_index] = new_data
17712+        self.my_uri.set_extension_params([self._k, self._segsize])
17713         return defer.succeed(None)
17714     def modify(self, modifier):
17715         # this does not implement FileTooLargeError, but the real one does
17716hunk ./src/allmydata/test/common.py 371
17717         old_contents = self.all_contents[self.storage_index]
17718         new_data = modifier(old_contents, None, True)
17719         self.all_contents[self.storage_index] = new_data
17720+        self.my_uri.set_extension_params([self._k, self._segsize])
17721         return None
17722 
17723     # As actually implemented, MutableFilenode and MutableFileVersion
17724hunk ./src/allmydata/test/common.py 433
17725     else:
17726         cap = make_mutable_file_cap()
17727 
17728-    filenode = FakeMutableFileNode(None, None, None, None)
17729+    encoding_params = {}
17730+    encoding_params['k'] = 3
17731+    encoding_params['max_segment_size'] = 128*1024
17732+
17733+    filenode = FakeMutableFileNode(None, None, encoding_params, None)
17734     filenode.init_from_cap(cap)
17735hunk ./src/allmydata/test/common.py 439
17736-    FakeMutableFileNode.all_contents[filenode.storage_index] = contents
17737+    if mdmf:
17738+        filenode.create(MutableData(contents), version=MDMF_VERSION)
17739+    else:
17740+        filenode.create(MutableData(contents), version=SDMF_VERSION)
17741     return filenode
17742 
17743 
17744hunk ./src/allmydata/test/test_cli.py 1098
17745             self.failUnlessEqual(rc, 0)
17746             self.failUnlessEqual(out, data2)
17747         d.addCallback(_got_data)
17748+        # Now strip the extension information off of the cap and try
17749+        # to put something to it.
17750+        def _make_bare_cap(ignored):
17751+            cap = self.cap.split(":")
17752+            cap = ":".join(cap[:len(cap) - 2])
17753+            self.cap = cap
17754+        d.addCallback(_make_bare_cap)
17755+        data3 = "data3" * 100000
17756+        fn3 = os.path.join(self.basedir, "data3")
17757+        fileutil.write(fn3, data3)
17758+        d.addCallback(lambda ignored:
17759+            self.do_cli("put", fn3, self.cap))
17760+        d.addCallback(lambda ignored:
17761+            self.do_cli("get", self.cap))
17762+        def _got_data3((rc, out, err)):
17763+            self.failUnlessEqual(rc, 0)
17764+            self.failUnlessEqual(out, data3)
17765+        d.addCallback(_got_data3)
17766         return d
17767 
17768     def test_put_to_sdmf_cap(self):
17769hunk ./src/allmydata/test/test_mutable.py 311
17770         def _created(n):
17771             self.failUnless(isinstance(n, MutableFileNode))
17772             s = n.get_uri()
17773-            s2 = "%s:3:131073" % s
17774-            n2 = self.nodemaker.create_from_cap(s2)
17775+            # We need to cheat a little and delete the nodemaker's
17776+            # cache, otherwise we'll get the same node instance back.
17777+            self.failUnlessIn(":3:131073", s)
17778+            n2 = self.nodemaker.create_from_cap(s)
17779 
17780             self.failUnlessEqual(n2.get_storage_index(), n.get_storage_index())
17781             self.failUnlessEqual(n.get_writekey(), n2.get_writekey())
17782hunk ./src/allmydata/test/test_mutable.py 318
17783+            hints = n2._downloader_hints
17784+            self.failUnlessEqual(hints['k'], 3)
17785+            self.failUnlessEqual(hints['segsize'], 131073)
17786         d.addCallback(_created)
17787         return d
17788 
17789hunk ./src/allmydata/test/test_mutable.py 346
17790         def _created(n):
17791             self.failUnless(isinstance(n, MutableFileNode))
17792             s = n.get_readonly_uri()
17793-            s = "%s:3:131073" % s
17794+            self.failUnlessIn(":3:131073", s)
17795 
17796             n2 = self.nodemaker.create_from_cap(s)
17797             self.failUnless(isinstance(n2, MutableFileNode))
17798hunk ./src/allmydata/test/test_mutable.py 352
17799             self.failUnless(n2.is_readonly())
17800             self.failUnlessEqual(n.get_storage_index(), n2.get_storage_index())
17801+            hints = n2._downloader_hints
17802+            self.failUnlessEqual(hints["k"], 3)
17803+            self.failUnlessEqual(hints["segsize"], 131073)
17804         d.addCallback(_created)
17805         return d
17806 
17807hunk ./src/allmydata/test/test_mutable.py 514
17808         return d
17809 
17810 
17811+    def test_create_and_download_from_bare_mdmf_cap(self):
17812+        # MDMF caps have extension parameters on them by default. We
17813+        # need to make sure that they work without extension parameters.
17814+        contents = MutableData("contents" * 100000)
17815+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION,
17816+                                               contents=contents)
17817+        def _created(node):
17818+            uri = node.get_uri()
17819+            self._created = node
17820+            self.failUnlessIn(":3:131073", uri)
17821+            # Now strip that off the end of the uri, then try creating
17822+            # and downloading the node again.
17823+            bare_uri = uri.replace(":3:131073", "")
17824+            assert ":3:131073" not in bare_uri
17825+
17826+            return self.nodemaker.create_from_cap(bare_uri)
17827+        d.addCallback(_created)
17828+        def _created_bare(node):
17829+            self.failUnlessEqual(node.get_writekey(),
17830+                                 self._created.get_writekey())
17831+            self.failUnlessEqual(node.get_readkey(),
17832+                                 self._created.get_readkey())
17833+            self.failUnlessEqual(node.get_storage_index(),
17834+                                 self._created.get_storage_index())
17835+            return node.download_best_version()
17836+        d.addCallback(_created_bare)
17837+        d.addCallback(lambda data:
17838+            self.failUnlessEqual(data, "contents" * 100000))
17839+        return d
17840+
17841+
17842     def test_mdmf_write_count(self):
17843         # Publishing an MDMF file should only cause one write for each
17844         # share that is to be published. Otherwise, we introduce
17845hunk ./src/allmydata/test/test_mutable.py 2155
17846         # and set the encoding parameters to something completely different
17847         fn2._required_shares = k
17848         fn2._total_shares = n
17849-        # Normally a servermap update would occur before a publish.
17850-        # Here, it doesn't, so we have to do it ourselves.
17851-        fn2.set_version(version)
17852 
17853         s = self._storage
17854         s._peers = {} # clear existing storage
17855hunk ./src/allmydata/test/test_mutable.py 2928
17856         # We need to define an API by which an uploader can set the
17857         # extension parameters, and by which a downloader can retrieve
17858         # extensions.
17859-        self.failUnless(False)
17860+        d = self.mdmf_node.get_best_mutable_version()
17861+        def _got_version(version):
17862+            hints = version.get_downloader_hints()
17863+            # Should be empty at this point.
17864+            self.failUnlessIn("k", hints)
17865+            self.failUnlessEqual(hints['k'], 3)
17866+            self.failUnlessIn('segsize', hints)
17867+            self.failUnlessEqual(hints['segsize'], 131073)
17868+        d.addCallback(_got_version)
17869+        return d
17870 
17871 
17872     def test_extensions_from_cap(self):
17873hunk ./src/allmydata/test/test_mutable.py 2941
17874-        self.failUnless(False)
17875+        # If we initialize a mutable file with a cap that has extension
17876+        # parameters in it and then grab the extension parameters using
17877+        # our API, we should see that they're set correctly.
17878+        mdmf_uri = self.mdmf_node.get_uri()
17879+        new_node = self.nm.create_from_cap(mdmf_uri)
17880+        d = new_node.get_best_mutable_version()
17881+        def _got_version(version):
17882+            hints = version.get_downloader_hints()
17883+            self.failUnlessIn("k", hints)
17884+            self.failUnlessEqual(hints["k"], 3)
17885+            self.failUnlessIn("segsize", hints)
17886+            self.failUnlessEqual(hints["segsize"], 131073)
17887+        d.addCallback(_got_version)
17888+        return d
17889 
17890 
17891     def test_extensions_from_upload(self):
17892hunk ./src/allmydata/test/test_mutable.py 2958
17893-        self.failUnless(False)
17894+        # If we create a new mutable file with some contents, we should
17895+        # get back an MDMF cap with the right hints in place.
17896+        contents = "foo bar baz" * 100000
17897+        d = self.nm.create_mutable_file(contents, version=MDMF_VERSION)
17898+        def _got_mutable_file(n):
17899+            rw_uri = n.get_uri()
17900+            expected_k = str(self.c.DEFAULT_ENCODING_PARAMETERS['k'])
17901+            self.failUnlessIn(expected_k, rw_uri)
17902+            # XXX: Get this more intelligently.
17903+            self.failUnlessIn("131073", rw_uri)
17904+
17905+            ro_uri = n.get_readonly_uri()
17906+            self.failUnlessIn(expected_k, ro_uri)
17907+            self.failUnlessIn("131073", ro_uri)
17908+        d.addCallback(_got_mutable_file)
17909+        return d
17910 
17911 
17912     def test_cap_after_upload(self):
17913hunk ./src/allmydata/test/test_web.py 52
17914         return stats
17915 
17916 class FakeNodeMaker(NodeMaker):
17917+    encoding_params = {
17918+        'k': 3,
17919+        'n': 10,
17920+        'happy': 7,
17921+        'max_segment_size':128*1024 # 1024=KiB
17922+    }
17923     def _create_lit(self, cap):
17924         return FakeCHKFileNode(cap)
17925     def _create_immutable(self, cap):
17926hunk ./src/allmydata/test/test_web.py 63
17927         return FakeCHKFileNode(cap)
17928     def _create_mutable(self, cap):
17929-        return FakeMutableFileNode(None, None, None, None).init_from_cap(cap)
17930+        return FakeMutableFileNode(None,
17931+                                   None,
17932+                                   self.encoding_params, None).init_from_cap(cap)
17933     def create_mutable_file(self, contents="", keysize=None,
17934                             version=SDMF_VERSION):
17935hunk ./src/allmydata/test/test_web.py 68
17936-        n = FakeMutableFileNode(None, None, None, None)
17937-        n.set_version(version)
17938-        return n.create(contents)
17939+        n = FakeMutableFileNode(None, None, self.encoding_params, None)
17940+        return n.create(contents, version=version)
17941 
17942 class FakeUploader(service.Service):
17943     name = "uploader"
17944hunk ./src/allmydata/test/test_web.py 307
17945         self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
17946         self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
17947 
17948-    def failUnlessIsQuuxJSON(self, res):
17949+    def failUnlessIsQuuxJSON(self, res, readonly=False):
17950         data = simplejson.loads(res)
17951         self.failUnless(isinstance(data, list))
17952         self.failUnlessEqual(data[0], "filenode")
17953hunk ./src/allmydata/test/test_web.py 313
17954         self.failUnless(isinstance(data[1], dict))
17955         metadata = data[1]
17956-        return self.failUnlessIsQuuxDotTxtMetadata(metadata)
17957+        return self.failUnlessIsQuuxDotTxtMetadata(metadata, readonly)
17958 
17959hunk ./src/allmydata/test/test_web.py 315
17960-    def failUnlessIsQuuxDotTxtMetadata(self, metadata):
17961+    def failUnlessIsQuuxDotTxtMetadata(self, metadata, readonly):
17962         self.failUnless(metadata['mutable'])
17963hunk ./src/allmydata/test/test_web.py 317
17964-        self.failUnless("rw_uri" in metadata)
17965-        self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
17966+        if readonly:
17967+            self.failIf("rw_uri" in metadata)
17968+        else:
17969+            self.failUnless("rw_uri" in metadata)
17970+            self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
17971         self.failUnless("ro_uri" in metadata)
17972         self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
17973         self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
17974hunk ./src/allmydata/test/test_web.py 892
17975         d.addCallback(self.failUnlessIsQuuxDotTxt)
17976         return d
17977 
17978+    def test_GET_FILE_URI_mdmf_bare_cap(self):
17979+        cap_elements = self._quux_txt_uri.split(":")
17980+        # 6 == expected cap length with two extensions.
17981+        self.failUnlessEqual(len(cap_elements), 6)
17982+
17983+        # Now lop off the extension parameters and stitch everything
17984+        # back together
17985+        quux_uri = ":".join(cap_elements[:len(cap_elements) - 2])
17986+
17987+        # Now GET that. We should get back quux.
17988+        base = "/uri/%s" % urllib.quote(quux_uri)
17989+        d = self.GET(base)
17990+        d.addCallback(self.failUnlessIsQuuxDotTxt)
17991+        return d
17992+
17993     def test_GET_FILE_URI_mdmf_readonly(self):
17994         base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
17995         d = self.GET(base)
17996hunk ./src/allmydata/test/test_web.py 954
17997                                                        res))
17998         return d
17999 
18000+    def test_PUT_FILE_URI_mdmf_bare_cap(self):
18001+        elements = self._quux_txt_uri.split(":")
18002+        self.failUnlessEqual(len(elements), 6)
18003+
18004+        quux_uri = ":".join(elements[:len(elements) - 2])
18005+        base = "/uri/%s" % urllib.quote(quux_uri)
18006+        self._quux_new_contents = "new_contents" * 50000
18007+
18008+        d = self.GET(base)
18009+        d.addCallback(self.failUnlessIsQuuxDotTxt)
18010+        d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
18011+        d.addCallback(lambda ignored: self.GET(base))
18012+        d.addCallback(lambda res:
18013+            self.failUnlessEqual(res, self._quux_new_contents))
18014+        return d
18015+
18016     def test_PUT_FILE_URI_mdmf_readonly(self):
18017         # We're not allowed to PUT things to a readonly cap.
18018         base = "/uri/%s" % self._quux_txt_readonly_uri
18019hunk ./src/allmydata/test/test_web.py 1046
18020         d.addCallback(_got)
18021         return d
18022 
18023+    def test_GET_FILEURL_info_mdmf_bare_cap(self):
18024+        elements = self._quux_txt_uri.split(":")
18025+        self.failUnlessEqual(len(elements), 6)
18026+
18027+        quux_uri = ":".join(elements[:len(elements) - 2])
18028+        base = "/uri/%s?t=info" % urllib.quote(quux_uri)
18029+        d = self.GET(base)
18030+        def _got(res):
18031+            self.failUnlessIn("mutable file (mdmf)", res)
18032+            self.failUnlessIn(quux_uri, res)
18033+        d.addCallback(_got)
18034+        return d
18035+
18036     def test_PUT_overwrite_only_files(self):
18037         # create a directory, put a file in that directory.
18038         contents, n, filecap = self.makefile(8)
18039hunk ./src/allmydata/test/test_web.py 1286
18040         d.addCallback(self.failUnlessIsQuuxJSON)
18041         return d
18042 
18043+    def test_GET_FILEURL_json_mdmf_bare_cap(self):
18044+        elements = self._quux_txt_uri.split(":")
18045+        self.failUnlessEqual(len(elements), 6)
18046+
18047+        quux_uri = ":".join(elements[:len(elements) - 2])
18048+        # so failUnlessIsQuuxJSON will work.
18049+        self._quux_txt_uri = quux_uri
18050+
18051+        # we need to alter the readonly URI in the same way, again so
18052+        # failUnlessIsQuuxJSON will work
18053+        elements = self._quux_txt_readonly_uri.split(":")
18054+        self.failUnlessEqual(len(elements), 6)
18055+        quux_ro_uri = ":".join(elements[:len(elements) - 2])
18056+        self._quux_txt_readonly_uri = quux_ro_uri
18057+
18058+        base = "/uri/%s?t=json" % urllib.quote(quux_uri)
18059+        d = self.GET(base)
18060+        d.addCallback(self.failUnlessIsQuuxJSON)
18061+        return d
18062+
18063+    def test_GET_FILEURL_json_mdmf_bare_readonly_cap(self):
18064+        elements = self._quux_txt_readonly_uri.split(":")
18065+        self.failUnlessEqual(len(elements), 6)
18066+
18067+        quux_readonly_uri = ":".join(elements[:len(elements) - 2])
18068+        # so failUnlessIsQuuxJSON will work
18069+        self._quux_txt_readonly_uri = quux_readonly_uri
18070+        base = "/uri/%s?t=json" % quux_readonly_uri
18071+        d = self.GET(base)
18072+        # XXX: We may need to make a method that knows how to check for
18073+        # readonly JSON, or else alter that one so that it knows how to
18074+        # do that.
18075+        d.addCallback(self.failUnlessIsQuuxJSON, readonly=True)
18076+        return d
18077+
18078     def test_GET_FILEURL_json_mdmf(self):
18079         d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
18080         d.addCallback(self.failUnlessIsQuuxJSON)
18081}
18082[Add MDMF dirnodes
18083Kevan Carstensen <kevan@isnotajoke.com>**20110617175808
18084 Ignore-this: e7d184ece57b272be0e5a3917cc7642a
18085] {
18086hunk ./src/allmydata/client.py 493
18087         # may get an opaque node if there were any problems.
18088         return self.nodemaker.create_from_cap(write_uri, read_uri, deep_immutable=deep_immutable, name=name)
18089 
18090-    def create_dirnode(self, initial_children={}):
18091-        d = self.nodemaker.create_new_mutable_directory(initial_children)
18092+    def create_dirnode(self, initial_children={}, version=SDMF_VERSION):
18093+        d = self.nodemaker.create_new_mutable_directory(initial_children, version=version)
18094         return d
18095 
18096     def create_immutable_dirnode(self, children, convergence=None):
18097hunk ./src/allmydata/dirnode.py 14
18098 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
18099      IImmutableFileNode, IMutableFileNode, \
18100      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
18101-     MustBeDeepImmutableError, CapConstraintError, ChildOfWrongTypeError
18102+     MustBeDeepImmutableError, CapConstraintError, ChildOfWrongTypeError, \
18103+     SDMF_VERSION, MDMF_VERSION
18104 from allmydata.check_results import DeepCheckResults, \
18105      DeepCheckAndRepairResults
18106 from allmydata.monitor import Monitor
18107hunk ./src/allmydata/dirnode.py 617
18108         d.addCallback(lambda res: deleter.old_child)
18109         return d
18110 
18111+    # XXX: Too many arguments? Worthwhile to break into mutable/immutable?
18112     def create_subdirectory(self, namex, initial_children={}, overwrite=True,
18113hunk ./src/allmydata/dirnode.py 619
18114-                            mutable=True, metadata=None):
18115+                            mutable=True, mutable_version=None, metadata=None):
18116         name = normalize(namex)
18117         if self.is_readonly():
18118             return defer.fail(NotWriteableError())
18119hunk ./src/allmydata/dirnode.py 624
18120         if mutable:
18121-            d = self._nodemaker.create_new_mutable_directory(initial_children)
18122+            if mutable_version:
18123+                d = self._nodemaker.create_new_mutable_directory(initial_children,
18124+                                                                 version=mutable_version)
18125+            else:
18126+                d = self._nodemaker.create_new_mutable_directory(initial_children)
18127         else:
18128hunk ./src/allmydata/dirnode.py 630
18129+            # mutable version doesn't make sense for immmutable directories.
18130+            assert mutable_version is None
18131             d = self._nodemaker.create_immutable_directory(initial_children)
18132         def _created(child):
18133             entries = {name: (child, metadata)}
18134hunk ./src/allmydata/nodemaker.py 88
18135         if isinstance(cap, (uri.DirectoryURI,
18136                             uri.ReadonlyDirectoryURI,
18137                             uri.ImmutableDirectoryURI,
18138-                            uri.LiteralDirectoryURI)):
18139+                            uri.LiteralDirectoryURI,
18140+                            uri.MDMFDirectoryURI,
18141+                            uri.ReadonlyMDMFDirectoryURI)):
18142             filenode = self._create_from_single_cap(cap.get_filenode_cap())
18143             return self._create_dirnode(filenode)
18144         return None
18145hunk ./src/allmydata/nodemaker.py 104
18146         d.addCallback(lambda res: n)
18147         return d
18148 
18149-    def create_new_mutable_directory(self, initial_children={}):
18150-        # mutable directories will always be SDMF for now, to help
18151-        # compatibility with older clients.
18152-        version = SDMF_VERSION
18153+    def create_new_mutable_directory(self, initial_children={},
18154+                                     version=SDMF_VERSION):
18155         # initial_children must have metadata (i.e. {} instead of None)
18156         for (name, (node, metadata)) in initial_children.iteritems():
18157             precondition(isinstance(metadata, dict),
18158hunk ./src/allmydata/uri.py 750
18159         return None
18160 
18161 
18162+class MDMFDirectoryURI(_DirectoryBaseURI):
18163+    implements(IDirectoryURI)
18164+
18165+    BASE_STRING='URI:DIR2-MDMF:'
18166+    BASE_STRING_RE=re.compile('^'+BASE_STRING)
18167+    BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-MDMF'+SEP)
18168+    INNER_URI_CLASS=WritableMDMFFileURI
18169+
18170+    def __init__(self, filenode_uri=None):
18171+        if filenode_uri:
18172+            assert not filenode_uri.is_readonly()
18173+        _DirectoryBaseURI.__init__(self, filenode_uri)
18174+
18175+    def is_readonly(self):
18176+        return False
18177+
18178+    def get_readonly(self):
18179+        return ReadonlyMDMFDirectoryURI(self._filenode_uri.get_readonly())
18180+
18181+    def get_verify_cap(self):
18182+        return MDMFDirectoryURIVerifier(self._filenode_uri.get_verify_cap())
18183+
18184+
18185+class ReadonlyMDMFDirectoryURI(_DirectoryBaseURI):
18186+    implements(IReadonlyDirectoryURI)
18187+
18188+    BASE_STRING='URI:DIR2-MDMF-RO:'
18189+    BASE_STRING_RE=re.compile('^'+BASE_STRING)
18190+    BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-MDMF-RO'+SEP)
18191+    INNER_URI_CLASS=ReadonlyMDMFFileURI
18192+
18193+    def __init__(self, filenode_uri=None):
18194+        if filenode_uri:
18195+            assert filenode_uri.is_readonly()
18196+        _DirectoryBaseURI.__init__(self, filenode_uri)
18197+
18198+    def is_readonly(self):
18199+        return True
18200+
18201+    def get_readonly(self):
18202+        return self
18203+
18204+    def get_verify_cap(self):
18205+        return MDMFDirectoryURIVerifier(self._filenode_uri.get_verify_cap())
18206+
18207 def wrap_dirnode_cap(filecap):
18208     if isinstance(filecap, WriteableSSKFileURI):
18209         return DirectoryURI(filecap)
18210hunk ./src/allmydata/uri.py 804
18211         return ImmutableDirectoryURI(filecap)
18212     if isinstance(filecap, LiteralFileURI):
18213         return LiteralDirectoryURI(filecap)
18214+    if isinstance(filecap, WritableMDMFFileURI):
18215+        return MDMFDirectoryURI(filecap)
18216+    if isinstance(filecap, ReadonlyMDMFFileURI):
18217+        return ReadonlyMDMFDirectoryURI(filecap)
18218     assert False, "cannot interpret as a directory cap: %s" % filecap.__class__
18219 
18220hunk ./src/allmydata/uri.py 810
18221+class MDMFDirectoryURIVerifier(_DirectoryBaseURI):
18222+    implements(IVerifierURI)
18223+
18224+    BASE_STRING='URI:DIR2-MDMF-Verifier:'
18225+    BASE_STRING_RE=re.compile('^'+BASE_STRING)
18226+    BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-MDMF-Verifier'+SEP)
18227+    INNER_URI_CLASS=MDMFVerifierURI
18228+
18229+    def __init__(self, filenode_uri=None):
18230+        if filenode_uri:
18231+            assert IVerifierURI.providedBy(filenode_uri)
18232+        self._filenode_uri = filenode_uri
18233+
18234+    def get_filenode_cap(self):
18235+        return self._filenode_uri
18236+
18237+    def is_mutable(self):
18238+        return False
18239 
18240 class DirectoryURIVerifier(_DirectoryBaseURI):
18241     implements(IVerifierURI)
18242hunk ./src/allmydata/uri.py 935
18243             return ImmutableDirectoryURI.init_from_string(s)
18244         elif s.startswith('URI:DIR2-LIT:'):
18245             return LiteralDirectoryURI.init_from_string(s)
18246+        elif s.startswith('URI:DIR2-MDMF:'):
18247+            if can_be_writeable:
18248+                return MDMFDirectoryURI.init_from_string(s)
18249+            kind = "URI:DIR2-MDMF directory writecap"
18250+        elif s.startswith('URI:DIR2-MDMF-RO:'):
18251+            if can_be_mutable:
18252+                return ReadonlyMDMFDirectoryURI.init_from_string(s)
18253+            kind = "URI:DIR2-MDMF-RO readcap to a mutable directory"
18254         elif s.startswith('x-tahoe-future-test-writeable:') and not can_be_writeable:
18255             # For testing how future writeable caps would behave in read-only contexts.
18256             kind = "x-tahoe-future-test-writeable: testing cap"
18257}
18258[Add tests for MDMF directories
18259Kevan Carstensen <kevan@isnotajoke.com>**20110617175950
18260 Ignore-this: 27882fd4cf827030d7574bd4b2b8cb77
18261] {
18262hunk ./src/allmydata/test/test_dirnode.py 14
18263 from allmydata.interfaces import IImmutableFileNode, IMutableFileNode, \
18264      ExistingChildError, NoSuchChildError, MustNotBeUnknownRWError, \
18265      MustBeDeepImmutableError, MustBeReadonlyError, \
18266-     IDeepCheckResults, IDeepCheckAndRepairResults
18267+     IDeepCheckResults, IDeepCheckAndRepairResults, \
18268+     MDMF_VERSION, SDMF_VERSION
18269 from allmydata.mutable.filenode import MutableFileNode
18270 from allmydata.mutable.common import UncoordinatedWriteError
18271 from allmydata.util import hashutil, base32
18272hunk ./src/allmydata/test/test_dirnode.py 61
18273               testutil.ReallyEqualMixin, testutil.ShouldFailMixin, testutil.StallMixin, ErrorMixin):
18274     timeout = 480 # It occasionally takes longer than 240 seconds on Francois's arm box.
18275 
18276-    def test_basic(self):
18277-        self.basedir = "dirnode/Dirnode/test_basic"
18278-        self.set_up_grid()
18279+    def _do_create_test(self, mdmf=False):
18280         c = self.g.clients[0]
18281hunk ./src/allmydata/test/test_dirnode.py 63
18282-        d = c.create_dirnode()
18283-        def _done(res):
18284-            self.failUnless(isinstance(res, dirnode.DirectoryNode))
18285-            self.failUnless(res.is_mutable())
18286-            self.failIf(res.is_readonly())
18287-            self.failIf(res.is_unknown())
18288-            self.failIf(res.is_allowed_in_immutable_directory())
18289-            res.raise_error()
18290-            rep = str(res)
18291-            self.failUnless("RW-MUT" in rep)
18292-        d.addCallback(_done)
18293+
18294+        self.expected_manifest = []
18295+        self.expected_verifycaps = set()
18296+        self.expected_storage_indexes = set()
18297+
18298+        d = None
18299+        if mdmf:
18300+            d = c.create_dirnode(version=MDMF_VERSION)
18301+        else:
18302+            d = c.create_dirnode()
18303+        def _then(n):
18304+            # /
18305+            self.rootnode = n
18306+            backing_node = n._node
18307+            if mdmf:
18308+                self.failUnlessEqual(backing_node.get_version(),
18309+                                     MDMF_VERSION)
18310+            else:
18311+                self.failUnlessEqual(backing_node.get_version(),
18312+                                     SDMF_VERSION)
18313+            self.failUnless(n.is_mutable())
18314+            u = n.get_uri()
18315+            self.failUnless(u)
18316+            cap_formats = []
18317+            if mdmf:
18318+                cap_formats = ["URI:DIR2-MDMF:",
18319+                               "URI:DIR2-MDMF-RO:",
18320+                               "URI:DIR2-MDMF-Verifier:"]
18321+            else:
18322+                cap_formats = ["URI:DIR2:",
18323+                               "URI:DIR2-RO",
18324+                               "URI:DIR2-Verifier:"]
18325+            rw, ro, v = cap_formats
18326+            self.failUnless(u.startswith(rw), u)
18327+            u_ro = n.get_readonly_uri()
18328+            self.failUnless(u_ro.startswith(ro), u_ro)
18329+            u_v = n.get_verify_cap().to_string()
18330+            self.failUnless(u_v.startswith(v), u_v)
18331+            u_r = n.get_repair_cap().to_string()
18332+            self.failUnlessReallyEqual(u_r, u)
18333+            self.expected_manifest.append( ((), u) )
18334+            self.expected_verifycaps.add(u_v)
18335+            si = n.get_storage_index()
18336+            self.expected_storage_indexes.add(base32.b2a(si))
18337+            expected_si = n._uri.get_storage_index()
18338+            self.failUnlessReallyEqual(si, expected_si)
18339+
18340+            d = n.list()
18341+            d.addCallback(lambda res: self.failUnlessEqual(res, {}))
18342+            d.addCallback(lambda res: n.has_child(u"missing"))
18343+            d.addCallback(lambda res: self.failIf(res))
18344+
18345+            fake_file_uri = make_mutable_file_uri()
18346+            other_file_uri = make_mutable_file_uri()
18347+            m = c.nodemaker.create_from_cap(fake_file_uri)
18348+            ffu_v = m.get_verify_cap().to_string()
18349+            self.expected_manifest.append( ((u"child",) , m.get_uri()) )
18350+            self.expected_verifycaps.add(ffu_v)
18351+            self.expected_storage_indexes.add(base32.b2a(m.get_storage_index()))
18352+            d.addCallback(lambda res: n.set_uri(u"child",
18353+                                                fake_file_uri, fake_file_uri))
18354+            d.addCallback(lambda res:
18355+                          self.shouldFail(ExistingChildError, "set_uri-no",
18356+                                          "child 'child' already exists",
18357+                                          n.set_uri, u"child",
18358+                                          other_file_uri, other_file_uri,
18359+                                          overwrite=False))
18360+            # /
18361+            # /child = mutable
18362+
18363+            d.addCallback(lambda res: n.create_subdirectory(u"subdir"))
18364+
18365+            # /
18366+            # /child = mutable
18367+            # /subdir = directory
18368+            def _created(subdir):
18369+                self.failUnless(isinstance(subdir, dirnode.DirectoryNode))
18370+                self.subdir = subdir
18371+                new_v = subdir.get_verify_cap().to_string()
18372+                assert isinstance(new_v, str)
18373+                self.expected_manifest.append( ((u"subdir",), subdir.get_uri()) )
18374+                self.expected_verifycaps.add(new_v)
18375+                si = subdir.get_storage_index()
18376+                self.expected_storage_indexes.add(base32.b2a(si))
18377+            d.addCallback(_created)
18378+
18379+            d.addCallback(lambda res:
18380+                          self.shouldFail(ExistingChildError, "mkdir-no",
18381+                                          "child 'subdir' already exists",
18382+                                          n.create_subdirectory, u"subdir",
18383+                                          overwrite=False))
18384+
18385+            d.addCallback(lambda res: n.list())
18386+            d.addCallback(lambda children:
18387+                          self.failUnlessReallyEqual(set(children.keys()),
18388+                                                     set([u"child", u"subdir"])))
18389+
18390+            d.addCallback(lambda res: n.start_deep_stats().when_done())
18391+            def _check_deepstats(stats):
18392+                self.failUnless(isinstance(stats, dict))
18393+                expected = {"count-immutable-files": 0,
18394+                            "count-mutable-files": 1,
18395+                            "count-literal-files": 0,
18396+                            "count-files": 1,
18397+                            "count-directories": 2,
18398+                            "size-immutable-files": 0,
18399+                            "size-literal-files": 0,
18400+                            #"size-directories": 616, # varies
18401+                            #"largest-directory": 616,
18402+                            "largest-directory-children": 2,
18403+                            "largest-immutable-file": 0,
18404+                            }
18405+                for k,v in expected.iteritems():
18406+                    self.failUnlessReallyEqual(stats[k], v,
18407+                                               "stats[%s] was %s, not %s" %
18408+                                               (k, stats[k], v))
18409+                self.failUnless(stats["size-directories"] > 500,
18410+                                stats["size-directories"])
18411+                self.failUnless(stats["largest-directory"] > 500,
18412+                                stats["largest-directory"])
18413+                self.failUnlessReallyEqual(stats["size-files-histogram"], [])
18414+            d.addCallback(_check_deepstats)
18415+
18416+            d.addCallback(lambda res: n.build_manifest().when_done())
18417+            def _check_manifest(res):
18418+                manifest = res["manifest"]
18419+                self.failUnlessReallyEqual(sorted(manifest),
18420+                                           sorted(self.expected_manifest))
18421+                stats = res["stats"]
18422+                _check_deepstats(stats)
18423+                self.failUnlessReallyEqual(self.expected_verifycaps,
18424+                                           res["verifycaps"])
18425+                self.failUnlessReallyEqual(self.expected_storage_indexes,
18426+                                           res["storage-index"])
18427+            d.addCallback(_check_manifest)
18428+
18429+            def _add_subsubdir(res):
18430+                return self.subdir.create_subdirectory(u"subsubdir")
18431+            d.addCallback(_add_subsubdir)
18432+            # /
18433+            # /child = mutable
18434+            # /subdir = directory
18435+            # /subdir/subsubdir = directory
18436+            d.addCallback(lambda res: n.get_child_at_path(u"subdir/subsubdir"))
18437+            d.addCallback(lambda subsubdir:
18438+                          self.failUnless(isinstance(subsubdir,
18439+                                                     dirnode.DirectoryNode)))
18440+            d.addCallback(lambda res: n.get_child_at_path(u""))
18441+            d.addCallback(lambda res: self.failUnlessReallyEqual(res.get_uri(),
18442+                                                                 n.get_uri()))
18443+
18444+            d.addCallback(lambda res: n.get_metadata_for(u"child"))
18445+            d.addCallback(lambda metadata:
18446+                          self.failUnlessEqual(set(metadata.keys()),
18447+                                               set(["tahoe"])))
18448+
18449+            d.addCallback(lambda res:
18450+                          self.shouldFail(NoSuchChildError, "gcamap-no",
18451+                                          "nope",
18452+                                          n.get_child_and_metadata_at_path,
18453+                                          u"subdir/nope"))
18454+            d.addCallback(lambda res:
18455+                          n.get_child_and_metadata_at_path(u""))
18456+            def _check_child_and_metadata1(res):
18457+                child, metadata = res
18458+                self.failUnless(isinstance(child, dirnode.DirectoryNode))
18459+                # edge-metadata needs at least one path segment
18460+                self.failUnlessEqual(set(metadata.keys()), set([]))
18461+            d.addCallback(_check_child_and_metadata1)
18462+            d.addCallback(lambda res:
18463+                          n.get_child_and_metadata_at_path(u"child"))
18464+
18465+            def _check_child_and_metadata2(res):
18466+                child, metadata = res
18467+                self.failUnlessReallyEqual(child.get_uri(),
18468+                                           fake_file_uri)
18469+                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
18470+            d.addCallback(_check_child_and_metadata2)
18471+
18472+            d.addCallback(lambda res:
18473+                          n.get_child_and_metadata_at_path(u"subdir/subsubdir"))
18474+            def _check_child_and_metadata3(res):
18475+                child, metadata = res
18476+                self.failUnless(isinstance(child, dirnode.DirectoryNode))
18477+                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
18478+            d.addCallback(_check_child_and_metadata3)
18479+
18480+            # set_uri + metadata
18481+            # it should be possible to add a child without any metadata
18482+            d.addCallback(lambda res: n.set_uri(u"c2",
18483+                                                fake_file_uri, fake_file_uri,
18484+                                                {}))
18485+            d.addCallback(lambda res: n.get_metadata_for(u"c2"))
18486+            d.addCallback(lambda metadata:
18487+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18488+
18489+            # You can't override the link timestamps.
18490+            d.addCallback(lambda res: n.set_uri(u"c2",
18491+                                                fake_file_uri, fake_file_uri,
18492+                                                { 'tahoe': {'linkcrtime': "bogus"}}))
18493+            d.addCallback(lambda res: n.get_metadata_for(u"c2"))
18494+            def _has_good_linkcrtime(metadata):
18495+                self.failUnless(metadata.has_key('tahoe'))
18496+                self.failUnless(metadata['tahoe'].has_key('linkcrtime'))
18497+                self.failIfEqual(metadata['tahoe']['linkcrtime'], 'bogus')
18498+            d.addCallback(_has_good_linkcrtime)
18499+
18500+            # if we don't set any defaults, the child should get timestamps
18501+            d.addCallback(lambda res: n.set_uri(u"c3",
18502+                                                fake_file_uri, fake_file_uri))
18503+            d.addCallback(lambda res: n.get_metadata_for(u"c3"))
18504+            d.addCallback(lambda metadata:
18505+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18506+
18507+            # we can also add specific metadata at set_uri() time
18508+            d.addCallback(lambda res: n.set_uri(u"c4",
18509+                                                fake_file_uri, fake_file_uri,
18510+                                                {"key": "value"}))
18511+            d.addCallback(lambda res: n.get_metadata_for(u"c4"))
18512+            d.addCallback(lambda metadata:
18513+                              self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18514+                                              (metadata['key'] == "value"), metadata))
18515+
18516+            d.addCallback(lambda res: n.delete(u"c2"))
18517+            d.addCallback(lambda res: n.delete(u"c3"))
18518+            d.addCallback(lambda res: n.delete(u"c4"))
18519+
18520+            # set_node + metadata
18521+            # it should be possible to add a child without any metadata except for timestamps
18522+            d.addCallback(lambda res: n.set_node(u"d2", n, {}))
18523+            d.addCallback(lambda res: c.create_dirnode())
18524+            d.addCallback(lambda n2:
18525+                          self.shouldFail(ExistingChildError, "set_node-no",
18526+                                          "child 'd2' already exists",
18527+                                          n.set_node, u"d2", n2,
18528+                                          overwrite=False))
18529+            d.addCallback(lambda res: n.get_metadata_for(u"d2"))
18530+            d.addCallback(lambda metadata:
18531+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18532+
18533+            # if we don't set any defaults, the child should get timestamps
18534+            d.addCallback(lambda res: n.set_node(u"d3", n))
18535+            d.addCallback(lambda res: n.get_metadata_for(u"d3"))
18536+            d.addCallback(lambda metadata:
18537+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18538+
18539+            # we can also add specific metadata at set_node() time
18540+            d.addCallback(lambda res: n.set_node(u"d4", n,
18541+                                                {"key": "value"}))
18542+            d.addCallback(lambda res: n.get_metadata_for(u"d4"))
18543+            d.addCallback(lambda metadata:
18544+                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18545+                                          (metadata["key"] == "value"), metadata))
18546+
18547+            d.addCallback(lambda res: n.delete(u"d2"))
18548+            d.addCallback(lambda res: n.delete(u"d3"))
18549+            d.addCallback(lambda res: n.delete(u"d4"))
18550+
18551+            # metadata through set_children()
18552+            d.addCallback(lambda res:
18553+                          n.set_children({
18554+                              u"e1": (fake_file_uri, fake_file_uri),
18555+                              u"e2": (fake_file_uri, fake_file_uri, {}),
18556+                              u"e3": (fake_file_uri, fake_file_uri,
18557+                                      {"key": "value"}),
18558+                              }))
18559+            d.addCallback(lambda n2: self.failUnlessIdentical(n2, n))
18560+            d.addCallback(lambda res:
18561+                          self.shouldFail(ExistingChildError, "set_children-no",
18562+                                          "child 'e1' already exists",
18563+                                          n.set_children,
18564+                                          { u"e1": (other_file_uri,
18565+                                                    other_file_uri),
18566+                                            u"new": (other_file_uri,
18567+                                                     other_file_uri),
18568+                                            },
18569+                                          overwrite=False))
18570+            # and 'new' should not have been created
18571+            d.addCallback(lambda res: n.list())
18572+            d.addCallback(lambda children: self.failIf(u"new" in children))
18573+            d.addCallback(lambda res: n.get_metadata_for(u"e1"))
18574+            d.addCallback(lambda metadata:
18575+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18576+            d.addCallback(lambda res: n.get_metadata_for(u"e2"))
18577+            d.addCallback(lambda metadata:
18578+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18579+            d.addCallback(lambda res: n.get_metadata_for(u"e3"))
18580+            d.addCallback(lambda metadata:
18581+                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18582+                                          (metadata["key"] == "value"), metadata))
18583+
18584+            d.addCallback(lambda res: n.delete(u"e1"))
18585+            d.addCallback(lambda res: n.delete(u"e2"))
18586+            d.addCallback(lambda res: n.delete(u"e3"))
18587+
18588+            # metadata through set_nodes()
18589+            d.addCallback(lambda res:
18590+                          n.set_nodes({ u"f1": (n, None),
18591+                                        u"f2": (n, {}),
18592+                                        u"f3": (n, {"key": "value"}),
18593+                                        }))
18594+            d.addCallback(lambda n2: self.failUnlessIdentical(n2, n))
18595+            d.addCallback(lambda res:
18596+                          self.shouldFail(ExistingChildError, "set_nodes-no",
18597+                                          "child 'f1' already exists",
18598+                                          n.set_nodes, { u"f1": (n, None),
18599+                                                         u"new": (n, None), },
18600+                                          overwrite=False))
18601+            # and 'new' should not have been created
18602+            d.addCallback(lambda res: n.list())
18603+            d.addCallback(lambda children: self.failIf(u"new" in children))
18604+            d.addCallback(lambda res: n.get_metadata_for(u"f1"))
18605+            d.addCallback(lambda metadata:
18606+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18607+            d.addCallback(lambda res: n.get_metadata_for(u"f2"))
18608+            d.addCallback(lambda metadata:
18609+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18610+            d.addCallback(lambda res: n.get_metadata_for(u"f3"))
18611+            d.addCallback(lambda metadata:
18612+                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18613+                                          (metadata["key"] == "value"), metadata))
18614+
18615+            d.addCallback(lambda res: n.delete(u"f1"))
18616+            d.addCallback(lambda res: n.delete(u"f2"))
18617+            d.addCallback(lambda res: n.delete(u"f3"))
18618+
18619+
18620+            d.addCallback(lambda res:
18621+                          n.set_metadata_for(u"child",
18622+                                             {"tags": ["web2.0-compatible"], "tahoe": {"bad": "mojo"}}))
18623+            d.addCallback(lambda n1: n1.get_metadata_for(u"child"))
18624+            d.addCallback(lambda metadata:
18625+                          self.failUnless((set(metadata.keys()) == set(["tags", "tahoe"])) and
18626+                                          metadata["tags"] == ["web2.0-compatible"] and
18627+                                          "bad" not in metadata["tahoe"], metadata))
18628+
18629+            d.addCallback(lambda res:
18630+                          self.shouldFail(NoSuchChildError, "set_metadata_for-nosuch", "",
18631+                                          n.set_metadata_for, u"nosuch", {}))
18632+
18633+
18634+            def _start(res):
18635+                self._start_timestamp = time.time()
18636+            d.addCallback(_start)
18637+            # simplejson-1.7.1 (as shipped on Ubuntu 'gutsy') rounds all
18638+            # floats to hundredeths (it uses str(num) instead of repr(num)).
18639+            # simplejson-1.7.3 does not have this bug. To prevent this bug
18640+            # from causing the test to fail, stall for more than a few
18641+            # hundrededths of a second.
18642+            d.addCallback(self.stall, 0.1)
18643+            d.addCallback(lambda res: n.add_file(u"timestamps",
18644+                                                 upload.Data("stamp me", convergence="some convergence string")))
18645+            d.addCallback(self.stall, 0.1)
18646+            def _stop(res):
18647+                self._stop_timestamp = time.time()
18648+            d.addCallback(_stop)
18649+
18650+            d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
18651+            def _check_timestamp1(metadata):
18652+                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
18653+                tahoe_md = metadata["tahoe"]
18654+                self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"]))
18655+
18656+                self.failUnlessGreaterOrEqualThan(tahoe_md["linkcrtime"],
18657+                                                  self._start_timestamp)
18658+                self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
18659+                                                  tahoe_md["linkcrtime"])
18660+                self.failUnlessGreaterOrEqualThan(tahoe_md["linkmotime"],
18661+                                                  self._start_timestamp)
18662+                self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
18663+                                                  tahoe_md["linkmotime"])
18664+                # Our current timestamp rules say that replacing an existing
18665+                # child should preserve the 'linkcrtime' but update the
18666+                # 'linkmotime'
18667+                self._old_linkcrtime = tahoe_md["linkcrtime"]
18668+                self._old_linkmotime = tahoe_md["linkmotime"]
18669+            d.addCallback(_check_timestamp1)
18670+            d.addCallback(self.stall, 2.0) # accomodate low-res timestamps
18671+            d.addCallback(lambda res: n.set_node(u"timestamps", n))
18672+            d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
18673+            def _check_timestamp2(metadata):
18674+                self.failUnlessIn("tahoe", metadata)
18675+                tahoe_md = metadata["tahoe"]
18676+                self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"]))
18677+
18678+                self.failUnlessReallyEqual(tahoe_md["linkcrtime"], self._old_linkcrtime)
18679+                self.failUnlessGreaterThan(tahoe_md["linkmotime"], self._old_linkmotime)
18680+                return n.delete(u"timestamps")
18681+            d.addCallback(_check_timestamp2)
18682+
18683+            d.addCallback(lambda res: n.delete(u"subdir"))
18684+            d.addCallback(lambda old_child:
18685+                          self.failUnlessReallyEqual(old_child.get_uri(),
18686+                                                     self.subdir.get_uri()))
18687+
18688+            d.addCallback(lambda res: n.list())
18689+            d.addCallback(lambda children:
18690+                          self.failUnlessReallyEqual(set(children.keys()),
18691+                                                     set([u"child"])))
18692+
18693+            uploadable1 = upload.Data("some data", convergence="converge")
18694+            d.addCallback(lambda res: n.add_file(u"newfile", uploadable1))
18695+            d.addCallback(lambda newnode:
18696+                          self.failUnless(IImmutableFileNode.providedBy(newnode)))
18697+            uploadable2 = upload.Data("some data", convergence="stuff")
18698+            d.addCallback(lambda res:
18699+                          self.shouldFail(ExistingChildError, "add_file-no",
18700+                                          "child 'newfile' already exists",
18701+                                          n.add_file, u"newfile",
18702+                                          uploadable2,
18703+                                          overwrite=False))
18704+            d.addCallback(lambda res: n.list())
18705+            d.addCallback(lambda children:
18706+                          self.failUnlessReallyEqual(set(children.keys()),
18707+                                                     set([u"child", u"newfile"])))
18708+            d.addCallback(lambda res: n.get_metadata_for(u"newfile"))
18709+            d.addCallback(lambda metadata:
18710+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18711+
18712+            uploadable3 = upload.Data("some data", convergence="converge")
18713+            d.addCallback(lambda res: n.add_file(u"newfile-metadata",
18714+                                                 uploadable3,
18715+                                                 {"key": "value"}))
18716+            d.addCallback(lambda newnode:
18717+                          self.failUnless(IImmutableFileNode.providedBy(newnode)))
18718+            d.addCallback(lambda res: n.get_metadata_for(u"newfile-metadata"))
18719+            d.addCallback(lambda metadata:
18720+                              self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18721+                                              (metadata['key'] == "value"), metadata))
18722+            d.addCallback(lambda res: n.delete(u"newfile-metadata"))
18723+
18724+            d.addCallback(lambda res: n.create_subdirectory(u"subdir2"))
18725+            def _created2(subdir2):
18726+                self.subdir2 = subdir2
18727+                # put something in the way, to make sure it gets overwritten
18728+                return subdir2.add_file(u"child", upload.Data("overwrite me",
18729+                                                              "converge"))
18730+            d.addCallback(_created2)
18731+
18732+            d.addCallback(lambda res:
18733+                          n.move_child_to(u"child", self.subdir2))
18734+            d.addCallback(lambda res: n.list())
18735+            d.addCallback(lambda children:
18736+                          self.failUnlessReallyEqual(set(children.keys()),
18737+                                                     set([u"newfile", u"subdir2"])))
18738+            d.addCallback(lambda res: self.subdir2.list())
18739+            d.addCallback(lambda children:
18740+                          self.failUnlessReallyEqual(set(children.keys()),
18741+                                                     set([u"child"])))
18742+            d.addCallback(lambda res: self.subdir2.get(u"child"))
18743+            d.addCallback(lambda child:
18744+                          self.failUnlessReallyEqual(child.get_uri(),
18745+                                                     fake_file_uri))
18746+
18747+            # move it back, using new_child_name=
18748+            d.addCallback(lambda res:
18749+                          self.subdir2.move_child_to(u"child", n, u"newchild"))
18750+            d.addCallback(lambda res: n.list())
18751+            d.addCallback(lambda children:
18752+                          self.failUnlessReallyEqual(set(children.keys()),
18753+                                                     set([u"newchild", u"newfile",
18754+                                                          u"subdir2"])))
18755+            d.addCallback(lambda res: self.subdir2.list())
18756+            d.addCallback(lambda children:
18757+                          self.failUnlessReallyEqual(set(children.keys()), set([])))
18758+
18759+            # now make sure that we honor overwrite=False
18760+            d.addCallback(lambda res:
18761+                          self.subdir2.set_uri(u"newchild",
18762+                                               other_file_uri, other_file_uri))
18763+
18764+            d.addCallback(lambda res:
18765+                          self.shouldFail(ExistingChildError, "move_child_to-no",
18766+                                          "child 'newchild' already exists",
18767+                                          n.move_child_to, u"newchild",
18768+                                          self.subdir2,
18769+                                          overwrite=False))
18770+            d.addCallback(lambda res: self.subdir2.get(u"newchild"))
18771+            d.addCallback(lambda child:
18772+                          self.failUnlessReallyEqual(child.get_uri(),
18773+                                                     other_file_uri))
18774+
18775+
18776+            # Setting the no-write field should diminish a mutable cap to read-only
18777+            # (for both files and directories).
18778+
18779+            d.addCallback(lambda ign: n.set_uri(u"mutable", other_file_uri, other_file_uri))
18780+            d.addCallback(lambda ign: n.get(u"mutable"))
18781+            d.addCallback(lambda mutable: self.failIf(mutable.is_readonly(), mutable))
18782+            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
18783+            d.addCallback(lambda ign: n.get(u"mutable"))
18784+            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
18785+            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
18786+            d.addCallback(lambda ign: n.get(u"mutable"))
18787+            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
18788+
18789+            d.addCallback(lambda ign: n.get(u"subdir2"))
18790+            d.addCallback(lambda subdir2: self.failIf(subdir2.is_readonly()))
18791+            d.addCallback(lambda ign: n.set_metadata_for(u"subdir2", {"no-write": True}))
18792+            d.addCallback(lambda ign: n.get(u"subdir2"))
18793+            d.addCallback(lambda subdir2: self.failUnless(subdir2.is_readonly(), subdir2))
18794+
18795+            d.addCallback(lambda ign: n.set_uri(u"mutable_ro", other_file_uri, other_file_uri,
18796+                                                metadata={"no-write": True}))
18797+            d.addCallback(lambda ign: n.get(u"mutable_ro"))
18798+            d.addCallback(lambda mutable_ro: self.failUnless(mutable_ro.is_readonly(), mutable_ro))
18799+
18800+            d.addCallback(lambda ign: n.create_subdirectory(u"subdir_ro", metadata={"no-write": True}))
18801+            d.addCallback(lambda ign: n.get(u"subdir_ro"))
18802+            d.addCallback(lambda subdir_ro: self.failUnless(subdir_ro.is_readonly(), subdir_ro))
18803+
18804+            return d
18805+
18806+        d.addCallback(_then)
18807+
18808+        d.addErrback(self.explain_error)
18809         return d
18810 
18811hunk ./src/allmydata/test/test_dirnode.py 581
18812-    def test_initial_children(self):
18813-        self.basedir = "dirnode/Dirnode/test_initial_children"
18814-        self.set_up_grid()
18815+
18816+    def _do_initial_children_test(self, mdmf=False):
18817         c = self.g.clients[0]
18818         nm = c.nodemaker
18819 
18820hunk ./src/allmydata/test/test_dirnode.py 597
18821                 u"empty_litdir": (nm.create_from_cap(empty_litdir_uri), {}),
18822                 u"tiny_litdir": (nm.create_from_cap(tiny_litdir_uri), {}),
18823                 }
18824-        d = c.create_dirnode(kids)
18825-       
18826+        d = None
18827+        if mdmf:
18828+            d = c.create_dirnode(kids, version=MDMF_VERSION)
18829+        else:
18830+            d = c.create_dirnode(kids)
18831         def _created(dn):
18832             self.failUnless(isinstance(dn, dirnode.DirectoryNode))
18833hunk ./src/allmydata/test/test_dirnode.py 604
18834+            backing_node = dn._node
18835+            if mdmf:
18836+                self.failUnlessEqual(backing_node.get_version(),
18837+                                     MDMF_VERSION)
18838+            else:
18839+                self.failUnlessEqual(backing_node.get_version(),
18840+                                     SDMF_VERSION)
18841             self.failUnless(dn.is_mutable())
18842             self.failIf(dn.is_readonly())
18843             self.failIf(dn.is_unknown())
18844hunk ./src/allmydata/test/test_dirnode.py 619
18845             rep = str(dn)
18846             self.failUnless("RW-MUT" in rep)
18847             return dn.list()
18848-        d.addCallback(_created)
18849-       
18850+
18851         def _check_kids(children):
18852             self.failUnlessReallyEqual(set(children.keys()),
18853                                        set([one_nfc, u"two", u"mut", u"fut", u"fro",
18854hunk ./src/allmydata/test/test_dirnode.py 623
18855-                                            u"fut-unic", u"fro-unic", u"empty_litdir", u"tiny_litdir"]))
18856+                                        u"fut-unic", u"fro-unic", u"empty_litdir", u"tiny_litdir"]))
18857             one_node, one_metadata = children[one_nfc]
18858             two_node, two_metadata = children[u"two"]
18859             mut_node, mut_metadata = children[u"mut"]
18860hunk ./src/allmydata/test/test_dirnode.py 683
18861             d2.addCallback(lambda children: children[u"short"][0].read(MemAccum()))
18862             d2.addCallback(lambda accum: self.failUnlessReallyEqual(accum.data, "The end."))
18863             return d2
18864-
18865+        d.addCallback(_created)
18866         d.addCallback(_check_kids)
18867 
18868         d.addCallback(lambda ign: nm.create_new_mutable_directory(kids))
18869hunk ./src/allmydata/test/test_dirnode.py 707
18870                                       bad_kids2))
18871         return d
18872 
18873+    def _do_basic_test(self, mdmf=False):
18874+        c = self.g.clients[0]
18875+        d = None
18876+        if mdmf:
18877+            d = c.create_dirnode(version=MDMF_VERSION)
18878+        else:
18879+            d = c.create_dirnode()
18880+        def _done(res):
18881+            self.failUnless(isinstance(res, dirnode.DirectoryNode))
18882+            self.failUnless(res.is_mutable())
18883+            self.failIf(res.is_readonly())
18884+            self.failIf(res.is_unknown())
18885+            self.failIf(res.is_allowed_in_immutable_directory())
18886+            res.raise_error()
18887+            rep = str(res)
18888+            self.failUnless("RW-MUT" in rep)
18889+        d.addCallback(_done)
18890+        return d
18891+
18892+    def test_basic(self):
18893+        self.basedir = "dirnode/Dirnode/test_basic"
18894+        self.set_up_grid()
18895+        return self._do_basic_test()
18896+
18897+    def test_basic_mdmf(self):
18898+        self.basedir = "dirnode/Dirnode/test_basic_mdmf"
18899+        self.set_up_grid()
18900+        return self._do_basic_test(mdmf=True)
18901+
18902+    def test_initial_children(self):
18903+        self.basedir = "dirnode/Dirnode/test_initial_children"
18904+        self.set_up_grid()
18905+        return self._do_initial_children_test()
18906+
18907     def test_immutable(self):
18908         self.basedir = "dirnode/Dirnode/test_immutable"
18909         self.set_up_grid()
18910hunk ./src/allmydata/test/test_dirnode.py 1025
18911         d.addCallback(_done)
18912         return d
18913 
18914-    def _test_deepcheck_create(self):
18915+    def _test_deepcheck_create(self, version=SDMF_VERSION):
18916         # create a small tree with a loop, and some non-directories
18917         #  root/
18918         #  root/subdir/
18919hunk ./src/allmydata/test/test_dirnode.py 1033
18920         #  root/subdir/link -> root
18921         #  root/rodir
18922         c = self.g.clients[0]
18923-        d = c.create_dirnode()
18924+        d = c.create_dirnode(version=version)
18925         def _created_root(rootnode):
18926             self._rootnode = rootnode
18927hunk ./src/allmydata/test/test_dirnode.py 1036
18928+            self.failUnlessEqual(rootnode._node.get_version(), version)
18929             return rootnode.create_subdirectory(u"subdir")
18930         d.addCallback(_created_root)
18931         def _created_subdir(subdir):
18932hunk ./src/allmydata/test/test_dirnode.py 1075
18933         d.addCallback(_check_results)
18934         return d
18935 
18936+    def test_deepcheck_mdmf(self):
18937+        self.basedir = "dirnode/Dirnode/test_deepcheck_mdmf"
18938+        self.set_up_grid()
18939+        d = self._test_deepcheck_create(MDMF_VERSION)
18940+        d.addCallback(lambda rootnode: rootnode.start_deep_check().when_done())
18941+        def _check_results(r):
18942+            self.failUnless(IDeepCheckResults.providedBy(r))
18943+            c = r.get_counters()
18944+            self.failUnlessReallyEqual(c,
18945+                                       {"count-objects-checked": 4,
18946+                                        "count-objects-healthy": 4,
18947+                                        "count-objects-unhealthy": 0,
18948+                                        "count-objects-unrecoverable": 0,
18949+                                        "count-corrupt-shares": 0,
18950+                                        })
18951+            self.failIf(r.get_corrupt_shares())
18952+            self.failUnlessReallyEqual(len(r.get_all_results()), 4)
18953+        d.addCallback(_check_results)
18954+        return d
18955+
18956     def test_deepcheck_and_repair(self):
18957         self.basedir = "dirnode/Dirnode/test_deepcheck_and_repair"
18958         self.set_up_grid()
18959hunk ./src/allmydata/test/test_dirnode.py 1124
18960         d.addCallback(_check_results)
18961         return d
18962 
18963+    def test_deepcheck_and_repair_mdmf(self):
18964+        self.basedir = "dirnode/Dirnode/test_deepcheck_and_repair_mdmf"
18965+        self.set_up_grid()
18966+        d = self._test_deepcheck_create(version=MDMF_VERSION)
18967+        d.addCallback(lambda rootnode:
18968+                      rootnode.start_deep_check_and_repair().when_done())
18969+        def _check_results(r):
18970+            self.failUnless(IDeepCheckAndRepairResults.providedBy(r))
18971+            c = r.get_counters()
18972+            self.failUnlessReallyEqual(c,
18973+                                       {"count-objects-checked": 4,
18974+                                        "count-objects-healthy-pre-repair": 4,
18975+                                        "count-objects-unhealthy-pre-repair": 0,
18976+                                        "count-objects-unrecoverable-pre-repair": 0,
18977+                                        "count-corrupt-shares-pre-repair": 0,
18978+                                        "count-objects-healthy-post-repair": 4,
18979+                                        "count-objects-unhealthy-post-repair": 0,
18980+                                        "count-objects-unrecoverable-post-repair": 0,
18981+                                        "count-corrupt-shares-post-repair": 0,
18982+                                        "count-repairs-attempted": 0,
18983+                                        "count-repairs-successful": 0,
18984+                                        "count-repairs-unsuccessful": 0,
18985+                                        })
18986+            self.failIf(r.get_corrupt_shares())
18987+            self.failIf(r.get_remaining_corrupt_shares())
18988+            self.failUnlessReallyEqual(len(r.get_all_results()), 4)
18989+        d.addCallback(_check_results)
18990+        return d
18991+
18992     def _mark_file_bad(self, rootnode):
18993         self.delete_shares_numbered(rootnode.get_uri(), [0])
18994         return rootnode
18995hunk ./src/allmydata/test/test_dirnode.py 1176
18996         d.addCallback(_check_results)
18997         return d
18998 
18999-    def test_readonly(self):
19000-        self.basedir = "dirnode/Dirnode/test_readonly"
19001+    def test_deepcheck_problems_mdmf(self):
19002+        self.basedir = "dirnode/Dirnode/test_deepcheck_problems_mdmf"
19003         self.set_up_grid()
19004hunk ./src/allmydata/test/test_dirnode.py 1179
19005+        d = self._test_deepcheck_create(version=MDMF_VERSION)
19006+        d.addCallback(lambda rootnode: self._mark_file_bad(rootnode))
19007+        d.addCallback(lambda rootnode: rootnode.start_deep_check().when_done())
19008+        def _check_results(r):
19009+            c = r.get_counters()
19010+            self.failUnlessReallyEqual(c,
19011+                                       {"count-objects-checked": 4,
19012+                                        "count-objects-healthy": 3,
19013+                                        "count-objects-unhealthy": 1,
19014+                                        "count-objects-unrecoverable": 0,
19015+                                        "count-corrupt-shares": 0,
19016+                                        })
19017+            #self.failUnlessReallyEqual(len(r.get_problems()), 1) # TODO
19018+        d.addCallback(_check_results)
19019+        return d
19020+
19021+    def _do_readonly_test(self, version=SDMF_VERSION):
19022         c = self.g.clients[0]
19023         nm = c.nodemaker
19024         filecap = make_chk_file_uri(1234)
19025hunk ./src/allmydata/test/test_dirnode.py 1202
19026         filenode = nm.create_from_cap(filecap)
19027         uploadable = upload.Data("some data", convergence="some convergence string")
19028 
19029-        d = c.create_dirnode()
19030+        d = c.create_dirnode(version=version)
19031         def _created(rw_dn):
19032hunk ./src/allmydata/test/test_dirnode.py 1204
19033+            backing_node = rw_dn._node
19034+            self.failUnlessEqual(backing_node.get_version(), version)
19035             d2 = rw_dn.set_uri(u"child", filecap, filecap)
19036             d2.addCallback(lambda res: rw_dn)
19037             return d2
19038hunk ./src/allmydata/test/test_dirnode.py 1245
19039         d.addCallback(_listed)
19040         return d
19041 
19042+    def test_readonly(self):
19043+        self.basedir = "dirnode/Dirnode/test_readonly"
19044+        self.set_up_grid()
19045+        return self._do_readonly_test()
19046+
19047+    def test_readonly_mdmf(self):
19048+        self.basedir = "dirnode/Dirnode/test_readonly_mdmf"
19049+        self.set_up_grid()
19050+        return self._do_readonly_test(version=MDMF_VERSION)
19051+
19052     def failUnlessGreaterThan(self, a, b):
19053         self.failUnless(a > b, "%r should be > %r" % (a, b))
19054 
19055hunk ./src/allmydata/test/test_dirnode.py 1264
19056     def test_create(self):
19057         self.basedir = "dirnode/Dirnode/test_create"
19058         self.set_up_grid()
19059-        c = self.g.clients[0]
19060-
19061-        self.expected_manifest = []
19062-        self.expected_verifycaps = set()
19063-        self.expected_storage_indexes = set()
19064-
19065-        d = c.create_dirnode()
19066-        def _then(n):
19067-            # /
19068-            self.rootnode = n
19069-            self.failUnless(n.is_mutable())
19070-            u = n.get_uri()
19071-            self.failUnless(u)
19072-            self.failUnless(u.startswith("URI:DIR2:"), u)
19073-            u_ro = n.get_readonly_uri()
19074-            self.failUnless(u_ro.startswith("URI:DIR2-RO:"), u_ro)
19075-            u_v = n.get_verify_cap().to_string()
19076-            self.failUnless(u_v.startswith("URI:DIR2-Verifier:"), u_v)
19077-            u_r = n.get_repair_cap().to_string()
19078-            self.failUnlessReallyEqual(u_r, u)
19079-            self.expected_manifest.append( ((), u) )
19080-            self.expected_verifycaps.add(u_v)
19081-            si = n.get_storage_index()
19082-            self.expected_storage_indexes.add(base32.b2a(si))
19083-            expected_si = n._uri.get_storage_index()
19084-            self.failUnlessReallyEqual(si, expected_si)
19085-
19086-            d = n.list()
19087-            d.addCallback(lambda res: self.failUnlessEqual(res, {}))
19088-            d.addCallback(lambda res: n.has_child(u"missing"))
19089-            d.addCallback(lambda res: self.failIf(res))
19090-
19091-            fake_file_uri = make_mutable_file_uri()
19092-            other_file_uri = make_mutable_file_uri()
19093-            m = c.nodemaker.create_from_cap(fake_file_uri)
19094-            ffu_v = m.get_verify_cap().to_string()
19095-            self.expected_manifest.append( ((u"child",) , m.get_uri()) )
19096-            self.expected_verifycaps.add(ffu_v)
19097-            self.expected_storage_indexes.add(base32.b2a(m.get_storage_index()))
19098-            d.addCallback(lambda res: n.set_uri(u"child",
19099-                                                fake_file_uri, fake_file_uri))
19100-            d.addCallback(lambda res:
19101-                          self.shouldFail(ExistingChildError, "set_uri-no",
19102-                                          "child 'child' already exists",
19103-                                          n.set_uri, u"child",
19104-                                          other_file_uri, other_file_uri,
19105-                                          overwrite=False))
19106-            # /
19107-            # /child = mutable
19108-
19109-            d.addCallback(lambda res: n.create_subdirectory(u"subdir"))
19110-
19111-            # /
19112-            # /child = mutable
19113-            # /subdir = directory
19114-            def _created(subdir):
19115-                self.failUnless(isinstance(subdir, dirnode.DirectoryNode))
19116-                self.subdir = subdir
19117-                new_v = subdir.get_verify_cap().to_string()
19118-                assert isinstance(new_v, str)
19119-                self.expected_manifest.append( ((u"subdir",), subdir.get_uri()) )
19120-                self.expected_verifycaps.add(new_v)
19121-                si = subdir.get_storage_index()
19122-                self.expected_storage_indexes.add(base32.b2a(si))
19123-            d.addCallback(_created)
19124-
19125-            d.addCallback(lambda res:
19126-                          self.shouldFail(ExistingChildError, "mkdir-no",
19127-                                          "child 'subdir' already exists",
19128-                                          n.create_subdirectory, u"subdir",
19129-                                          overwrite=False))
19130-
19131-            d.addCallback(lambda res: n.list())
19132-            d.addCallback(lambda children:
19133-                          self.failUnlessReallyEqual(set(children.keys()),
19134-                                                     set([u"child", u"subdir"])))
19135-
19136-            d.addCallback(lambda res: n.start_deep_stats().when_done())
19137-            def _check_deepstats(stats):
19138-                self.failUnless(isinstance(stats, dict))
19139-                expected = {"count-immutable-files": 0,
19140-                            "count-mutable-files": 1,
19141-                            "count-literal-files": 0,
19142-                            "count-files": 1,
19143-                            "count-directories": 2,
19144-                            "size-immutable-files": 0,
19145-                            "size-literal-files": 0,
19146-                            #"size-directories": 616, # varies
19147-                            #"largest-directory": 616,
19148-                            "largest-directory-children": 2,
19149-                            "largest-immutable-file": 0,
19150-                            }
19151-                for k,v in expected.iteritems():
19152-                    self.failUnlessReallyEqual(stats[k], v,
19153-                                               "stats[%s] was %s, not %s" %
19154-                                               (k, stats[k], v))
19155-                self.failUnless(stats["size-directories"] > 500,
19156-                                stats["size-directories"])
19157-                self.failUnless(stats["largest-directory"] > 500,
19158-                                stats["largest-directory"])
19159-                self.failUnlessReallyEqual(stats["size-files-histogram"], [])
19160-            d.addCallback(_check_deepstats)
19161-
19162-            d.addCallback(lambda res: n.build_manifest().when_done())
19163-            def _check_manifest(res):
19164-                manifest = res["manifest"]
19165-                self.failUnlessReallyEqual(sorted(manifest),
19166-                                           sorted(self.expected_manifest))
19167-                stats = res["stats"]
19168-                _check_deepstats(stats)
19169-                self.failUnlessReallyEqual(self.expected_verifycaps,
19170-                                           res["verifycaps"])
19171-                self.failUnlessReallyEqual(self.expected_storage_indexes,
19172-                                           res["storage-index"])
19173-            d.addCallback(_check_manifest)
19174-
19175-            def _add_subsubdir(res):
19176-                return self.subdir.create_subdirectory(u"subsubdir")
19177-            d.addCallback(_add_subsubdir)
19178-            # /
19179-            # /child = mutable
19180-            # /subdir = directory
19181-            # /subdir/subsubdir = directory
19182-            d.addCallback(lambda res: n.get_child_at_path(u"subdir/subsubdir"))
19183-            d.addCallback(lambda subsubdir:
19184-                          self.failUnless(isinstance(subsubdir,
19185-                                                     dirnode.DirectoryNode)))
19186-            d.addCallback(lambda res: n.get_child_at_path(u""))
19187-            d.addCallback(lambda res: self.failUnlessReallyEqual(res.get_uri(),
19188-                                                                 n.get_uri()))
19189-
19190-            d.addCallback(lambda res: n.get_metadata_for(u"child"))
19191-            d.addCallback(lambda metadata:
19192-                          self.failUnlessEqual(set(metadata.keys()),
19193-                                               set(["tahoe"])))
19194-
19195-            d.addCallback(lambda res:
19196-                          self.shouldFail(NoSuchChildError, "gcamap-no",
19197-                                          "nope",
19198-                                          n.get_child_and_metadata_at_path,
19199-                                          u"subdir/nope"))
19200-            d.addCallback(lambda res:
19201-                          n.get_child_and_metadata_at_path(u""))
19202-            def _check_child_and_metadata1(res):
19203-                child, metadata = res
19204-                self.failUnless(isinstance(child, dirnode.DirectoryNode))
19205-                # edge-metadata needs at least one path segment
19206-                self.failUnlessEqual(set(metadata.keys()), set([]))
19207-            d.addCallback(_check_child_and_metadata1)
19208-            d.addCallback(lambda res:
19209-                          n.get_child_and_metadata_at_path(u"child"))
19210-
19211-            def _check_child_and_metadata2(res):
19212-                child, metadata = res
19213-                self.failUnlessReallyEqual(child.get_uri(),
19214-                                           fake_file_uri)
19215-                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
19216-            d.addCallback(_check_child_and_metadata2)
19217-
19218-            d.addCallback(lambda res:
19219-                          n.get_child_and_metadata_at_path(u"subdir/subsubdir"))
19220-            def _check_child_and_metadata3(res):
19221-                child, metadata = res
19222-                self.failUnless(isinstance(child, dirnode.DirectoryNode))
19223-                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
19224-            d.addCallback(_check_child_and_metadata3)
19225-
19226-            # set_uri + metadata
19227-            # it should be possible to add a child without any metadata
19228-            d.addCallback(lambda res: n.set_uri(u"c2",
19229-                                                fake_file_uri, fake_file_uri,
19230-                                                {}))
19231-            d.addCallback(lambda res: n.get_metadata_for(u"c2"))
19232-            d.addCallback(lambda metadata:
19233-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19234-
19235-            # You can't override the link timestamps.
19236-            d.addCallback(lambda res: n.set_uri(u"c2",
19237-                                                fake_file_uri, fake_file_uri,
19238-                                                { 'tahoe': {'linkcrtime': "bogus"}}))
19239-            d.addCallback(lambda res: n.get_metadata_for(u"c2"))
19240-            def _has_good_linkcrtime(metadata):
19241-                self.failUnless(metadata.has_key('tahoe'))
19242-                self.failUnless(metadata['tahoe'].has_key('linkcrtime'))
19243-                self.failIfEqual(metadata['tahoe']['linkcrtime'], 'bogus')
19244-            d.addCallback(_has_good_linkcrtime)
19245-
19246-            # if we don't set any defaults, the child should get timestamps
19247-            d.addCallback(lambda res: n.set_uri(u"c3",
19248-                                                fake_file_uri, fake_file_uri))
19249-            d.addCallback(lambda res: n.get_metadata_for(u"c3"))
19250-            d.addCallback(lambda metadata:
19251-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19252-
19253-            # we can also add specific metadata at set_uri() time
19254-            d.addCallback(lambda res: n.set_uri(u"c4",
19255-                                                fake_file_uri, fake_file_uri,
19256-                                                {"key": "value"}))
19257-            d.addCallback(lambda res: n.get_metadata_for(u"c4"))
19258-            d.addCallback(lambda metadata:
19259-                              self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
19260-                                              (metadata['key'] == "value"), metadata))
19261-
19262-            d.addCallback(lambda res: n.delete(u"c2"))
19263-            d.addCallback(lambda res: n.delete(u"c3"))
19264-            d.addCallback(lambda res: n.delete(u"c4"))
19265-
19266-            # set_node + metadata
19267-            # it should be possible to add a child without any metadata except for timestamps
19268-            d.addCallback(lambda res: n.set_node(u"d2", n, {}))
19269-            d.addCallback(lambda res: c.create_dirnode())
19270-            d.addCallback(lambda n2:
19271-                          self.shouldFail(ExistingChildError, "set_node-no",
19272-                                          "child 'd2' already exists",
19273-                                          n.set_node, u"d2", n2,
19274-                                          overwrite=False))
19275-            d.addCallback(lambda res: n.get_metadata_for(u"d2"))
19276-            d.addCallback(lambda metadata:
19277-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19278-
19279-            # if we don't set any defaults, the child should get timestamps
19280-            d.addCallback(lambda res: n.set_node(u"d3", n))
19281-            d.addCallback(lambda res: n.get_metadata_for(u"d3"))
19282-            d.addCallback(lambda metadata:
19283-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19284-
19285-            # we can also add specific metadata at set_node() time
19286-            d.addCallback(lambda res: n.set_node(u"d4", n,
19287-                                                {"key": "value"}))
19288-            d.addCallback(lambda res: n.get_metadata_for(u"d4"))
19289-            d.addCallback(lambda metadata:
19290-                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
19291-                                          (metadata["key"] == "value"), metadata))
19292-
19293-            d.addCallback(lambda res: n.delete(u"d2"))
19294-            d.addCallback(lambda res: n.delete(u"d3"))
19295-            d.addCallback(lambda res: n.delete(u"d4"))
19296-
19297-            # metadata through set_children()
19298-            d.addCallback(lambda res:
19299-                          n.set_children({
19300-                              u"e1": (fake_file_uri, fake_file_uri),
19301-                              u"e2": (fake_file_uri, fake_file_uri, {}),
19302-                              u"e3": (fake_file_uri, fake_file_uri,
19303-                                      {"key": "value"}),
19304-                              }))
19305-            d.addCallback(lambda n2: self.failUnlessIdentical(n2, n))
19306-            d.addCallback(lambda res:
19307-                          self.shouldFail(ExistingChildError, "set_children-no",
19308-                                          "child 'e1' already exists",
19309-                                          n.set_children,
19310-                                          { u"e1": (other_file_uri,
19311-                                                    other_file_uri),
19312-                                            u"new": (other_file_uri,
19313-                                                     other_file_uri),
19314-                                            },
19315-                                          overwrite=False))
19316-            # and 'new' should not have been created
19317-            d.addCallback(lambda res: n.list())
19318-            d.addCallback(lambda children: self.failIf(u"new" in children))
19319-            d.addCallback(lambda res: n.get_metadata_for(u"e1"))
19320-            d.addCallback(lambda metadata:
19321-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19322-            d.addCallback(lambda res: n.get_metadata_for(u"e2"))
19323-            d.addCallback(lambda metadata:
19324-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19325-            d.addCallback(lambda res: n.get_metadata_for(u"e3"))
19326-            d.addCallback(lambda metadata:
19327-                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
19328-                                          (metadata["key"] == "value"), metadata))
19329-
19330-            d.addCallback(lambda res: n.delete(u"e1"))
19331-            d.addCallback(lambda res: n.delete(u"e2"))
19332-            d.addCallback(lambda res: n.delete(u"e3"))
19333-
19334-            # metadata through set_nodes()
19335-            d.addCallback(lambda res:
19336-                          n.set_nodes({ u"f1": (n, None),
19337-                                        u"f2": (n, {}),
19338-                                        u"f3": (n, {"key": "value"}),
19339-                                        }))
19340-            d.addCallback(lambda n2: self.failUnlessIdentical(n2, n))
19341-            d.addCallback(lambda res:
19342-                          self.shouldFail(ExistingChildError, "set_nodes-no",
19343-                                          "child 'f1' already exists",
19344-                                          n.set_nodes, { u"f1": (n, None),
19345-                                                         u"new": (n, None), },
19346-                                          overwrite=False))
19347-            # and 'new' should not have been created
19348-            d.addCallback(lambda res: n.list())
19349-            d.addCallback(lambda children: self.failIf(u"new" in children))
19350-            d.addCallback(lambda res: n.get_metadata_for(u"f1"))
19351-            d.addCallback(lambda metadata:
19352-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19353-            d.addCallback(lambda res: n.get_metadata_for(u"f2"))
19354-            d.addCallback(lambda metadata:
19355-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19356-            d.addCallback(lambda res: n.get_metadata_for(u"f3"))
19357-            d.addCallback(lambda metadata:
19358-                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
19359-                                          (metadata["key"] == "value"), metadata))
19360-
19361-            d.addCallback(lambda res: n.delete(u"f1"))
19362-            d.addCallback(lambda res: n.delete(u"f2"))
19363-            d.addCallback(lambda res: n.delete(u"f3"))
19364-
19365-
19366-            d.addCallback(lambda res:
19367-                          n.set_metadata_for(u"child",
19368-                                             {"tags": ["web2.0-compatible"], "tahoe": {"bad": "mojo"}}))
19369-            d.addCallback(lambda n1: n1.get_metadata_for(u"child"))
19370-            d.addCallback(lambda metadata:
19371-                          self.failUnless((set(metadata.keys()) == set(["tags", "tahoe"])) and
19372-                                          metadata["tags"] == ["web2.0-compatible"] and
19373-                                          "bad" not in metadata["tahoe"], metadata))
19374-
19375-            d.addCallback(lambda res:
19376-                          self.shouldFail(NoSuchChildError, "set_metadata_for-nosuch", "",
19377-                                          n.set_metadata_for, u"nosuch", {}))
19378-
19379-
19380-            def _start(res):
19381-                self._start_timestamp = time.time()
19382-            d.addCallback(_start)
19383-            # simplejson-1.7.1 (as shipped on Ubuntu 'gutsy') rounds all
19384-            # floats to hundredeths (it uses str(num) instead of repr(num)).
19385-            # simplejson-1.7.3 does not have this bug. To prevent this bug
19386-            # from causing the test to fail, stall for more than a few
19387-            # hundrededths of a second.
19388-            d.addCallback(self.stall, 0.1)
19389-            d.addCallback(lambda res: n.add_file(u"timestamps",
19390-                                                 upload.Data("stamp me", convergence="some convergence string")))
19391-            d.addCallback(self.stall, 0.1)
19392-            def _stop(res):
19393-                self._stop_timestamp = time.time()
19394-            d.addCallback(_stop)
19395-
19396-            d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
19397-            def _check_timestamp1(metadata):
19398-                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
19399-                tahoe_md = metadata["tahoe"]
19400-                self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"]))
19401-
19402-                self.failUnlessGreaterOrEqualThan(tahoe_md["linkcrtime"],
19403-                                                  self._start_timestamp)
19404-                self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
19405-                                                  tahoe_md["linkcrtime"])
19406-                self.failUnlessGreaterOrEqualThan(tahoe_md["linkmotime"],
19407-                                                  self._start_timestamp)
19408-                self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
19409-                                                  tahoe_md["linkmotime"])
19410-                # Our current timestamp rules say that replacing an existing
19411-                # child should preserve the 'linkcrtime' but update the
19412-                # 'linkmotime'
19413-                self._old_linkcrtime = tahoe_md["linkcrtime"]
19414-                self._old_linkmotime = tahoe_md["linkmotime"]
19415-            d.addCallback(_check_timestamp1)
19416-            d.addCallback(self.stall, 2.0) # accomodate low-res timestamps
19417-            d.addCallback(lambda res: n.set_node(u"timestamps", n))
19418-            d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
19419-            def _check_timestamp2(metadata):
19420-                self.failUnlessIn("tahoe", metadata)
19421-                tahoe_md = metadata["tahoe"]
19422-                self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"]))
19423-
19424-                self.failUnlessReallyEqual(tahoe_md["linkcrtime"], self._old_linkcrtime)
19425-                self.failUnlessGreaterThan(tahoe_md["linkmotime"], self._old_linkmotime)
19426-                return n.delete(u"timestamps")
19427-            d.addCallback(_check_timestamp2)
19428-
19429-            d.addCallback(lambda res: n.delete(u"subdir"))
19430-            d.addCallback(lambda old_child:
19431-                          self.failUnlessReallyEqual(old_child.get_uri(),
19432-                                                     self.subdir.get_uri()))
19433-
19434-            d.addCallback(lambda res: n.list())
19435-            d.addCallback(lambda children:
19436-                          self.failUnlessReallyEqual(set(children.keys()),
19437-                                                     set([u"child"])))
19438-
19439-            uploadable1 = upload.Data("some data", convergence="converge")
19440-            d.addCallback(lambda res: n.add_file(u"newfile", uploadable1))
19441-            d.addCallback(lambda newnode:
19442-                          self.failUnless(IImmutableFileNode.providedBy(newnode)))
19443-            uploadable2 = upload.Data("some data", convergence="stuff")
19444-            d.addCallback(lambda res:
19445-                          self.shouldFail(ExistingChildError, "add_file-no",
19446-                                          "child 'newfile' already exists",
19447-                                          n.add_file, u"newfile",
19448-                                          uploadable2,
19449-                                          overwrite=False))
19450-            d.addCallback(lambda res: n.list())
19451-            d.addCallback(lambda children:
19452-                          self.failUnlessReallyEqual(set(children.keys()),
19453-                                                     set([u"child", u"newfile"])))
19454-            d.addCallback(lambda res: n.get_metadata_for(u"newfile"))
19455-            d.addCallback(lambda metadata:
19456-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19457-
19458-            uploadable3 = upload.Data("some data", convergence="converge")
19459-            d.addCallback(lambda res: n.add_file(u"newfile-metadata",
19460-                                                 uploadable3,
19461-                                                 {"key": "value"}))
19462-            d.addCallback(lambda newnode:
19463-                          self.failUnless(IImmutableFileNode.providedBy(newnode)))
19464-            d.addCallback(lambda res: n.get_metadata_for(u"newfile-metadata"))
19465-            d.addCallback(lambda metadata:
19466-                              self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
19467-                                              (metadata['key'] == "value"), metadata))
19468-            d.addCallback(lambda res: n.delete(u"newfile-metadata"))
19469-
19470-            d.addCallback(lambda res: n.create_subdirectory(u"subdir2"))
19471-            def _created2(subdir2):
19472-                self.subdir2 = subdir2
19473-                # put something in the way, to make sure it gets overwritten
19474-                return subdir2.add_file(u"child", upload.Data("overwrite me",
19475-                                                              "converge"))
19476-            d.addCallback(_created2)
19477-
19478-            d.addCallback(lambda res:
19479-                          n.move_child_to(u"child", self.subdir2))
19480-            d.addCallback(lambda res: n.list())
19481-            d.addCallback(lambda children:
19482-                          self.failUnlessReallyEqual(set(children.keys()),
19483-                                                     set([u"newfile", u"subdir2"])))
19484-            d.addCallback(lambda res: self.subdir2.list())
19485-            d.addCallback(lambda children:
19486-                          self.failUnlessReallyEqual(set(children.keys()),
19487-                                                     set([u"child"])))
19488-            d.addCallback(lambda res: self.subdir2.get(u"child"))
19489-            d.addCallback(lambda child:
19490-                          self.failUnlessReallyEqual(child.get_uri(),
19491-                                                     fake_file_uri))
19492-
19493-            # move it back, using new_child_name=
19494-            d.addCallback(lambda res:
19495-                          self.subdir2.move_child_to(u"child", n, u"newchild"))
19496-            d.addCallback(lambda res: n.list())
19497-            d.addCallback(lambda children:
19498-                          self.failUnlessReallyEqual(set(children.keys()),
19499-                                                     set([u"newchild", u"newfile",
19500-                                                          u"subdir2"])))
19501-            d.addCallback(lambda res: self.subdir2.list())
19502-            d.addCallback(lambda children:
19503-                          self.failUnlessReallyEqual(set(children.keys()), set([])))
19504-
19505-            # now make sure that we honor overwrite=False
19506-            d.addCallback(lambda res:
19507-                          self.subdir2.set_uri(u"newchild",
19508-                                               other_file_uri, other_file_uri))
19509-
19510-            d.addCallback(lambda res:
19511-                          self.shouldFail(ExistingChildError, "move_child_to-no",
19512-                                          "child 'newchild' already exists",
19513-                                          n.move_child_to, u"newchild",
19514-                                          self.subdir2,
19515-                                          overwrite=False))
19516-            d.addCallback(lambda res: self.subdir2.get(u"newchild"))
19517-            d.addCallback(lambda child:
19518-                          self.failUnlessReallyEqual(child.get_uri(),
19519-                                                     other_file_uri))
19520-
19521-
19522-            # Setting the no-write field should diminish a mutable cap to read-only
19523-            # (for both files and directories).
19524-
19525-            d.addCallback(lambda ign: n.set_uri(u"mutable", other_file_uri, other_file_uri))
19526-            d.addCallback(lambda ign: n.get(u"mutable"))
19527-            d.addCallback(lambda mutable: self.failIf(mutable.is_readonly(), mutable))
19528-            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
19529-            d.addCallback(lambda ign: n.get(u"mutable"))
19530-            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
19531-            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
19532-            d.addCallback(lambda ign: n.get(u"mutable"))
19533-            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
19534-
19535-            d.addCallback(lambda ign: n.get(u"subdir2"))
19536-            d.addCallback(lambda subdir2: self.failIf(subdir2.is_readonly()))
19537-            d.addCallback(lambda ign: n.set_metadata_for(u"subdir2", {"no-write": True}))
19538-            d.addCallback(lambda ign: n.get(u"subdir2"))
19539-            d.addCallback(lambda subdir2: self.failUnless(subdir2.is_readonly(), subdir2))
19540-
19541-            d.addCallback(lambda ign: n.set_uri(u"mutable_ro", other_file_uri, other_file_uri,
19542-                                                metadata={"no-write": True}))
19543-            d.addCallback(lambda ign: n.get(u"mutable_ro"))
19544-            d.addCallback(lambda mutable_ro: self.failUnless(mutable_ro.is_readonly(), mutable_ro))
19545-
19546-            d.addCallback(lambda ign: n.create_subdirectory(u"subdir_ro", metadata={"no-write": True}))
19547-            d.addCallback(lambda ign: n.get(u"subdir_ro"))
19548-            d.addCallback(lambda subdir_ro: self.failUnless(subdir_ro.is_readonly(), subdir_ro))
19549-
19550-            return d
19551-
19552-        d.addCallback(_then)
19553-
19554-        d.addErrback(self.explain_error)
19555-        return d
19556+        return self._do_create_test()
19557 
19558     def test_update_metadata(self):
19559         (t1, t2, t3) = (626644800.0, 634745640.0, 892226160.0)
19560hunk ./src/allmydata/test/test_dirnode.py 1283
19561         self.failUnlessEqual(md4, {"bool": True, "number": 42,
19562                                    "tahoe":{"linkcrtime": t1, "linkmotime": t1}})
19563 
19564-    def test_create_subdirectory(self):
19565-        self.basedir = "dirnode/Dirnode/test_create_subdirectory"
19566-        self.set_up_grid()
19567+    def _do_create_subdirectory_test(self, version=SDMF_VERSION):
19568         c = self.g.clients[0]
19569         nm = c.nodemaker
19570 
19571hunk ./src/allmydata/test/test_dirnode.py 1287
19572-        d = c.create_dirnode()
19573+        d = c.create_dirnode(version=version)
19574         def _then(n):
19575             # /
19576             self.rootnode = n
19577hunk ./src/allmydata/test/test_dirnode.py 1297
19578             kids = {u"kid1": (nm.create_from_cap(fake_file_uri), {}),
19579                     u"kid2": (nm.create_from_cap(other_file_uri), md),
19580                     }
19581-            d = n.create_subdirectory(u"subdir", kids)
19582+            d = n.create_subdirectory(u"subdir", kids,
19583+                                      mutable_version=version)
19584             def _check(sub):
19585                 d = n.get_child_at_path(u"subdir")
19586                 d.addCallback(lambda sub2: self.failUnlessReallyEqual(sub2.get_uri(),
19587hunk ./src/allmydata/test/test_dirnode.py 1314
19588         d.addCallback(_then)
19589         return d
19590 
19591+    def test_create_subdirectory(self):
19592+        self.basedir = "dirnode/Dirnode/test_create_subdirectory"
19593+        self.set_up_grid()
19594+        return self._do_create_subdirectory_test()
19595+
19596+    def test_create_subdirectory_mdmf(self):
19597+        self.basedir = "dirnode/Dirnode/test_create_subdirectory_mdmf"
19598+        self.set_up_grid()
19599+        return self._do_create_subdirectory_test(version=MDMF_VERSION)
19600+
19601+    def test_create_mdmf(self):
19602+        self.basedir = "dirnode/Dirnode/test_mdmf"
19603+        self.set_up_grid()
19604+        return self._do_create_test(mdmf=True)
19605+
19606+    def test_mdmf_initial_children(self):
19607+        self.basedir = "dirnode/Dirnode/test_mdmf"
19608+        self.set_up_grid()
19609+        return self._do_initial_children_test(mdmf=True)
19610+
19611 class MinimalFakeMutableFile:
19612     def get_writekey(self):
19613         return "writekey"
19614hunk ./src/allmydata/test/test_dirnode.py 1706
19615             self.failUnless(n.get_readonly_uri().startswith("imm."), i)
19616 
19617 
19618+
19619 class DeepStats(testutil.ReallyEqualMixin, unittest.TestCase):
19620     timeout = 240 # It takes longer than 120 seconds on Francois's arm box.
19621     def test_stats(self):
19622hunk ./src/allmydata/test/test_uri.py 795
19623         self.failUnlessReallyEqual(u1.get_storage_index(), None)
19624         self.failUnlessReallyEqual(u1.abbrev_si(), "<LIT>")
19625 
19626+    def test_mdmf(self):
19627+        writekey = "\x01" * 16
19628+        fingerprint = "\x02" * 32
19629+        uri1 = uri.WritableMDMFFileURI(writekey, fingerprint)
19630+        d1 = uri.MDMFDirectoryURI(uri1)
19631+        self.failIf(d1.is_readonly())
19632+        self.failUnless(d1.is_mutable())
19633+        self.failUnless(IURI.providedBy(d1))
19634+        self.failUnless(IDirnodeURI.providedBy(d1))
19635+        d1_uri = d1.to_string()
19636+
19637+        d2 = uri.from_string(d1_uri)
19638+        self.failUnlessIsInstance(d2, uri.MDMFDirectoryURI)
19639+        self.failIf(d2.is_readonly())
19640+        self.failUnless(d2.is_mutable())
19641+        self.failUnless(IURI.providedBy(d2))
19642+        self.failUnless(IDirnodeURI.providedBy(d2))
19643+
19644+        # It doesn't make sense to ask for a deep immutable URI for a
19645+        # mutable directory, and we should get back a result to that
19646+        # effect.
19647+        d3 = uri.from_string(d2.to_string(), deep_immutable=True)
19648+        self.failUnlessIsInstance(d3, uri.UnknownURI)
19649+
19650+    def test_mdmf_with_extensions(self):
19651+        writekey = "\x01" * 16
19652+        fingerprint = "\x02" * 32
19653+        uri1 = uri.WritableMDMFFileURI(writekey, fingerprint)
19654+        d1 = uri.MDMFDirectoryURI(uri1)
19655+        d1_uri = d1.to_string()
19656+        # Add some extensions, verify that the URI is interpreted
19657+        # correctly.
19658+        d1_uri += ":3:131073"
19659+        uri2 = uri.from_string(d1_uri)
19660+        self.failUnlessIsInstance(uri2, uri.MDMFDirectoryURI)
19661+        self.failUnless(IURI.providedBy(uri2))
19662+        self.failUnless(IDirnodeURI.providedBy(uri2))
19663+        self.failUnless(uri1.is_mutable())
19664+        self.failIf(uri1.is_readonly())
19665+
19666+        d2_uri = uri2.to_string()
19667+        self.failUnlessIn(":3:131073", d2_uri)
19668+
19669+        # Now attenuate, verify that the extensions persist
19670+        ro_uri = uri2.get_readonly()
19671+        self.failUnlessIsInstance(ro_uri, uri.ReadonlyMDMFDirectoryURI)
19672+        self.failUnless(ro_uri.is_mutable())
19673+        self.failUnless(ro_uri.is_readonly())
19674+        self.failUnless(IURI.providedBy(ro_uri))
19675+        self.failUnless(IDirnodeURI.providedBy(ro_uri))
19676+        ro_uri_str = ro_uri.to_string()
19677+        self.failUnlessIn(":3:131073", ro_uri_str)
19678+
19679+    def test_mdmf_attenuation(self):
19680+        writekey = "\x01" * 16
19681+        fingerprint = "\x02" * 32
19682+
19683+        uri1 = uri.WritableMDMFFileURI(writekey, fingerprint)
19684+        d1 = uri.MDMFDirectoryURI(uri1)
19685+        self.failUnless(d1.is_mutable())
19686+        self.failIf(d1.is_readonly())
19687+        self.failUnless(IURI.providedBy(d1))
19688+        self.failUnless(IDirnodeURI.providedBy(d1))
19689+
19690+        d1_uri = d1.to_string()
19691+        d1_uri_from_fn = uri.MDMFDirectoryURI(d1.get_filenode_cap()).to_string()
19692+        self.failUnlessEqual(d1_uri_from_fn, d1_uri)
19693+
19694+        uri2 = uri.from_string(d1_uri)
19695+        self.failUnlessIsInstance(uri2, uri.MDMFDirectoryURI)
19696+        self.failUnless(IURI.providedBy(uri2))
19697+        self.failUnless(IDirnodeURI.providedBy(uri2))
19698+        self.failUnless(uri2.is_mutable())
19699+        self.failIf(uri2.is_readonly())
19700+
19701+        ro = uri2.get_readonly()
19702+        self.failUnlessIsInstance(ro, uri.ReadonlyMDMFDirectoryURI)
19703+        self.failUnless(ro.is_mutable())
19704+        self.failUnless(ro.is_readonly())
19705+        self.failUnless(IURI.providedBy(ro))
19706+        self.failUnless(IDirnodeURI.providedBy(ro))
19707+
19708+        ro_uri = ro.to_string()
19709+        n = uri.from_string(ro_uri, deep_immutable=True)
19710+        self.failUnlessIsInstance(n, uri.UnknownURI)
19711+
19712+        fn_cap = ro.get_filenode_cap()
19713+        fn_ro_cap = fn_cap.get_readonly()
19714+        d3 = uri.ReadonlyMDMFDirectoryURI(fn_ro_cap)
19715+        self.failUnlessEqual(ro.to_string(), d3.to_string())
19716+        self.failUnless(ro.is_mutable())
19717+        self.failUnless(ro.is_readonly())
19718+
19719+    def test_mdmf_verifier(self):
19720+        # I'm not sure what I want to write here yet.
19721+        writekey = "\x01" * 16
19722+        fingerprint = "\x02" * 32
19723+        uri1 = uri.WritableMDMFFileURI(writekey, fingerprint)
19724+        d1 = uri.MDMFDirectoryURI(uri1)
19725+        v1 = d1.get_verify_cap()
19726+        self.failUnlessIsInstance(v1, uri.MDMFDirectoryURIVerifier)
19727+        self.failIf(v1.is_mutable())
19728+
19729+        d2 = uri.from_string(d1.to_string())
19730+        v2 = d2.get_verify_cap()
19731+        self.failUnlessIsInstance(v2, uri.MDMFDirectoryURIVerifier)
19732+        self.failIf(v2.is_mutable())
19733+        self.failUnlessEqual(v2.to_string(), v1.to_string())
19734+
19735+        # Now attenuate and make sure that works correctly.
19736+        r3 = d2.get_readonly()
19737+        v3 = r3.get_verify_cap()
19738+        self.failUnlessIsInstance(v3, uri.MDMFDirectoryURIVerifier)
19739+        self.failIf(v3.is_mutable())
19740+        self.failUnlessEqual(v3.to_string(), v1.to_string())
19741+        r4 = uri.from_string(r3.to_string())
19742+        v4 = r4.get_verify_cap()
19743+        self.failUnlessIsInstance(v4, uri.MDMFDirectoryURIVerifier)
19744+        self.failIf(v4.is_mutable())
19745+        self.failUnlessEqual(v4.to_string(), v3.to_string())
19746+
19747}
19748[web: teach WUI and webapi to create MDMF directories
19749Kevan Carstensen <kevan@isnotajoke.com>**20110617180019
19750 Ignore-this: 956a60542a26c2d5118085ab9e3c470e
19751] {
19752hunk ./src/allmydata/web/common.py 41
19753         return None # interpreted by the caller as "let the nodemaker decide"
19754 
19755     arg = arg.lower()
19756-    assert arg in ("mdmf", "sdmf")
19757-
19758     if arg == "mdmf":
19759         return MDMF_VERSION
19760hunk ./src/allmydata/web/common.py 43
19761+    elif arg == "sdmf":
19762+        return SDMF_VERSION
19763 
19764hunk ./src/allmydata/web/common.py 46
19765-    return SDMF_VERSION
19766+    return "invalid"
19767 
19768 
19769 def parse_offset_arg(offset):
19770hunk ./src/allmydata/web/directory.py 26
19771      IOpHandleTable, NeedOperationHandleError, \
19772      boolean_of_arg, get_arg, get_root, parse_replace_arg, \
19773      should_create_intermediate_directories, \
19774-     getxmlfile, RenderMixin, humanize_failure, convert_children_json
19775+     getxmlfile, RenderMixin, humanize_failure, convert_children_json, \
19776+     parse_mutable_type_arg
19777 from allmydata.web.filenode import ReplaceMeMixin, \
19778      FileNodeHandler, PlaceHolderNodeHandler
19779 from allmydata.web.check_results import CheckResults, \
19780hunk ./src/allmydata/web/directory.py 112
19781                     mutable = True
19782                     if t == "mkdir-immutable":
19783                         mutable = False
19784+
19785+                    mt = None
19786+                    if mutable:
19787+                        arg = get_arg(req, "mutable-type", None)
19788+                        mt = parse_mutable_type_arg(arg)
19789+                        if mt is "invalid":
19790+                            raise WebError("Unknown type: %s" % arg,
19791+                                           http.BAD_REQUEST)
19792                     d = self.node.create_subdirectory(name, kids,
19793hunk ./src/allmydata/web/directory.py 121
19794-                                                      mutable=mutable)
19795+                                                      mutable=mutable,
19796+                                                      mutable_version=mt)
19797                     d.addCallback(make_handler_for,
19798                                   self.client, self.node, name)
19799                     return d
19800hunk ./src/allmydata/web/directory.py 253
19801         name = name.decode("utf-8")
19802         replace = boolean_of_arg(get_arg(req, "replace", "true"))
19803         kids = {}
19804-        d = self.node.create_subdirectory(name, kids, overwrite=replace)
19805+        arg = get_arg(req, "mutable-type", None)
19806+        mt = parse_mutable_type_arg(arg)
19807+        if mt is not None and mt is not "invalid":
19808+            d = self.node.create_subdirectory(name, kids, overwrite=replace,
19809+                                          mutable_version=mt)
19810+        elif mt is "invalid":
19811+            raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
19812+        else:
19813+            d = self.node.create_subdirectory(name, kids, overwrite=replace)
19814         d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
19815         return d
19816 
19817hunk ./src/allmydata/web/directory.py 277
19818         req.content.seek(0)
19819         kids_json = req.content.read()
19820         kids = convert_children_json(self.client.nodemaker, kids_json)
19821-        d = self.node.create_subdirectory(name, kids, overwrite=False)
19822+        arg = get_arg(req, "mutable-type", None)
19823+        mt = parse_mutable_type_arg(arg)
19824+        if mt is not None and mt is not "invalid":
19825+            d = self.node.create_subdirectory(name, kids, overwrite=False,
19826+                                              mutable_version=mt)
19827+        elif mt is "invalid":
19828+            raise WebError("Unknown type: %s" % arg)
19829+        else:
19830+            d = self.node.create_subdirectory(name, kids, overwrite=False)
19831         d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
19832         return d
19833 
19834hunk ./src/allmydata/web/directory.py 786
19835 
19836         return ctx.tag
19837 
19838+    # XXX: Duplicated from root.py.
19839     def render_forms(self, ctx, data):
19840         forms = []
19841 
19842hunk ./src/allmydata/web/directory.py 795
19843         if self.dirnode_children is None:
19844             return T.div["No upload forms: directory is unreadable"]
19845 
19846+        mdmf_directory_input = T.input(type='radio', name='mutable-type',
19847+                                       id='mutable-directory-mdmf',
19848+                                       value='mdmf')
19849+        sdmf_directory_input = T.input(type='radio', name='mutable-type',
19850+                                       id='mutable-directory-sdmf',
19851+                                       value='sdmf', checked='checked')
19852         mkdir = T.form(action=".", method="post",
19853                        enctype="multipart/form-data")[
19854             T.fieldset[
19855hunk ./src/allmydata/web/directory.py 809
19856             T.legend(class_="freeform-form-label")["Create a new directory in this directory"],
19857             "New directory name: ",
19858             T.input(type="text", name="name"), " ",
19859+            T.label(for_='mutable-directory-sdmf')["SDMF"],
19860+            sdmf_directory_input,
19861+            T.label(for_='mutable-directory-mdmf')["MDMF"],
19862+            mdmf_directory_input,
19863             T.input(type="submit", value="Create"),
19864             ]]
19865         forms.append(T.div(class_="freeform-form")[mkdir])
19866hunk ./src/allmydata/web/filenode.py 29
19867         # a new file is being uploaded in our place.
19868         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
19869         if mutable:
19870-            mutable_type = parse_mutable_type_arg(get_arg(req,
19871-                                                          "mutable-type",
19872-                                                          None))
19873+            arg = get_arg(req, "mutable-type", None)
19874+            mutable_type = parse_mutable_type_arg(arg)
19875+            if mutable_type is "invalid":
19876+                raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
19877+
19878             data = MutableFileHandle(req.content)
19879             d = client.create_mutable_file(data, version=mutable_type)
19880             def _uploaded(newnode):
19881hunk ./src/allmydata/web/filenode.py 76
19882         # create an immutable file
19883         contents = req.fields["file"]
19884         if mutable:
19885-            mutable_type = parse_mutable_type_arg(get_arg(req, "mutable-type",
19886-                                                          None))
19887+            arg = get_arg(req, "mutable-type", None)
19888+            mutable_type = parse_mutable_type_arg(arg)
19889+            if mutable_type is "invalid":
19890+                raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
19891             uploadable = MutableFileHandle(contents.file)
19892             d = client.create_mutable_file(uploadable, version=mutable_type)
19893             def _uploaded(newnode):
19894hunk ./src/allmydata/web/root.py 50
19895         if t == "":
19896             mutable = boolean_of_arg(get_arg(req, "mutable", "false").strip())
19897             if mutable:
19898-                version = parse_mutable_type_arg(get_arg(req, "mutable-type",
19899-                                                 None))
19900+                arg = get_arg(req, "mutable-type", None)
19901+                version = parse_mutable_type_arg(arg)
19902+                if version == "invalid":
19903+                    errmsg = "Unknown type: %s" % arg
19904+                    raise WebError(errmsg, http.BAD_REQUEST)
19905+
19906                 return unlinked.PUTUnlinkedSSK(req, self.client, version)
19907             else:
19908                 return unlinked.PUTUnlinkedCHK(req, self.client)
19909hunk ./src/allmydata/web/root.py 74
19910         if t in ("", "upload"):
19911             mutable = bool(get_arg(req, "mutable", "").strip())
19912             if mutable:
19913-                version = parse_mutable_type_arg(get_arg(req, "mutable-type",
19914-                                                         None))
19915+                arg = get_arg(req, "mutable-type", None)
19916+                version = parse_mutable_type_arg(arg)
19917+                if version is "invalid":
19918+                    raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
19919                 return unlinked.POSTUnlinkedSSK(req, self.client, version)
19920             else:
19921                 return unlinked.POSTUnlinkedCHK(req, self.client)
19922hunk ./src/allmydata/web/root.py 376
19923 
19924     def render_mkdir_form(self, ctx, data):
19925         # this is a form where users can create new directories
19926+        mdmf_input = T.input(type='radio', name='mutable-type',
19927+                             value='mdmf', id='mutable-directory-mdmf')
19928+        sdmf_input = T.input(type='radio', name='mutable-type',
19929+                             value='sdmf', id='mutable-directory-sdmf',
19930+                             checked='checked')
19931         form = T.form(action="uri", method="post",
19932                       enctype="multipart/form-data")[
19933             T.fieldset[
19934hunk ./src/allmydata/web/root.py 385
19935             T.legend(class_="freeform-form-label")["Create a directory"],
19936+            T.label(for_='mutable-directory-sdmf')["SDMF"],
19937+            sdmf_input,
19938+            T.label(for_='mutable-directory-mdmf')["MDMF"],
19939+            mdmf_input,
19940             T.input(type="hidden", name="t", value="mkdir"),
19941             T.input(type="hidden", name="redirect_to_result", value="true"),
19942             T.input(type="submit", value="Create a directory"),
19943hunk ./src/allmydata/web/unlinked.py 9
19944 from allmydata.immutable.upload import FileHandle
19945 from allmydata.mutable.publish import MutableFileHandle
19946 from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
19947-     convert_children_json, WebError
19948+     convert_children_json, WebError, parse_mutable_type_arg
19949 from allmydata.web import status
19950 
19951 def PUTUnlinkedCHK(req, client):
19952hunk ./src/allmydata/web/unlinked.py 30
19953 
19954 def PUTUnlinkedCreateDirectory(req, client):
19955     # "PUT /uri?t=mkdir", to create an unlinked directory.
19956-    d = client.create_dirnode()
19957+    arg = get_arg(req, "mutable-type", None)
19958+    mt = parse_mutable_type_arg(arg)
19959+    if mt is not None and mt is not "invalid":
19960+        d = client.create_dirnode(version=mt)
19961+    elif mt is "invalid":
19962+        msg = "Unknown type: %s" % arg
19963+        raise WebError(msg, http.BAD_REQUEST)
19964+    else:
19965+        d = client.create_dirnode()
19966     d.addCallback(lambda dirnode: dirnode.get_uri())
19967     # XXX add redirect_to_result
19968     return d
19969hunk ./src/allmydata/web/unlinked.py 115
19970             raise WebError("t=mkdir does not accept children=, "
19971                            "try t=mkdir-with-children instead",
19972                            http.BAD_REQUEST)
19973-    d = client.create_dirnode()
19974+    arg = get_arg(req, "mutable-type", None)
19975+    mt = parse_mutable_type_arg(arg)
19976+    if mt is not None and mt is not "invalid":
19977+        d = client.create_dirnode(version=mt)
19978+    elif mt is "invalid":
19979+        msg = "Unknown type: %s" % arg
19980+        raise WebError(msg, http.BAD_REQUEST)
19981+    else:
19982+        d = client.create_dirnode()
19983     redirect = get_arg(req, "redirect_to_result", "false")
19984     if boolean_of_arg(redirect):
19985         def _then_redir(res):
19986}
19987[test/test_web: test webapi and WUI for MDMF directory handling
19988Kevan Carstensen <kevan@isnotajoke.com>**20110617180100
19989 Ignore-this: 63ed7832872fd35eb7319cf6a6f251b
19990] {
19991hunk ./src/allmydata/test/test_web.py 1122
19992         d.addCallback(lambda json: self.failUnlessIn("sdmf", json))
19993         return d
19994 
19995+    def test_PUT_NEWFILEURL_unlinked_bad_mutable_type(self):
19996+        contents = self.NEWFILE_CONTENTS * 300000
19997+        return self.shouldHTTPError("test bad mutable type",
19998+                                    400, "Bad Request", "Unknown type: foo",
19999+                                    self.PUT, "/uri?mutable=true&mutable-type=foo",
20000+                                    contents)
20001+
20002     def test_PUT_NEWFILEURL_range_bad(self):
20003         headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
20004         target = self.public_url + "/foo/new.txt"
20005hunk ./src/allmydata/test/test_web.py 1365
20006             self.failUnless(CSS_STYLE.search(res), res)
20007         d.addCallback(_check)
20008         return d
20009-   
20010+
20011     def test_GET_FILEURL_uri_missing(self):
20012         d = self.GET(self.public_url + "/foo/missing?t=uri")
20013         d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
20014hunk ./src/allmydata/test/test_web.py 1375
20015         d = self.GET(self.public_url + "/foo", followRedirect=True)
20016         def _check(res):
20017             self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>',res)
20018+            # These are radio buttons that allow a user to toggle
20019+            # whether a particular mutable file is SDMF or MDMF.
20020             self.failUnlessIn("mutable-type-mdmf", res)
20021             self.failUnlessIn("mutable-type-sdmf", res)
20022hunk ./src/allmydata/test/test_web.py 1379
20023+            # Similarly, these toggle whether a particular directory
20024+            # should be MDMF or SDMF.
20025+            self.failUnlessIn("mutable-directory-mdmf", res)
20026+            self.failUnlessIn("mutable-directory-sdmf", res)
20027             self.failUnlessIn("quux", res)
20028         d.addCallback(_check)
20029         return d
20030hunk ./src/allmydata/test/test_web.py 1396
20031             # whether a particular mutable file is MDMF or SDMF.
20032             self.failUnlessIn("mutable-type-mdmf", html)
20033             self.failUnlessIn("mutable-type-sdmf", html)
20034+            # We should also have the ability to create a mutable directory.
20035+            self.failUnlessIn("mkdir", html)
20036+            # ...and we should have the ability to say whether that's an
20037+            # MDMF or SDMF directory
20038+            self.failUnlessIn("mutable-directory-mdmf", html)
20039+            self.failUnlessIn("mutable-directory-sdmf", html)
20040         d.addCallback(_got_html)
20041         return d
20042 
20043hunk ./src/allmydata/test/test_web.py 1710
20044         d.addCallback(self.failUnlessNodeKeysAre, [])
20045         return d
20046 
20047+    def test_PUT_NEWDIRURL_mdmf(self):
20048+        d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&mutable-type=mdmf", "")
20049+        d.addCallback(lambda res:
20050+                      self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
20051+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20052+        d.addCallback(lambda node:
20053+            self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
20054+        return d
20055+
20056+    def test_PUT_NEWDIRURL_sdmf(self):
20057+        d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&mutable-type=sdmf",
20058+                     "")
20059+        d.addCallback(lambda res:
20060+                      self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
20061+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20062+        d.addCallback(lambda node:
20063+            self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
20064+        return d
20065+
20066+    def test_PUT_NEWDIRURL_bad_mutable_type(self):
20067+        return self.shouldHTTPError("test bad mutable type",
20068+                             400, "Bad Request", "Unknown type: foo",
20069+                             self.PUT, self.public_url + \
20070+                             "/foo/newdir=?t=mkdir&mutable-type=foo", "")
20071+
20072     def test_POST_NEWDIRURL(self):
20073         d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
20074         d.addCallback(lambda res:
20075hunk ./src/allmydata/test/test_web.py 1743
20076         d.addCallback(self.failUnlessNodeKeysAre, [])
20077         return d
20078 
20079+    def test_POST_NEWDIRURL_mdmf(self):
20080+        d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&mutable-type=mdmf", "")
20081+        d.addCallback(lambda res:
20082+                      self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
20083+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20084+        d.addCallback(lambda node:
20085+            self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
20086+        return d
20087+
20088+    def test_POST_NEWDIRURL_sdmf(self):
20089+        d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&mutable-type=sdmf", "")
20090+        d.addCallback(lambda res:
20091+            self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
20092+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20093+        d.addCallback(lambda node:
20094+            self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
20095+        return d
20096+
20097+    def test_POST_NEWDIRURL_bad_mutable_type(self):
20098+        return self.shouldHTTPError("test bad mutable type",
20099+                                    400, "Bad Request", "Unknown type: foo",
20100+                                    self.POST2, self.public_url + \
20101+                                    "/foo/newdir?t=mkdir&mutable-type=foo", "")
20102+
20103     def test_POST_NEWDIRURL_emptyname(self):
20104         # an empty pathname component (i.e. a double-slash) is disallowed
20105         d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_emptyname",
20106hunk ./src/allmydata/test/test_web.py 1775
20107                              self.POST, self.public_url + "//?t=mkdir")
20108         return d
20109 
20110-    def test_POST_NEWDIRURL_initial_children(self):
20111+    def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
20112         (newkids, caps) = self._create_initial_children()
20113hunk ./src/allmydata/test/test_web.py 1777
20114-        d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children",
20115+        query = "/foo/newdir?t=mkdir-with-children"
20116+        if version == MDMF_VERSION:
20117+            query += "&mutable-type=mdmf"
20118+        elif version == SDMF_VERSION:
20119+            query += "&mutable-type=sdmf"
20120+        else:
20121+            version = SDMF_VERSION # for later
20122+        d = self.POST2(self.public_url + query,
20123                        simplejson.dumps(newkids))
20124         def _check(uri):
20125             n = self.s.create_node_from_uri(uri.strip())
20126hunk ./src/allmydata/test/test_web.py 1789
20127             d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
20128+            self.failUnlessEqual(n._node.get_version(), version)
20129             d2.addCallback(lambda ign:
20130                            self.failUnlessROChildURIIs(n, u"child-imm",
20131                                                        caps['filecap1']))
20132hunk ./src/allmydata/test/test_web.py 1827
20133         d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
20134         return d
20135 
20136+    def test_POST_NEWDIRURL_initial_children(self):
20137+        return self._do_POST_NEWDIRURL_initial_children_test()
20138+
20139+    def test_POST_NEWDIRURL_initial_children_mdmf(self):
20140+        return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
20141+
20142+    def test_POST_NEWDIRURL_initial_children_sdmf(self):
20143+        return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
20144+
20145+    def test_POST_NEWDIRURL_initial_children_bad_mutable_type(self):
20146+        (newkids, caps) = self._create_initial_children()
20147+        return self.shouldHTTPError("test bad mutable type",
20148+                                    400, "Bad Request", "Unknown type: foo",
20149+                                    self.POST2, self.public_url + \
20150+                                    "/foo/newdir?t=mkdir-with-children&mutable-type=foo",
20151+                                    simplejson.dumps(newkids))
20152+
20153     def test_POST_NEWDIRURL_immutable(self):
20154         (newkids, caps) = self._create_immutable_children()
20155         d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
20156hunk ./src/allmydata/test/test_web.py 1944
20157         d.addCallback(self.failUnlessNodeKeysAre, [])
20158         return d
20159 
20160+    def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
20161+        d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&mutable-type=mdmf", "")
20162+        d.addCallback(lambda ignored:
20163+            self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
20164+        d.addCallback(lambda ignored:
20165+            self.failIfNodeHasChild(self._foo_node, u"newdir"))
20166+        d.addCallback(lambda ignored:
20167+            self._foo_node.get_child_at_path(u"subdir"))
20168+        def _got_subdir(subdir):
20169+            # XXX: What we want?
20170+            #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
20171+            self.failUnlessNodeHasChild(subdir, u"newdir")
20172+            return subdir.get_child_at_path(u"newdir")
20173+        d.addCallback(_got_subdir)
20174+        d.addCallback(lambda newdir:
20175+            self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
20176+        return d
20177+
20178+    def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
20179+        d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&mutable-type=sdmf", "")
20180+        d.addCallback(lambda ignored:
20181+            self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
20182+        d.addCallback(lambda ignored:
20183+            self.failIfNodeHasChild(self._foo_node, u"newdir"))
20184+        d.addCallback(lambda ignored:
20185+            self._foo_node.get_child_at_path(u"subdir"))
20186+        def _got_subdir(subdir):
20187+            # XXX: What we want?
20188+            #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
20189+            self.failUnlessNodeHasChild(subdir, u"newdir")
20190+            return subdir.get_child_at_path(u"newdir")
20191+        d.addCallback(_got_subdir)
20192+        d.addCallback(lambda newdir:
20193+            self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
20194+        return d
20195+
20196+    def test_PUT_NEWDIRURL_mkdirs_bad_mutable_type(self):
20197+        return self.shouldHTTPError("test bad mutable type",
20198+                                    400, "Bad Request", "Unknown type: foo",
20199+                                    self.PUT, self.public_url + \
20200+                                    "/foo/subdir/newdir?t=mkdir&mutable-type=foo",
20201+                                    "")
20202+
20203     def test_DELETE_DIRURL(self):
20204         d = self.DELETE(self.public_url + "/foo")
20205         d.addCallback(lambda res:
20206hunk ./src/allmydata/test/test_web.py 2254
20207         d.addCallback(_got_json, "mdmf")
20208         return d
20209 
20210+    def test_POST_upload_mutable_type_unlinked_bad_mutable_type(self):
20211+        return self.shouldHTTPError("test bad mutable type",
20212+                                    400, "Bad Request", "Unknown type: foo",
20213+                                    self.POST,
20214+                                    "/uri?5=upload&mutable=true&mutable-type=foo",
20215+                                    file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
20216+
20217     def test_POST_upload_mutable_type(self):
20218         d = self.POST(self.public_url + \
20219                       "/foo?t=upload&mutable=true&mutable-type=sdmf",
20220hunk ./src/allmydata/test/test_web.py 2290
20221         d.addCallback(_got_json, "mdmf")
20222         return d
20223 
20224+    def test_POST_upload_bad_mutable_type(self):
20225+        return self.shouldHTTPError("test bad mutable type",
20226+                                    400, "Bad Request", "Unknown type: foo",
20227+                                    self.POST, self.public_url + \
20228+                                    "/foo?t=upload&mutable=true&mutable-type=foo",
20229+                                    file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
20230+
20231     def test_POST_upload_mutable(self):
20232         # this creates a mutable file
20233         d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
20234hunk ./src/allmydata/test/test_web.py 2796
20235         d.addCallback(self.failUnlessNodeKeysAre, [])
20236         return d
20237 
20238+    def test_POST_mkdir_mdmf(self):
20239+        d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&mutable-type=mdmf")
20240+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20241+        d.addCallback(lambda node:
20242+            self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
20243+        return d
20244+
20245+    def test_POST_mkdir_sdmf(self):
20246+        d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&mutable-type=sdmf")
20247+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20248+        d.addCallback(lambda node:
20249+            self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
20250+        return d
20251+
20252+    def test_POST_mkdir_bad_mutable_type(self):
20253+        return self.shouldHTTPError("test bad mutable type",
20254+                                    400, "Bad Request", "Unknown type: foo",
20255+                                    self.POST, self.public_url + \
20256+                                    "/foo?t=mkdir&name=newdir&mutable-type=foo")
20257+
20258     def test_POST_mkdir_initial_children(self):
20259         (newkids, caps) = self._create_initial_children()
20260         d = self.POST2(self.public_url +
20261hunk ./src/allmydata/test/test_web.py 2829
20262         d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
20263         return d
20264 
20265+    def test_POST_mkdir_initial_children_mdmf(self):
20266+        (newkids, caps) = self._create_initial_children()
20267+        d = self.POST2(self.public_url +
20268+                       "/foo?t=mkdir-with-children&name=newdir&mutable-type=mdmf",
20269+                       simplejson.dumps(newkids))
20270+        d.addCallback(lambda res:
20271+                      self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
20272+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20273+        d.addCallback(lambda node:
20274+            self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
20275+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20276+        d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
20277+                       caps['filecap1'])
20278+        return d
20279+
20280+    # XXX: Duplication.
20281+    def test_POST_mkdir_initial_children_sdmf(self):
20282+        (newkids, caps) = self._create_initial_children()
20283+        d = self.POST2(self.public_url +
20284+                       "/foo?t=mkdir-with-children&name=newdir&mutable-type=sdmf",
20285+                       simplejson.dumps(newkids))
20286+        d.addCallback(lambda res:
20287+                      self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
20288+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20289+        d.addCallback(lambda node:
20290+            self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
20291+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20292+        d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
20293+                       caps['filecap1'])
20294+        return d
20295+
20296+    def test_POST_mkdir_initial_children_bad_mutable_type(self):
20297+        (newkids, caps) = self._create_initial_children()
20298+        return self.shouldHTTPError("test bad mutable type",
20299+                                    400, "Bad Request", "Unknown type: foo",
20300+                                    self.POST, self.public_url + \
20301+                                    "/foo?t=mkdir-with-children&name=newdir&mutable-type=foo",
20302+                                    simplejson.dumps(newkids))
20303+
20304     def test_POST_mkdir_immutable(self):
20305         (newkids, caps) = self._create_immutable_children()
20306         d = self.POST2(self.public_url +
20307hunk ./src/allmydata/test/test_web.py 2924
20308         d.addCallback(_after_mkdir)
20309         return d
20310 
20311+    def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
20312+        d = self.POST("/uri?t=mkdir&mutable-type=mdmf")
20313+        def _after_mkdir(res):
20314+            u = uri.from_string(res)
20315+            # Check that this is an MDMF writecap
20316+            self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
20317+        d.addCallback(_after_mkdir)
20318+        return d
20319+
20320+    def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
20321+        d = self.POST("/uri?t=mkdir&mutable-type=sdmf")
20322+        def _after_mkdir(res):
20323+            u = uri.from_string(res)
20324+            self.failUnlessIsInstance(u, uri.DirectoryURI)
20325+        d.addCallback(_after_mkdir)
20326+        return d
20327+
20328+    def test_POST_mkdir_no_parentdir_noredirect_bad_mutable_type(self):
20329+        return self.shouldHTTPError("test bad mutable type",
20330+                                    400, "Bad Request", "Unknown type: foo",
20331+                                    self.POST, self.public_url + \
20332+                                    "/uri?t=mkdir&mutable-type=foo")
20333+
20334     def test_POST_mkdir_no_parentdir_noredirect2(self):
20335         # make sure form-based arguments (as on the welcome page) still work
20336         d = self.POST("/uri", t="mkdir")
20337hunk ./src/allmydata/test/test_web.py 3584
20338         d.addCallback(_got_json)
20339         return d
20340 
20341+    def test_PUT_NEWFILEURL_bad_mutable_type(self):
20342+       new_contents = self.NEWFILE_CONTENTS * 300000
20343+       return self.shouldHTTPError("test bad mutable type",
20344+                                   400, "Bad Request", "Unknown type: foo",
20345+                                   self.PUT, self.public_url + \
20346+                                   "/foo/foo.txt?mutable=true&mutable-type=foo",
20347+                                   new_contents)
20348+
20349     def test_PUT_NEWFILEURL_uri_replace(self):
20350         contents, n, new_uri = self.makefile(8)
20351         d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
20352hunk ./src/allmydata/test/test_web.py 3701
20353         d.addCallback(self.failUnlessIsEmptyJSON)
20354         return d
20355 
20356+    def test_PUT_mkdir_mdmf(self):
20357+        d = self.PUT("/uri?t=mkdir&mutable-type=mdmf", "")
20358+        def _got(res):
20359+            u = uri.from_string(res)
20360+            # Check that this is an MDMF writecap
20361+            self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
20362+        d.addCallback(_got)
20363+        return d
20364+
20365+    def test_PUT_mkdir_sdmf(self):
20366+        d = self.PUT("/uri?t=mkdir&mutable-type=sdmf", "")
20367+        def _got(res):
20368+            u = uri.from_string(res)
20369+            self.failUnlessIsInstance(u, uri.DirectoryURI)
20370+        d.addCallback(_got)
20371+        return d
20372+
20373+    def test_PUT_mkdir_bad_mutable_type(self):
20374+        return self.shouldHTTPError("bad mutable type",
20375+                                    400, "Bad Request", "Unknown type: foo",
20376+                                    self.PUT, "/uri?t=mkdir&mutable-type=foo",
20377+                                    "")
20378+
20379     def test_POST_check(self):
20380         d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
20381         def _done(res):
20382}
20383[scripts: teach CLI to make MDMF directories
20384Kevan Carstensen <kevan@isnotajoke.com>**20110617180137
20385 Ignore-this: 5dc968bd22278033b534a561f230a4f
20386] {
20387hunk ./src/allmydata/scripts/cli.py 53
20388 
20389 
20390 class MakeDirectoryOptions(VDriveOptions):
20391+    optParameters = [
20392+        ("mutable-type", None, False, "Create a mutable file in the given format. Valid formats are 'sdmf' for SDMF and 'mdmf' for MDMF"),
20393+        ]
20394+
20395     def parseArgs(self, where=""):
20396         self.where = argv_to_unicode(where)
20397 
20398merger 0.0 (
20399hunk ./src/allmydata/scripts/cli.py 59
20400+
20401+    def getSynopsis(self):
20402+        return "Usage:  %s mkdir [options] [REMOTE_DIR]" % (self.command_name,)
20403+
20404hunk ./src/allmydata/scripts/cli.py 59
20405+        if self['mutable-type'] and self['mutable-type'] not in ("sdmf", "mdmf"):
20406+            raise usage.UsageError("%s is an invalid format" % self['mutable-type'])
20407)
20408hunk ./src/allmydata/scripts/tahoe_mkdir.py 25
20409     if not where or not path:
20410         # create a new unlinked directory
20411         url = nodeurl + "uri?t=mkdir"
20412+        if options["mutable-type"]:
20413+            url += "&mutable-type=%s" % urllib.quote(options['mutable-type'])
20414         resp = do_http("POST", url)
20415         rc = check_http_error(resp, stderr)
20416         if rc:
20417hunk ./src/allmydata/scripts/tahoe_mkdir.py 42
20418     # path must be "/".join([s.encode("utf-8") for s in segments])
20419     url = nodeurl + "uri/%s/%s?t=mkdir" % (urllib.quote(rootcap),
20420                                            urllib.quote(path))
20421+    if options['mutable-type']:
20422+        url += "&mutable-type=%s" % urllib.quote(options['mutable-type'])
20423+
20424     resp = do_http("POST", url)
20425     check_http_error(resp, stderr)
20426     new_uri = resp.read().strip()
20427}
20428[test/test_cli: test CLI's MDMF creation powers
20429Kevan Carstensen <kevan@isnotajoke.com>**20110617180209
20430 Ignore-this: d4b493b266446b2be3ce3c5f2505577d
20431] hunk ./src/allmydata/test/test_cli.py 3156
20432 
20433         return d
20434 
20435+    def test_mkdir_mutable_type(self):
20436+        self.basedir = os.path.dirname(self.mktemp())
20437+        self.set_up_grid()
20438+        d = self.do_cli("create-alias", "tahoe")
20439+        d.addCallback(lambda ignored:
20440+            self.do_cli("mkdir", "--mutable-type=sdmf", "tahoe:foo"))
20441+        def _check((rc, out, err), st):
20442+            self.failUnlessReallyEqual(rc, 0)
20443+            self.failUnlessReallyEqual(err, "")
20444+            self.failUnlessIn(st, out)
20445+            return out
20446+        def _stash_dircap(cap):
20447+            self._dircap = cap
20448+            u = uri.from_string(cap)
20449+            fn_uri = u.get_filenode_cap()
20450+            self._filecap = fn_uri.to_string()
20451+        d.addCallback(_check, "URI:DIR2")
20452+        d.addCallback(_stash_dircap)
20453+        d.addCallback(lambda ignored:
20454+            self.do_cli("ls", "--json", "tahoe:foo"))
20455+        d.addCallback(_check, "URI:DIR2")
20456+        d.addCallback(lambda ignored:
20457+            self.do_cli("ls", "--json", self._filecap))
20458+        d.addCallback(_check, '"mutable-type": "sdmf"')
20459+        d.addCallback(lambda ignored:
20460+            self.do_cli("mkdir", "--mutable-type=mdmf", "tahoe:bar"))
20461+        d.addCallback(_check, "URI:DIR2-MDMF")
20462+        d.addCallback(_stash_dircap)
20463+        d.addCallback(lambda ignored:
20464+            self.do_cli("ls", "--json", "tahoe:bar"))
20465+        d.addCallback(_check, "URI:DIR2-MDMF")
20466+        d.addCallback(lambda ignored:
20467+            self.do_cli("ls", "--json", self._filecap))
20468+        d.addCallback(_check, '"mutable-type": "mdmf"')
20469+        return d
20470+
20471+    def test_mkdir_mutable_type_unlinked(self):
20472+        self.basedir = os.path.dirname(self.mktemp())
20473+        self.set_up_grid()
20474+        d = self.do_cli("mkdir", "--mutable-type=sdmf")
20475+        def _check((rc, out, err), st):
20476+            self.failUnlessReallyEqual(rc, 0)
20477+            self.failUnlessReallyEqual(err, "")
20478+            self.failUnlessIn(st, out)
20479+            return out
20480+        d.addCallback(_check, "URI:DIR2")
20481+        def _stash_dircap(cap):
20482+            self._dircap = cap
20483+            # Now we're going to feed the cap into uri.from_string...
20484+            u = uri.from_string(cap)
20485+            # ...grab the underlying filenode uri.
20486+            fn_uri = u.get_filenode_cap()
20487+            # ...and stash that.
20488+            self._filecap = fn_uri.to_string()
20489+        d.addCallback(_stash_dircap)
20490+        d.addCallback(lambda res: self.do_cli("ls", "--json",
20491+                                              self._filecap))
20492+        d.addCallback(_check, '"mutable-type": "sdmf"')
20493+        d.addCallback(lambda res: self.do_cli("mkdir", "--mutable-type=mdmf"))
20494+        d.addCallback(_check, "URI:DIR2-MDMF")
20495+        d.addCallback(_stash_dircap)
20496+        d.addCallback(lambda res: self.do_cli("ls", "--json",
20497+                                              self._filecap))
20498+        d.addCallback(_check, '"mutable-type": "mdmf"')
20499+        return d
20500+
20501+    def test_mkdir_bad_mutable_type(self):
20502+        o = cli.MakeDirectoryOptions()
20503+        self.failUnlessRaises(usage.UsageError,
20504+                              o.parseOptions,
20505+                              ["--mutable", "--mutable-type=ldmf"])
20506+
20507     def test_mkdir_unicode(self):
20508         self.basedir = os.path.dirname(self.mktemp())
20509         self.set_up_grid()
20510[mutable/layout: Add tighter bounds on the sizes of certain share components
20511Kevan Carstensen <kevan@isnotajoke.com>**20110727162855
20512 Ignore-this: a75cb54bcccb6ff78d1112c4c87eed1d
20513] {
20514hunk ./src/allmydata/mutable/layout.py 2
20515 
20516-import struct
20517+import struct, math
20518 from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError
20519 from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \
20520                                  MDMF_VERSION, IMutableSlotWriter
20521hunk ./src/allmydata/mutable/layout.py 547
20522 MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
20523 MDMFOFFSETS = ">QQQQQQQQ"
20524 MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
20525-# XXX Fix this.
20526-PRIVATE_KEY_SIZE = 2000
20527-SIGNATURE_SIZE = 10000
20528-VERIFICATION_KEY_SIZE = 2000
20529-# We know we won't ever have more than 256 shares.
20530-# XXX: This, too, can be
20531-SHARE_HASH_CHAIN_SIZE = HASH_SIZE * 256
20532+
20533+PRIVATE_KEY_SIZE = 1220
20534+SIGNATURE_SIZE = 260
20535+VERIFICATION_KEY_SIZE = 292
20536+# We know we won't have more than 256 shares, and we know that we won't
20537+# need to store more than lg 256 of them to validate, so that's our
20538+# bound. We add 1 to the int cast to round to the next integer.
20539+SHARE_HASH_CHAIN_SIZE = int(math.log(HASH_SIZE * 256)) + 1
20540 
20541 class MDMFSlotWriteProxy:
20542     implements(IMutableSlotWriter)
20543hunk ./src/allmydata/mutable/layout.py 1078
20544 
20545         self._offsets['verification_key_end'] = \
20546             self._offsets['verification_key'] + len(verification_key)
20547+        assert self._offsets['verification_key_end'] <= self._offsets['share_data']
20548         self._writevs.append(tuple([self._offsets['verification_key'],
20549                             verification_key]))
20550 
20551}
20552[test/test_mutable: write tests that see what happens when there are 255 shares, amend existing tests to also test for this condition.
20553Kevan Carstensen <kevan@isnotajoke.com>**20110727162955
20554 Ignore-this: 282ce3d8bffd41561b0820d4fd721448
20555] {
20556hunk ./src/allmydata/test/test_mutable.py 271
20557         d.addCallback(_created)
20558         return d
20559 
20560+    def test_max_shares(self):
20561+        self.nodemaker.default_encoding_parameters['n'] = 255
20562+        d = self.nodemaker.create_mutable_file(version=SDMF_VERSION)
20563+        def _created(n):
20564+            self.failUnless(isinstance(n, MutableFileNode))
20565+            self.failUnlessEqual(n.get_storage_index(), n._storage_index)
20566+            sb = self.nodemaker.storage_broker
20567+            num_shares = sum([len(self._storage._peers[x].keys()) for x \
20568+                              in sb.get_all_serverids()])
20569+            self.failUnlessEqual(num_shares, 255)
20570+            self._node = n
20571+            return n
20572+        d.addCallback(_created)
20573+        # Now we upload some contents
20574+        d.addCallback(lambda n:
20575+            n.overwrite(MutableData("contents" * 50000)))
20576+        # ...then download contents
20577+        d.addCallback(lambda ignored:
20578+            self._node.download_best_version())
20579+        # ...and check to make sure everything went okay.
20580+        d.addCallback(lambda contents:
20581+            self.failUnlessEqual("contents" * 50000, contents))
20582+        return d
20583+
20584+    def test_max_shares_mdmf(self):
20585+        # Test how files behave when there are 255 shares.
20586+        self.nodemaker.default_encoding_parameters['n'] = 255
20587+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
20588+        def _created(n):
20589+            self.failUnless(isinstance(n, MutableFileNode))
20590+            self.failUnlessEqual(n.get_storage_index(), n._storage_index)
20591+            sb = self.nodemaker.storage_broker
20592+            num_shares = sum([len(self._storage._peers[x].keys()) for x \
20593+                              in sb.get_all_serverids()])
20594+            self.failUnlessEqual(num_shares, 255)
20595+            self._node = n
20596+            return n
20597+        d.addCallback(_created)
20598+        d.addCallback(lambda n:
20599+            n.overwrite(MutableData("contents" * 50000)))
20600+        d.addCallback(lambda ignored:
20601+            self._node.download_best_version())
20602+        d.addCallback(lambda contents:
20603+            self.failUnlessEqual(contents, "contents" * 50000))
20604+        return d
20605 
20606     def test_mdmf_filenode_cap(self):
20607         # Test that an MDMF filenode, once created, returns an MDMF URI.
20608hunk ./src/allmydata/test/test_mutable.py 3250
20609             self.mdmf_node = n1
20610             self.sdmf_node = n2
20611         dl.addCallback(_then)
20612-        return dl
20613+        # Make SDMF and MDMF mutable file nodes that have 255 shares.
20614+        def _make_max_shares(ign):
20615+            self.nm.default_encoding_parameters['n'] = 255
20616+            self.nm.default_encoding_parameters['k'] = 127
20617+            d1 = self.nm.create_mutable_file(MutableData(self.data),
20618+                                             version=MDMF_VERSION)
20619+            d2 = \
20620+                self.nm.create_mutable_file(MutableData(self.small_data))
20621+            return gatherResults([d1, d2])
20622+        dl.addCallback(_make_max_shares)
20623+        def _stash((n1, n2)):
20624+            assert isinstance(n1, MutableFileNode)
20625+            assert isinstance(n2, MutableFileNode)
20626 
20627hunk ./src/allmydata/test/test_mutable.py 3264
20628+            self.mdmf_max_shares_node = n1
20629+            self.sdmf_max_shares_node = n2
20630+        dl.addCallback(_stash)
20631+        return dl
20632 
20633     def test_append(self):
20634         # We should be able to append data to the middle of a mutable
20635hunk ./src/allmydata/test/test_mutable.py 3273
20636         # file and get what we expect.
20637         new_data = self.data + "appended"
20638-        d = self.mdmf_node.get_best_mutable_version()
20639-        d.addCallback(lambda mv:
20640-            mv.update(MutableData("appended"), len(self.data)))
20641-        d.addCallback(lambda ignored:
20642-            self.mdmf_node.download_best_version())
20643-        d.addCallback(lambda results:
20644-            self.failUnlessEqual(results, new_data))
20645+        for node in (self.mdmf_node, self.mdmf_max_shares_node):
20646+            d = node.get_best_mutable_version()
20647+            d.addCallback(lambda mv:
20648+                mv.update(MutableData("appended"), len(self.data)))
20649+            d.addCallback(lambda ignored, node=node:
20650+                node.download_best_version())
20651+            d.addCallback(lambda results:
20652+                self.failUnlessEqual(results, new_data))
20653         return d
20654hunk ./src/allmydata/test/test_mutable.py 3282
20655-    test_append.timeout = 15
20656-
20657 
20658     def test_replace(self):
20659         # We should be able to replace data in the middle of a mutable
20660hunk ./src/allmydata/test/test_mutable.py 3289
20661         new_data = self.data[:100]
20662         new_data += "appended"
20663         new_data += self.data[108:]
20664-        d = self.mdmf_node.get_best_mutable_version()
20665-        d.addCallback(lambda mv:
20666-            mv.update(MutableData("appended"), 100))
20667-        d.addCallback(lambda ignored:
20668-            self.mdmf_node.download_best_version())
20669-        d.addCallback(lambda results:
20670-            self.failUnlessEqual(results, new_data))
20671+        for node in (self.mdmf_node, self.mdmf_max_shares_node):
20672+            d = node.get_best_mutable_version()
20673+            d.addCallback(lambda mv:
20674+                mv.update(MutableData("appended"), 100))
20675+            d.addCallback(lambda ignored, node=node:
20676+                node.download_best_version())
20677+            d.addCallback(lambda results:
20678+                self.failUnlessEqual(results, new_data))
20679         return d
20680 
20681     def test_replace_beginning(self):
20682hunk ./src/allmydata/test/test_mutable.py 3304
20683         # without truncating the file
20684         B = "beginning"
20685         new_data = B + self.data[len(B):]
20686-        d = self.mdmf_node.get_best_mutable_version()
20687-        d.addCallback(lambda mv: mv.update(MutableData(B), 0))
20688-        d.addCallback(lambda ignored: self.mdmf_node.download_best_version())
20689-        d.addCallback(lambda results: self.failUnlessEqual(results, new_data))
20690+        for node in (self.mdmf_node, self.mdmf_max_shares_node):
20691+            d = node.get_best_mutable_version()
20692+            d.addCallback(lambda mv: mv.update(MutableData(B), 0))
20693+            d.addCallback(lambda ignored, node=node:
20694+                node.download_best_version())
20695+            d.addCallback(lambda results: self.failUnlessEqual(results, new_data))
20696         return d
20697 
20698     def test_replace_segstart1(self):
20699hunk ./src/allmydata/test/test_mutable.py 3316
20700         offset = 128*1024+1
20701         new_data = "NNNN"
20702         expected = self.data[:offset]+new_data+self.data[offset+4:]
20703-        d = self.mdmf_node.get_best_mutable_version()
20704-        d.addCallback(lambda mv:
20705-            mv.update(MutableData(new_data), offset))
20706-        d.addCallback(lambda ignored:
20707-            self.mdmf_node.download_best_version())
20708-        def _check(results):
20709-            if results != expected:
20710-                print
20711-                print "got: %s ... %s" % (results[:20], results[-20:])
20712-                print "exp: %s ... %s" % (expected[:20], expected[-20:])
20713-                self.fail("results != expected")
20714-        d.addCallback(_check)
20715+        for node in (self.mdmf_node, self.mdmf_max_shares_node):
20716+            d = node.get_best_mutable_version()
20717+            d.addCallback(lambda mv:
20718+                mv.update(MutableData(new_data), offset))
20719+            # close around node.
20720+            d.addCallback(lambda ignored, node=node:
20721+                node.download_best_version())
20722+            def _check(results):
20723+                if results != expected:
20724+                    print
20725+                    print "got: %s ... %s" % (results[:20], results[-20:])
20726+                    print "exp: %s ... %s" % (expected[:20], expected[-20:])
20727+                    self.fail("results != expected")
20728+            d.addCallback(_check)
20729         return d
20730 
20731     def _check_differences(self, got, expected):
20732hunk ./src/allmydata/test/test_mutable.py 3386
20733             d.addCallback(self._check_differences, expected)
20734         return d
20735 
20736+    def test_replace_locations_max_shares(self):
20737+        # exercise fencepost conditions
20738+        expected = self.data
20739+        SEGSIZE = 128*1024
20740+        suspects = range(SEGSIZE-3, SEGSIZE+1)+range(2*SEGSIZE-3, 2*SEGSIZE+1)
20741+        letters = iter("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
20742+        d = defer.succeed(None)
20743+        for offset in suspects:
20744+            new_data = letters.next()*2 # "AA", then "BB", etc
20745+            expected = expected[:offset]+new_data+expected[offset+2:]
20746+            d.addCallback(lambda ign:
20747+                          self.mdmf_max_shares_node.get_best_mutable_version())
20748+            def _modify(mv, offset=offset, new_data=new_data):
20749+                # close over 'offset','new_data'
20750+                md = MutableData(new_data)
20751+                return mv.update(md, offset)
20752+            d.addCallback(_modify)
20753+            d.addCallback(lambda ignored:
20754+                          self.mdmf_max_shares_node.download_best_version())
20755+            d.addCallback(self._check_differences, expected)
20756+        return d
20757 
20758     def test_replace_and_extend(self):
20759         # We should be able to replace data in the middle of a mutable
20760hunk ./src/allmydata/test/test_mutable.py 3413
20761         # file and extend that mutable file and get what we expect.
20762         new_data = self.data[:100]
20763         new_data += "modified " * 100000
20764-        d = self.mdmf_node.get_best_mutable_version()
20765-        d.addCallback(lambda mv:
20766-            mv.update(MutableData("modified " * 100000), 100))
20767-        d.addCallback(lambda ignored:
20768-            self.mdmf_node.download_best_version())
20769-        d.addCallback(lambda results:
20770-            self.failUnlessEqual(results, new_data))
20771+        for node in (self.mdmf_node, self.mdmf_max_shares_node):
20772+            d = node.get_best_mutable_version()
20773+            d.addCallback(lambda mv:
20774+                mv.update(MutableData("modified " * 100000), 100))
20775+            d.addCallback(lambda ignored, node=node:
20776+                node.download_best_version())
20777+            d.addCallback(lambda results:
20778+                self.failUnlessEqual(results, new_data))
20779         return d
20780 
20781 
20782hunk ./src/allmydata/test/test_mutable.py 3435
20783         # power-of-two boundary.
20784         segment = "a" * DEFAULT_MAX_SEGMENT_SIZE
20785         new_data = self.data + (segment * 2)
20786-        d = self.mdmf_node.get_best_mutable_version()
20787-        d.addCallback(lambda mv:
20788-            mv.update(MutableData(segment * 2), len(self.data)))
20789-        d.addCallback(lambda ignored:
20790-            self.mdmf_node.download_best_version())
20791-        d.addCallback(lambda results:
20792-            self.failUnlessEqual(results, new_data))
20793+        for node in (self.mdmf_node, self.mdmf_max_shares_node):
20794+            d = node.get_best_mutable_version()
20795+            d.addCallback(lambda mv:
20796+                mv.update(MutableData(segment * 2), len(self.data)))
20797+            d.addCallback(lambda ignored, node=node:
20798+                node.download_best_version())
20799+            d.addCallback(lambda results:
20800+                self.failUnlessEqual(results, new_data))
20801         return d
20802     test_append_power_of_two.timeout = 15
20803 
20804hunk ./src/allmydata/test/test_mutable.py 3450
20805     def test_update_sdmf(self):
20806         # Running update on a single-segment file should still work.
20807         new_data = self.small_data + "appended"
20808-        d = self.sdmf_node.get_best_mutable_version()
20809-        d.addCallback(lambda mv:
20810-            mv.update(MutableData("appended"), len(self.small_data)))
20811-        d.addCallback(lambda ignored:
20812-            self.sdmf_node.download_best_version())
20813-        d.addCallback(lambda results:
20814-            self.failUnlessEqual(results, new_data))
20815+        for node in (self.sdmf_node, self.sdmf_max_shares_node):
20816+            d = node.get_best_mutable_version()
20817+            d.addCallback(lambda mv:
20818+                mv.update(MutableData("appended"), len(self.small_data)))
20819+            d.addCallback(lambda ignored, node=node:
20820+                node.download_best_version())
20821+            d.addCallback(lambda results:
20822+                self.failUnlessEqual(results, new_data))
20823         return d
20824 
20825     def test_replace_in_last_segment(self):
20826hunk ./src/allmydata/test/test_mutable.py 3467
20827         new_data = self.data[:replace_offset] + "replaced"
20828         rest_offset = replace_offset + len("replaced")
20829         new_data += self.data[rest_offset:]
20830-        d = self.mdmf_node.get_best_mutable_version()
20831-        d.addCallback(lambda mv:
20832-            mv.update(MutableData("replaced"), replace_offset))
20833-        d.addCallback(lambda ignored:
20834-            self.mdmf_node.download_best_version())
20835-        d.addCallback(lambda results:
20836-            self.failUnlessEqual(results, new_data))
20837+        for node in (self.mdmf_node, self.mdmf_max_shares_node):
20838+            d = node.get_best_mutable_version()
20839+            d.addCallback(lambda mv:
20840+                mv.update(MutableData("replaced"), replace_offset))
20841+            d.addCallback(lambda ignored, node=node:
20842+                node.download_best_version())
20843+            d.addCallback(lambda results:
20844+                self.failUnlessEqual(results, new_data))
20845         return d
20846 
20847 
20848hunk ./src/allmydata/test/test_mutable.py 3486
20849         new_data += "replaced"
20850         rest_offset = len(new_data)
20851         new_data += self.data[rest_offset:]
20852-        d = self.mdmf_node.get_best_mutable_version()
20853-        d.addCallback(lambda mv:
20854-            mv.update(MutableData((2 * new_segment) + "replaced"),
20855-                      replace_offset))
20856-        d.addCallback(lambda ignored:
20857-            self.mdmf_node.download_best_version())
20858-        d.addCallback(lambda results:
20859-            self.failUnlessEqual(results, new_data))
20860+        for node in (self.mdmf_node, self.mdmf_max_shares_node):
20861+            d = node.get_best_mutable_version()
20862+            d.addCallback(lambda mv:
20863+                mv.update(MutableData((2 * new_segment) + "replaced"),
20864+                          replace_offset))
20865+            d.addCallback(lambda ignored, node=node:
20866+                node.download_best_version())
20867+            d.addCallback(lambda results:
20868+                self.failUnlessEqual(results, new_data))
20869         return d
20870}
20871[test/test_mutable: add interoperability tests
20872Kevan Carstensen <kevan@isnotajoke.com>**20110728171601
20873 Ignore-this: e7afc73f26b87df32766f200d51c1069
20874] {
20875hunk ./src/allmydata/test/test_mutable.py 2
20876 
20877-import os, re
20878+import os, re, base64
20879 from cStringIO import StringIO
20880 from twisted.trial import unittest
20881 from twisted.internet import defer, reactor
20882hunk ./src/allmydata/test/test_mutable.py 10
20883 from zope.interface import implements
20884 from allmydata import uri, client
20885 from allmydata.nodemaker import NodeMaker
20886-from allmydata.util import base32, consumer
20887+from allmydata.util import base32, consumer, fileutil
20888 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
20889      ssk_pubkey_fingerprint_hash
20890 from allmydata.util.deferredutil import gatherResults
20891hunk ./src/allmydata/test/test_mutable.py 22
20892 from foolscap.api import eventually, fireEventually
20893 from foolscap.logging import log
20894 from allmydata.storage_client import StorageFarmBroker
20895+from allmydata.storage.common import storage_index_to_dir, si_b2a
20896 
20897 from allmydata.mutable.filenode import MutableFileNode, BackoffAgent
20898 from allmydata.mutable.common import ResponseCache, \
20899hunk ./src/allmydata/test/test_mutable.py 3497
20900             d.addCallback(lambda results:
20901                 self.failUnlessEqual(results, new_data))
20902         return d
20903+
20904+class Interoperability(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
20905+    sdmf_old_shares = {}
20906+    sdmf_old_shares[0] = "VGFob2UgbXV0YWJsZSBjb250YWluZXIgdjEKdQlEA47ESLbTdKdpLJXCpBxd5OH239tl5hvAiz1dvGdE5rIOpf8cbfxbPcwNF+Y5dM92uBVbmV6KAAAAAAAAB/wAAAAAAAAJ0AAAAAFOWSw7jSx7WXzaMpdleJYXwYsRCV82jNA5oex9m2YhXSnb2POh+vvC1LE1NAfRc9GOb2zQG84Xdsx1Jub2brEeKkyt0sRIttN0p2kslcKkHF3k4fbf22XmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABamJprL6ecrsOoFKdrXUmWveLq8nzEGDOjFnyK9detI3noX3uyK2MwSnFdAfyN0tuAwoAAAAAAAAAFQAAAAAAAAAVAAABjwAAAo8AAAMXAAADNwAAAAAAAAM+AAAAAAAAB/wwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC1IkainlJF12IBXBQdpRK1zXB7a26vuEYqRmQM09YjC6sQjCs0F2ICk8n9m/2Kw4l16eIEboB2Au9pODCE+u/dEAakEFh4qidTMn61rbGUbsLK8xzuWNW22ezzz9/nPia0HDrulXt51/FYtfnnAuD1RJGXJv/8tDllE9FL/18TzlH4WuB6Fp8FTgv7QdbZAfWJHDGFIpVCJr1XxOCsSZNFJIqGwZnD2lsChiWw5OJDbKd8otqN1hIbfHyMyfMOJ/BzRzvZXaUt4Dv5nf93EmQDWClxShRwpuX/NkZ5B2K9OFonFTbOCexm/MjMAdCBqebKKaiHFkiknUCn9eJQpZ5bAgERgV50VKj+AVTDfgTpqfO2vfo4wrufi6ZBb8QV7hllhUFBjYogQ9C96dnS7skv0s+cqFuUjwMILr5/rsbEmEMGvl0T0ytyAbtlXuowEFVj/YORNknM4yjY72YUtEPTlMpk0Cis7aIgTvu5qWMPER26PMApZuRqiwRsGIkaJIvOVOTHHjFYe3/YzdMkc7OZtqRMfQLtwVl2/zKQQV8b/a9vaT6q3mRLRd4P3esaAFe/+7sR/t+9tmB+a8kxtKM6kmaVQJMbXJZ4aoHGfeLX0m35Rcvu2Bmph7QfSDjk/eaE3q55zYSoGWShmlhlw4Kwg84sMuhmcVhLvo0LovR8bKmbdgACtTh7+7gs/l5w1lOkgbF6w7rkXLNslK7L2KYF4SPFLUcABOOLy8EETxh7h7/z9d62EiPu9CNpRrCOLxUhn+JUS+DuAAhgcAb/adrQFrhlrRNoRpvjDuxmFebA4F0qCyqWssm61AAQ/EX4eC/1+hGOQ/h4EiKUkqxdsfzdcPlDvd11SGWZ0VHsUclZChTzuBAU2zLTXm+cG8IFhO50ly6Ey/DB44NtMKVaVzO0nU8DE0Wua7Lx6Bnad5n91qmHAnwSEJE5YIhQM634omd6cq9Wk4seJCUIn+ucoknrpxp0IR9QMxpKSMRHRUg2K8ZegnY3YqFunRZKCfsq9ufQEKgjZN12AFqi551KPBdn4/3V5HK6xTv0P4robSsE/BvuIfByvRf/W7ZrDx+CFC4EEcsBOACOZCrkhhqd5TkYKbe9RA+vs56+9N5qZGurkxcoKviiyEncxvTuShD65DK/6x6kMDMgQv/EdZDI3x9GtHTnRBYXwDGnPJ19w+q2zC3e2XarbxTGYQIPEC5mYx0gAA0sbjf018NGfwBhl6SB54iGsa8uLvR3jHv6OSRJgwxL6j7P0Ts4Hv2EtO12P0Lv21pwi3JC1O/WviSrKCvrQD5lMHL9Uym3hwFi2zu0mqwZvxOAbGy7kfOPXkLYKOHTZLthzKj3PsdjeceWBfYIvPGKYcd6wDr36d1aXSYS4IWeApTS2AQ2lu0DUcgSefAvsA8NkgOklvJY1cjTMSg6j6cxQo48Bvl8RAWGLbr4h2S/8KwDGxwLsSv0Gop/gnFc3GzCsmL0EkEyHHWkCA8YRXCghfW80KLDV495ff7yF5oiwK56GniqowZ3RG9Jxp5MXoJQgsLV1VMQFMAmsY69yz8eoxRH3wl9L0dMyndLulhWWzNwPMQ2I0yAWdzA/pksVmwTJTFenB3MHCiWc5rEwJ3yofe6NZZnZQrYyL9r1TNnVwfTwRUiykPiLSk4x9Mi6DX7RamDAxc8u3gDVfjPsTOTagBOEGUWlGAL54KE/E6sgCQ5DEAt12chk8AxbjBFLPgV+/idrzS0lZHOL+IVBI9D0i3Bq1yZcSIqcjZB0M3IbxbPm4gLAYOWEiTUN2ecsEHHg9nt6rhgffVoqSbCCFPbpC0xf7WOC3+BQORIZECOCC7cUAciXq3xn+GuxpFE40RWRJeKAK7bBQ21X89ABIXlQFkFddZ9kRvlZ2Pnl0oeF+2pjnZu0Yc2czNfZEQF2P7BKIdLrgMgxG89snxAY8qAYTCKyQw6xTG87wkjDcpy1wzsZLP3WsOuO7cAm7b27xU0jRKq8Cw4d1hDoyRG+RdS53F8RFJzVMaNNYgxU2tfRwUvXpTRXiOheeRVvh25+YGVnjakUXjx/dSDnOw4ETHGHD+7styDkeSfc3BdSZxswzc6OehgMI+xsCxeeRym15QUm9hxvg8X7Bfz/0WulgFwgzrm11TVynZYOmvyHpiZKoqQyQyKahIrfhwuchCr7lMsZ4a+umIkNkKxCLZnI+T7jd+eGFMgKItjz3kTTxRl3IhaJG3LbPmwRUJynMxQKdMi4Uf0qy0U7+i8hIJ9m50QXc+3tw2bwDSbx22XYJ9Wf14gxx5G5SPTb1JVCbhe4fxNt91xIxCow2zk62tzbYfRe6dfmDmgYHkv2PIEtMJZK8iKLDjFfu2ZUxsKT2A5g1q17og6o9MeXeuFS3mzJXJYFQZd+3UzlFR9qwkFkby9mg5y4XSeMvRLOHPt/H/r5SpEqBE6a9MadZYt61FBV152CUEzd43ihXtrAa0XH9HdsiySBcWI1SpM3mv9rRP0DiLjMUzHw/K1D8TE2f07zW4t/9kvE11tFj/NpICixQAAAAA="
20907+    sdmf_old_shares[1] = "VGFob2UgbXV0YWJsZSBjb250YWluZXIgdjEKdQlEA47ESLbTdKdpLJXCpBxd5OH239tl5hvAiz1dvGdE5rIOpf8cbfxbPcwNF+Y5dM92uBVbmV6KAAAAAAAAB/wAAAAAAAAJ0AAAAAFOWSw7jSx7WXzaMpdleJYXwYsRCV82jNA5oex9m2YhXSnb2POh+vvC1LE1NAfRc9GOb2zQG84Xdsx1Jub2brEeKkyt0sRIttN0p2kslcKkHF3k4fbf22XmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABamJprL6ecrsOoFKdrXUmWveLq8nzEGDOjFnyK9detI3noX3uyK2MwSnFdAfyN0tuAwoAAAAAAAAAFQAAAAAAAAAVAAABjwAAAo8AAAMXAAADNwAAAAAAAAM+AAAAAAAAB/wwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC1IkainlJF12IBXBQdpRK1zXB7a26vuEYqRmQM09YjC6sQjCs0F2ICk8n9m/2Kw4l16eIEboB2Au9pODCE+u/dEAakEFh4qidTMn61rbGUbsLK8xzuWNW22ezzz9/nPia0HDrulXt51/FYtfnnAuD1RJGXJv/8tDllE9FL/18TzlH4WuB6Fp8FTgv7QdbZAfWJHDGFIpVCJr1XxOCsSZNFJIqGwZnD2lsChiWw5OJDbKd8otqN1hIbfHyMyfMOJ/BzRzvZXaUt4Dv5nf93EmQDWClxShRwpuX/NkZ5B2K9OFonFTbOCexm/MjMAdCBqebKKaiHFkiknUCn9eJQpZ5bAgERgV50VKj+AVTDfgTpqfO2vfo4wrufi6ZBb8QV7hllhUFBjYogQ9C96dnS7skv0s+cqFuUjwMILr5/rsbEmEMGvl0T0ytyAbtlXuowEFVj/YORNknM4yjY72YUtEPTlMpk0Cis7aIgTvu5qWMPER26PMApZuRqiwRsGIkaJIvOVOTHHjFYe3/YzdMkc7OZtqRMfQLtwVl2/zKQQV8b/a9vaT6q3mRLRd4P3esaAFe/+7sR/t+9tmB+a8kxtKM6kmaVQJMbXJZ4aoHGfeLX0m35Rcvu2Bmph7QfSDjk/eaE3q55zYSoGWShmlhlw4Kwg84sMuhmcVhLvo0LovR8bKmbdgACtTh7+7gs/l5w1lOkgbF6w7rkXLNslK7L2KYF4SPFLUcABOOLy8EETxh7h7/z9d62EiPu9CNpRrCOLxUhn+JUS+DuAAhgcAb/adrQFrhlrRNoRpvjDuxmFebA4F0qCyqWssm61AAP7FHJWQoU87gQFNsy015vnBvCBYTudJcuhMvwweODbTD8Rfh4L/X6EY5D+HgSIpSSrF2x/N1w+UO93XVIZZnRUeePDXEwhqYDE0Wua7Lx6Bnad5n91qmHAnwSEJE5YIhQM634omd6cq9Wk4seJCUIn+ucoknrpxp0IR9QMxpKSMRHRUg2K8ZegnY3YqFunRZKCfsq9ufQEKgjZN12AFqi551KPBdn4/3V5HK6xTv0P4robSsE/BvuIfByvRf/W7ZrDx+CFC4EEcsBOACOZCrkhhqd5TkYKbe9RA+vs56+9N5qZGurkxcoKviiyEncxvTuShD65DK/6x6kMDMgQv/EdZDI3x9GtHTnRBYXwDGnPJ19w+q2zC3e2XarbxTGYQIPEC5mYx0gAA0sbjf018NGfwBhl6SB54iGsa8uLvR3jHv6OSRJgwxL6j7P0Ts4Hv2EtO12P0Lv21pwi3JC1O/WviSrKCvrQD5lMHL9Uym3hwFi2zu0mqwZvxOAbGy7kfOPXkLYKOHTZLthzKj3PsdjeceWBfYIvPGKYcd6wDr36d1aXSYS4IWeApTS2AQ2lu0DUcgSefAvsA8NkgOklvJY1cjTMSg6j6cxQo48Bvl8RAWGLbr4h2S/8KwDGxwLsSv0Gop/gnFc3GzCsmL0EkEyHHWkCA8YRXCghfW80KLDV495ff7yF5oiwK56GniqowZ3RG9Jxp5MXoJQgsLV1VMQFMAmsY69yz8eoxRH3wl9L0dMyndLulhWWzNwPMQ2I0yAWdzA/pksVmwTJTFenB3MHCiWc5rEwJ3yofe6NZZnZQrYyL9r1TNnVwfTwRUiykPiLSk4x9Mi6DX7RamDAxc8u3gDVfjPsTOTagBOEGUWlGAL54KE/E6sgCQ5DEAt12chk8AxbjBFLPgV+/idrzS0lZHOL+IVBI9D0i3Bq1yZcSIqcjZB0M3IbxbPm4gLAYOWEiTUN2ecsEHHg9nt6rhgffVoqSbCCFPbpC0xf7WOC3+BQORIZECOCC7cUAciXq3xn+GuxpFE40RWRJeKAK7bBQ21X89ABIXlQFkFddZ9kRvlZ2Pnl0oeF+2pjnZu0Yc2czNfZEQF2P7BKIdLrgMgxG89snxAY8qAYTCKyQw6xTG87wkjDcpy1wzsZLP3WsOuO7cAm7b27xU0jRKq8Cw4d1hDoyRG+RdS53F8RFJzVMaNNYgxU2tfRwUvXpTRXiOheeRVvh25+YGVnjakUXjx/dSDnOw4ETHGHD+7styDkeSfc3BdSZxswzc6OehgMI+xsCxeeRym15QUm9hxvg8X7Bfz/0WulgFwgzrm11TVynZYOmvyHpiZKoqQyQyKahIrfhwuchCr7lMsZ4a+umIkNkKxCLZnI+T7jd+eGFMgKItjz3kTTxRl3IhaJG3LbPmwRUJynMxQKdMi4Uf0qy0U7+i8hIJ9m50QXc+3tw2bwDSbx22XYJ9Wf14gxx5G5SPTb1JVCbhe4fxNt91xIxCow2zk62tzbYfRe6dfmDmgYHkv2PIEtMJZK8iKLDjFfu2ZUxsKT2A5g1q17og6o9MeXeuFS3mzJXJYFQZd+3UzlFR9qwkFkby9mg5y4XSeMvRLOHPt/H/r5SpEqBE6a9MadZYt61FBV152CUEzd43ihXtrAa0XH9HdsiySBcWI1SpM3mv9rRP0DiLjMUzHw/K1D8TE2f07zW4t/9kvE11tFj/NpICixQAAAAA="
20908+    sdmf_old_shares[2] = "VGFob2UgbXV0YWJsZSBjb250YWluZXIgdjEKdQlEA47ESLbTdKdpLJXCpBxd5OH239tl5hvAiz1dvGdE5rIOpf8cbfxbPcwNF+Y5dM92uBVbmV6KAAAAAAAAB/wAAAAAAAAJ0AAAAAFOWSw7jSx7WXzaMpdleJYXwYsRCV82jNA5oex9m2YhXSnb2POh+vvC1LE1NAfRc9GOb2zQG84Xdsx1Jub2brEeKkyt0sRIttN0p2kslcKkHF3k4fbf22XmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABamJprL6ecrsOoFKdrXUmWveLq8nzEGDOjFnyK9detI3noX3uyK2MwSnFdAfyN0tuAwoAAAAAAAAAFQAAAAAAAAAVAAABjwAAAo8AAAMXAAADNwAAAAAAAAM+AAAAAAAAB/wwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC1IkainlJF12IBXBQdpRK1zXB7a26vuEYqRmQM09YjC6sQjCs0F2ICk8n9m/2Kw4l16eIEboB2Au9pODCE+u/dEAakEFh4qidTMn61rbGUbsLK8xzuWNW22ezzz9/nPia0HDrulXt51/FYtfnnAuD1RJGXJv/8tDllE9FL/18TzlH4WuB6Fp8FTgv7QdbZAfWJHDGFIpVCJr1XxOCsSZNFJIqGwZnD2lsChiWw5OJDbKd8otqN1hIbfHyMyfMOJ/BzRzvZXaUt4Dv5nf93EmQDWClxShRwpuX/NkZ5B2K9OFonFTbOCexm/MjMAdCBqebKKaiHFkiknUCn9eJQpZ5bAgERgV50VKj+AVTDfgTpqfO2vfo4wrufi6ZBb8QV7hllhUFBjYogQ9C96dnS7skv0s+cqFuUjwMILr5/rsbEmEMGvl0T0ytyAbtlXuowEFVj/YORNknM4yjY72YUtEPTlMpk0Cis7aIgTvu5qWMPER26PMApZuRqiwRsGIkaJIvOVOTHHjFYe3/YzdMkc7OZtqRMfQLtwVl2/zKQQV8b/a9vaT6q3mRLRd4P3esaAFe/+7sR/t+9tmB+a8kxtKM6kmaVQJMbXJZ4aoHGfeLX0m35Rcvu2Bmph7QfSDjk/eaE3q55zYSoGWShmlhlw4Kwg84sMuhmcVhLvo0LovR8bKmbdgACtTh7+7gs/l5w1lOkgbF6w7rkXLNslK7L2KYF4SPFLUcABOOLy8EETxh7h7/z9d62EiPu9CNpRrCOLxUhn+JUS+DuAAd8jdiCodW233N1acXhZGnulDKR3hiNsMdEIsijRPemewASoSCFpVj4utEE+eVFM146xfgC6DX39GaQ2zT3YKsWX3GiLwKtGffwqV7IlZIcBEVqMfTXSTZsY+dZm1MxxCZH0Zd33VY0yggDE0Wua7Lx6Bnad5n91qmHAnwSEJE5YIhQM634omd6cq9Wk4seJCUIn+ucoknrpxp0IR9QMxpKSMRHRUg2K8ZegnY3YqFunRZKCfsq9ufQEKgjZN12AFqi551KPBdn4/3V5HK6xTv0P4robSsE/BvuIfByvRf/W7ZrDx+CFC4EEcsBOACOZCrkhhqd5TkYKbe9RA+vs56+9N5qZGurkxcoKviiyEncxvTuShD65DK/6x6kMDMgQv/EdZDI3x9GtHTnRBYXwDGnPJ19w+q2zC3e2XarbxTGYQIPEC5mYx0gAA0sbjf018NGfwBhl6SB54iGsa8uLvR3jHv6OSRJgwxL6j7P0Ts4Hv2EtO12P0Lv21pwi3JC1O/WviSrKCvrQD5lMHL9Uym3hwFi2zu0mqwZvxOAbGy7kfOPXkLYKOHTZLthzKj3PsdjeceWBfYIvPGKYcd6wDr36d1aXSYS4IWeApTS2AQ2lu0DUcgSefAvsA8NkgOklvJY1cjTMSg6j6cxQo48Bvl8RAWGLbr4h2S/8KwDGxwLsSv0Gop/gnFc3GzCsmL0EkEyHHWkCA8YRXCghfW80KLDV495ff7yF5oiwK56GniqowZ3RG9Jxp5MXoJQgsLV1VMQFMAmsY69yz8eoxRH3wl9L0dMyndLulhWWzNwPMQ2I0yAWdzA/pksVmwTJTFenB3MHCiWc5rEwJ3yofe6NZZnZQrYyL9r1TNnVwfTwRUiykPiLSk4x9Mi6DX7RamDAxc8u3gDVfjPsTOTagBOEGUWlGAL54KE/E6sgCQ5DEAt12chk8AxbjBFLPgV+/idrzS0lZHOL+IVBI9D0i3Bq1yZcSIqcjZB0M3IbxbPm4gLAYOWEiTUN2ecsEHHg9nt6rhgffVoqSbCCFPbpC0xf7WOC3+BQORIZECOCC7cUAciXq3xn+GuxpFE40RWRJeKAK7bBQ21X89ABIXlQFkFddZ9kRvlZ2Pnl0oeF+2pjnZu0Yc2czNfZEQF2P7BKIdLrgMgxG89snxAY8qAYTCKyQw6xTG87wkjDcpy1wzsZLP3WsOuO7cAm7b27xU0jRKq8Cw4d1hDoyRG+RdS53F8RFJzVMaNNYgxU2tfRwUvXpTRXiOheeRVvh25+YGVnjakUXjx/dSDnOw4ETHGHD+7styDkeSfc3BdSZxswzc6OehgMI+xsCxeeRym15QUm9hxvg8X7Bfz/0WulgFwgzrm11TVynZYOmvyHpiZKoqQyQyKahIrfhwuchCr7lMsZ4a+umIkNkKxCLZnI+T7jd+eGFMgKItjz3kTTxRl3IhaJG3LbPmwRUJynMxQKdMi4Uf0qy0U7+i8hIJ9m50QXc+3tw2bwDSbx22XYJ9Wf14gxx5G5SPTb1JVCbhe4fxNt91xIxCow2zk62tzbYfRe6dfmDmgYHkv2PIEtMJZK8iKLDjFfu2ZUxsKT2A5g1q17og6o9MeXeuFS3mzJXJYFQZd+3UzlFR9qwkFkby9mg5y4XSeMvRLOHPt/H/r5SpEqBE6a9MadZYt61FBV152CUEzd43ihXtrAa0XH9HdsiySBcWI1SpM3mv9rRP0DiLjMUzHw/K1D8TE2f07zW4t/9kvE11tFj/NpICixQAAAAA="
20909+    sdmf_old_shares[3] = "VGFob2UgbXV0YWJsZSBjb250YWluZXIgdjEKdQlEA47ESLbTdKdpLJXCpBxd5OH239tl5hvAiz1dvGdE5rIOpf8cbfxbPcwNF+Y5dM92uBVbmV6KAAAAAAAAB/wAAAAAAAAJ0AAAAAFOWSw7jSx7WXzaMpdleJYXwYsRCV82jNA5oex9m2YhXSnb2POh+vvC1LE1NAfRc9GOb2zQG84Xdsx1Jub2brEeKkyt0sRIttN0p2kslcKkHF3k4fbf22XmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABamJprL6ecrsOoFKdrXUmWveLq8nzEGDOjFnyK9detI3noX3uyK2MwSnFdAfyN0tuAwoAAAAAAAAAFQAAAAAAAAAVAAABjwAAAo8AAAMXAAADNwAAAAAAAAM+AAAAAAAAB/wwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC1IkainlJF12IBXBQdpRK1zXB7a26vuEYqRmQM09YjC6sQjCs0F2ICk8n9m/2Kw4l16eIEboB2Au9pODCE+u/dEAakEFh4qidTMn61rbGUbsLK8xzuWNW22ezzz9/nPia0HDrulXt51/FYtfnnAuD1RJGXJv/8tDllE9FL/18TzlH4WuB6Fp8FTgv7QdbZAfWJHDGFIpVCJr1XxOCsSZNFJIqGwZnD2lsChiWw5OJDbKd8otqN1hIbfHyMyfMOJ/BzRzvZXaUt4Dv5nf93EmQDWClxShRwpuX/NkZ5B2K9OFonFTbOCexm/MjMAdCBqebKKaiHFkiknUCn9eJQpZ5bAgERgV50VKj+AVTDfgTpqfO2vfo4wrufi6ZBb8QV7hllhUFBjYogQ9C96dnS7skv0s+cqFuUjwMILr5/rsbEmEMGvl0T0ytyAbtlXuowEFVj/YORNknM4yjY72YUtEPTlMpk0Cis7aIgTvu5qWMPER26PMApZuRqiwRsGIkaJIvOVOTHHjFYe3/YzdMkc7OZtqRMfQLtwVl2/zKQQV8b/a9vaT6q3mRLRd4P3esaAFe/+7sR/t+9tmB+a8kxtKM6kmaVQJMbXJZ4aoHGfeLX0m35Rcvu2Bmph7QfSDjk/eaE3q55zYSoGWShmlhlw4Kwg84sMuhmcVhLvo0LovR8bKmbdgACtTh7+7gs/l5w1lOkgbF6w7rkXLNslK7L2KYF4SPFLUcABOOLy8EETxh7h7/z9d62EiPu9CNpRrCOLxUhn+JUS+DuAAd8jdiCodW233N1acXhZGnulDKR3hiNsMdEIsijRPemewARoi8CrRn38KleyJWSHARFajH010k2bGPnWZtTMcQmR9GhIIWlWPi60QT55UUzXjrF+ALoNff0ZpDbNPdgqxZfcSNSplrHqtsDE0Wua7Lx6Bnad5n91qmHAnwSEJE5YIhQM634omd6cq9Wk4seJCUIn+ucoknrpxp0IR9QMxpKSMRHRUg2K8ZegnY3YqFunRZKCfsq9ufQEKgjZN12AFqi551KPBdn4/3V5HK6xTv0P4robSsE/BvuIfByvRf/W7ZrDx+CFC4EEcsBOACOZCrkhhqd5TkYKbe9RA+vs56+9N5qZGurkxcoKviiyEncxvTuShD65DK/6x6kMDMgQv/EdZDI3x9GtHTnRBYXwDGnPJ19w+q2zC3e2XarbxTGYQIPEC5mYx0gAA0sbjf018NGfwBhl6SB54iGsa8uLvR3jHv6OSRJgwxL6j7P0Ts4Hv2EtO12P0Lv21pwi3JC1O/WviSrKCvrQD5lMHL9Uym3hwFi2zu0mqwZvxOAbGy7kfOPXkLYKOHTZLthzKj3PsdjeceWBfYIvPGKYcd6wDr36d1aXSYS4IWeApTS2AQ2lu0DUcgSefAvsA8NkgOklvJY1cjTMSg6j6cxQo48Bvl8RAWGLbr4h2S/8KwDGxwLsSv0Gop/gnFc3GzCsmL0EkEyHHWkCA8YRXCghfW80KLDV495ff7yF5oiwK56GniqowZ3RG9Jxp5MXoJQgsLV1VMQFMAmsY69yz8eoxRH3wl9L0dMyndLulhWWzNwPMQ2I0yAWdzA/pksVmwTJTFenB3MHCiWc5rEwJ3yofe6NZZnZQrYyL9r1TNnVwfTwRUiykPiLSk4x9Mi6DX7RamDAxc8u3gDVfjPsTOTagBOEGUWlGAL54KE/E6sgCQ5DEAt12chk8AxbjBFLPgV+/idrzS0lZHOL+IVBI9D0i3Bq1yZcSIqcjZB0M3IbxbPm4gLAYOWEiTUN2ecsEHHg9nt6rhgffVoqSbCCFPbpC0xf7WOC3+BQORIZECOCC7cUAciXq3xn+GuxpFE40RWRJeKAK7bBQ21X89ABIXlQFkFddZ9kRvlZ2Pnl0oeF+2pjnZu0Yc2czNfZEQF2P7BKIdLrgMgxG89snxAY8qAYTCKyQw6xTG87wkjDcpy1wzsZLP3WsOuO7cAm7b27xU0jRKq8Cw4d1hDoyRG+RdS53F8RFJzVMaNNYgxU2tfRwUvXpTRXiOheeRVvh25+YGVnjakUXjx/dSDnOw4ETHGHD+7styDkeSfc3BdSZxswzc6OehgMI+xsCxeeRym15QUm9hxvg8X7Bfz/0WulgFwgzrm11TVynZYOmvyHpiZKoqQyQyKahIrfhwuchCr7lMsZ4a+umIkNkKxCLZnI+T7jd+eGFMgKItjz3kTTxRl3IhaJG3LbPmwRUJynMxQKdMi4Uf0qy0U7+i8hIJ9m50QXc+3tw2bwDSbx22XYJ9Wf14gxx5G5SPTb1JVCbhe4fxNt91xIxCow2zk62tzbYfRe6dfmDmgYHkv2PIEtMJZK8iKLDjFfu2ZUxsKT2A5g1q17og6o9MeXeuFS3mzJXJYFQZd+3UzlFR9qwkFkby9mg5y4XSeMvRLOHPt/H/r5SpEqBE6a9MadZYt61FBV152CUEzd43ihXtrAa0XH9HdsiySBcWI1SpM3mv9rRP0DiLjMUzHw/K1D8TE2f07zW4t/9kvE11tFj/NpICixQAAAAA="
20910+    sdmf_old_shares[4] = "VGFob2UgbXV0YWJsZSBjb250YWluZXIgdjEKdQlEA47ESLbTdKdpLJXCpBxd5OH239tl5hvAiz1dvGdE5rIOpf8cbfxbPcwNF+Y5dM92uBVbmV6KAAAAAAAAB/wAAAAAAAAJ0AAAAAFOWSw7jSx7WXzaMpdleJYXwYsRCV82jNA5oex9m2YhXSnb2POh+vvC1LE1NAfRc9GOb2zQG84Xdsx1Jub2brEeKkyt0sRIttN0p2kslcKkHF3k4fbf22XmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABamJprL6ecrsOoFKdrXUmWveLq8nzEGDOjFnyK9detI3noX3uyK2MwSnFdAfyN0tuAwoAAAAAAAAAFQAAAAAAAAAVAAABjwAAAo8AAAMXAAADNwAAAAAAAAM+AAAAAAAAB/wwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC1IkainlJF12IBXBQdpRK1zXB7a26vuEYqRmQM09YjC6sQjCs0F2ICk8n9m/2Kw4l16eIEboB2Au9pODCE+u/dEAakEFh4qidTMn61rbGUbsLK8xzuWNW22ezzz9/nPia0HDrulXt51/FYtfnnAuD1RJGXJv/8tDllE9FL/18TzlH4WuB6Fp8FTgv7QdbZAfWJHDGFIpVCJr1XxOCsSZNFJIqGwZnD2lsChiWw5OJDbKd8otqN1hIbfHyMyfMOJ/BzRzvZXaUt4Dv5nf93EmQDWClxShRwpuX/NkZ5B2K9OFonFTbOCexm/MjMAdCBqebKKaiHFkiknUCn9eJQpZ5bAgERgV50VKj+AVTDfgTpqfO2vfo4wrufi6ZBb8QV7hllhUFBjYogQ9C96dnS7skv0s+cqFuUjwMILr5/rsbEmEMGvl0T0ytyAbtlXuowEFVj/YORNknM4yjY72YUtEPTlMpk0Cis7aIgTvu5qWMPER26PMApZuRqiwRsGIkaJIvOVOTHHjFYe3/YzdMkc7OZtqRMfQLtwVl2/zKQQV8b/a9vaT6q3mRLRd4P3esaAFe/+7sR/t+9tmB+a8kxtKM6kmaVQJMbXJZ4aoHGfeLX0m35Rcvu2Bmph7QfSDjk/eaE3q55zYSoGWShmlhlw4Kwg84sMuhmcVhLvo0LovR8bKmbdgACtTh7+7gs/l5w1lOkgbF6w7rkXLNslK7L2KYF4SPFLUcAA6dlE140Fc7FgB77PeM5Phv+bypQEYtyfLQHxd+OxlG3AAoIM8M4XulprmLd4gGMobS2Bv9CmwB5LpK/ySHE1QWjdwAUMA7/aVz7Mb1em0eks+biC8ZuVUhuAEkTVOAF4YulIjE8JlfW0dS1XKk62u0586QxiN38NTsluUDx8EAPTL66yRsfb1f3rRIDE0Wua7Lx6Bnad5n91qmHAnwSEJE5YIhQM634omd6cq9Wk4seJCUIn+ucoknrpxp0IR9QMxpKSMRHRUg2K8ZegnY3YqFunRZKCfsq9ufQEKgjZN12AFqi551KPBdn4/3V5HK6xTv0P4robSsE/BvuIfByvRf/W7ZrDx+CFC4EEcsBOACOZCrkhhqd5TkYKbe9RA+vs56+9N5qZGurkxcoKviiyEncxvTuShD65DK/6x6kMDMgQv/EdZDI3x9GtHTnRBYXwDGnPJ19w+q2zC3e2XarbxTGYQIPEC5mYx0gAA0sbjf018NGfwBhl6SB54iGsa8uLvR3jHv6OSRJgwxL6j7P0Ts4Hv2EtO12P0Lv21pwi3JC1O/WviSrKCvrQD5lMHL9Uym3hwFi2zu0mqwZvxOAbGy7kfOPXkLYKOHTZLthzKj3PsdjeceWBfYIvPGKYcd6wDr36d1aXSYS4IWeApTS2AQ2lu0DUcgSefAvsA8NkgOklvJY1cjTMSg6j6cxQo48Bvl8RAWGLbr4h2S/8KwDGxwLsSv0Gop/gnFc3GzCsmL0EkEyHHWkCA8YRXCghfW80KLDV495ff7yF5oiwK56GniqowZ3RG9Jxp5MXoJQgsLV1VMQFMAmsY69yz8eoxRH3wl9L0dMyndLulhWWzNwPMQ2I0yAWdzA/pksVmwTJTFenB3MHCiWc5rEwJ3yofe6NZZnZQrYyL9r1TNnVwfTwRUiykPiLSk4x9Mi6DX7RamDAxc8u3gDVfjPsTOTagBOEGUWlGAL54KE/E6sgCQ5DEAt12chk8AxbjBFLPgV+/idrzS0lZHOL+IVBI9D0i3Bq1yZcSIqcjZB0M3IbxbPm4gLAYOWEiTUN2ecsEHHg9nt6rhgffVoqSbCCFPbpC0xf7WOC3+BQORIZECOCC7cUAciXq3xn+GuxpFE40RWRJeKAK7bBQ21X89ABIXlQFkFddZ9kRvlZ2Pnl0oeF+2pjnZu0Yc2czNfZEQF2P7BKIdLrgMgxG89snxAY8qAYTCKyQw6xTG87wkjDcpy1wzsZLP3WsOuO7cAm7b27xU0jRKq8Cw4d1hDoyRG+RdS53F8RFJzVMaNNYgxU2tfRwUvXpTRXiOheeRVvh25+YGVnjakUXjx/dSDnOw4ETHGHD+7styDkeSfc3BdSZxswzc6OehgMI+xsCxeeRym15QUm9hxvg8X7Bfz/0WulgFwgzrm11TVynZYOmvyHpiZKoqQyQyKahIrfhwuchCr7lMsZ4a+umIkNkKxCLZnI+T7jd+eGFMgKItjz3kTTxRl3IhaJG3LbPmwRUJynMxQKdMi4Uf0qy0U7+i8hIJ9m50QXc+3tw2bwDSbx22XYJ9Wf14gxx5G5SPTb1JVCbhe4fxNt91xIxCow2zk62tzbYfRe6dfmDmgYHkv2PIEtMJZK8iKLDjFfu2ZUxsKT2A5g1q17og6o9MeXeuFS3mzJXJYFQZd+3UzlFR9qwkFkby9mg5y4XSeMvRLOHPt/H/r5SpEqBE6a9MadZYt61FBV152CUEzd43ihXtrAa0XH9HdsiySBcWI1SpM3mv9rRP0DiLjMUzHw/K1D8TE2f07zW4t/9kvE11tFj/NpICixQAAAAA="
20911+    sdmf_old_shares[5] = "VGFob2UgbXV0YWJsZSBjb250YWluZXIgdjEKdQlEA47ESLbTdKdpLJXCpBxd5OH239tl5hvAiz1dvGdE5rIOpf8cbfxbPcwNF+Y5dM92uBVbmV6KAAAAAAAAB/wAAAAAAAAJ0AAAAAFOWSw7jSx7WXzaMpdleJYXwYsRCV82jNA5oex9m2YhXSnb2POh+vvC1LE1NAfRc9GOb2zQG84Xdsx1Jub2brEeKkyt0sRIttN0p2kslcKkHF3k4fbf22XmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABamJprL6ecrsOoFKdrXUmWveLq8nzEGDOjFnyK9detI3noX3uyK2MwSnFdAfyN0tuAwoAAAAAAAAAFQAAAAAAAAAVAAABjwAAAo8AAAMXAAADNwAAAAAAAAM+AAAAAAAAB/wwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC1IkainlJF12IBXBQdpRK1zXB7a26vuEYqRmQM09YjC6sQjCs0F2ICk8n9m/2Kw4l16eIEboB2Au9pODCE+u/dEAakEFh4qidTMn61rbGUbsLK8xzuWNW22ezzz9/nPia0HDrulXt51/FYtfnnAuD1RJGXJv/8tDllE9FL/18TzlH4WuB6Fp8FTgv7QdbZAfWJHDGFIpVCJr1XxOCsSZNFJIqGwZnD2lsChiWw5OJDbKd8otqN1hIbfHyMyfMOJ/BzRzvZXaUt4Dv5nf93EmQDWClxShRwpuX/NkZ5B2K9OFonFTbOCexm/MjMAdCBqebKKaiHFkiknUCn9eJQpZ5bAgERgV50VKj+AVTDfgTpqfO2vfo4wrufi6ZBb8QV7hllhUFBjYogQ9C96dnS7skv0s+cqFuUjwMILr5/rsbEmEMGvl0T0ytyAbtlXuowEFVj/YORNknM4yjY72YUtEPTlMpk0Cis7aIgTvu5qWMPER26PMApZuRqiwRsGIkaJIvOVOTHHjFYe3/YzdMkc7OZtqRMfQLtwVl2/zKQQV8b/a9vaT6q3mRLRd4P3esaAFe/+7sR/t+9tmB+a8kxtKM6kmaVQJMbXJZ4aoHGfeLX0m35Rcvu2Bmph7QfSDjk/eaE3q55zYSoGWShmlhlw4Kwg84sMuhmcVhLvo0LovR8bKmbdgACtTh7+7gs/l5w1lOkgbF6w7rkXLNslK7L2KYF4SPFLUcAA6dlE140Fc7FgB77PeM5Phv+bypQEYtyfLQHxd+OxlG3AAoIM8M4XulprmLd4gGMobS2Bv9CmwB5LpK/ySHE1QWjdwATPCZX1tHUtVypOtrtOfOkMYjd/DU7JblA8fBAD0y+uskwDv9pXPsxvV6bR6Sz5uILxm5VSG4ASRNU4AXhi6UiMUKZHBmcmEgDE0Wua7Lx6Bnad5n91qmHAnwSEJE5YIhQM634omd6cq9Wk4seJCUIn+ucoknrpxp0IR9QMxpKSMRHRUg2K8ZegnY3YqFunRZKCfsq9ufQEKgjZN12AFqi551KPBdn4/3V5HK6xTv0P4robSsE/BvuIfByvRf/W7ZrDx+CFC4EEcsBOACOZCrkhhqd5TkYKbe9RA+vs56+9N5qZGurkxcoKviiyEncxvTuShD65DK/6x6kMDMgQv/EdZDI3x9GtHTnRBYXwDGnPJ19w+q2zC3e2XarbxTGYQIPEC5mYx0gAA0sbjf018NGfwBhl6SB54iGsa8uLvR3jHv6OSRJgwxL6j7P0Ts4Hv2EtO12P0Lv21pwi3JC1O/WviSrKCvrQD5lMHL9Uym3hwFi2zu0mqwZvxOAbGy7kfOPXkLYKOHTZLthzKj3PsdjeceWBfYIvPGKYcd6wDr36d1aXSYS4IWeApTS2AQ2lu0DUcgSefAvsA8NkgOklvJY1cjTMSg6j6cxQo48Bvl8RAWGLbr4h2S/8KwDGxwLsSv0Gop/gnFc3GzCsmL0EkEyHHWkCA8YRXCghfW80KLDV495ff7yF5oiwK56GniqowZ3RG9Jxp5MXoJQgsLV1VMQFMAmsY69yz8eoxRH3wl9L0dMyndLulhWWzNwPMQ2I0yAWdzA/pksVmwTJTFenB3MHCiWc5rEwJ3yofe6NZZnZQrYyL9r1TNnVwfTwRUiykPiLSk4x9Mi6DX7RamDAxc8u3gDVfjPsTOTagBOEGUWlGAL54KE/E6sgCQ5DEAt12chk8AxbjBFLPgV+/idrzS0lZHOL+IVBI9D0i3Bq1yZcSIqcjZB0M3IbxbPm4gLAYOWEiTUN2ecsEHHg9nt6rhgffVoqSbCCFPbpC0xf7WOC3+BQORIZECOCC7cUAciXq3xn+GuxpFE40RWRJeKAK7bBQ21X89ABIXlQFkFddZ9kRvlZ2Pnl0oeF+2pjnZu0Yc2czNfZEQF2P7BKIdLrgMgxG89snxAY8qAYTCKyQw6xTG87wkjDcpy1wzsZLP3WsOuO7cAm7b27xU0jRKq8Cw4d1hDoyRG+RdS53F8RFJzVMaNNYgxU2tfRwUvXpTRXiOheeRVvh25+YGVnjakUXjx/dSDnOw4ETHGHD+7styDkeSfc3BdSZxswzc6OehgMI+xsCxeeRym15QUm9hxvg8X7Bfz/0WulgFwgzrm11TVynZYOmvyHpiZKoqQyQyKahIrfhwuchCr7lMsZ4a+umIkNkKxCLZnI+T7jd+eGFMgKItjz3kTTxRl3IhaJG3LbPmwRUJynMxQKdMi4Uf0qy0U7+i8hIJ9m50QXc+3tw2bwDSbx22XYJ9Wf14gxx5G5SPTb1JVCbhe4fxNt91xIxCow2zk62tzbYfRe6dfmDmgYHkv2PIEtMJZK8iKLDjFfu2ZUxsKT2A5g1q17og6o9MeXeuFS3mzJXJYFQZd+3UzlFR9qwkFkby9mg5y4XSeMvRLOHPt/H/r5SpEqBE6a9MadZYt61FBV152CUEzd43ihXtrAa0XH9HdsiySBcWI1SpM3mv9rRP0DiLjMUzHw/K1D8TE2f07zW4t/9kvE11tFj/NpICixQAAAAA="
20912+    sdmf_old_shares[6] = "VGFob2UgbXV0YWJsZSBjb250YWluZXIgdjEKdQlEA47ESLbTdKdpLJXCpBxd5OH239tl5hvAiz1dvGdE5rIOpf8cbfxbPcwNF+Y5dM92uBVbmV6KAAAAAAAAB/wAAAAAAAAJ0AAAAAFOWSw7jSx7WXzaMpdleJYXwYsRCV82jNA5oex9m2YhXSnb2POh+vvC1LE1NAfRc9GOb2zQG84Xdsx1Jub2brEeKkyt0sRIttN0p2kslcKkHF3k4fbf22XmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABamJprL6ecrsOoFKdrXUmWveLq8nzEGDOjFnyK9detI3noX3uyK2MwSnFdAfyN0tuAwoAAAAAAAAAFQAAAAAAAAAVAAABjwAAAo8AAAMXAAADNwAAAAAAAAM+AAAAAAAAB/wwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC1IkainlJF12IBXBQdpRK1zXB7a26vuEYqRmQM09YjC6sQjCs0F2ICk8n9m/2Kw4l16eIEboB2Au9pODCE+u/dEAakEFh4qidTMn61rbGUbsLK8xzuWNW22ezzz9/nPia0HDrulXt51/FYtfnnAuD1RJGXJv/8tDllE9FL/18TzlH4WuB6Fp8FTgv7QdbZAfWJHDGFIpVCJr1XxOCsSZNFJIqGwZnD2lsChiWw5OJDbKd8otqN1hIbfHyMyfMOJ/BzRzvZXaUt4Dv5nf93EmQDWClxShRwpuX/NkZ5B2K9OFonFTbOCexm/MjMAdCBqebKKaiHFkiknUCn9eJQpZ5bAgERgV50VKj+AVTDfgTpqfO2vfo4wrufi6ZBb8QV7hllhUFBjYogQ9C96dnS7skv0s+cqFuUjwMILr5/rsbEmEMGvl0T0ytyAbtlXuowEFVj/YORNknM4yjY72YUtEPTlMpk0Cis7aIgTvu5qWMPER26PMApZuRqiwRsGIkaJIvOVOTHHjFYe3/YzdMkc7OZtqRMfQLtwVl2/zKQQV8b/a9vaT6q3mRLRd4P3esaAFe/+7sR/t+9tmB+a8kxtKM6kmaVQJMbXJZ4aoHGfeLX0m35Rcvu2Bmph7QfSDjk/eaE3q55zYSoGWShmlhlw4Kwg84sMuhmcVhLvo0LovR8bKmbdgACtTh7+7gs/l5w1lOkgbF6w7rkXLNslK7L2KYF4SPFLUcAA6dlE140Fc7FgB77PeM5Phv+bypQEYtyfLQHxd+OxlG3AAlyHZU7RfTJjbHu1gjabWZsTu+7nAeRVG6/ZSd4iMQ1ZgAWDSFSPvKzcFzRcuRlVgKUf0HBce1MCF8SwpUbPPEyfVJty4xLZ7DvNU/Eh/R6BarsVAagVXdp+GtEu0+fok7nilT4LchmHo8DE0Wua7Lx6Bnad5n91qmHAnwSEJE5YIhQM634omd6cq9Wk4seJCUIn+ucoknrpxp0IR9QMxpKSMRHRUg2K8ZegnY3YqFunRZKCfsq9ufQEKgjZN12AFqi551KPBdn4/3V5HK6xTv0P4robSsE/BvuIfByvRf/W7ZrDx+CFC4EEcsBOACOZCrkhhqd5TkYKbe9RA+vs56+9N5qZGurkxcoKviiyEncxvTuShD65DK/6x6kMDMgQv/EdZDI3x9GtHTnRBYXwDGnPJ19w+q2zC3e2XarbxTGYQIPEC5mYx0gAA0sbjf018NGfwBhl6SB54iGsa8uLvR3jHv6OSRJgwxL6j7P0Ts4Hv2EtO12P0Lv21pwi3JC1O/WviSrKCvrQD5lMHL9Uym3hwFi2zu0mqwZvxOAbGy7kfOPXkLYKOHTZLthzKj3PsdjeceWBfYIvPGKYcd6wDr36d1aXSYS4IWeApTS2AQ2lu0DUcgSefAvsA8NkgOklvJY1cjTMSg6j6cxQo48Bvl8RAWGLbr4h2S/8KwDGxwLsSv0Gop/gnFc3GzCsmL0EkEyHHWkCA8YRXCghfW80KLDV495ff7yF5oiwK56GniqowZ3RG9Jxp5MXoJQgsLV1VMQFMAmsY69yz8eoxRH3wl9L0dMyndLulhWWzNwPMQ2I0yAWdzA/pksVmwTJTFenB3MHCiWc5rEwJ3yofe6NZZnZQrYyL9r1TNnVwfTwRUiykPiLSk4x9Mi6DX7RamDAxc8u3gDVfjPsTOTagBOEGUWlGAL54KE/E6sgCQ5DEAt12chk8AxbjBFLPgV+/idrzS0lZHOL+IVBI9D0i3Bq1yZcSIqcjZB0M3IbxbPm4gLAYOWEiTUN2ecsEHHg9nt6rhgffVoqSbCCFPbpC0xf7WOC3+BQORIZECOCC7cUAciXq3xn+GuxpFE40RWRJeKAK7bBQ21X89ABIXlQFkFddZ9kRvlZ2Pnl0oeF+2pjnZu0Yc2czNfZEQF2P7BKIdLrgMgxG89snxAY8qAYTCKyQw6xTG87wkjDcpy1wzsZLP3WsOuO7cAm7b27xU0jRKq8Cw4d1hDoyRG+RdS53F8RFJzVMaNNYgxU2tfRwUvXpTRXiOheeRVvh25+YGVnjakUXjx/dSDnOw4ETHGHD+7styDkeSfc3BdSZxswzc6OehgMI+xsCxeeRym15QUm9hxvg8X7Bfz/0WulgFwgzrm11TVynZYOmvyHpiZKoqQyQyKahIrfhwuchCr7lMsZ4a+umIkNkKxCLZnI+T7jd+eGFMgKItjz3kTTxRl3IhaJG3LbPmwRUJynMxQKdMi4Uf0qy0U7+i8hIJ9m50QXc+3tw2bwDSbx22XYJ9Wf14gxx5G5SPTb1JVCbhe4fxNt91xIxCow2zk62tzbYfRe6dfmDmgYHkv2PIEtMJZK8iKLDjFfu2ZUxsKT2A5g1q17og6o9MeXeuFS3mzJXJYFQZd+3UzlFR9qwkFkby9mg5y4XSeMvRLOHPt/H/r5SpEqBE6a9MadZYt61FBV152CUEzd43ihXtrAa0XH9HdsiySBcWI1SpM3mv9rRP0DiLjMUzHw/K1D8TE2f07zW4t/9kvE11tFj/NpICixQAAAAA="
20913+    sdmf_old_shares[7] = "VGFob2UgbXV0YWJsZSBjb250YWluZXIgdjEKdQlEA47ESLbTdKdpLJXCpBxd5OH239tl5hvAiz1dvGdE5rIOpf8cbfxbPcwNF+Y5dM92uBVbmV6KAAAAAAAAB/wAAAAAAAAJ0AAAAAFOWSw7jSx7WXzaMpdleJYXwYsRCV82jNA5oex9m2YhXSnb2POh+vvC1LE1NAfRc9GOb2zQG84Xdsx1Jub2brEeKkyt0sRIttN0p2kslcKkHF3k4fbf22XmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABamJprL6ecrsOoFKdrXUmWveLq8nzEGDOjFnyK9detI3noX3uyK2MwSnFdAfyN0tuAwoAAAAAAAAAFQAAAAAAAAAVAAABjwAAAo8AAAMXAAADNwAAAAAAAAM+AAAAAAAAB/wwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC1IkainlJF12IBXBQdpRK1zXB7a26vuEYqRmQM09YjC6sQjCs0F2ICk8n9m/2Kw4l16eIEboB2Au9pODCE+u/dEAakEFh4qidTMn61rbGUbsLK8xzuWNW22ezzz9/nPia0HDrulXt51/FYtfnnAuD1RJGXJv/8tDllE9FL/18TzlH4WuB6Fp8FTgv7QdbZAfWJHDGFIpVCJr1XxOCsSZNFJIqGwZnD2lsChiWw5OJDbKd8otqN1hIbfHyMyfMOJ/BzRzvZXaUt4Dv5nf93EmQDWClxShRwpuX/NkZ5B2K9OFonFTbOCexm/MjMAdCBqebKKaiHFkiknUCn9eJQpZ5bAgERgV50VKj+AVTDfgTpqfO2vfo4wrufi6ZBb8QV7hllhUFBjYogQ9C96dnS7skv0s+cqFuUjwMILr5/rsbEmEMGvl0T0ytyAbtlXuowEFVj/YORNknM4yjY72YUtEPTlMpk0Cis7aIgTvu5qWMPER26PMApZuRqiwRsGIkaJIvOVOTHHjFYe3/YzdMkc7OZtqRMfQLtwVl2/zKQQV8b/a9vaT6q3mRLRd4P3esaAFe/+7sR/t+9tmB+a8kxtKM6kmaVQJMbXJZ4aoHGfeLX0m35Rcvu2Bmph7QfSDjk/eaE3q55zYSoGWShmlhlw4Kwg84sMuhmcVhLvo0LovR8bKmbdgACtTh7+7gs/l5w1lOkgbF6w7rkXLNslK7L2KYF4SPFLUcAA6dlE140Fc7FgB77PeM5Phv+bypQEYtyfLQHxd+OxlG3AAlyHZU7RfTJjbHu1gjabWZsTu+7nAeRVG6/ZSd4iMQ1ZgAVbcuMS2ew7zVPxIf0egWq7FQGoFV3afhrRLtPn6JO54oNIVI+8rNwXNFy5GVWApR/QcFx7UwIXxLClRs88TJ9UtLnNF4/mM0DE0Wua7Lx6Bnad5n91qmHAnwSEJE5YIhQM634omd6cq9Wk4seJCUIn+ucoknrpxp0IR9QMxpKSMRHRUg2K8ZegnY3YqFunRZKCfsq9ufQEKgjZN12AFqi551KPBdn4/3V5HK6xTv0P4robSsE/BvuIfByvRf/W7ZrDx+CFC4EEcsBOACOZCrkhhqd5TkYKbe9RA+vs56+9N5qZGurkxcoKviiyEncxvTuShD65DK/6x6kMDMgQv/EdZDI3x9GtHTnRBYXwDGnPJ19w+q2zC3e2XarbxTGYQIPEC5mYx0gAA0sbjf018NGfwBhl6SB54iGsa8uLvR3jHv6OSRJgwxL6j7P0Ts4Hv2EtO12P0Lv21pwi3JC1O/WviSrKCvrQD5lMHL9Uym3hwFi2zu0mqwZvxOAbGy7kfOPXkLYKOHTZLthzKj3PsdjeceWBfYIvPGKYcd6wDr36d1aXSYS4IWeApTS2AQ2lu0DUcgSefAvsA8NkgOklvJY1cjTMSg6j6cxQo48Bvl8RAWGLbr4h2S/8KwDGxwLsSv0Gop/gnFc3GzCsmL0EkEyHHWkCA8YRXCghfW80KLDV495ff7yF5oiwK56GniqowZ3RG9Jxp5MXoJQgsLV1VMQFMAmsY69yz8eoxRH3wl9L0dMyndLulhWWzNwPMQ2I0yAWdzA/pksVmwTJTFenB3MHCiWc5rEwJ3yofe6NZZnZQrYyL9r1TNnVwfTwRUiykPiLSk4x9Mi6DX7RamDAxc8u3gDVfjPsTOTagBOEGUWlGAL54KE/E6sgCQ5DEAt12chk8AxbjBFLPgV+/idrzS0lZHOL+IVBI9D0i3Bq1yZcSIqcjZB0M3IbxbPm4gLAYOWEiTUN2ecsEHHg9nt6rhgffVoqSbCCFPbpC0xf7WOC3+BQORIZECOCC7cUAciXq3xn+GuxpFE40RWRJeKAK7bBQ21X89ABIXlQFkFddZ9kRvlZ2Pnl0oeF+2pjnZu0Yc2czNfZEQF2P7BKIdLrgMgxG89snxAY8qAYTCKyQw6xTG87wkjDcpy1wzsZLP3WsOuO7cAm7b27xU0jRKq8Cw4d1hDoyRG+RdS53F8RFJzVMaNNYgxU2tfRwUvXpTRXiOheeRVvh25+YGVnjakUXjx/dSDnOw4ETHGHD+7styDkeSfc3BdSZxswzc6OehgMI+xsCxeeRym15QUm9hxvg8X7Bfz/0WulgFwgzrm11TVynZYOmvyHpiZKoqQyQyKahIrfhwuchCr7lMsZ4a+umIkNkKxCLZnI+T7jd+eGFMgKItjz3kTTxRl3IhaJG3LbPmwRUJynMxQKdMi4Uf0qy0U7+i8hIJ9m50QXc+3tw2bwDSbx22XYJ9Wf14gxx5G5SPTb1JVCbhe4fxNt91xIxCow2zk62tzbYfRe6dfmDmgYHkv2PIEtMJZK8iKLDjFfu2ZUxsKT2A5g1q17og6o9MeXeuFS3mzJXJYFQZd+3UzlFR9qwkFkby9mg5y4XSeMvRLOHPt/H/r5SpEqBE6a9MadZYt61FBV152CUEzd43ihXtrAa0XH9HdsiySBcWI1SpM3mv9rRP0DiLjMUzHw/K1D8TE2f07zW4t/9kvE11tFj/NpICixQAAAAA="
20914+    sdmf_old_shares[8] = "VGFob2UgbXV0YWJsZSBjb250YWluZXIgdjEKdQlEA47ESLbTdKdpLJXCpBxd5OH239tl5hvAiz1dvGdE5rIOpf8cbfxbPcwNF+Y5dM92uBVbmV6KAAAAAAAAB/wAAAAAAAAJ0AAAAAFOWSw7jSx7WXzaMpdleJYXwYsRCV82jNA5oex9m2YhXSnb2POh+vvC1LE1NAfRc9GOb2zQG84Xdsx1Jub2brEeKkyt0sRIttN0p2kslcKkHF3k4fbf22XmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABamJprL6ecrsOoFKdrXUmWveLq8nzEGDOjFnyK9detI3noX3uyK2MwSnFdAfyN0tuAwoAAAAAAAAAFQAAAAAAAAAVAAABjwAAAo8AAAMXAAADNwAAAAAAAAM+AAAAAAAAB/wwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC1IkainlJF12IBXBQdpRK1zXB7a26vuEYqRmQM09YjC6sQjCs0F2ICk8n9m/2Kw4l16eIEboB2Au9pODCE+u/dEAakEFh4qidTMn61rbGUbsLK8xzuWNW22ezzz9/nPia0HDrulXt51/FYtfnnAuD1RJGXJv/8tDllE9FL/18TzlH4WuB6Fp8FTgv7QdbZAfWJHDGFIpVCJr1XxOCsSZNFJIqGwZnD2lsChiWw5OJDbKd8otqN1hIbfHyMyfMOJ/BzRzvZXaUt4Dv5nf93EmQDWClxShRwpuX/NkZ5B2K9OFonFTbOCexm/MjMAdCBqebKKaiHFkiknUCn9eJQpZ5bAgERgV50VKj+AVTDfgTpqfO2vfo4wrufi6ZBb8QV7hllhUFBjYogQ9C96dnS7skv0s+cqFuUjwMILr5/rsbEmEMGvl0T0ytyAbtlXuowEFVj/YORNknM4yjY72YUtEPTlMpk0Cis7aIgTvu5qWMPER26PMApZuRqiwRsGIkaJIvOVOTHHjFYe3/YzdMkc7OZtqRMfQLtwVl2/zKQQV8b/a9vaT6q3mRLRd4P3esaAFe/+7sR/t+9tmB+a8kxtKM6kmaVQJMbXJZ4aoHGfeLX0m35Rcvu2Bmph7QfSDjk/eaE3q55zYSoGWShmlhlw4Kwg84sMuhmcVhLvo0LovR8bKmbdgABUSzNKiMx0E91q51/WH6ASL0fDEOLef9oxuyBX5F5cpoABojmWkDX3k3FKfgNHIeptE3lxB8HHzxDfSD250psyfNCAAwGsKbMxbmI2NpdTozZ3SICrySwgGkatA1gsDOJmOnTzgAYmqKY7A9vQChuYa17fYSyKerIb3682jxiIneQvCMWCK5WcuI4PMeIsUAj8yxdxHvV+a9vtSCEsDVvymrrooDKX1GK98t37yoDE0Wua7Lx6Bnad5n91qmHAnwSEJE5YIhQM634omd6cq9Wk4seJCUIn+ucoknrpxp0IR9QMxpKSMRHRUg2K8ZegnY3YqFunRZKCfsq9ufQEKgjZN12AFqi551KPBdn4/3V5HK6xTv0P4robSsE/BvuIfByvRf/W7ZrDx+CFC4EEcsBOACOZCrkhhqd5TkYKbe9RA+vs56+9N5qZGurkxcoKviiyEncxvTuShD65DK/6x6kMDMgQv/EdZDI3x9GtHTnRBYXwDGnPJ19w+q2zC3e2XarbxTGYQIPEC5mYx0gAA0sbjf018NGfwBhl6SB54iGsa8uLvR3jHv6OSRJgwxL6j7P0Ts4Hv2EtO12P0Lv21pwi3JC1O/WviSrKCvrQD5lMHL9Uym3hwFi2zu0mqwZvxOAbGy7kfOPXkLYKOHTZLthzKj3PsdjeceWBfYIvPGKYcd6wDr36d1aXSYS4IWeApTS2AQ2lu0DUcgSefAvsA8NkgOklvJY1cjTMSg6j6cxQo48Bvl8RAWGLbr4h2S/8KwDGxwLsSv0Gop/gnFc3GzCsmL0EkEyHHWkCA8YRXCghfW80KLDV495ff7yF5oiwK56GniqowZ3RG9Jxp5MXoJQgsLV1VMQFMAmsY69yz8eoxRH3wl9L0dMyndLulhWWzNwPMQ2I0yAWdzA/pksVmwTJTFenB3MHCiWc5rEwJ3yofe6NZZnZQrYyL9r1TNnVwfTwRUiykPiLSk4x9Mi6DX7RamDAxc8u3gDVfjPsTOTagBOEGUWlGAL54KE/E6sgCQ5DEAt12chk8AxbjBFLPgV+/idrzS0lZHOL+IVBI9D0i3Bq1yZcSIqcjZB0M3IbxbPm4gLAYOWEiTUN2ecsEHHg9nt6rhgffVoqSbCCFPbpC0xf7WOC3+BQORIZECOCC7cUAciXq3xn+GuxpFE40RWRJeKAK7bBQ21X89ABIXlQFkFddZ9kRvlZ2Pnl0oeF+2pjnZu0Yc2czNfZEQF2P7BKIdLrgMgxG89snxAY8qAYTCKyQw6xTG87wkjDcpy1wzsZLP3WsOuO7cAm7b27xU0jRKq8Cw4d1hDoyRG+RdS53F8RFJzVMaNNYgxU2tfRwUvXpTRXiOheeRVvh25+YGVnjakUXjx/dSDnOw4ETHGHD+7styDkeSfc3BdSZxswzc6OehgMI+xsCxeeRym15QUm9hxvg8X7Bfz/0WulgFwgzrm11TVynZYOmvyHpiZKoqQyQyKahIrfhwuchCr7lMsZ4a+umIkNkKxCLZnI+T7jd+eGFMgKItjz3kTTxRl3IhaJG3LbPmwRUJynMxQKdMi4Uf0qy0U7+i8hIJ9m50QXc+3tw2bwDSbx22XYJ9Wf14gxx5G5SPTb1JVCbhe4fxNt91xIxCow2zk62tzbYfRe6dfmDmgYHkv2PIEtMJZK8iKLDjFfu2ZUxsKT2A5g1q17og6o9MeXeuFS3mzJXJYFQZd+3UzlFR9qwkFkby9mg5y4XSeMvRLOHPt/H/r5SpEqBE6a9MadZYt61FBV152CUEzd43ihXtrAa0XH9HdsiySBcWI1SpM3mv9rRP0DiLjMUzHw/K1D8TE2f07zW4t/9kvE11tFj/NpICixQAAAAA="
20915+    sdmf_old_shares[9] = "VGFob2UgbXV0YWJsZSBjb250YWluZXIgdjEKdQlEA47ESLbTdKdpLJXCpBxd5OH239tl5hvAiz1dvGdE5rIOpf8cbfxbPcwNF+Y5dM92uBVbmV6KAAAAAAAAB/wAAAAAAAAJ0AAAAAFOWSw7jSx7WXzaMpdleJYXwYsRCV82jNA5oex9m2YhXSnb2POh+vvC1LE1NAfRc9GOb2zQG84Xdsx1Jub2brEeKkyt0sRIttN0p2kslcKkHF3k4fbf22XmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABamJprL6ecrsOoFKdrXUmWveLq8nzEGDOjFnyK9detI3noX3uyK2MwSnFdAfyN0tuAwoAAAAAAAAAFQAAAAAAAAAVAAABjwAAAo8AAAMXAAADNwAAAAAAAAM+AAAAAAAAB/wwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC1IkainlJF12IBXBQdpRK1zXB7a26vuEYqRmQM09YjC6sQjCs0F2ICk8n9m/2Kw4l16eIEboB2Au9pODCE+u/dEAakEFh4qidTMn61rbGUbsLK8xzuWNW22ezzz9/nPia0HDrulXt51/FYtfnnAuD1RJGXJv/8tDllE9FL/18TzlH4WuB6Fp8FTgv7QdbZAfWJHDGFIpVCJr1XxOCsSZNFJIqGwZnD2lsChiWw5OJDbKd8otqN1hIbfHyMyfMOJ/BzRzvZXaUt4Dv5nf93EmQDWClxShRwpuX/NkZ5B2K9OFonFTbOCexm/MjMAdCBqebKKaiHFkiknUCn9eJQpZ5bAgERgV50VKj+AVTDfgTpqfO2vfo4wrufi6ZBb8QV7hllhUFBjYogQ9C96dnS7skv0s+cqFuUjwMILr5/rsbEmEMGvl0T0ytyAbtlXuowEFVj/YORNknM4yjY72YUtEPTlMpk0Cis7aIgTvu5qWMPER26PMApZuRqiwRsGIkaJIvOVOTHHjFYe3/YzdMkc7OZtqRMfQLtwVl2/zKQQV8b/a9vaT6q3mRLRd4P3esaAFe/+7sR/t+9tmB+a8kxtKM6kmaVQJMbXJZ4aoHGfeLX0m35Rcvu2Bmph7QfSDjk/eaE3q55zYSoGWShmlhlw4Kwg84sMuhmcVhLvo0LovR8bKmbdgABUSzNKiMx0E91q51/WH6ASL0fDEOLef9oxuyBX5F5cpoABojmWkDX3k3FKfgNHIeptE3lxB8HHzxDfSD250psyfNCAAwGsKbMxbmI2NpdTozZ3SICrySwgGkatA1gsDOJmOnTzgAXVnLiODzHiLFAI/MsXcR71fmvb7UghLA1b8pq66KAyl+aopjsD29AKG5hrXt9hLIp6shvfrzaPGIid5C8IxYIrjgBj1YohGgDE0Wua7Lx6Bnad5n91qmHAnwSEJE5YIhQM634omd6cq9Wk4seJCUIn+ucoknrpxp0IR9QMxpKSMRHRUg2K8ZegnY3YqFunRZKCfsq9ufQEKgjZN12AFqi551KPBdn4/3V5HK6xTv0P4robSsE/BvuIfByvRf/W7ZrDx+CFC4EEcsBOACOZCrkhhqd5TkYKbe9RA+vs56+9N5qZGurkxcoKviiyEncxvTuShD65DK/6x6kMDMgQv/EdZDI3x9GtHTnRBYXwDGnPJ19w+q2zC3e2XarbxTGYQIPEC5mYx0gAA0sbjf018NGfwBhl6SB54iGsa8uLvR3jHv6OSRJgwxL6j7P0Ts4Hv2EtO12P0Lv21pwi3JC1O/WviSrKCvrQD5lMHL9Uym3hwFi2zu0mqwZvxOAbGy7kfOPXkLYKOHTZLthzKj3PsdjeceWBfYIvPGKYcd6wDr36d1aXSYS4IWeApTS2AQ2lu0DUcgSefAvsA8NkgOklvJY1cjTMSg6j6cxQo48Bvl8RAWGLbr4h2S/8KwDGxwLsSv0Gop/gnFc3GzCsmL0EkEyHHWkCA8YRXCghfW80KLDV495ff7yF5oiwK56GniqowZ3RG9Jxp5MXoJQgsLV1VMQFMAmsY69yz8eoxRH3wl9L0dMyndLulhWWzNwPMQ2I0yAWdzA/pksVmwTJTFenB3MHCiWc5rEwJ3yofe6NZZnZQrYyL9r1TNnVwfTwRUiykPiLSk4x9Mi6DX7RamDAxc8u3gDVfjPsTOTagBOEGUWlGAL54KE/E6sgCQ5DEAt12chk8AxbjBFLPgV+/idrzS0lZHOL+IVBI9D0i3Bq1yZcSIqcjZB0M3IbxbPm4gLAYOWEiTUN2ecsEHHg9nt6rhgffVoqSbCCFPbpC0xf7WOC3+BQORIZECOCC7cUAciXq3xn+GuxpFE40RWRJeKAK7bBQ21X89ABIXlQFkFddZ9kRvlZ2Pnl0oeF+2pjnZu0Yc2czNfZEQF2P7BKIdLrgMgxG89snxAY8qAYTCKyQw6xTG87wkjDcpy1wzsZLP3WsOuO7cAm7b27xU0jRKq8Cw4d1hDoyRG+RdS53F8RFJzVMaNNYgxU2tfRwUvXpTRXiOheeRVvh25+YGVnjakUXjx/dSDnOw4ETHGHD+7styDkeSfc3BdSZxswzc6OehgMI+xsCxeeRym15QUm9hxvg8X7Bfz/0WulgFwgzrm11TVynZYOmvyHpiZKoqQyQyKahIrfhwuchCr7lMsZ4a+umIkNkKxCLZnI+T7jd+eGFMgKItjz3kTTxRl3IhaJG3LbPmwRUJynMxQKdMi4Uf0qy0U7+i8hIJ9m50QXc+3tw2bwDSbx22XYJ9Wf14gxx5G5SPTb1JVCbhe4fxNt91xIxCow2zk62tzbYfRe6dfmDmgYHkv2PIEtMJZK8iKLDjFfu2ZUxsKT2A5g1q17og6o9MeXeuFS3mzJXJYFQZd+3UzlFR9qwkFkby9mg5y4XSeMvRLOHPt/H/r5SpEqBE6a9MadZYt61FBV152CUEzd43ihXtrAa0XH9HdsiySBcWI1SpM3mv9rRP0DiLjMUzHw/K1D8TE2f07zW4t/9kvE11tFj/NpICixQAAAAA="
20916+    sdmf_old_cap = "URI:SSK:gmjgofw6gan57gwpsow6gtrz3e:5adm6fayxmu3e4lkmfvt6lkkfix34ai2wop2ioqr4bgvvhiol3kq"
20917+    sdmf_old_contents = "This is a test file.\n"
20918+    def copy_sdmf_shares(self):
20919+        # We'll basically be short-circuiting the upload process.
20920+        servernums = self.g.servers_by_number.keys()
20921+        assert len(servernums) == 10
20922+
20923+        assignments = zip(self.sdmf_old_shares.keys(), servernums)
20924+        # Get the storage index.
20925+        cap = uri.from_string(self.sdmf_old_cap)
20926+        si = cap.get_storage_index()
20927+
20928+        # Now execute each assignment by writing the storage.
20929+        for (share, servernum) in assignments:
20930+            sharedata = base64.b64decode(self.sdmf_old_shares[share])
20931+            storedir = self.get_serverdir(servernum)
20932+            storage_path = os.path.join(storedir, "shares",
20933+                                        storage_index_to_dir(si))
20934+            fileutil.make_dirs(storage_path)
20935+            fileutil.write(os.path.join(storage_path, "%d" % share),
20936+                           sharedata)
20937+        # ...and verify that the shares are there.
20938+        shares = self.find_uri_shares(self.sdmf_old_cap)
20939+        assert len(shares) == 10
20940+
20941+    def test_new_downloader_can_read_old_shares(self):
20942+        self.basedir = "mutable/Interoperability/new_downloader_can_read_old_shares"
20943+        self.set_up_grid()
20944+        self.copy_sdmf_shares()
20945+        nm = self.g.clients[0].nodemaker
20946+        n = nm.create_from_cap(self.sdmf_old_cap)
20947+        d = n.download_best_version()
20948+        d.addCallback(self.failUnlessEqual, self.sdmf_old_contents)
20949+        return d
20950}
20951[scripts/cli: resolve merge conflicts
20952Kevan Carstensen <kevan@isnotajoke.com>**20110728234015
20953 Ignore-this: 5c9d3ee5e4cac5d3c31779cdcf475c8c
20954] hunk ./src/allmydata/scripts/cli.py 59
20955 
20956     def parseArgs(self, where=""):
20957         self.where = argv_to_unicode(where)
20958+
20959+        if self['mutable-type'] and self['mutable-type'] not in ("sdmf", "mdmf"):
20960+            raise usage.UsageError("%s is an invalid format" % self['mutable-type'])
20961+
20962+    def getSynopsis(self):
20963+        return "Usage:  %s mkdir [options] [REMOTE_DIR]" % (self.command_name,)
20964+
20965     longdesc = """Create a new directory, either unlinked or as a subdirectory."""
20966 
20967 class AddAliasOptions(VDriveOptions):
20968[mutable/publish: clean up error handling.
20969Kevan Carstensen <kevan@isnotajoke.com>**20110730214113
20970 Ignore-this: e2480b276ed2abc3e9111bb02c8c74b7
20971] {
20972hunk ./src/allmydata/mutable/publish.py 640
20973 
20974         # If we make it to this point, we were successful in placing the
20975         # file.
20976-        return self._done(None)
20977+        return self._done()
20978 
20979 
20980     def push_segment(self, segnum):
20981hunk ./src/allmydata/mutable/publish.py 658
20982             self._current_segment += 1
20983         # XXX: I don't think we need to do addBoth here -- any errBacks
20984         # should be handled within push_segment.
20985-        d.addBoth(_increment_segnum)
20986-        d.addBoth(self._turn_barrier)
20987-        d.addBoth(self._push)
20988+        d.addCallback(_increment_segnum)
20989+        d.addCallback(self._turn_barrier)
20990+        d.addCallback(self._push)
20991+        d.addErrback(self._failure)
20992 
20993 
20994     def _turn_barrier(self, result):
20995hunk ./src/allmydata/mutable/publish.py 1133
20996         return
20997 
20998 
20999-    def _done(self, res):
21000+    def _done(self):
21001         if not self._running:
21002             return
21003         self._running = False
21004hunk ./src/allmydata/mutable/publish.py 1152
21005         hints['segsize'] = self.segment_size
21006         hints['k'] = self.required_shares
21007         self._node.set_downloader_hints(hints)
21008-        eventually(self.done_deferred.callback, res)
21009+        eventually(self.done_deferred.callback, None)
21010 
21011hunk ./src/allmydata/mutable/publish.py 1154
21012-    def _failure(self):
21013+    def _failure(self, f=None):
21014+        if f:
21015+            self._last_failure = f
21016 
21017         if not self.surprised:
21018             # We ran out of servers
21019}
21020[mutable: address failure to publish when there are as  many writers as k, add/fix tests for this
21021Kevan Carstensen <kevan@isnotajoke.com>**20110730220208
21022 Ignore-this: 51900a9abbfca54137d2855fa7a2f81b
21023] {
21024hunk ./src/allmydata/mutable/publish.py 112
21025         self._log_number = num
21026         self._running = True
21027         self._first_write_error = None
21028+        self._last_failure = None
21029 
21030         self._status = PublishStatus()
21031         self._status.set_storage_index(self._storage_index)
21032hunk ./src/allmydata/mutable/publish.py 627
21033         # Can we still successfully publish this file?
21034         # TODO: Keep track of outstanding queries before aborting the
21035         #       process.
21036-        if len(self.writers) <= self.required_shares or self.surprised:
21037+        if len(self.writers) < self.required_shares or self.surprised:
21038             return self._failure()
21039 
21040         # Figure out what we need to do next. Each of these needs to
21041hunk ./src/allmydata/mutable/publish.py 1161
21042 
21043         if not self.surprised:
21044             # We ran out of servers
21045-            self.log("Publish ran out of good servers, "
21046-                     "last failure was: %s" % str(self._last_failure))
21047-            e = NotEnoughServersError("Ran out of non-bad servers, "
21048-                                      "last failure was %s" %
21049-                                      str(self._last_failure))
21050+            msg = "Publish ran out of good servers"
21051+            if self._last_failure:
21052+                msg += ", last failure was: %s" % str(self._last_failure)
21053+            self.log(msg)
21054+            e = NotEnoughServersError(msg)
21055+
21056         else:
21057             # We ran into shares that we didn't recognize, which means
21058             # that we need to return an UncoordinatedWriteError.
21059hunk ./src/allmydata/test/test_mutable.py 272
21060         d.addCallback(_created)
21061         return d
21062 
21063+    def test_single_share(self):
21064+        # Make sure that we tolerate publishing a single share.
21065+        self.nodemaker.default_encoding_parameters['k'] = 1
21066+        self.nodemaker.default_encoding_parameters['happy'] = 1
21067+        self.nodemaker.default_encoding_parameters['n'] = 1
21068+        d = defer.succeed(None)
21069+        for v in (SDMF_VERSION, MDMF_VERSION):
21070+            d.addCallback(lambda ignored:
21071+                self.nodemaker.create_mutable_file(version=v))
21072+            def _created(n):
21073+                self.failUnless(isinstance(n, MutableFileNode))
21074+                self._node = n
21075+                return n
21076+            d.addCallback(_created)
21077+            d.addCallback(lambda n:
21078+                n.overwrite(MutableData("Contents" * 50000)))
21079+            d.addCallback(lambda ignored:
21080+                self._node.download_best_version())
21081+            d.addCallback(lambda contents:
21082+                self.failUnlessEqual(contents, "Contents" * 50000))
21083+        return d
21084+
21085     def test_max_shares(self):
21086         self.nodemaker.default_encoding_parameters['n'] = 255
21087         d = self.nodemaker.create_mutable_file(version=SDMF_VERSION)
21088hunk ./src/allmydata/test/test_mutable.py 2688
21089 
21090         d = self.shouldFail(NotEnoughServersError,
21091                             "test_publish_all_servers_bad",
21092-                            "Ran out of non-bad servers",
21093+                            "ran out of good servers",
21094                             nm.create_mutable_file, MutableData("contents"))
21095         return d
21096 
21097}
21098[test/test_cli: rework a tahoe cp test that relied on an old webapi error message
21099Kevan Carstensen <kevan@isnotajoke.com>**20110730220300
21100 Ignore-this: e9c37ad8f4dd1f5bf362fa9227e302a1
21101] hunk ./src/allmydata/test/test_cli.py 2150
21102             self.do_cli("cp", replacement_file_path, "tahoe:test_file.txt"))
21103         def _check_error_message((rc, out, err)):
21104             self.failUnlessEqual(rc, 1)
21105-            self.failUnlessIn("need write capability to publish", err)
21106+            self.failUnlessIn("replace or update requested with read-only cap", err)
21107         d.addCallback(_check_error_message)
21108         # Make extra sure that that didn't work.
21109         d.addCallback(lambda ignored:
21110
21111Context:
21112
21113[test_cli.py: use to_str on fields loaded using simplejson.loads in new tests. refs #1304
21114david-sarah@jacaranda.org**20110730032521
21115 Ignore-this: d1d6dfaefd1b4e733181bf127c79c00b
21116] 
21117[cli: make 'tahoe cp' overwrite mutable files in-place
21118Kevan Carstensen <kevan@isnotajoke.com>**20110729202039
21119 Ignore-this: b2ad21a19439722f05c49bfd35b01855
21120] 
21121[SFTP: write an error message to standard error for unrecognized shell commands. Change the existing message for shell sessions to be written to standard error, and refactor some duplicated code. Also change the lines of the error messages to end in CRLF, and take into account Kevan's review comments. fixes #1442, #1446
21122david-sarah@jacaranda.org**20110729233102
21123 Ignore-this: d2f2bb4664f25007d1602bf7333e2cdd
21124] 
21125[src/allmydata/scripts/cli.py: fix pyflakes warning.
21126david-sarah@jacaranda.org**20110728021402
21127 Ignore-this: 94050140ddb99865295973f49927c509
21128] 
21129[Fix the help synopses of CLI commands to include [options] in the right place. fixes #1359, fixes #636
21130david-sarah@jacaranda.org**20110724225440
21131 Ignore-this: 2a8e488a5f63dabfa9db9efd83768a5
21132] 
21133[encodingutil: argv and output encodings are always the same on all platforms. Lose the unnecessary generality of them being different. fixes #1120
21134david-sarah@jacaranda.org**20110629185356
21135 Ignore-this: 5ebacbe6903dfa83ffd3ff8436a97787
21136] 
21137[docs/man/tahoe.1: add man page. fixes #1420
21138david-sarah@jacaranda.org**20110724171728
21139 Ignore-this: fc7601ec7f25494288d6141d0ae0004c
21140] 
21141[Update the dependency on zope.interface to fix an incompatiblity between Nevow and zope.interface 3.6.4. fixes #1435
21142david-sarah@jacaranda.org**20110721234941
21143 Ignore-this: 2ff3fcfc030fca1a4d4c7f1fed0f2aa9
21144] 
21145[frontends/ftpd.py: remove the check for IWriteFile.close since we're now guaranteed to be using Twisted >= 10.1 which has it.
21146david-sarah@jacaranda.org**20110722000320
21147 Ignore-this: 55cd558b791526113db3f83c00ec328a
21148] 
21149[Update the dependency on Twisted to >= 10.1. This allows us to simplify some documentation: it's no longer necessary to install pywin32 on Windows, or apply a patch to Twisted in order to use the FTP frontend. fixes #1274, #1438. refs #1429
21150david-sarah@jacaranda.org**20110721233658
21151 Ignore-this: 81b41745477163c9b39c0b59db91cc62
21152] 
21153[misc/build_helpers/run_trial.py: undo change to block pywin32 (it didn't work because run_trial.py is no longer used). refs #1334
21154david-sarah@jacaranda.org**20110722035402
21155 Ignore-this: 5d03f544c4154f088e26c7107494bf39
21156] 
21157[misc/build_helpers/run_trial.py: ensure that pywin32 is not on the sys.path when running the test suite. Includes some temporary debugging printouts that will be removed. refs #1334
21158david-sarah@jacaranda.org**20110722024907
21159 Ignore-this: 5141a9f83a4085ed4ca21f0bbb20bb9c
21160] 
21161[docs/running.rst: use 'tahoe run ~/.tahoe' instead of 'tahoe run' (the default is the current directory, unlike 'tahoe start').
21162david-sarah@jacaranda.org**20110718005949
21163 Ignore-this: 81837fbce073e93d88a3e7ae3122458c
21164] 
21165[docs/running.rst: say to put the introducer.furl in tahoe.cfg.
21166david-sarah@jacaranda.org**20110717194315
21167 Ignore-this: 954cc4c08e413e8c62685d58ff3e11f3
21168] 
21169[README.txt: say that quickstart.rst is in the docs directory.
21170david-sarah@jacaranda.org**20110717192400
21171 Ignore-this: bc6d35a85c496b77dbef7570677ea42a
21172] 
21173[setup: remove the dependency on foolscap's "secure_connections" extra, add a dependency on pyOpenSSL
21174zooko@zooko.com**20110717114226
21175 Ignore-this: df222120d41447ce4102616921626c82
21176 fixes #1383
21177] 
21178[test_sftp.py cleanup: remove a redundant definition of failUnlessReallyEqual.
21179david-sarah@jacaranda.org**20110716181813
21180 Ignore-this: 50113380b368c573f07ac6fe2eb1e97f
21181] 
21182[docs: add missing link in NEWS.rst
21183zooko@zooko.com**20110712153307
21184 Ignore-this: be7b7eb81c03700b739daa1027d72b35
21185] 
21186[contrib: remove the contributed fuse modules and the entire contrib/ directory, which is now empty
21187zooko@zooko.com**20110712153229
21188 Ignore-this: 723c4f9e2211027c79d711715d972c5
21189 Also remove a couple of vestigial references to figleaf, which is long gone.
21190 fixes #1409 (remove contrib/fuse)
21191] 
21192[add Protovis.js-based download-status timeline visualization
21193Brian Warner <warner@lothar.com>**20110629222606
21194 Ignore-this: 477ccef5c51b30e246f5b6e04ab4a127
21195 
21196 provide status overlap info on the webapi t=json output, add decode/decrypt
21197 rate tooltips, add zoomin/zoomout buttons
21198] 
21199[add more download-status data, fix tests
21200Brian Warner <warner@lothar.com>**20110629222555
21201 Ignore-this: e9e0b7e0163f1e95858aa646b9b17b8c
21202] 
21203[prepare for viz: improve DownloadStatus events
21204Brian Warner <warner@lothar.com>**20110629222542
21205 Ignore-this: 16d0bde6b734bb501aa6f1174b2b57be
21206 
21207 consolidate IDownloadStatusHandlingConsumer stuff into DownloadNode
21208] 
21209[docs: fix error in crypto specification that was noticed by Taylor R Campbell <campbell+tahoe@mumble.net>
21210zooko@zooko.com**20110629185711
21211 Ignore-this: b921ed60c1c8ba3c390737fbcbe47a67
21212] 
21213[setup.py: don't make bin/tahoe.pyscript executable. fixes #1347
21214david-sarah@jacaranda.org**20110130235809
21215 Ignore-this: 3454c8b5d9c2c77ace03de3ef2d9398a
21216] 
21217[Makefile: remove targets relating to 'setup.py check_auto_deps' which no longer exists. fixes #1345
21218david-sarah@jacaranda.org**20110626054124
21219 Ignore-this: abb864427a1b91bd10d5132b4589fd90
21220] 
21221[Makefile: add 'make check' as an alias for 'make test'. Also remove an unnecessary dependency of 'test' on 'build' and 'src/allmydata/_version.py'. fixes #1344
21222david-sarah@jacaranda.org**20110623205528
21223 Ignore-this: c63e23146c39195de52fb17c7c49b2da
21224] 
21225[Rename test_package_initialization.py to (much shorter) test_import.py .
21226Brian Warner <warner@lothar.com>**20110611190234
21227 Ignore-this: 3eb3dbac73600eeff5cfa6b65d65822
21228 
21229 The former name was making my 'ls' listings hard to read, by forcing them
21230 down to just two columns.
21231] 
21232[tests: fix tests to accomodate [20110611153758-92b7f-0ba5e4726fb6318dac28fb762a6512a003f4c430]
21233zooko@zooko.com**20110611163741
21234 Ignore-this: 64073a5f39e7937e8e5e1314c1a302d1
21235 Apparently none of the two authors (stercor, terrell), three reviewers (warner, davidsarah, terrell), or one committer (me) actually ran the tests. This is presumably due to #20.
21236 fixes #1412
21237] 
21238[wui: right-align the size column in the WUI
21239zooko@zooko.com**20110611153758
21240 Ignore-this: 492bdaf4373c96f59f90581c7daf7cd7
21241 Thanks to Ted "stercor" Rolle Jr. and Terrell Russell.
21242 fixes #1412
21243] 
21244[docs: three minor fixes
21245zooko@zooko.com**20110610121656
21246 Ignore-this: fec96579eb95aceb2ad5fc01a814c8a2
21247 CREDITS for arc for stats tweak
21248 fix link to .zip file in quickstart.rst (thanks to ChosenOne for noticing)
21249 English usage tweak
21250] 
21251[docs/running.rst: fix stray HTML (not .rst) link noticed by ChosenOne.
21252david-sarah@jacaranda.org**20110609223719
21253 Ignore-this: fc50ac9c94792dcac6f1067df8ac0d4a
21254] 
21255[server.py:  get_latencies now reports percentiles _only_ if there are sufficient observations for the interpretation of the percentile to be unambiguous.
21256wilcoxjg@gmail.com**20110527120135
21257 Ignore-this: 2e7029764bffc60e26f471d7c2b6611e
21258 interfaces.py:  modified the return type of RIStatsProvider.get_stats to allow for None as a return value
21259 NEWS.rst, stats.py: documentation of change to get_latencies
21260 stats.rst: now documents percentile modification in get_latencies
21261 test_storage.py:  test_latencies now expects None in output categories that contain too few samples for the associated percentile to be unambiguously reported.
21262 fixes #1392
21263] 
21264[docs: revert link in relnotes.txt from NEWS.rst to NEWS, since the former did not exist at revision 5000.
21265david-sarah@jacaranda.org**20110517011214
21266 Ignore-this: 6a5be6e70241e3ec0575641f64343df7
21267] 
21268[docs: convert NEWS to NEWS.rst and change all references to it.
21269david-sarah@jacaranda.org**20110517010255
21270 Ignore-this: a820b93ea10577c77e9c8206dbfe770d
21271] 
21272[docs: remove out-of-date docs/testgrid/introducer.furl and containing directory. fixes #1404
21273david-sarah@jacaranda.org**20110512140559
21274 Ignore-this: 784548fc5367fac5450df1c46890876d
21275] 
21276[scripts/common.py: don't assume that the default alias is always 'tahoe' (it is, but the API of get_alias doesn't say so). refs #1342
21277david-sarah@jacaranda.org**20110130164923
21278 Ignore-this: a271e77ce81d84bb4c43645b891d92eb
21279] 
21280[setup: don't catch all Exception from check_requirement(), but only PackagingError and ImportError
21281zooko@zooko.com**20110128142006
21282 Ignore-this: 57d4bc9298b711e4bc9dc832c75295de
21283 I noticed this because I had accidentally inserted a bug which caused AssertionError to be raised from check_requirement().
21284] 
21285[M-x whitespace-cleanup
21286zooko@zooko.com**20110510193653
21287 Ignore-this: dea02f831298c0f65ad096960e7df5c7
21288] 
21289[docs: fix typo in running.rst, thanks to arch_o_median
21290zooko@zooko.com**20110510193633
21291 Ignore-this: ca06de166a46abbc61140513918e79e8
21292] 
21293[relnotes.txt: don't claim to work on Cygwin (which has been untested for some time). refs #1342
21294david-sarah@jacaranda.org**20110204204902
21295 Ignore-this: 85ef118a48453d93fa4cddc32d65b25b
21296] 
21297[relnotes.txt: forseeable -> foreseeable. refs #1342
21298david-sarah@jacaranda.org**20110204204116
21299 Ignore-this: 746debc4d82f4031ebf75ab4031b3a9
21300] 
21301[replace remaining .html docs with .rst docs
21302zooko@zooko.com**20110510191650
21303 Ignore-this: d557d960a986d4ac8216d1677d236399
21304 Remove install.html (long since deprecated).
21305 Also replace some obsolete references to install.html with references to quickstart.rst.
21306 Fix some broken internal references within docs/historical/historical_known_issues.txt.
21307 Thanks to Ravi Pinjala and Patrick McDonald.
21308 refs #1227
21309] 
21310[docs: FTP-and-SFTP.rst: fix a minor error and update the information about which version of Twisted fixes #1297
21311zooko@zooko.com**20110428055232
21312 Ignore-this: b63cfb4ebdbe32fb3b5f885255db4d39
21313] 
21314[munin tahoe_files plugin: fix incorrect file count
21315francois@ctrlaltdel.ch**20110428055312
21316 Ignore-this: 334ba49a0bbd93b4a7b06a25697aba34
21317 fixes #1391
21318] 
21319[corrected "k must never be smaller than N" to "k must never be greater than N"
21320secorp@allmydata.org**20110425010308
21321 Ignore-this: 233129505d6c70860087f22541805eac
21322] 
21323[Fix a test failure in test_package_initialization on Python 2.4.x due to exceptions being stringified differently than in later versions of Python. refs #1389
21324david-sarah@jacaranda.org**20110411190738
21325 Ignore-this: 7847d26bc117c328c679f08a7baee519
21326] 
21327[tests: add test for including the ImportError message and traceback entry in the summary of errors from importing dependencies. refs #1389
21328david-sarah@jacaranda.org**20110410155844
21329 Ignore-this: fbecdbeb0d06a0f875fe8d4030aabafa
21330] 
21331[allmydata/__init__.py: preserve the message and last traceback entry (file, line number, function, and source line) of ImportErrors in the package versions string. fixes #1389
21332david-sarah@jacaranda.org**20110410155705
21333 Ignore-this: 2f87b8b327906cf8bfca9440a0904900
21334] 
21335[remove unused variable detected by pyflakes
21336zooko@zooko.com**20110407172231
21337 Ignore-this: 7344652d5e0720af822070d91f03daf9
21338] 
21339[allmydata/__init__.py: Nicer reporting of unparseable version numbers in dependencies. fixes #1388
21340david-sarah@jacaranda.org**20110401202750
21341 Ignore-this: 9c6bd599259d2405e1caadbb3e0d8c7f
21342] 
21343[update FTP-and-SFTP.rst: the necessary patch is included in Twisted-10.1
21344Brian Warner <warner@lothar.com>**20110325232511
21345 Ignore-this: d5307faa6900f143193bfbe14e0f01a
21346] 
21347[control.py: remove all uses of s.get_serverid()
21348warner@lothar.com**20110227011203
21349 Ignore-this: f80a787953bd7fa3d40e828bde00e855
21350] 
21351[web: remove some uses of s.get_serverid(), not all
21352warner@lothar.com**20110227011159
21353 Ignore-this: a9347d9cf6436537a47edc6efde9f8be
21354] 
21355[immutable/downloader/fetcher.py: remove all get_serverid() calls
21356warner@lothar.com**20110227011156
21357 Ignore-this: fb5ef018ade1749348b546ec24f7f09a
21358] 
21359[immutable/downloader/fetcher.py: fix diversity bug in server-response handling
21360warner@lothar.com**20110227011153
21361 Ignore-this: bcd62232c9159371ae8a16ff63d22c1b
21362 
21363 When blocks terminate (either COMPLETE or CORRUPT/DEAD/BADSEGNUM), the
21364 _shares_from_server dict was being popped incorrectly (using shnum as the
21365 index instead of serverid). I'm still thinking through the consequences of
21366 this bug. It was probably benign and really hard to detect. I think it would
21367 cause us to incorrectly believe that we're pulling too many shares from a
21368 server, and thus prefer a different server rather than asking for a second
21369 share from the first server. The diversity code is intended to spread out the
21370 number of shares simultaneously being requested from each server, but with
21371 this bug, it might be spreading out the total number of shares requested at
21372 all, not just simultaneously. (note that SegmentFetcher is scoped to a single
21373 segment, so the effect doesn't last very long).
21374] 
21375[immutable/downloader/share.py: reduce get_serverid(), one left, update ext deps
21376warner@lothar.com**20110227011150
21377 Ignore-this: d8d56dd8e7b280792b40105e13664554
21378 
21379 test_download.py: create+check MyShare instances better, make sure they share
21380 Server objects, now that finder.py cares
21381] 
21382[immutable/downloader/finder.py: reduce use of get_serverid(), one left
21383warner@lothar.com**20110227011146
21384 Ignore-this: 5785be173b491ae8a78faf5142892020
21385] 
21386[immutable/offloaded.py: reduce use of get_serverid() a bit more
21387warner@lothar.com**20110227011142
21388 Ignore-this: b48acc1b2ae1b311da7f3ba4ffba38f
21389] 
21390[immutable/upload.py: reduce use of get_serverid()
21391warner@lothar.com**20110227011138
21392 Ignore-this: ffdd7ff32bca890782119a6e9f1495f6
21393] 
21394[immutable/checker.py: remove some uses of s.get_serverid(), not all
21395warner@lothar.com**20110227011134
21396 Ignore-this: e480a37efa9e94e8016d826c492f626e
21397] 
21398[add remaining get_* methods to storage_client.Server, NoNetworkServer, and
21399warner@lothar.com**20110227011132
21400 Ignore-this: 6078279ddf42b179996a4b53bee8c421
21401 MockIServer stubs
21402] 
21403[upload.py: rearrange _make_trackers a bit, no behavior changes
21404warner@lothar.com**20110227011128
21405 Ignore-this: 296d4819e2af452b107177aef6ebb40f
21406] 
21407[happinessutil.py: finally rename merge_peers to merge_servers
21408warner@lothar.com**20110227011124
21409 Ignore-this: c8cd381fea1dd888899cb71e4f86de6e
21410] 
21411[test_upload.py: factor out FakeServerTracker
21412warner@lothar.com**20110227011120
21413 Ignore-this: 6c182cba90e908221099472cc159325b
21414] 
21415[test_upload.py: server-vs-tracker cleanup
21416warner@lothar.com**20110227011115
21417 Ignore-this: 2915133be1a3ba456e8603885437e03
21418] 
21419[happinessutil.py: server-vs-tracker cleanup
21420warner@lothar.com**20110227011111
21421 Ignore-this: b856c84033562d7d718cae7cb01085a9
21422] 
21423[upload.py: more tracker-vs-server cleanup
21424warner@lothar.com**20110227011107
21425 Ignore-this: bb75ed2afef55e47c085b35def2de315
21426] 
21427[upload.py: fix var names to avoid confusion between 'trackers' and 'servers'
21428warner@lothar.com**20110227011103
21429 Ignore-this: 5d5e3415b7d2732d92f42413c25d205d
21430] 
21431[refactor: s/peer/server/ in immutable/upload, happinessutil.py, test_upload
21432warner@lothar.com**20110227011100
21433 Ignore-this: 7ea858755cbe5896ac212a925840fe68
21434 
21435 No behavioral changes, just updating variable/method names and log messages.
21436 The effects outside these three files should be minimal: some exception
21437 messages changed (to say "server" instead of "peer"), and some internal class
21438 names were changed. A few things still use "peer" to minimize external
21439 changes, like UploadResults.timings["peer_selection"] and
21440 happinessutil.merge_peers, which can be changed later.
21441] 
21442[storage_client.py: clean up test_add_server/test_add_descriptor, remove .test_servers
21443warner@lothar.com**20110227011056
21444 Ignore-this: efad933e78179d3d5fdcd6d1ef2b19cc
21445] 
21446[test_client.py, upload.py:: remove KiB/MiB/etc constants, and other dead code
21447warner@lothar.com**20110227011051
21448 Ignore-this: dc83c5794c2afc4f81e592f689c0dc2d
21449] 
21450[test: increase timeout on a network test because Francois's ARM machine hit that timeout
21451zooko@zooko.com**20110317165909
21452 Ignore-this: 380c345cdcbd196268ca5b65664ac85b
21453 I'm skeptical that the test was proceeding correctly but ran out of time. It seems more likely that it had gotten hung. But if we raise the timeout to an even more extravagant number then we can be even more certain that the test was never going to finish.
21454] 
21455[docs/configuration.rst: add a "Frontend Configuration" section
21456Brian Warner <warner@lothar.com>**20110222014323
21457 Ignore-this: 657018aa501fe4f0efef9851628444ca
21458 
21459 this points to docs/frontends/*.rst, which were previously underlinked
21460] 
21461[web/filenode.py: avoid calling req.finish() on closed HTTP connections. Closes #1366
21462"Brian Warner <warner@lothar.com>"**20110221061544
21463 Ignore-this: 799d4de19933f2309b3c0c19a63bb888
21464] 
21465[Add unit tests for cross_check_pkg_resources_versus_import, and a regression test for ref #1355. This requires a little refactoring to make it testable.
21466david-sarah@jacaranda.org**20110221015817
21467 Ignore-this: 51d181698f8c20d3aca58b057e9c475a
21468] 
21469[allmydata/__init__.py: .name was used in place of the correct .__name__ when printing an exception. Also, robustify string formatting by using %r instead of %s in some places. fixes #1355.
21470david-sarah@jacaranda.org**20110221020125
21471 Ignore-this: b0744ed58f161bf188e037bad077fc48
21472] 
21473[Refactor StorageFarmBroker handling of servers
21474Brian Warner <warner@lothar.com>**20110221015804
21475 Ignore-this: 842144ed92f5717699b8f580eab32a51
21476 
21477 Pass around IServer instance instead of (peerid, rref) tuple. Replace
21478 "descriptor" with "server". Other replacements:
21479 
21480  get_all_servers -> get_connected_servers/get_known_servers
21481  get_servers_for_index -> get_servers_for_psi (now returns IServers)
21482 
21483 This change still needs to be pushed further down: lots of code is now
21484 getting the IServer and then distributing (peerid, rref) internally.
21485 Instead, it ought to distribute the IServer internally and delay
21486 extracting a serverid or rref until the last moment.
21487 
21488 no_network.py was updated to retain parallelism.
21489] 
21490[TAG allmydata-tahoe-1.8.2
21491warner@lothar.com**20110131020101] 
21492Patch bundle hash:
2149384ad39c67cb591e9201475eb2e54ecf4cca51738